The barotropic vorticity model describes the evolution of a 2D non-divergent flow with velocity components $\mathbf{u} = (u,v)$ through self-advection, forces and dissipation. Due to the non-divergent nature of the flow, it can be described by (the vertical component) of the relative vorticity $\zeta = \nabla \times \mathbf{u}$.
The dynamical core presented here to solve the barotropic vorticity equations largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2].
The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity $\zeta$ with advection, Coriolis force, forcing and diffusion in a single global layer on the sphere.
We denote time$t$, velocity vector $\mathbf{u} = (u, v)$, Coriolis parameter $f$, and hyperdiffusion $(-1)^{n+1} \nu \nabla^{2n} \zeta$ ($n$ is the hyperdiffusion order, see Horizontal diffusion). We also include a forcing vector $\mathbf{F} = (F_u,F_v)$ which acts on the zonal velocity $u$ and the meridional velocity $v$ and hence its curl $\nabla \times \mathbf{F}$ is a tendency for relative vorticity $\zeta$.
Starting with some relative vorticity $\zeta$, the Laplacian is inverted to obtain the stream function $\Psi$
\[\Psi = \nabla^{-2}\zeta\]
The zonal velocity $u$ and meridional velocity $v$ are then the (negative) meridional gradient and zonal gradient of $\Psi$
which is described in Derivatives in spherical coordinates. Using $u$ and $v$ we can then advect the absolute vorticity $\zeta + f$. In order to avoid to calculate both the curl and the divergence of a flux we rewrite the barotropic vorticity equation as
with $\mathbf{u}_\perp = (v,-u)$ the rotated velocity vector, because $-\nabla\cdot\mathbf{u} = \nabla \times \mathbf{u}_\perp$. This is the form that is solved in the BarotropicModel, as outlined in the following section.
In SpeedyWeather.jl we use hyerdiffusion through an $n$-th power Laplacian $(-1)^{n+1}\nabla^{2n}$ (hyper when $n>1$) which can be implemented as a multiplication of the spectral coefficients $\Psi_{lm}$ with $(-l(l+1))^nR^{-2n}$ (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the $(-l(l+1))^nR^{-2n}$ can be precomputed. Note the sign change $(-1)^{n+1}$ here is such that the dissipative nature of the diffusion operator is retained for $n$ odd and even.
In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step $\Delta t$ of variable $\zeta$ to obtain from time steps $i-1$ and $i$, the next time step $i+1$
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t d\zeta,\]
with $d\zeta$ being some tendency evaluated from $\zeta_i$. Now we want to add a diffusion term $(-1)^{n+1}\nu \nabla^{2n}\zeta$ with coefficient $\nu$, which however, is implicitly calculated from $\zeta_{i+1}$, then
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t (d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i+1})\]
As the application of $(-1)^{n+1}\nu\nabla^{2n}$ is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to
\[\zeta_{i+1} = \frac{\zeta_{i-1} + 2\Delta t d\zeta}{1 - 2\Delta (-1)^{n+1}\nu\nabla^{2n}},\]
and expand the numerator to
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t \frac{d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t (-1)^{n+1}\nu \nabla^{2n}},\]
Hence the diffusion can be applied implicitly by updating the tendency $d\zeta$ as
\[d\zeta \to \frac{d\zeta + (-1)^{n+1}\nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t \nu \nabla^{2n}}\]
which only depends on $\zeta_{i-1}$. Now let $D_\text{explicit} = (-1)^{n+1}\nu\nabla^{2n}$ be the explicit part and $D_\text{implicit} = 1 - (-1)^{n+1} 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are $D_\text{implicit} = 1 - 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic $l,m$ we do
Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power $n$.
In physics, the Laplace operator $\nabla^2$ is often used to represent diffusion due to viscosity in a fluid or diffusion that needs to be added to retain numerical stability. In both cases, the coefficient is $\nu$ of units $\text{m}^2\text{s}^{-1}$ and the full operator reads as $\nu \nabla^2$ with units $(\text{m}^2\text{s}^{-1})(\text{m}^{-2}) = \text{s}^{-1}$. This motivates us to normalize the Laplace operator by a constant of units $\text{m}^{-2}$ and the coefficient by its inverse such that it becomes a damping timescale of unit $\text{s}^{-1}$. Given the application in spectral space we decide to normalize by the largest eigenvalue $-l_\text{max}(l_\text{max}+1)$ such that all entries in the discrete spectral Laplace operator are in $[0,1]$. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized coefficient $\nu^* = l_\text{max}(l_\text{max}+1)\nu$ (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have
and the implicit part is accordingly $D^\text{implicit,n}_{l,m} = 1 - 2\Delta t D^\text{explicit,n}_{l,m}$. Note that the diffusion time scale $\nu^*$ is then also scaled by the radius, see next section.
Similar to a non-dimensionalization of the equations, SpeedyWeather.jl scales the barotropic vorticity equation with $R^2$ to obtain normalized gradient operators as follows. A scaling for vorticity $\zeta$ and stream function $\Psi$ is used that is
This is also convenient as vorticity is often $10^{-5}\text{ s}^{-1}$ in the atmosphere, but the streamfunction more like $10^5\text{ m}^2\text{ s}^{-1}$ and so this scaling brings both closer to 1 with a typical radius of the Earth of 6371km. The inversion of the Laplacians in order to obtain $\Psi$ from $\zeta$ therefore becomes
\[\tilde{\zeta} = \tilde{\nabla}^2 \tilde{\Psi}\]
where the dimensionless gradients simply omit the scaling with $1/R$, $\tilde{\nabla} = R\nabla$. The Barotropic vorticity equation scaled with $R^2$ is
$\mathbf{u} = (u,v)$, the velocity vector (no scaling applied)
$\tilde{f} = fR$, the scaled Coriolis parameter $f$
$\tilde{\mathbf{F}} = R\mathbf{F}$, the scaled forcing vector $\mathbf{F}$
$\tilde{\nu} = \nu^* R$, the scaled diffusion coefficient $\nu^*$, which itself is normalized to a damping time scale, see Normalization of diffusion.
So scaling with the radius squared means we can use dimensionless operators, however, this comes at the cost of needing to deal with both a time step in seconds as well as a scaled time step in seconds per meter, which can be confusing. Furthermore, some constants like Coriolis or the diffusion coefficient need to be scaled too during initialisation, which may be confusing too because values are not what users expect them to be. SpeedyWeather.jl follows the logic that the scaling to the prognostic variables is only applied just before the time integration and variables are unscaled for output and after the time integration finished. That way, the scaling is hidden as much as possible from the user. In hopefully many other cases it is clearly denoted that a variable or constant is scaled.
meaning we step from the previous time step $i-1$, leapfrogging over the current time step$i$ to the next time step $i+1$ by evaluating the tendencies on the right-hand side $RHS$ at the current time step $i$. The time stepping is done in spectral space. Once the right-hand side $RHS$ is evaluated, leapfrogging is a linear operation, meaning that its simply applied to every spectral coefficient $\zeta_{lm}$ as one would evaluate it on every grid point in grid-point models.
For the Leapfrog time integration two time steps of the prognostic variables have to be stored, $i-1$ and $i$. Time step $i$ is used to evaluate the tendencies which are then added to $i-1$ in a step that also swaps the indices for the next time step $i \to i-1$ and $i+1 \to i$, so that no additional memory than two time steps have to be stored at the same time.
The Leapfrog time integration has to be initialised with an Euler forward step in order to have a second time step $i+1$ available when starting from $i$ to actually leapfrog over. SpeedyWeather.jl therefore does two initial time steps that are different from the leapfrog time steps that follow and that have been described above.
an Euler forward step with $\Delta t/2$, then
one leapfrog time step with $\Delta t$, then
leapfrog with $2 \Delta t$ till the end
This is particularly done in a way that after 2. we have $t=0$ at $i-1$ and $t=\Delta t$ at $i$ available so that 3. can start the leapfrogging without any offset from the intuitive spacing $0,\Delta t, 2\Delta t, 3\Delta t,...$. The following schematic can be useful
time at step $i-1$
time at step $i$
time step at $i+1$
Initial conditions
$t = 0$
1: Euler
(T) $\quad t = 0$
$t=\Delta t/2$
2: Leapfrog with $\Delta t$
$t = 0$
(T) $\quad t = \Delta t/2$
$t = \Delta t$
3 to $n$: Leapfrog with $2\Delta t$
$t-\Delta t$
(T) $\qquad \quad \quad t$
$t+\Delta t$
The time step that is used to evaluate the tendencies is denoted with (T). It is always the time step furthest in time that is available.
The standard leapfrog time integration is often combined with a Robert-Asselin filter[Robert66][Asselin72] to dampen a computational mode. The idea is to start with a standard leapfrog step to obtain the next time step $i+1$ but then to correct the current time step $i$ by applying a filter which dampens the computational mode. The filter looks like a discrete Laplacian in time with a $(1, -2, 1)$ stencil, and so, maybe unsurprisingly, is efficient to filter out a "grid-scale oscillation" in time, aka the computational mode. Let $v$ be the unfiltered variable and $u$ be the filtered variable, $F$ the right-hand side tendency, then the standard leapfrog step is
\[v_{i+1} = u_{i-1} + 2\Delta tF(v_i)\]
Meaning we start with a filtered variable $u$ at the previous time step $i-1$, evaluate the tendency $F(v_i)$ based on the current time step $i$ to obtain an unfiltered next time step $v_{i+1}$. We then filter the current time step $i$ (which will become $i-1$ on the next iteration)
by adding a discrete Laplacian with coefficient $\tfrac{\nu}{2}$ to it, evaluated from the available filtered and unfiltered time steps centred around $i$: $v_{i-1}$ is not available anymore because it was overwritten by the filtering at the previous iteration, $u_i, u_{i+1}$ are not filtered yet when applying the Laplacian. The filter parameter $\nu$ is typically chosen between 0.01-0.2, with stronger filtering for higher values.
Williams[Williams2009] then proposed an additional filter step to regain accuracy that is otherwise lost with a strong Robert-Asselin filter[Amezcua2011][Williams2011]. Now let $w$ be unfiltered, $v$ be once filtered, and $u$ twice filtered, then
with the Williams filter parameter $\alpha \in [0.5,1]$. For $\alpha=1$ we're back with the Robert-Asselin filter (the first two lines).
The Laplacian in the parentheses is often called a displacement, meaning that the filtered value is displaced (or corrected) in the direction of the two surrounding time steps. The Williams filter now also applies the same displacement, but in the opposite direction to the next time step $i+1$ as a correction step (line 3 above) for a once-filtered value $v_{i+1}$ which will then be twice-filtered by the Robert-Asselin filter on the next iteration. For more details see the referenced publications.
The initial Euler step (see Time integration, Table) is not filtered. Both the the Robert-Asselin and Williams filter are then switched on for all following leapfrog time steps.
Robert66Robert, André. “The Integration of a Low Order Spectral Form of the Primitive Meteorological Equations.” Journal of the Meteorological Society of Japan 44 (1966): 237-245.
Williams2009Williams, P. D., 2009: A Proposed Modification to the Robert–Asselin Time Filter. Mon. Wea. Rev., 137, 2538–2546, 10.1175/2009MWR2724.1.
Amezcua2011Amezcua, J., E. Kalnay, and P. D. Williams, 2011: The Effects of the RAW Filter on the Climatology and Forecast Skill of the SPEEDY Model. Mon. Wea. Rev., 139, 608–619, doi:10.1175/2010MWR3530.1.
Williams2011Williams, P. D., 2011: The RAW Filter: An Improvement to the Robert–Asselin Filter in Semi-Implicit Integrations. Mon. Wea. Rev., 139, 1996–2007, doi:10.1175/2010MWR3601.1.
Settings
This document was generated with Documenter.jl version 0.27.25 on Wednesday 2 August 2023. Using Julia version 1.8.5.
diff --git a/dev/conventions/index.html b/dev/conventions/index.html
new file mode 100644
index 000000000..6ab01c099
--- /dev/null
+++ b/dev/conventions/index.html
@@ -0,0 +1,12 @@
+
+Style and convention guide · SpeedyWeather.jl
The prognostic variables in spectral space are called
vor # Vorticity of horizontal wind field
+ div # Divergence of horizontal wind field
+ temp # Absolute temperature [K]
+ pres_surf # Logarithm of surface pressure [log(Pa)]
+ humid # Specific humidity [g/kg]
their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are
We follow Julia's style guide and highlight here some important aspects of it.
Bang (!) convention. A function func does not change its input arguments, however, func! does.
Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.
Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup
but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.
Settings
This document was generated with Documenter.jl version 0.27.25 on Wednesday 2 August 2023. Using Julia version 1.8.5.
This document was generated with Documenter.jl version 0.27.25 on Wednesday 2 August 2023. Using Julia version 1.8.5.
diff --git a/dev/functions/index.html b/dev/functions/index.html
new file mode 100644
index 000000000..84c9599a8
--- /dev/null
+++ b/dev/functions/index.html
@@ -0,0 +1,634 @@
+
+Function and type index · SpeedyWeather.jl
The BarotropicModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.
spectral_grid::SpectralGrid: dictates resolution for many other components
planet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics
atmosphere::SpeedyWeather.AbstractAtmosphere
forcing::SpeedyWeather.AbstractForcing{NF} where NF<:AbstractFloat
Mutable struct that contains all prognostic (copies thereof) and diagnostic variables in a single column needed to evaluate the physical parametrizations. For now the struct is mutable as we will reuse the struct to iterate over horizontal grid points. Every column vector has nlev entries, from [1] at the top to [end] at the lowermost model level at the planetary boundary layer.
nlev::Int64
nband::Int64
n_stratosphere_levels::Int64
jring::Int64
lond::AbstractFloat
latd::AbstractFloat
u::Vector{NF} where NF<:AbstractFloat
v::Vector{NF} where NF<:AbstractFloat
temp::Vector{NF} where NF<:AbstractFloat
humid::Vector{NF} where NF<:AbstractFloat
ln_pres::Vector{NF} where NF<:AbstractFloat
pres::Vector{NF} where NF<:AbstractFloat
u_tend::Vector{NF} where NF<:AbstractFloat
v_tend::Vector{NF} where NF<:AbstractFloat
temp_tend::Vector{NF} where NF<:AbstractFloat
humid_tend::Vector{NF} where NF<:AbstractFloat
geopot::Vector{NF} where NF<:AbstractFloat
flux_u_upward::Vector{NF} where NF<:AbstractFloat
flux_u_downward::Vector{NF} where NF<:AbstractFloat
flux_v_upward::Vector{NF} where NF<:AbstractFloat
flux_v_downward::Vector{NF} where NF<:AbstractFloat
flux_temp_upward::Vector{NF} where NF<:AbstractFloat
flux_temp_downward::Vector{NF} where NF<:AbstractFloat
flux_humid_upward::Vector{NF} where NF<:AbstractFloat
flux_humid_downward::Vector{NF} where NF<:AbstractFloat
sat_humid::Vector{NF} where NF<:AbstractFloat
sat_vap_pres::Vector{NF} where NF<:AbstractFloat
dry_static_energy::Vector{NF} where NF<:AbstractFloat
moist_static_energy::Vector{NF} where NF<:AbstractFloat
humid_half::Vector{NF} where NF<:AbstractFloat
sat_humid_half::Vector{NF} where NF<:AbstractFloat
sat_moist_static_energy::Vector{NF} where NF<:AbstractFloat
dry_static_energy_half::Vector{NF} where NF<:AbstractFloat
sat_moist_static_energy_half::Vector{NF} where NF<:AbstractFloat
conditional_instability::Bool
activate_convection::Bool
cloud_top::Int64
excess_humidity::AbstractFloat
cloud_base_mass_flux::AbstractFloat
precip_convection::AbstractFloat
net_flux_humid::Vector{NF} where NF<:AbstractFloat
net_flux_dry_static_energy::Vector{NF} where NF<:AbstractFloat
entrainment_profile::Vector{NF} where NF<:AbstractFloat
Create a struct Earth<:AbstractPlanet, with the following physical/orbital characteristics. Note that radius is not part of it as this should be chosen in SpectralGrid. Keyword arguments are
rotation::Float64: angular frequency of Earth's rotation [rad/s]
Create a struct EarthAtmosphere<:AbstractPlanet, with the following physical/chemical characteristics. Note that radius is not part of it as this should be chosen in SpectralGrid. Keyword arguments are
mol_mass_dry_air::Float64: molar mass of dry air [g/mol]
mol_mass_vapour::Float64: molar mass of water vapour [g/mol]
cₚ::Float64: specific heat at constant pressure [J/K/kg]
R_gas::Float64: universal gas constant [J/K/mol]
R_dry::Float64: specific gas constant for dry air [J/kg/K]
R_vapour::Float64: specific gas constant for water vapour [J/kg/K]
water_density::Float64: water density [kg/m³]
latent_heat_condensation::Float64: latent heat of condensation [J/g] for consistency with specific humidity [g/Kg], also called alhc
latent_heat_sublimation::Float64: latent heat of sublimation [J/g], also called alhs
Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF.
Construct Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. Pass on SpectralGrid to calculate the following fields
spectral_grid::SpectralGrid: SpectralGrid that defines spectral and grid resolution
Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid of the dynamical core
nlat_half::Int64: resolution parameter nlat_half of Grid, # of latitudes on one hemisphere (incl Equator)
nlon_max::Int64: maximum number of longitudes (at/around Equator)
nlon::Int64: =nlon_max, same (used for compatibility), TODO: still needed?
nlat::Int64: number of latitude rings
nlev::Int64: number of vertical levels
npoints::Int64: total number of grid points
radius::AbstractFloat: Planet's radius [m]
latd::Vector{Float64}: array of latitudes in degrees (90˚...-90˚)
lond::Vector{Float64}: array of longitudes in degrees (0...360˚), empty for non-full grids
londs::Vector{NF} where NF<:AbstractFloat: longitude (-180˚...180˚) for each grid point in ring order
latds::Vector{NF} where NF<:AbstractFloat: latitude (-90˚...˚90) for each grid point in ring order
sinlat::Vector{NF} where NF<:AbstractFloat: sin of latitudes
coslat::Vector{NF} where NF<:AbstractFloat: cos of latitudes
coslat⁻¹::Vector{NF} where NF<:AbstractFloat: = 1/cos(lat)
coslat²::Vector{NF} where NF<:AbstractFloat: = cos²(lat)
coslat⁻²::Vector{NF} where NF<:AbstractFloat: # = 1/cos²(lat)
σ_levels_half::Vector{NF} where NF<:AbstractFloat: σ at half levels, σ_k+1/2
σ_levels_full::Vector{NF} where NF<:AbstractFloat: σ at full levels, σₖ
σ_levels_thick::Vector{NF} where NF<:AbstractFloat: σ level thicknesses, σₖ₊₁ - σₖ
ln_σ_levels_full::Vector{NF} where NF<:AbstractFloat: log of σ at full levels, include surface (σ=1) as last element
Struct for horizontal hyper diffusion of vor, div, temp; implicitly in spectral space with a power of the Laplacian (default=4) and the strength controlled by time_scale. Options exist to scale the diffusion by resolution, and adaptive depending on the current vorticity maximum to increase diffusion in active layers. Furthermore the power can be decreased above the tapering_σ to power_stratosphere (default 2). For Barotropic, ShallowWater, the default non-adaptive constant-time scale hyper diffusion is used. Options are
trunc::Int64: spectral resolution
nlev::Int64: number of vertical levels
power::Float64: power of Laplacian
time_scale::Float64: diffusion time scales [hrs]
resolution_scaling::Float64: stronger diffusion with resolution? 0: constant with trunc, 1: (inverse) linear with trunc, etc
power_stratosphere::Float64: different power for tropopause/stratosphere
tapering_σ::Float64: linearly scale towards power_stratosphere above this σ
adaptive::Bool: adaptive = higher diffusion for layers with higher vorticity levels.
vor_max::Float64: above this (absolute) vorticity level [1/s], diffusion is increased
adaptive_strength::Float64: increase strength above vor_max by this factor times max(abs(vor))/vor_max
Struct that holds various precomputed arrays for the semi-implicit correction to prevent gravity waves from amplifying in the primitive equation model.
NetCDF output writer. Contains all output options and auxiliary fields for output interpolation. To be initialised with OutputWriter(::SpectralGrid,::Type{<:ModelSetup},kwargs...) to pass on the resolution information and the model type which chooses which variables to output. Options include
spectral_grid::SpectralGrid
output::Bool
path::String: [OPTION] path to output folder, run_???? will be created within
id::String: [OPTION] run identification number/string
run_path::String
filename::String: [OPTION] name of the output netcdf file
write_restart::Bool: [OPTION] also write restart file if output==true?
pkg_version::VersionNumber
startdate::Dates.DateTime
output_dt::Float64: [OPTION] output frequency, time step [hrs]
output_dt_sec::Int64: actual output time step [sec]
output_vars::Vector{Symbol}: [OPTION] which variables to output, u, v, vor, div, pres, temp, humid
missing_value::Union{Float32, Float64}: [OPTION] missing value to be used in netcdf output
compression_level::Int64: [OPTION] lossless compression level; 1=low but fast, 9=high but slow
keepbits::SpeedyWeather.Keepbits: [OPTION] mantissa bits to keep for every variable
The PrimitiveDryModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.
spectral_grid::SpectralGrid: dictates resolution for many other components
planet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics
The PrimitiveDryModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.
spectral_grid::SpectralGrid: dictates resolution for many other components
planet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics
The ShallowWaterModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.
spectral_grid::SpectralGrid: dictates resolution for many other components
planet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics
atmosphere::SpeedyWeather.AbstractAtmosphere
forcing::SpeedyWeather.AbstractForcing{NF} where NF<:AbstractFloat
Restart from a previous SpeedyWeather.jl simulation via the restart file restart.jld2 Applies interpolation in the horizontal but not in the vertical. restart.jld2 is identified by
path::String: path for restart file
id::Union{Int64, String}: run_id of restart file in run_????/restart.jld2
Create a struct that contains all parameters for the Galewsky et al, 2004 zonal jet intitial conditions for the shallow water model. Default values as in Galewsky.
latitude::Float64: jet latitude [˚N]
width::Float64: jet width [˚], default ≈ 19.29˚
umax::Float64: jet maximum velocity [m/s]
perturb_lat::Float64: perturbation latitude [˚N], position in jet by default
Create a struct that contains all parameters for the Jablonowski and Williamson, 2006 intitial conditions for the primitive equation model. Default values as in Jablonowski.
η₀::Float64: conversion from σ to Jablonowski's ηᵥ-coordinates
u₀::Float64: max amplitude of zonal wind [m/s]
perturb_lat::Float64: perturbation centred at [˚N]
perturb_lon::Float64: perturbation centred at [˚E]
perturb_uₚ::Float64: perturbation strength [m/s]
perturb_radius::Float64: radius of Gaussian perturbation in units of Earth's radius [1]
ΔT::Float64: temperature difference used for stratospheric lapse rate [K], Jablonowski uses ΔT = 4.8e5 [K]
Tmin::Float64: minimum temperature [K] of profile
pressure_on_orography::Bool: initialize pressure given the atmosphere.lapse_rate on orography?
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for primitive equation models. Updates grid vorticity, grid divergence, grid temperature, pressure (pres_grid) and the velocities u,v.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities u,v.
Vertical sigma coordinates defined by their nlev+1 half levels σ_levels_half. Sigma coordinates are fraction of surface pressure (p/p0) and are sorted from top (stratosphere) to bottom (surface). The first half level is at 0 the last at 1. Evaluate a generalised logistic function with coefficients in P for the distribution of values in between. Default coefficients follow the L31 configuration historically used at ECMWF.
Performs the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.
Calculate the geopotential based on temp in a single column. This exclues the surface geopotential that would need to be added to the returned vector. Function not used in the dynamical core but for post-processing and analysis.
Update C::ColumnVariables by copying the prognostic variables from D::DiagnosticVariables at gridpoint index ij. Provide G::Geometry for coordinate information.
Checks existing run_???? folders in path to determine a 4-digit id number by counting up. E.g. if folder run_0001 exists it will return the string "0002". Does not create a folder for the returned run id.
Calculate thermodynamic quantities like saturation vapour pressure, saturation specific humidity, dry static energy, moist static energy and saturation moist static energy from the prognostic column variables.
Apply horizontal diffusion applied to vorticity, diffusion and temperature in the PrimitiveEquation models. Uses the constant diffusion for temperature but possibly adaptive diffusion for vorticity and divergence.
Apply horizontal diffusion to a 2D field A in spectral space by updating its tendency tendency with an implicitly calculated diffusion term. The implicit diffusion of the next time step is split into an explicit part ∇²ⁿ_expl and an implicit part ∇²ⁿ_impl, such that both can be calculated in a single forward step by using A as well as its tendency tendency.
implicit_correction!(
+ diagn::SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},
+ progn::SpeedyWeather.PrognosticLayerTimesteps{NF},
+ diagn_surface::SpeedyWeather.SurfaceVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},
+ progn_surface::SpeedyWeather.PrognosticSurfaceTimesteps{NF},
+ implicit::SpeedyWeather.ImplicitShallowWater
+)
+
Apply correction to the tendencies in diagn to prevent the gravity waves from amplifying. The correction is implicitly evaluated using the parameter implicit.α to switch between forward, centered implicit or backward evaluation of the gravity wave terms.
Precomputes the hyper diffusion terms in scheme for layer k based on the model time step in L, the vertical level sigma level in G, and the current (absolute) vorticity maximum level vor_max
initialize the JablonowskiRelaxation temperature relaxation by precomputing terms for the equilibrium temperature Teq and the frequency (strength of relaxation).
Calls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.
Calls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.
Calls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.
Creates a netcdf file on disk and the corresponding netcdf_file object preallocated with output variables and dimensions. write_output! then writes consecuitive time steps into this file.
Large-scale condensation for a column by relaxation back to a reference relative humidity if larger than that. Calculates the tendencies for specific humidity and temperature and integrates the large-scale precipitation vertically for output.
So that the second term inside the Laplace operator can be added to the geopotential. Rd is the gas constant, Tᵥ the virtual temperature and Tᵥ' its anomaly wrt to the average or reference temperature Tₖ, lnpₛ is the logarithm of surface pressure.
Linear virtual temperature for model::PrimitiveDry: Just copy over arrays from temp to temp_virt at timestep lf in spectral space as humidity is zero in this model.
with the static energy SE, the latent heat of condensation Lc, the geopotential Φ. As well as the saturation moist static energy which replaces Q with Q_sat
Compute tendencies for u,v,temp,humid from physical parametrizations. Extract for each vertical atmospheric column the prognostic variables (stored in diagn as they are grid-point transformed), loop over all grid-points, compute all parametrizations on a single-column basis, then write the tendencies back into a horizontal field of tendencies.
Returns Dates.CompoundPeriod rounding to either (days, hours), (hours, minutes), (minutes, seconds), or seconds with 1 decimal place accuracy for >10s and two for less. E.g.
Compute (1) the saturation vapour pressure as a function of temperature using the August-Roche-Magnus formula,
eᵢ(T) = e₀ * exp(Cᵢ * (T - T₀) / (T - Tᵢ)),
where T is in Kelvin and i = 1,2 for saturation with respect to water and ice, respectively. And (2) the saturation specific humidity according to the formula,
0.622 * e / (p - (1 - 0.622) * e),
where e is the saturation vapour pressure, p is the pressure, and 0.622 is the ratio of the molecular weight of water to dry air.
Sets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.
set_var!(progn::PrognosticVariables{NF},
+ varname::Symbol,
+ var::Vector{<:LowerTriangularMatrix};
+ lf::Integer=1) where NF
Sets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in spectral space.
Sets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.
set_var!(progn::PrognosticVariables{NF},
+ varname::Symbol,
+ var::Vector{<:AbstractGrid};
+ lf::Integer=1) where NF
Sets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.
Computes the tendency of the logarithm of surface pressure as
-(ū*px + v̄*py) - D̄
with ū,v̄ being the vertically averaged velocities; px, py the gradients of the logarithm of surface pressure ln(p_s) and D̄ the vertically averaged divergence.
Calculate ∇ln(p_s) in spectral space, convert to grid.
Multiply ū,v̄ with ∇ln(p_s) in grid-point space, convert to spectral.
D̄ is subtracted in spectral space.
Set tendency of the l=m=0 mode to 0 for better mass conservation.
+= because the tendencies already contain parameterizations and vertical advection. T' is the anomaly with respect to the reference/average temperature. Tᵥ is the virtual temperature used in the adiabatic term κTᵥ*Dlnp/Dt.
Virtual temperature in grid-point space: For the PrimitiveDry temperature and virtual temperature are the same (humidity=0). Just copy over the arrays.
Tendencies for vorticity and divergence. Excluding Bernoulli potential with geopotential and linear pressure gradient inside the Laplace operator, which are added later in spectral space.
+= because the tendencies already contain the parameterizations and vertical advection. f is coriolis, ζ relative vorticity, R the gas constant Tᵥ' the virtual temperature anomaly, ∇lnp the gradient of surface pressure and _x and _y its zonal/meridional components. The tendencies are then curled/dived to get the tendencies for vorticity/divergence in spectral space
with Fᵤ,Fᵥ from u_tend_grid/v_tend_grid that are assumed to be alread set in forcing!. Set div=false for the BarotropicModel which doesn't require the divergence tendency.
Write the parametrization tendencies from C::ColumnVariables into the horizontal fields of tendencies stored in D::DiagnosticVariables at gridpoint index ij.
Writes the variables from diagn of time step i at time time into outputter.netcdf_file. Simply escapes for no netcdf output of if output shouldn't be written on this time step. Interpolates onto output grid and resolution as specified in outputter, converts to output number format, truncates the mantissa for higher compression and applies lossless compression.
A restart file restart.jld2 with the prognostic variables is written to the output folder (or current path) that can be used to restart the model. restart.jld2 will then be used as initial conditions. The prognostic variables are bitrounded for compression and the 2nd leapfrog time step is discarded. Variables in restart file are unscaled.
The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and but let's start but how they can be used
The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object which defines the resolution in spectral and in grid-point space. The generator SpectralGrid() can take as a keyword argument Grid which can be any of the grids described below. The resolution of the grid, however, is not directly chosen, but determined from the spectral resolution trunc and the dealiasing factor. More in Matching spectral and grid resolution.
RingGrids is a module too!
While RingGrids is the underlying module that SpeedyWeather.jl uses for data structs on the sphere, the module can also be used independently of SpeedyWeather, for example to interpolate between data on different grids. See RingGrids
SpeedyWeather.jl's spectral transform supports all ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on these rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.
Is the FullClenshawGrid a longitude-latitude grid?
Short answer: Yes. The FullClenshawGrid is a specific longitude-latitude grid with equi-angle spacing. The most common grids for geoscientific data use regular spacings for 0-360˚E in longitude and 90˚N-90˚S. The FullClenshawGrid does that too, but it does not have a point on the North or South pole, and the central latitude ring sits exactly on the Equator. We name it Clenshaw following the Clenshaw-Curtis quadrature that is used in the Legendre transfrom in the same way as Gaussian refers to the Gaussian quadrature.
All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).
Currently the following full grids <: AbstractFullGrid are implemented
FullGaussianGrid, a full grid with Gaussian latitudes
FullClenshawGrid, a full grid with equi-angle latitudes
and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are
OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron
OctahedralClenshawGrid, similar but based on equi-angle latitudes
HEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces
OctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.
An overview of these grids is visualised here
Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.
All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half.
HEALPix grids do not use Nside as resolution parameter
The original formulation for HEALPix grids use $N_{side}$, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use $N_{side}$ for the documentation or within functions though.
A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation $T$ with a grid resolution $N$ (=nlat_half) such that $T + 1 = N$. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at $l_{max}=31$ in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid. In SpeedyWeather.jl the choice of the order of truncation is controlled with the dealiasing parameter in the SpectralGrid construction.
Let J be the total number of rings. Then we have
$T \approx J$ for linear truncation, i.e. dealiasing = 1
$\frac{3}{2}T \approx J$ for quadratic truncation, i.e. dealiasing = 2
$2T \approx J$ for cubic truncation, , i.e. dealiasing = 3
and in general $\frac{m+1}{2}T \approx J$ for m-th order truncation. So the higher the truncation order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. A quick overview of how the grid resolution changes when dealiasing is passed onto SpectralGrid on the FullGaussianGrid
trunc
dealiasing
FullGaussianGrid size
31
1
64x32
31
2
96x48
31
3
128x64
42
1
96x48
42
2
128x64
42
3
192x96
...
...
...
You will obtain this information every time you create a SpectralGrid(;Grid,trunc,dealiasing).
Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are $N_\varphi$ basepixels in zonal direction and $N_\theta$ basepixels in meridional direction. For $N_\varphi = 4$ and $N_\theta = 3$ we obtain the classical HEALPix grid with $N_\varphi N_\theta = 12$ basepixels shown above in Implemented grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always $2N$, so 32 at $N=16$) and there are polar caps above and below the equatorial zone with the border at $\cos(\theta) = 2/3$ ($\theta$ in colatitudes).
Following Górski, 2004[1], the $z=cos(\theta)$ colatitude of the $j$-th ring in the north polar cap, $j=1,...,N_{side}$ with $2N_{side} = N$ is
\[z = 1 - \frac{j^2}{3N_{side}^2}\]
and on that ring, the longitude $\phi$ of the $i$-th point ($i$ is the in-ring-index) is at
\[\phi = \frac{\pi}{2j}(i-\tfrac{1}{2})\]
The in-ring index $i$ goes from $i=1,...,4$ for the first (i.e. northern-most) ring, $i=1,...,8$ for the second ring and $i = 1,...,4j$ for the $j$-th ring in the northern polar cap.
In the north equatorial belt $j=N_{side},...,2N_{side}$ this changes to
\[z = \frac{4}{3} - \frac{2j}{3N_{side}}\]
and the longitudes change to ($i$ is always $i = 1,...,4N_{side}$ in the equatorial belt meaning the number of longitude points is constant here)
The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.
The cell boundaries are obtained by setting $i = k + 1/2$ or $i = k + 1/2 + j$ (half indices) into the equations above, such that $z(\phi,k)$, a function for the cosine of colatitude $z$ of index $k$ and the longitude $\phi$ is obtained. These are then (northern polar cap)
While the classic HEALPix grid is based on a dodecahedron, other choices for $N_\varphi$ and $N_\theta$ in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With $N_\varphi = 4$ and $N_\theta = 1$ we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, $2\pi$ around the Equator versus $\pi$ between the poles. $N_\varphi = 6, N_\theta = 2$ or $N_\varphi = 8, N_\theta = 3$ are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visualizations of these grids.
We call the $N_\varphi = 4, N_\theta = 1$ HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As $N_\theta = 1$ there is no equatorial belt which simplifies the grid. The latitude of the $j$-th isolatitude ring on the OctaHEALPixGrid is defined by
\[z = 1 - \frac{j^2}{N^2},\]
with $j=1,...,N$, and similarly for the southern hemisphere by symmetry. On this grid $N_{side} = N$ where $N$= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index $i = 1,...,4j$ are
\[\phi = \frac{\pi}{2j}(i - \tfrac{1}{2})\]
and again, the southern hemisphere grid points are obtained by symmetry.
The $3N_{side}^2$ in the denominator of the HEALPix grid came simply $N^2$ for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).
1Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976
Settings
This document was generated with Documenter.jl version 0.27.25 on Wednesday 2 August 2023. Using Julia version 1.8.5.
diff --git a/dev/how_to_run_speedy/index.html b/dev/how_to_run_speedy/index.html
new file mode 100644
index 000000000..737c7a19e
--- /dev/null
+++ b/dev/how_to_run_speedy/index.html
@@ -0,0 +1,57 @@
+
+How to run SpeedyWeather.jl · SpeedyWeather.jl
We want to use the barotropic model to simulate some free-decaying 2D turbulence on the sphere without rotation. We start by defining the SpectralGrid object. To have a resolution of about 100km, we choose a spectral resolution of T127 (see Available horizontal resolutions) and nlev=1 vertical levels. The SpectralGrid object will provide us with some more information
There are other options to create a planet but they are irrelevant for the barotropic vorticity equations. We also want to specify the initial conditions, randomly distributed vorticity is already defined
By default, the power of vorticity is spectrally distributed with $k^{-3}$, $k$ being the horizontal wavenumber, and the amplitude is $10^{-5}\text{ s}^{-1}$.
Now we want to construct a BarotropicModel with these
julia> model = BarotropicModel(;spectral_grid, initial_conditions, planet=still_earth);
The model contains all the parameters, but isn't initialized yet, which we can do with and then run it.
The run! command will always return the prognostic variables, which, by default, are plotted for surface relative vorticity with a unicode plot. The resolution of the plot is not necessarily representative but it lets us have a quick look at the result
Woohoo! I can see turbulence! You could pick up where this simulation stopped by simply doing run!(simulation,n_days=50) again. We didn't store any output, which you can do by run!(simulation,output=true), which will switch on NetCDF output with default settings. More options on output in NetCDF output.
As a second example, let's investigate the Galewsky et al.[1] test case for the shallow water equations with and without mountains. As the shallow water system has also only one level, we can reuse the SpectralGrid from Example 1.
Although the orography is zero, you have to pass on spectral_grid so that it can still initialize zero-arrays of the right size and element type. Awesome. This time the initial conditions should be set the the Galewsky et al.[1] zonal jet, which is already defined as
The jet sits at 45˚N with a maximum velocity of 80m/s and a perturbation as described in their paper. Now we construct a model, but this time a ShallowWaterModel
julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);
+julia> simulation = initialize!(model);
Oh yeah. That looks like the wobbly jet in their paper. Let's run it again for another 6 days but this time also store NetCDF output.
julia> run!(simulation,n_days=6,output=true)
+Weather is speedy: run 0002 100%|███████████████████████| Time: 0:00:12 (115.37 years/day)
The progress bar tells us that the simulation run got the identification "0002", meaning that data is stored in the folder /run_0002, so let's plot that data properly (and not just using UnicodePlots).
julia> using PyPlot, NCDatasets
+julia> ds = NCDataset("run_0002/output.nc");
+julia> ds["vor"]
+vor (384 × 192 × 1 × 25)
+ Datatype: Float32
+ Dimensions: lon × lat × lev × time
+ Attributes:
+ units = 1/s
+ missing_value = NaN
+ long_name = relative vorticity
+ _FillValue = NaN
Vorticity vor is stored as a 384x192x1x25 array, we may want to look at the first time step, which is the end of the previous simulation (time=6days) which we didn't store output for.
julia> vor = ds["vor"][:,:,1,1];
+julia> lat = ds["lat"][:];
+julia> lon = ds["lon"][:];
+julia> pcolormesh(lon,lat,vor')
Which looks like
You see that the unicode plot heavily coarse-grains the simulation, well it's unicode after all! And now the last time step, that means time=12days is
julia> vor = ds["vor"][:,:,1,25];
+julia> pcolormesh(lon,lat,vor')
The jet broke up into many small eddies, but the turbulence is still confined to the northern hemisphere, cool! How this may change when we add mountains (we had NoOrography above!), say Earth's orography, you may ask? Let's try it out! We create an EarthOrography struct like so
It will read the orography from file as shown, and there are some smoothing options too, but let's not change them. Same as before, create a model, initialize into a simulation, run. This time directly for 12 days so that we can compare with the last plot
julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);
+julia> simulation = initialize!(model);
+julia> run!(simulation,n_days=12,output=true)
+Weather is speedy: run 0003 100%|███████████████████████| Time: 0:00:35 (79.16 years/day)
This time the run got the id "0003", but otherwise we do as before.
Interesting! The initial conditions have zero velocity in the southern hemisphere, but still, one can see some imprint of the orography on vorticity. You can spot the coastline of Antarctica; the Andes and Greenland are somewhat visible too. Mountains also completely changed the flow after 12 days, probably not surprising!
[1] Galewsky, Scott, Polvani, 2004. An initial-value problem for testing numerical models of the global shallow-water equations, Tellus A. DOI: 10.3402/tellusa.v56i5.14436
Settings
This document was generated with Documenter.jl version 0.27.25 on Wednesday 2 August 2023. Using Julia version 1.8.5.
Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.
SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to simulate the general circulation of the atmosphere. The prognostic variables used are vorticity, divergence, temperature, surface pressure and specific humidity. Simple parameterizations represent various climate processes: Radiation, clouds, precipitation, surface fluxes, among others.
SpeedyWeather.jl defines
BarotropicModel for the 2D barotropic vorticity equation
ShallowWaterModel for the 2D shallow water equations
PrimitiveDryModel for the 3D primitive equations without humidity
PrimitiveWetModel for the 3D primitive equations with humidity
and solves these equations in spherical coordinates as described in this documentation.
MK received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022. Since 2023 this project is also funded by the National Science Foundation NSF.
Settings
This document was generated with Documenter.jl version 0.27.25 on Wednesday 2 August 2023. Using Julia version 1.8.5.
LowerTriangularMatrices is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.
This module defines LowerTriangularMatrix, a lower triangular matrix, which in contrast to LinearAlgebra.LowerTriangular does not store the entries above the diagonal. SpeedyWeather.jl uses LowerTriangularMatrix which is defined as a subtype of AbstractMatrix to store the spherical harmonic coefficients (see Spectral packing).
LowerTriangularMatrix supports two types of indexing: 1) by denoting two indices, column and row [l,m] or 2) by denoting a single index [lm]. The double index works as expected
But the single index skips the zero entries in the upper triangle, i.e.
julia> L[4]
+Float16(0.478)
which, important, is different from single indices of an AbstractMatrix
julia> Matrix(L)[4]
+Float16(0.0)
In performance-critical code a single index should be used, as this directly maps to the index of the underlying data vector. The double index is somewhat slower as it first has to be converted to the corresponding single index.
Consequently, many loops in SpeedyWeather.jl are build with the following structure
n,m = size(L)
+ij = 0
+for j in 1:m
+ for i in j:n
+ ij += 1
+ L[ij] = i+j
+ end
+end
which loops over all lower triangle entries of L::LowerTriangularMatrix and the single index ij is simply counted up. However, one could also use [i,j] as indices in the loop body or to perform any calculation (i+j here). An iterator over all entries in the lower triangle can be created by
for ij in eachindex(L)
+ # do something
+end
The setindex! functionality of matrixes will throw a BoundsError when trying to write into the upper triangle of a LowerTriangularMatrix, for example
julia> L[2,1] = 0 # valid index
+0
+
+julia> L[1,2] = 0 # invalid index in the upper triangle
+ERROR: BoundsError: attempt to access 3×3 LowerTriangularMatrix{Float32} at index [1, 2]
The LowerTriangularMatrices module's main purpose is not linear algebra, and it's implementation may not be efficient, however, many operations work as expected
Note, however, that the latter includes a conversion to Matrix, which is true for many operations, including inv or \. Hence when trying to do more sophisticated linear algebra with LowerTriangularMatrix we quickly leave lower triangular-land and go back to normal matrix-land.
L = LowerTriangularMatrix{T}(v::Vector{T},m::Int,n::Int)
A lower triangular matrix implementation that only stores the non-zero entries explicitly. L<:AbstractMatrix although in general we have L[i] != Matrix(L)[i], the former skips zero entries, tha latter includes them.
creates unit_range::UnitRange to loop over all non-zeros in the LowerTriangularMatrices provided as arguments. Checks bounds first. All LowerTriangularMatrix's need to be of the same size. Like eachindex but skips the upper triangle with zeros in L.
k = ij2k( i::Integer, # row index of matrix
+ j::Integer, # column index of matrix
+ m::Integer) # number of rows in matrix
Converts the index pair i,j of an mxn LowerTriangularMatrix L to a single index k that indexes the same element in the corresponding vector that stores only the lower triangle (the non-zero entries) of L.
SpeedyWeather.jl uses NetCDF to output the data of a simulation. The following describes the details of this and how to change the way in which the NetCDF output is written. There are many options to this available.
The output writer is a component of every Model, i.e. BarotropicModel, ShallowWaterModel, PrimitiveDryModel and PrimitiveWetModel, hence a non-default output writer can be passed on as a keyword argument to the model constructor
julia> using SpeedyWeather
+julia> spectral_grid = SpectralGrid()
+julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry)
+julia> model = PrimitiveDryModel(;spectral_grid, output=my_output_writer)
So after we have defined the grid through the SpectralGrid object we can use and change the implemented OutputWriter by passing on the following arguments
the spectral_grid has to be the first argument then the model type (Barotropic, ShallowWater, PrimitiveDry, PrimitiveWet) which helps the output writer to make default choices on which variables to output. However, we can also pass on further keyword arguments. So let's start with an example.
which will now output every hour. It is important to pass on the new output writer my_output_writer to the model constructor, otherwise it will not be part of your model and the default is used instead. Note that output_dt has to be understood as the minimum frequency or maximum output time step. Example, we run the model at a resolution of T85 and the time step is going to be 670s
This means that after 32 time steps 5h 57min and 20s will have passed where output will happen as the next time step would be >6h. The time axis of the NetCDF output will look like
This is so that we don't interpolate in time during output to hit exactly every 6 hours, but at the same time have a constant spacing in time between output time steps.
Say we want to run the model at a given horizontal resolution but want to output on another resolution, the OutputWriter takes as argument output_Grid<:AbstractFullGrid and nlat_half::Int. So for example output_Grid=FullClenshawGrid and nlat_half=48 will always interpolate onto a regular 192x95 longitude-latitude grid of 1.875˚ resolution, regardless the grid and resolution used for the model integration.
Note that by default the output is on the corresponding full of the grid used in the dynamical core so that interpolation only happens at most in the zonal direction as they share the location of the latitude rings. You can check this by
So the corresponding full grid of an OctahedralGaussianGrid is the FullGaussiangrid and the same resolution nlat_half is chosen by default in the output writer (which you can change though as shown above). Overview of the corresponding full grids
Grid
Corresponding full grid
FullGaussianGrid
FullGaussianGrid
FullClenshawGrid
FullClenshawGrid
OctahadralGaussianGrid
FullGaussianGrid
OctahedralClensawhGrid
FullClenshawGrid
HEALPixGrid
FullHEALPixGrid
OctaHEALPixGrid
FullOctaHEALPixGrid
The grids FullHEALPixGrid, FullOctaHEALPixGrid share the same latitude rings as their reduced grids, but have always as many longitude points as they are at most around the equator. These grids are not tested in the dynamical core (but you may use them experimentally) and mostly designed for output purposes.
That's easy by passing on path="/my/favourite/path/" and the folder run_* with * the identification of the run (that's the id keyword, which can be manually set but is also automatically determined as a number counting up depending on which folders already exist) will be created within.
which will be used instead of a 4 digit number like 0001, 0002 which is automatically determined if id is not provided. You will see the id of the run in the progress bar
Weather is speedy: run diffusion_test 100%|███████████████████████| Time: 0:00:12 (19.20 years/day)
and the run folder, here run_diffusion_test, is also named accordingly
Further options are described in the OutputWriter docstring, (also accessible via julia>?OutputWriter for example). Note that some fields are actual options, but others are derived from the options you provided or are arrays/objects the output writer needs, but shouldn't be passed on by the user. The actual options are declared as [OPTION] in the following
Missing docstring.
Missing docstring for OutputWriter. Check Documenter's build log for details.
Settings
This document was generated with Documenter.jl version 0.27.25 on Wednesday 2 August 2023. Using Julia version 1.8.5.
This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmosphere. Every section is followed by a brief description of implementation details.
The primitive equations are a hydrostatic approximation of the compressible Navier-Stokes equations for an ideal gas on a rotating sphere. We largely follow the idealised spectral dynamical core developed by GFDL[1] and documented therein[2].
The primitive equations solved by SpeedyWeather.jl for relative vorticity $\zeta$, divergence $\mathcal{D}$, logarithm of surface pressure $\ln p_s$, temperature $T$ and specific humidity $q$ are
with velocity $\mathbf{u} = (u,v)$, rotated velocity $\mathbf{u}_\perp = (v,-u)$, Coriolis parameter $f$, $W$ the vertical advection operator, dry air gas constant $R_d$, virtual temperature $T_v$, geopotential $\Phi$, pressure $p$, thermodynamic $\kappa = R\_d/c_p$ with $c_p$ the heat capacity at constant pressure. Horizontal hyper diffusion of the form $(-1)^{n+1}\nu\nabla^{2n}$ with coefficient $\nu$ and power $n$ is added for every variable that is advected, meaning $\zeta, \mathcal{D}, T, q$, but left out here for clarity, see Horizontal diffusion.
The parameterizations for the tendencies of $u,v,T,q$ from physical processes are denoted as $\mathcal{P}_\mathbf{u} = (\mathcal{P}_u, \mathcal{P}_v), \mathcal{P}_T, \mathcal{P}_q$ and are further described in the corresponding sections, see Parameterizations.
SpeedyWeather.jl implements a PrimitiveWet and a PrimitiveDry dynamical core. For a dry atmosphere, we have $q = 0$ and the virtual temperature $T_v = T$ equals the temperature (often called absolute to distinguish from the virtual temperature). The terms in the primitive equations and their discretizations are discussed in the following sections.
Virtual temperature is the temperature dry air would need to have to be as light as moist air. It is used in the dynamical core to include the effect of humidity on the density while replacing density through the ideal gas law with temperature.
We assume the atmosphere to be composed of two ideal gases: Dry air and water vapour. Given a specific humidity $q$ both gases mix, their pressures $p_d$, $p_w$ ($d$ for dry, $w$ for water vapour), and densities $\rho_d, \rho_w$ add in a given air parcel that has temperature $T$. The ideal gas law then holds for both gases
\[\begin{aligned}
+p_d &= \rho_d R_d T \\
+p_w &= \rho_w R_w T \\
+\end{aligned}\]
with the respective specific gas constants $R_d = R/m_d$ and $R_w = R/m_w$ obtained from the univeral gas constant $R$ divided by the molecular masses of the gas. The total pressure $p$ in the air parcel is
\[p = p_d + p_w = (\rho_d R_d + \rho_w R_w)T\]
We ultimately want to replace the density $\rho = \rho_w + \rho_d$ in the dynamical core, using the ideal gas law, with the temperature $T$, so that we never have to calculate the density explicitly. However, in order to not deal with two densities (dry air and water vapour) we would like to replace temperature with a virtual temperature that includes the effect of humidity on the density. So, whereever we use the ideal gas law to replace density with temperature, we would use the virtual temperature, which is a function of the absolute temperature and specific humidity, instead. A higher specific humidity in an air parcel lowers the density as water vapour is lighter than dry air. Consequently, the virtual temperature of moist air is higher than its absolute temperature because warmer air is lighter too at constant pressure. We therefore think of the virtual temperature as the temperature dry air would need to have to be as light as moist air.
Starting with the last equation, with some manipulation we can write the ideal gas law as total density $rho$ times a gas constant times the virtual temperature that is supposed to be a function of absolute temperature, humidity and some constants
as some constant that is positive for water vapour being lighter than dry air ($\tfrac{R_d}{R_w} = \tfrac{m_w}{m_d} < 1$) and
\[q = \frac{\rho_w}{\rho_w + \rho_d}\]
as the specific humidity. Given temperature $T$ and specific humidity $q$, we can therefore calculate the virtual temperature $T_v$ as
\[T_v = (1 + \mu q)T\]
For completeness we want to mention here that the above product, because it is a product of two variables $q,T$ has to be computed in grid-point space, see [Spectral Transform]. To obtain an approximation to the virtual temperature in spectral space without expensive transforms one can linearize
\[T_v = T + \mu q\bar{T}\]
With a global constant temperature $\bar{T}$, for example obtained from the $l=m=0$ mode, $\bar{T} = T_{0,0}\frac{1}{\sqrt{4\pi}}$ but depending on the normalization of the spherical harmonics that factor needs adjustment.
In the hydrostatic approximation the vertical momentum equation becomes
\[\frac{\partial p}{\partial z} = -\rho g,\]
meaning that the (negative) vertical pressure gradient is given by the density in that layer times the gravitational acceleration. The heavier the fluid the more the pressure will increase below. Inserting the ideal gas law
Note that we use the Virtual temperature here as we replaced the density through the ideal gas law with temperature. Given a vertical temperature profile $T_v$ and the (constant) surface geopotential $\Phi_s = gz_s$ where $z_s$ is the orography, we can integrate this equation from the surface to the top to obtain $\Phi_k$ on every layer $k$. The surface is at $k = N+\tfrac{1}{2}$ (see Vertical coordinates) with $N$ vertical levels. We can integrate the geopotential onto half levels as
RingGrids is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.
RingGrids defines several iso-latitude grids, which are mathematically described in the section on Grids. In brief, they include the regular latitude-longitude grids (here called FullClenshawGrid) as well as grids which latitudes are shifted to the Gaussian latitudes and reduced grids, meaning that they have a decreasing number of longitudinal points towards the poles to be more equal-area than full grids.
RingGrids defines and exports the following grids:
full grids: FullClenshawGrid, FullGaussianGrid, FullHEALPix, and FullOctaHEALPix
reduced grids: OctahedralGaussianGrid, OctahedralClenshawGrid, OctaHEALPixGrid and HEALPixGrid
The following explanation of how to use these can be mostly applied to any of them, however, there are certain functions that are not defined, e.g. the full grids can be trivially converted to a Matrix (i.e. they are rectangular grids) but not the OctahedralGaussianGrid.
What is a ring?
We use the term ring, short for iso-latitude ring, to refer to a sequence of grid points that all share the same latitude. A latitude-longitude grid is a ring grid, as it organises its grid-points into rings. However, other grids, like the cubed-sphere are not based on iso-latitude rings. SpeedyWeather.jl only works with ring grids because its a requirement for the Spherical Harmonic Transform to be efficient. See Grids.
Every grid in RingGrids has a grid.data field, which is a vector containing the data on the grid. The grid points are unravelled west to east then north to south, meaning that it starts at 90˚N and 0˚E then walks eastward for 360˚ before jumping on the next latitude ring further south, this way circling around the sphere till reaching the south pole. This may also be called ring order.
Data in a Matrix which follows this ring order can be put on a FullGaussianGrid like so
using SpeedyWeather.RingGrids
+map = randn(Float32,8,4)
A full Gaussian grid has always $2N$ x $N$ grid points, but a FullClenshawGrid has $2N$ x $N-1$, if those dimensions don't match, the creation will throw an error. To reobtain the data from a grid, you can access its data field which returns a normal Vector
Which can be reshaped to reobtain map from above. Alternatively you can Matrix(grid) to do this in one step
map == Matrix(FullGaussianGrid(map))
true
You can also use zeros,ones,rand,randn to create a grid, whereby nlat_half, i.e. the number of latitude rings on one hemisphere, Equator included, is used as a resolution parameter and here as a second argument.
As only the full grids can be reshaped into a matrix, the underlying data structure of any AbstractGrid is a vector. As shown in the examples above, one can therefore inspect the data as if it was a vector. But as that data has, through its <:AbstractGrid type, all the geometric information available to plot it on a map, RingGrids also exports plot function, based on UnicodePlots' heatmap.
All RingGrids have a single index ij which follows the ring order. While this is obviously not super exciting here are some examples how to make better use of the information that the data sits on a grid.
We obtain the latitudes of the rings of a grid by calling get_latd (get_lond is only defined for full grids, or use get_latdlonds for latitudes, longitudes per grid point not per ring)
Now we could calculate Coriolis and add it on the grid as follows
rotation = 7.29e-5 # angular frequency of Earth's rotation [rad/s]
+coriolis = 2rotation*sind.(latd) # vector of coriolis parameters per latitude ring
+
+rings = eachring(grid)
+for (j,ring) in enumerate(rings)
+ f = coriolis[j]
+ for ij in ring
+ grid[ij] += f
+ end
+end
eachring creates a vector of UnitRange indices, such that we can loop over the ring index j (j=1 being closest to the North pole) pull the coriolis parameter at that latitude and then loop over all in-ring indices i (changing longitudes) to do something on the grid. Something similar can be done to scale/unscale with the cosine of latitude for example. We can always loop over all grid-points like so
In most cases we will want to use RingGrids so that our data directly comes with the geometric information of where the grid-point is one the sphere. We have seen how to use get_latd, get_lond, ... for that above. This information generally can also be used to interpolate our data from grid to another or to request an interpolated value on some coordinates. Using our data on grid which is an OctahedralGaussianGrid from above we can use the interpolate function to get it onto a FullGaussianGrid (or any other grid for purpose)
By default this will linearly interpolate (it's an Anvil interpolator, see below) onto a grid with the same nlat_half, but we can also coarse-grain or fine-grain by specifying nlat_half directly as 2nd argument
So we got from an 8-ring OctahedralGaussianGrid{Float16} to a 12-ring FullGaussianGrid{Float64}, so it did a conversion from Float16 to Float64 on the fly too, because the default precision is Float64 unless specified. interpolate(FullGaussianGrid{Float16},6,grid) would have interpolated onto a grid with element type Float16.
One can also interpolate onto a given coordinate ˚N, ˚E like so
interpolate(30.0,10.0,grid)
-1.2619363f0
we interpolated the data from grid onto 30˚N, 10˚E. To do this simultaneously for many coordinates they can be packed into a vector too
Every time an interpolation like interpolate(30.0,10.0,grid) is called, several things happen, which are important to understand to know how to get the fastest interpolation out of this module in a given situation. Under the hood an interpolation takes three arguments
output vector
input grid
interpolator
The output vector is just an array into which the interpolated data is written, providing this prevents unnecessary allocation of memory in case the destination array of the interpolation already exists. The input grid contains the data which is subject to interpolation, it must come on a ring grid, however, its coordinate information is actually already in the interpolator. The interpolator knows about the geometry of the grid the data is coming on and the coordinates it is supposed to interpolate onto. It has therefore precalculated the indices that are needed to access the right data on the input grid and the weights it needs to apply in the actual interpolation operation. The only thing it does not know is the actual data values of that grid. So in the case you want to interpolate from grid A to grid B many times, you can just reuse the same interpolator. If you want to change the coordinates of the output grid but its total number of points remain constants then you can update the locator inside the interpolator and only else you will need to create a new interpolator. Let's look at this in practice. Say we have two grids an want to interpolate between them
Now we have created an interpolator interp which knows about the geometry where to interpolate from and the coordinates there to interpolate to. It is also initialized, meaning it has precomputed the indices to of grid_in that are supposed to be used. It just does not know about the data of grid_in (and neither of grid_out which will be overwritten anyway). We can now do
which is identical to interpolate(grid_out,grid_in) but you can reuse interp for other data. The interpolation can also handle various element types (the interpolator interp does not have to be updated for this either)
and we have converted data from a HEALPixGrid{Float64} (Float64 is always default if not specified) to a FullClenshawGrid{Float16} including the type conversion Float64-Float16 on the fly. Technically there are three data types and their combinations possible: The input data will come with a type, the output array has an element type and the interpolator has precomputed weights with a given type. Say we want to go from Float16 data on an OctahedralGaussianGrid to Float16 on a FullClenshawGrid but using Float32 precision for the interpolation itself, we would do this by
As a last example we want to illustrate a situation where we would always want to interpolate onto 10 coordinates, but their locations may change. In order to avoid recreating an interpolator object we would do (AnvilInterpolator is described in Anvil interpolator)
npoints = 10 # number of coordinates to interpolate onto
+interp = AnvilInterpolator(Float32,HEALPixGrid,24,npoints)
with the first argument being the number format used during interpolation, then the input grid type, its resolution in terms of nlat_half and then the number of points to interpolate onto. However, interp is not yet initialized as it does not know about the destination coordinates yet. Let's define them, but note that we already decided there's only 10 of them above.
but allows for a reuse of the interpolator. Note that the two output arrays are not exactly identical because we manually set our interpolator interp to use Float32 for the interpolation whereas the default is Float64.
Currently the only interpolator implemented is a 4-point bilinear interpolator, which schematically works as follows. Anvil interpolation is the bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate.
0..............1 # fraction of distance Δab between a,b
+ |< Δab >|
+
+0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x
+.Δy |
+. |
+.v x
+. |
+1 c ------ o ---- d
+
+ |< Δcd >|
+ 0...............1 # fraction of distance Δcd between c,d
+
+^ fraction of distance Δy between a-b and c-d.
This interpolation is chosen as by definition of the ring grids, a and b share the same latitude, so do c and d, but the longitudes can be different for all four, a,b,c,d.
abstract type AbstractFullGrid{T} <: AbstractGrid{T} end
An AbstractFullGrid is a horizontal grid with a constant number of longitude points across latitude rings. Different latitudes can be used, Gaussian latitudes, equi-angle latitdes, or others.
abstract type AbstractGrid{T} <: AbstractVector{T} end
The abstract supertype for all spatial grids on the sphere supported by SpeedyWeather.jl. Every new grid has to be of the form
abstract type AbstractGridClass{T} <: AbstractGrid{T} end
+struct MyNewGrid{T} <: AbstractGridClass{T}
+ data::Vector{T} # all grid points unravelled into a vector
+ nlat_half::Int # resolution: latitude rings on one hemisphere (Equator incl)
+end
MyNewGrid should belong to a grid class like AbstractFullGrid, AbstractOctahedralGrid or AbstractHEALPixGrid (that already exist but you may introduce a new class of grids) that share certain features such as the number of longitude points per latitude ring and indexing, but may have different latitudes or offset rotations. Each new grid Grid (or grid class) then has to implement the following methods (as an example, see octahedral.jl)
Fundamental grid properties getnpoints # total number of grid points nlatodd # does the grid have an odd number of latitude rings? getnlat # total number of latitude rings getnlat_half # number of latitude rings on one hemisphere incl Equator
Indexing getnlonmax # maximum number of longitudes points (at the Equator) getnlonperring # number of longitudes on ring j eachindexinring # a unit range that indexes all longitude points on a ring
Coordinates getcolat # vector of colatitudes (radians) getcolatlon # vectors of colatitudes, longitudes (both radians)
Spectral truncation truncationorder # linear, quadratic, cubic = 1,2,3 for grid gettruncation # spectral truncation given a grid resolution get_resolution # grid resolution given a spectral truncation
Quadrature weights and solid angles getquadratureweights # = sinθ Δθ for grid points on ring j for meridional integration getsolidangle # = sinθ Δθ Δϕ, solid angle of grid points on ring j
abstract type AbstractHEALPixGrid{T} <: AbstractGrid{T} end
An AbstractHEALPixGrid is a horizontal grid similar to the standard HEALPixGrid, but different latitudes can be used, the default HEALPix latitudes or others.
Supertype of every Locator, which locates the indices on a grid to be used to perform an interpolation. E.g. AnvilLocator uses a 4-point stencil for every new coordinate to interpolate onto. Higher order stencils can be implemented by defining OtherLocator <: AbstractLocactor.
abstract type AbstractOctaHEALPixGrid{T} <: AbstractGrid{T} end
An AbstractOctaHEALPixGrid is a horizontal grid similar to the standard OctahedralGrid, but the number of points in the ring closest to the Poles starts from 4 instead of 20, and the longitude of the first point in each ring is shifted as in HEALPixGrid. Also, different latitudes can be used.
abstract type AbstractOctahedralGrid{T} <: AbstractGrid{T} end
An AbstractOctahedralGrid is a horizontal grid with 16+4i longitude points on the latitude ring i starting with i=1 around the pole. Different latitudes can be used, Gaussian latitudes, equi-angle latitdes, or others.
Contains arrays that locates grid points of a given field to be uses in an interpolation and their weights. This Locator is a 4-point average in an anvil-shaped grid-point arrangement between two latitude rings.
L = AnvilLocator( ::Type{NF}, # number format used for the interpolation
+ npoints::Integer # number of points to interpolate onto
+ ) where {NF<:AbstractFloat}
Zero generator function for the 4-point average AnvilLocator. Use update_locator! to update the grid indices used for interpolation and their weights. The number format NF is the format used for the calculations within the interpolation, the input data and/or output data formats may differ.
A FullClenshawGrid is a regular latitude-longitude grid with an odd number of nlat equi-spaced latitudes, the central latitude ring is on the Equator. The same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field data that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
A full Gaussian grid is a regular latitude-longitude grid that uses nlat Gaussian latitudes, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
A full HEALPix grid is a regular latitude-longitude grid that uses nlat latitudes from the HEALPix grid, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
A full OctaHEALPix grid is a regular latitude-longitude grid that uses nlat OctaHEALPix latitudes, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
G = GridGeometry( Grid::Type{<:AbstractGrid},
+ nlat_half::Integer)
Precomputed arrays describing the geometry of the Grid with resolution nlat_half. Contains latitudes and longitudes of grid points, their ring index j and their unravelled indices ij.
A HEALPix grid with 12 faces, each nsidexnside grid points, each covering the same area. The number of latitude rings on one hemisphere (incl Equator) nlat_half is used as resolution parameter. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
A OctaHEALPix grid with 4 base faces, each nlat_halfxnlat_half grid points, each covering the same area. The values of all grid points are stored in a vector field data that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
An Octahedral Clenshaw grid that uses nlat equi-spaced latitudes. Like FullClenshawGrid, the central latitude ring is on the Equator. Like OctahedralGaussianGrid, the number of longitude points per latitude ring decreases towards the poles. Starting with 20 equi-spaced longitude points (starting at 0˚E) on the rings around the poles, each latitude ring towards the equator has consecuitively 4 more points, one for each face of the octahedron. E.g. 20,24,28,32,...nlon-4,nlon,nlon,nlon-4,...,32,28,24,20. The maximum number of longitue points is nlon. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
An Octahedral Gaussian grid that uses nlat Gaussian latitudes, but a decreasing number of longitude points per latitude ring towards the poles. Starting with 20 equi-spaced longitude points (starting at 0˚E) on the rings around the poles, each latitude ring towards the equator has consecuitively 4 more points, one for each face of the octahedron. E.g. 20,24,28,32,...nlon-4,nlon,nlon,nlon-4,...,32,28,24,20. The maximum number of longitue points is nlon. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
Sorts the gridpoints in G into the matrix M without interpolation. Every quadrant of the grid G is rotated as specified in quadrant_rotation, 0 is no rotation, 1 is 90˚ clockwise, 2 is 180˚ etc. Grid quadrants are counted eastward starting from 0˚E. The grid quadrants are moved into the matrix quadrant (i,j) as specified. Defaults are equivalent to centered at 0˚E and a rotation such that the North Pole is at M's midpoint.
Sorts the gridpoints in G into the matrix M without interpolation. Every quadrant of the grid G is rotated as specified in quadrant_rotation, 0 is no rotation, 1 is 90˚ clockwise, 2 is 180˚ etc. Grid quadrants are counted eastward starting from 0˚E. The grid quadrants are moved into the matrix quadrant (i,j) as specified. Defaults are equivalent to centered at 0˚E and a rotation such that the North Pole is at M's midpoint.
Like Matrix!(::AbstractMatrix,::OctaHEALPixGrid) but for simultaneous processing of tuples ((M1,G1),(M2,G2),...) with matrices Mi and grids Gi. All matrices and grids have to be of the same size respectively.
Like Matrix!(::AbstractMatrix,::OctahedralClenshawGrid) but for simultaneous processing of tuples ((M1,G1),(M2,G2),...) with matrices Mi and grids Gi. All matrices and grids have to be of the same size respectively.
The bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate. See schematic:
0..............1 # fraction of distance Δab between a,b
+ |< Δab >|
+
+ 0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x
+ .Δy |
+ . |
+ .v x
+ . |
+ 1 c ------ o ---- d
+
+ |< Δcd >|
+ 0...............1 # fraction of distance Δcd between c,d
Computes the average at the North and South pole from a given grid A and it's precomputed ring indices rings. The North pole average is an equally weighted average of all grid points on the northern-most ring. Similar for the South pole.
Returns a vector nlons for the number of longitude points per latitude ring, north to south. Provide grid Grid and its resolution parameter nlat_half. For both_hemisphere==false only the northern hemisphere (incl Equator) is returned.
This document was generated with Documenter.jl version 0.27.25 on Wednesday 2 August 2023. Using Julia version 1.8.5.
diff --git a/dev/search_index.js b/dev/search_index.js
new file mode 100644
index 000000000..06ae2c750
--- /dev/null
+++ b/dev/search_index.js
@@ -0,0 +1,3 @@
+var documenterSearchIndex = {"docs":
+[{"location":"parameterizations/#parameterizations","page":"Parameterizations","title":"Parameterizations","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmosphere. Every section is followed by a brief description of implementation details.","category":"page"},{"location":"parameterizations/#Convection","page":"Parameterizations","title":"Convection","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Large-scale-condensation","page":"Parameterizations","title":"Large-scale condensation","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Clouds","page":"Parameterizations","title":"Clouds","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Short-wave-radiation","page":"Parameterizations","title":"Short-wave radiation","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Long-wave-radiation","page":"Parameterizations","title":"Long-wave radiation","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Surface-fluxes-of-momentum-and-energy","page":"Parameterizations","title":"Surface fluxes of momentum and energy","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Vertical-diffusion","page":"Parameterizations","title":"Vertical diffusion","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"development/#Development-notes","page":"Development notes","title":"Development notes","text":"","category":"section"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To run tests, from the path of your local clone of the repository do:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=. -e 'import Pkg; Pkg.test()'","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To install dependencies:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project -e 'import Pkg; Pkg.instantiate()`","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"then opening:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"you are able to using SpeedyWeather.","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To generate the docs:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=docs -e 'import Pkg; Pkg.develop(path=\".\"); Pkg.instantiate()'\njulia --project=docs docs/make.jl","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"If the docs are generated successfully, you view them by opening docs/build/index.html in your favorite browser.","category":"page"},{"location":"barotropic/#Barotropic-vorticity-model","page":"Barotropic model","title":"Barotropic vorticity model","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The barotropic vorticity model describes the evolution of a 2D non-divergent flow with velocity components mathbfu = (uv) through self-advection, forces and dissipation. Due to the non-divergent nature of the flow, it can be described by (the vertical component) of the relative vorticity zeta = nabla times mathbfu.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The dynamical core presented here to solve the barotropic vorticity equations largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2].","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Many concepts of the Shallow water model and the Primitive equation model are similar, such that for example horizontal diffusion and the Time integration are only explained here.","category":"page"},{"location":"barotropic/#Barotropic-vorticity-equation","page":"Barotropic model","title":"Barotropic vorticity equation","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity zeta with advection, Coriolis force, forcing and diffusion in a single global layer on the sphere.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"fracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) =\nnabla times mathbfF + (-1)^n+1nunabla^2nzeta","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"We denote timet, velocity vector mathbfu = (u v), Coriolis parameter f, and hyperdiffusion (-1)^n+1 nu nabla^2n zeta (n is the hyperdiffusion order, see Horizontal diffusion). We also include a forcing vector mathbfF = (F_uF_v) which acts on the zonal velocity u and the meridional velocity v and hence its curl nabla times mathbfF is a tendency for relative vorticity zeta.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Starting with some relative vorticity zeta, the Laplacian is inverted to obtain the stream function Psi","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Psi = nabla^-2zeta","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The zonal velocity u and meridional velocity v are then the (negative) meridional gradient and zonal gradient of Psi","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"beginaligned\nu = -frac1R fracpartial Psipartial theta \nv = frac1Rcos(theta) fracpartial Psipartial phi \nendaligned","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"which is described in Derivatives in spherical coordinates. Using u and v we can then advect the absolute vorticity zeta + f. In order to avoid to calculate both the curl and the divergence of a flux we rewrite the barotropic vorticity equation as","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"fracpartial zetapartial t =\nnabla times (mathbfF + mathbfu_perp(zeta + f)) + (-1)^n+1nunabla^2nzeta","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"with mathbfu_perp = (v-u) the rotated velocity vector, because -nablacdotmathbfu = nabla times mathbfu_perp. This is the form that is solved in the BarotropicModel, as outlined in the following section.","category":"page"},{"location":"barotropic/#Algorithm","page":"Barotropic model","title":"Algorithm","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"We briefly outline the algorithm that SpeedyWeather.jl uses in order to integrate the barotropic vorticity equation. As an initial step","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"0. Start with initial conditions of zeta_lm in spectral space and transform this model state to grid-point space:","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Invert the Laplacian of vorticity zeta_lm to obtain the stream function Psi_lm in spectral space\nobtain zonal velocity (cos(theta)u)_lm through a Meridional derivative\nobtain meridional velocity (cos(theta)v)_lm through a Zonal derivative\nTransform zonal and meridional velocity (cos(theta)u)_lm, (cos(theta)v)_lm to grid-point space\nUnscale the cos(theta) factor to obtain uv\nTransform zeta_lm to zeta in grid-point space","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Now loop over","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Compute the forcing vector mathbfF = (F_uF_v) for u and v\nMultiply uv with zeta+f in grid-point space\nAdd A = F_u + v(zeta + f) and B = F_v - u(zeta + f)\nTransform these vector components to spectral space A_lm, B_lm\nCompute the curl of (AB)_lm in spectral space which is the tendency of zeta_lm\nCompute the horizontal diffusion based on that tendency\nCompute a leapfrog time step as described in Time integration with a Robert-Asselin and Williams filter\nTransform the new spectral state of zeta_lm to grid-point uvzeta as described in 0.\nPossibly do some output\nRepeat from 1.","category":"page"},{"location":"barotropic/#diffusion","page":"Barotropic model","title":"Horizontal diffusion","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"In SpeedyWeather.jl we use hyerdiffusion through an n-th power Laplacian (-1)^n+1nabla^2n (hyper when n1) which can be implemented as a multiplication of the spectral coefficients Psi_lm with (-l(l+1))^nR^-2n (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the (-l(l+1))^nR^-2n can be precomputed. Note the sign change (-1)^n+1 here is such that the dissipative nature of the diffusion operator is retained for n odd and even.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step Delta t of variable zeta to obtain from time steps i-1 and i, the next time step i+1","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"zeta_i+1 = zeta_i-1 + 2Delta t dzeta","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"with dzeta being some tendency evaluated from zeta_i. Now we want to add a diffusion term (-1)^n+1nu nabla^2nzeta with coefficient nu, which however, is implicitly calculated from zeta_i+1, then","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"zeta_i+1 = zeta_i-1 + 2Delta t (dzeta + (-1)^n+1 nunabla^2nzeta_i+1)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"As the application of (-1)^n+1nunabla^2n is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"zeta_i+1 = fraczeta_i-1 + 2Delta t dzeta1 - 2Delta (-1)^n+1nunabla^2n","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"and expand the numerator to","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"zeta_i+1 = zeta_i-1 + 2Delta t fracdzeta + (-1)^n+1 nunabla^2nzeta_i-11 - 2Delta t (-1)^n+1nu nabla^2n","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Hence the diffusion can be applied implicitly by updating the tendency dzeta as","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"dzeta to fracdzeta + (-1)^n+1nunabla^2nzeta_i-11 - 2Delta t nu nabla^2n","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"which only depends on zeta_i-1. Now let D_textexplicit = (-1)^n+1nunabla^2n be the explicit part and D_textimplicit = 1 - (-1)^n+1 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are D_textimplicit = 1 - 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic lm we do","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"dzeta to D_textimplicit^-1(dzeta + D_textexplicitzeta_i-1)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power n.","category":"page"},{"location":"barotropic/#Normalization-of-diffusion","page":"Barotropic model","title":"Normalization of diffusion","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"In physics, the Laplace operator nabla^2 is often used to represent diffusion due to viscosity in a fluid or diffusion that needs to be added to retain numerical stability. In both cases, the coefficient is nu of units textm^2texts^-1 and the full operator reads as nu nabla^2 with units (textm^2texts^-1)(textm^-2) = texts^-1. This motivates us to normalize the Laplace operator by a constant of units textm^-2 and the coefficient by its inverse such that it becomes a damping timescale of unit texts^-1. Given the application in spectral space we decide to normalize by the largest eigenvalue -l_textmax(l_textmax+1) such that all entries in the discrete spectral Laplace operator are in 01. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized coefficient nu^* = l_textmax(l_textmax+1)nu (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have ","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"D^textexplicit_lm = -nu^* fracl(l+1)l_textmax(l_textmax+1)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"and the hyper-Laplacian of power n follows as","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"D^textexplicitn_lm = -nu^* left(fracl(l+1)l_textmax(l_textmax+1)right)^n","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"and the implicit part is accordingly D^textimplicitn_lm = 1 - 2Delta t D^textexplicitn_lm. Note that the diffusion time scale nu^* is then also scaled by the radius, see next section.","category":"page"},{"location":"barotropic/#scaling","page":"Barotropic model","title":"Radius scaling","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Similar to a non-dimensionalization of the equations, SpeedyWeather.jl scales the barotropic vorticity equation with R^2 to obtain normalized gradient operators as follows. A scaling for vorticity zeta and stream function Psi is used that is","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"tildezeta = zeta R tildePsi = Psi R^-1","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"This is also convenient as vorticity is often 10^-5text s^-1 in the atmosphere, but the streamfunction more like 10^5text m^2text s^-1 and so this scaling brings both closer to 1 with a typical radius of the Earth of 6371km. The inversion of the Laplacians in order to obtain Psi from zeta therefore becomes","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"tildezeta = tildenabla^2 tildePsi","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"where the dimensionless gradients simply omit the scaling with 1R, tildenabla = Rnabla. The Barotropic vorticity equation scaled with R^2 is","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"partial_tildettildezeta + tildenabla cdot (mathbfu(tildezeta + tildef)) =\nnabla times tildemathbfF + (-1)^n+1tildenutildenabla^2ntildezeta","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"with","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"tildet = tR^-1, the scaled time t\nmathbfu = (uv), the velocity vector (no scaling applied)\ntildef = fR, the scaled Coriolis parameter f\ntildemathbfF = RmathbfF, the scaled forcing vector mathbfF\ntildenu = nu^* R, the scaled diffusion coefficient nu^*, which itself is normalized to a damping time scale, see Normalization of diffusion.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"So scaling with the radius squared means we can use dimensionless operators, however, this comes at the cost of needing to deal with both a time step in seconds as well as a scaled time step in seconds per meter, which can be confusing. Furthermore, some constants like Coriolis or the diffusion coefficient need to be scaled too during initialisation, which may be confusing too because values are not what users expect them to be. SpeedyWeather.jl follows the logic that the scaling to the prognostic variables is only applied just before the time integration and variables are unscaled for output and after the time integration finished. That way, the scaling is hidden as much as possible from the user. In hopefully many other cases it is clearly denoted that a variable or constant is scaled.","category":"page"},{"location":"barotropic/#leapfrog","page":"Barotropic model","title":"Time integration","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"SpeedyWeather.jl is based on the Leapfrog time integration, which, for relative vorticity zeta, is in its simplest form","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"fraczeta_i+1 - zeta_i-12Delta t = RHS(zeta_i)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"meaning we step from the previous time step i-1, leapfrogging over the current time stepi to the next time step i+1 by evaluating the tendencies on the right-hand side RHS at the current time step i. The time stepping is done in spectral space. Once the right-hand side RHS is evaluated, leapfrogging is a linear operation, meaning that its simply applied to every spectral coefficient zeta_lm as one would evaluate it on every grid point in grid-point models.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"For the Leapfrog time integration two time steps of the prognostic variables have to be stored, i-1 and i. Time step i is used to evaluate the tendencies which are then added to i-1 in a step that also swaps the indices for the next time step i to i-1 and i+1 to i, so that no additional memory than two time steps have to be stored at the same time.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The Leapfrog time integration has to be initialised with an Euler forward step in order to have a second time step i+1 available when starting from i to actually leapfrog over. SpeedyWeather.jl therefore does two initial time steps that are different from the leapfrog time steps that follow and that have been described above.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"an Euler forward step with Delta t2, then\none leapfrog time step with Delta t, then\nleapfrog with 2 Delta t till the end","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"This is particularly done in a way that after 2. we have t=0 at i-1 and t=Delta t at i available so that 3. can start the leapfrogging without any offset from the intuitive spacing 0Delta t 2Delta t 3Delta t. The following schematic can be useful","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":" time at step i-1 time at step i time step at i+1\nInitial conditions t = 0 \n1: Euler (T) quad t = 0 t=Delta t2 \n2: Leapfrog with Delta t t = 0 (T) quad t = Delta t2 t = Delta t\n3 to n: Leapfrog with 2Delta t t-Delta t (T) qquad quad quad t t+Delta t","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The time step that is used to evaluate the tendencies is denoted with (T). It is always the time step furthest in time that is available.","category":"page"},{"location":"barotropic/#Robert-Asselin-and-Williams-filter","page":"Barotropic model","title":"Robert-Asselin and Williams filter","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The standard leapfrog time integration is often combined with a Robert-Asselin filter[Robert66][Asselin72] to dampen a computational mode. The idea is to start with a standard leapfrog step to obtain the next time step i+1 but then to correct the current time step i by applying a filter which dampens the computational mode. The filter looks like a discrete Laplacian in time with a (1 -2 1) stencil, and so, maybe unsurprisingly, is efficient to filter out a \"grid-scale oscillation\" in time, aka the computational mode. Let v be the unfiltered variable and u be the filtered variable, F the right-hand side tendency, then the standard leapfrog step is","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"v_i+1 = u_i-1 + 2Delta tF(v_i)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Meaning we start with a filtered variable u at the previous time step i-1, evaluate the tendency F(v_i) based on the current time step i to obtain an unfiltered next time step v_i+1. We then filter the current time step i (which will become i-1 on the next iteration)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"u_i = v_i + fracnu2(v_i+1 - 2v_i + u_i-1)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"by adding a discrete Laplacian with coefficient tfracnu2 to it, evaluated from the available filtered and unfiltered time steps centred around i: v_i-1 is not available anymore because it was overwritten by the filtering at the previous iteration, u_i u_i+1 are not filtered yet when applying the Laplacian. The filter parameter nu is typically chosen between 0.01-0.2, with stronger filtering for higher values.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Williams[Williams2009] then proposed an additional filter step to regain accuracy that is otherwise lost with a strong Robert-Asselin filter[Amezcua2011][Williams2011]. Now let w be unfiltered, v be once filtered, and u twice filtered, then","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"beginaligned\nw_i+1 = u_i-1 + 2Delta tF(v_i) \nu_i = v_i + fracnualpha2(w_i+1 - 2v_i + u_i-1) \nv_i+1 = w_i+1 - fracnu(1-alpha)2(w_i+1 - 2v_i + u_i-1)\nendaligned","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"with the Williams filter parameter alpha in 051. For alpha=1 we're back with the Robert-Asselin filter (the first two lines).","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The Laplacian in the parentheses is often called a displacement, meaning that the filtered value is displaced (or corrected) in the direction of the two surrounding time steps. The Williams filter now also applies the same displacement, but in the opposite direction to the next time step i+1 as a correction step (line 3 above) for a once-filtered value v_i+1 which will then be twice-filtered by the Robert-Asselin filter on the next iteration. For more details see the referenced publications.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The initial Euler step (see Time integration, Table) is not filtered. Both the the Robert-Asselin and Williams filter are then switched on for all following leapfrog time steps.","category":"page"},{"location":"barotropic/#References","page":"Barotropic model","title":"References","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[1]: Geophysical Fluid Dynamics Laboratory, Idealized models with spectral dynamics","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[2]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[Robert66]: Robert, André. “The Integration of a Low Order Spectral Form of the Primitive Meteorological Equations.” Journal of the Meteorological Society of Japan 44 (1966): 237-245.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[Asselin72]: ASSELIN, R., 1972: Frequency Filter for Time Integrations. Mon. Wea. Rev., 100, 487–490, doi:10.1175/1520-0493(1972)100<0487:FFFTI>2.3.CO;2","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[Williams2009]: Williams, P. D., 2009: A Proposed Modification to the Robert–Asselin Time Filter. Mon. Wea. Rev., 137, 2538–2546, 10.1175/2009MWR2724.1.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[Amezcua2011]: Amezcua, J., E. Kalnay, and P. D. Williams, 2011: The Effects of the RAW Filter on the Climatology and Forecast Skill of the SPEEDY Model. Mon. Wea. Rev., 139, 608–619, doi:10.1175/2010MWR3530.1. ","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[Williams2011]: Williams, P. D., 2011: The RAW Filter: An Improvement to the Robert–Asselin Filter in Semi-Implicit Integrations. Mon. Wea. Rev., 139, 1996–2007, doi:10.1175/2010MWR3601.1. ","category":"page"},{"location":"installation/#Installation","page":"Installation","title":"Installation","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"SpeedyWeather.jl is registered in the Julia Registry. In most cases just open the Julia REPL and type","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia> using Pkg\njulia> Pkg.add(\"SpeedyWeather\")","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"or, equivalently, (] opens the package manager)","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia>] add SpeedyWeather","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"which will automatically install the latest release and all necessary dependencies. If you run into any troubles please raise an issue.","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"However, you may want to make use of the latest features, then install directly from the main branch with","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia> Pkg.add(url=\"https://github.com/SpeedyWeather/SpeedyWeather.jl\",rev=\"main\")","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"other branches than main can be similarly installed. You can also type, equivalently,","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia>] add https://github.com/SpeedyWeather/SpeedyWeather.jl#main","category":"page"},{"location":"installation/#Compatibility-with-Julia-versions","page":"Installation","title":"Compatibility with Julia versions","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"SpeedyWeather.jl usually lives on the latest minor release and/or its predecessor. At the moment (June 2023) this means ","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"Julia v1.8\nJulia v1.9","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"are supported, but we dropped the support of earlier versions.","category":"page"},{"location":"output/#NetCDF-output","page":"NetCDF output","title":"NetCDF output","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"SpeedyWeather.jl uses NetCDF to output the data of a simulation. The following describes the details of this and how to change the way in which the NetCDF output is written. There are many options to this available.","category":"page"},{"location":"output/#Accessing-the-NetCDF-output-writer","page":"NetCDF output","title":"Accessing the NetCDF output writer","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"The output writer is a component of every Model, i.e. BarotropicModel, ShallowWaterModel, PrimitiveDryModel and PrimitiveWetModel, hence a non-default output writer can be passed on as a keyword argument to the model constructor","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> using SpeedyWeather\njulia> spectral_grid = SpectralGrid()\njulia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry)\njulia> model = PrimitiveDryModel(;spectral_grid, output=my_output_writer)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"So after we have defined the grid through the SpectralGrid object we can use and change the implemented OutputWriter by passing on the following arguments","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, kwargs...)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"the spectral_grid has to be the first argument then the model type (Barotropic, ShallowWater, PrimitiveDry, PrimitiveWet) which helps the output writer to make default choices on which variables to output. However, we can also pass on further keyword arguments. So let's start with an example.","category":"page"},{"location":"output/#Example-1:-NetCDF-output-every-hour","page":"NetCDF output","title":"Example 1: NetCDF output every hour","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"If we want to increase the frequency of the output we can choose output_dt (default =6 in hours) like so","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, output_dt=1)\njulia> model = PrimitiveDryModel(;spectral_grid, output=my_output_writer)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"which will now output every hour. It is important to pass on the new output writer my_output_writer to the model constructor, otherwise it will not be part of your model and the default is used instead. Note that output_dt has to be understood as the minimum frequency or maximum output time step. Example, we run the model at a resolution of T85 and the time step is going to be 670s","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> spectral_grid = SpectralGrid(trunc=85)\njulia> time_stepper = Leapfrog(spectral_grid)\nLeapfrog{Float32}:\n...\n Δt_sec::Int64 = 670\n...","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This means that after 32 time steps 5h 57min and 20s will have passed where output will happen as the next time step would be >6h. The time axis of the NetCDF output will look like","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> using NCDatasets\njulia> ds = NCDataset(\"run_0001/output.nc\");\njulia> ds[\"time\"][:]\n5-element Vector{Dates.DateTime}:\n 2000-01-01T00:00:00\n 2000-01-01T05:57:20\n 2000-01-01T11:54:40\n 2000-01-01T17:52:00\n 2000-01-01T23:49:20\n\njulia> diff(ds[\"time\"][:])\n4-element Vector{Dates.Millisecond}:\n 21440000 milliseconds\n 21440000 milliseconds\n 21440000 milliseconds\n 21440000 milliseconds","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This is so that we don't interpolate in time during output to hit exactly every 6 hours, but at the same time have a constant spacing in time between output time steps.","category":"page"},{"location":"output/#Example-2:-Output-onto-a-higher/lower-resolution-grid","page":"NetCDF output","title":"Example 2: Output onto a higher/lower resolution grid","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Say we want to run the model at a given horizontal resolution but want to output on another resolution, the OutputWriter takes as argument output_Grid<:AbstractFullGrid and nlat_half::Int. So for example output_Grid=FullClenshawGrid and nlat_half=48 will always interpolate onto a regular 192x95 longitude-latitude grid of 1.875˚ resolution, regardless the grid and resolution used for the model integration.","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, output_Grid=FullClenshawGrid, nlat_half=48)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Note that by default the output is on the corresponding full of the grid used in the dynamical core so that interpolation only happens at most in the zonal direction as they share the location of the latitude rings. You can check this by","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> RingGrids.full_grid(OctahedralGaussianGrid)\nFullGaussianGrid","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"So the corresponding full grid of an OctahedralGaussianGrid is the FullGaussiangrid and the same resolution nlat_half is chosen by default in the output writer (which you can change though as shown above). Overview of the corresponding full grids","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Grid Corresponding full grid\nFullGaussianGrid FullGaussianGrid\nFullClenshawGrid FullClenshawGrid\nOctahadralGaussianGrid FullGaussianGrid\nOctahedralClensawhGrid FullClenshawGrid\nHEALPixGrid FullHEALPixGrid\nOctaHEALPixGrid FullOctaHEALPixGrid","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"The grids FullHEALPixGrid, FullOctaHEALPixGrid share the same latitude rings as their reduced grids, but have always as many longitude points as they are at most around the equator. These grids are not tested in the dynamical core (but you may use them experimentally) and mostly designed for output purposes.","category":"page"},{"location":"output/#Example-3:-Changing-the-output-path-or-identification","page":"NetCDF output","title":"Example 3: Changing the output path or identification","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"That's easy by passing on path=\"/my/favourite/path/\" and the folder run_* with * the identification of the run (that's the id keyword, which can be manually set but is also automatically determined as a number counting up depending on which folders already exist) will be created within.","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> path = pwd()\n\"/Users/milan\"\njulia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, path=path)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This folder must already exist. If you want to give your run a name/identification you can pass on id","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid,PrimitiveDry,id=\"diffusion_test\");","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"which will be used instead of a 4 digit number like 0001, 0002 which is automatically determined if id is not provided. You will see the id of the run in the progress bar","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Weather is speedy: run diffusion_test 100%|███████████████████████| Time: 0:00:12 (19.20 years/day)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"and the run folder, here run_diffusion_test, is also named accordingly","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"shell> ls\n...\nrun_diffusion_test\n...","category":"page"},{"location":"output/#Further-options","page":"NetCDF output","title":"Further options","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Further options are described in the OutputWriter docstring, (also accessible via julia>?OutputWriter for example). Note that some fields are actual options, but others are derived from the options you provided or are arrays/objects the output writer needs, but shouldn't be passed on by the user. The actual options are declared as [OPTION] in the following","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"OutputWriter","category":"page"},{"location":"functions/#Function-and-type-index","page":"Function and type index","title":"Function and type index","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"Modules = [SpeedyWeather]","category":"page"},{"location":"functions/#SpeedyWeather.AbstractDevice","page":"Function and type index","title":"SpeedyWeather.AbstractDevice","text":"abstract type AbstractDevice\n\nSupertype of all devices SpeedyWeather.jl can ran on\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.BarotropicModel","page":"Function and type index","title":"SpeedyWeather.BarotropicModel","text":"The BarotropicModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.\n\nspectral_grid::SpectralGrid: dictates resolution for many other components\nplanet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics\natmosphere::SpeedyWeather.AbstractAtmosphere\nforcing::SpeedyWeather.AbstractForcing{NF} where NF<:AbstractFloat\ninitial_conditions::SpeedyWeather.InitialConditions\ntime_stepping::SpeedyWeather.TimeStepper{NF} where NF<:AbstractFloat\nspectral_transform::SpectralTransform\nhorizontal_diffusion::SpeedyWeather.HorizontalDiffusion{NF} where NF<:AbstractFloat\nimplicit::SpeedyWeather.AbstractImplicit{NF} where NF<:AbstractFloat\ngeometry::Geometry\nconstants::DynamicsConstants\ndevice_setup::SpeedyWeather.DeviceSetup\noutput::SpeedyWeather.AbstractOutputWriter\nfeedback::SpeedyWeather.AbstractFeedback\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.CPUDevice","page":"Function and type index","title":"SpeedyWeather.CPUDevice","text":"CPUDevice <: AbstractDevice\n\nIndicates that SpeedyWeather.jl runs on a single CPU \n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Clock","page":"Function and type index","title":"SpeedyWeather.Clock","text":"Clock struct keeps track of the model time, how many days to integrate for and how many time steps this takes\n\ntime::Dates.DateTime: current model time\nn_days::Float64: number of days to integrate for, set in run!(::Simulation)\nn_timesteps::Int64: number of time steps to integrate for, set in initialize!(::Clock,::TimeStepper)\n\n.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ColumnVariables","page":"Function and type index","title":"SpeedyWeather.ColumnVariables","text":"Mutable struct that contains all prognostic (copies thereof) and diagnostic variables in a single column needed to evaluate the physical parametrizations. For now the struct is mutable as we will reuse the struct to iterate over horizontal grid points. Every column vector has nlev entries, from [1] at the top to [end] at the lowermost model level at the planetary boundary layer.\n\nnlev::Int64\nnband::Int64\nn_stratosphere_levels::Int64\njring::Int64\nlond::AbstractFloat\nlatd::AbstractFloat\nu::Vector{NF} where NF<:AbstractFloat\nv::Vector{NF} where NF<:AbstractFloat\ntemp::Vector{NF} where NF<:AbstractFloat\nhumid::Vector{NF} where NF<:AbstractFloat\nln_pres::Vector{NF} where NF<:AbstractFloat\npres::Vector{NF} where NF<:AbstractFloat\nu_tend::Vector{NF} where NF<:AbstractFloat\nv_tend::Vector{NF} where NF<:AbstractFloat\ntemp_tend::Vector{NF} where NF<:AbstractFloat\nhumid_tend::Vector{NF} where NF<:AbstractFloat\ngeopot::Vector{NF} where NF<:AbstractFloat\nflux_u_upward::Vector{NF} where NF<:AbstractFloat\nflux_u_downward::Vector{NF} where NF<:AbstractFloat\nflux_v_upward::Vector{NF} where NF<:AbstractFloat\nflux_v_downward::Vector{NF} where NF<:AbstractFloat\nflux_temp_upward::Vector{NF} where NF<:AbstractFloat\nflux_temp_downward::Vector{NF} where NF<:AbstractFloat\nflux_humid_upward::Vector{NF} where NF<:AbstractFloat\nflux_humid_downward::Vector{NF} where NF<:AbstractFloat\nsat_humid::Vector{NF} where NF<:AbstractFloat\nsat_vap_pres::Vector{NF} where NF<:AbstractFloat\ndry_static_energy::Vector{NF} where NF<:AbstractFloat\nmoist_static_energy::Vector{NF} where NF<:AbstractFloat\nhumid_half::Vector{NF} where NF<:AbstractFloat\nsat_humid_half::Vector{NF} where NF<:AbstractFloat\nsat_moist_static_energy::Vector{NF} where NF<:AbstractFloat\ndry_static_energy_half::Vector{NF} where NF<:AbstractFloat\nsat_moist_static_energy_half::Vector{NF} where NF<:AbstractFloat\nconditional_instability::Bool\nactivate_convection::Bool\ncloud_top::Int64\nexcess_humidity::AbstractFloat\ncloud_base_mass_flux::AbstractFloat\nprecip_convection::AbstractFloat\nnet_flux_humid::Vector{NF} where NF<:AbstractFloat\nnet_flux_dry_static_energy::Vector{NF} where NF<:AbstractFloat\nentrainment_profile::Vector{NF} where NF<:AbstractFloat\nprecip_large_scale::AbstractFloat\nwvi::Matrix{NF} where NF<:AbstractFloat\ntau2::Matrix{NF} where NF<:AbstractFloat\ndfabs::Vector{NF} where NF<:AbstractFloat\nfsfcd::AbstractFloat\nst4a::Matrix{NF} where NF<:AbstractFloat\nflux::Vector{NF} where NF<:AbstractFloat\nfsfcu::AbstractFloat\nts::AbstractFloat\nfsfc::AbstractFloat\nftop::AbstractFloat\nstratc::Vector{NF} where NF<:AbstractFloat\ntyear::AbstractFloat\ncsol::AbstractFloat\ntopsr::AbstractFloat\nfsol::AbstractFloat\nozupp::AbstractFloat\nozone::AbstractFloat\nzenit::AbstractFloat\nstratz::AbstractFloat\nalbsfc::AbstractFloat\nssrd::AbstractFloat\nssr::AbstractFloat\ntsr::AbstractFloat\ntend_t_rsw::Vector{NF} where NF<:AbstractFloat\nnorm_pres::AbstractFloat\nicltop::Int64\ncloudc::AbstractFloat\nclstr::AbstractFloat\nqcloud::AbstractFloat\nfmask::AbstractFloat\nrel_hum::Vector{NF} where NF<:AbstractFloat\ngrad_dry_static_energy::AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.DeviceSetup","page":"Function and type index","title":"SpeedyWeather.DeviceSetup","text":"DeviceSetup{S<:AbstractDevice}\n\nHolds information about the device the model is running on and workgroup size. \n\ndevice::AbstractDevice: Device the model is running on \ndevice_KA::KernelAbstractions.Device: Device for use with KernelAbstractions\nn: workgroup size \n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.DiagnosticVariables","page":"Function and type index","title":"SpeedyWeather.DiagnosticVariables","text":"DiagnosticVariables{Grid<:AbstractGrid,NF<:AbstractFloat}\n\nStruct holding the diagnostic variables.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.DynamicsConstants","page":"Function and type index","title":"SpeedyWeather.DynamicsConstants","text":"Struct holding constants needed at runtime for the dynamical core in number format NF.\n\nradius::AbstractFloat: Radius of Planet [m]\nrotation::AbstractFloat: Angular frequency of Planet's rotation [1/s]\ngravity::AbstractFloat: Gravitational acceleration [m/s^2]\nlayer_thickness::AbstractFloat: shallow water layer thickness [m]\nR_dry::AbstractFloat: specific gas constant for dry air [J/kg/K]\nR_vapour::AbstractFloat: specific gas constant for water vapour [J/kg/K]\nμ_virt_temp::AbstractFloat: used in Tv = T(1+μq) for virt temp Tv(T,q) calculation\ncₚ::AbstractFloat: specific heat at constant pressure [J/K/kg]\nκ::AbstractFloat: = R_dry/cₚ, gas const for air over heat capacity\nwater_density::AbstractFloat: water density [kg/m³]\nf_coriolis::Vector{NF} where NF<:AbstractFloat: coriolis frequency 1/s = 2Ωsin(lat)radius\nσ_lnp_A::Vector{NF} where NF<:AbstractFloat: σ-related factor A needed for adiabatic terms\nσ_lnp_B::Vector{NF} where NF<:AbstractFloat: σ-related factor B needed for adiabatic terms\nΔp_geopot_half::Vector{NF} where NF<:AbstractFloat: = R*(ln(pk+1) - ln(pk+1/2)), for half level geopotential\nΔp_geopot_full::Vector{NF} where NF<:AbstractFloat: = R*(ln(pk+1/2) - ln(pk)), for full level geopotential\ntemp_ref_profile::Vector{NF} where NF<:AbstractFloat: reference temperature profile\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.DynamicsConstants-Tuple{SpectralGrid, SpeedyWeather.AbstractPlanet, SpeedyWeather.AbstractAtmosphere, Geometry}","page":"Function and type index","title":"SpeedyWeather.DynamicsConstants","text":"DynamicsConstants(\n spectral_grid::SpectralGrid,\n planet::SpeedyWeather.AbstractPlanet,\n atmosphere::SpeedyWeather.AbstractAtmosphere,\n geometry::Geometry\n) -> Any\n\n\nGenerator function for a DynamicsConstants struct.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.DynamicsVariables","page":"Function and type index","title":"SpeedyWeather.DynamicsVariables","text":"DynamicsVariables{Grid<:AbstractGrid,NF<:AbstractFloat}\n\nStruct holding intermediate quantities for the dynamics of a given layer.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Earth","page":"Function and type index","title":"SpeedyWeather.Earth","text":"Create a struct Earth<:AbstractPlanet, with the following physical/orbital characteristics. Note that radius is not part of it as this should be chosen in SpectralGrid. Keyword arguments are\n\nrotation::Float64: angular frequency of Earth's rotation [rad/s]\ngravity::Float64: gravitational acceleration [m/s^2]\ndaily_cycle::Bool: switch on/off daily cycle\nlength_of_day::Float64: [hrs] in a day\nseasonal_cycle::Bool: switch on/off seasonal cycle\nlength_of_year::Float64: [days] in a year\nequinox::Dates.DateTime: time of spring equinox (year irrelevant)\naxial_tilt::Float64: angle [˚] rotation axis tilt wrt to orbit\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.EarthAtmosphere","page":"Function and type index","title":"SpeedyWeather.EarthAtmosphere","text":"Create a struct EarthAtmosphere<:AbstractPlanet, with the following physical/chemical characteristics. Note that radius is not part of it as this should be chosen in SpectralGrid. Keyword arguments are\n\nmol_mass_dry_air::Float64: molar mass of dry air [g/mol]\nmol_mass_vapour::Float64: molar mass of water vapour [g/mol]\ncₚ::Float64: specific heat at constant pressure [J/K/kg]\nR_gas::Float64: universal gas constant [J/K/mol]\nR_dry::Float64: specific gas constant for dry air [J/kg/K]\nR_vapour::Float64: specific gas constant for water vapour [J/kg/K]\nwater_density::Float64: water density [kg/m³]\nlatent_heat_condensation::Float64: latent heat of condensation [J/g] for consistency with specific humidity [g/Kg], also called alhc\nlatent_heat_sublimation::Float64: latent heat of sublimation [J/g], also called alhs\nstefan_boltzmann::Float64: stefan-Boltzmann constant [W/m²/K⁴]\nlapse_rate::Float64: moist adiabatic temperature lapse rate -dTdz [K/km]\ntemp_ref::Float64: absolute temperature at surface z=0 [K]\ntemp_top::Float64: absolute temperature in stratosphere [K]\nΔT_stratosphere::Float64: for stratospheric lapse rate [K] after Jablonowski\nσ_tropopause::Float64: start of the stratosphere in sigma coordinates\nσ_boundary_layer::Float64: top of the planetary boundary layer in sigma coordinates\nscale_height::Float64: scale height for pressure [km]\npres_ref::Float64: surface pressure [hPa]\nscale_height_humid::Float64: scale height for specific humidity [km]\nrelhumid_ref::Float64: relative humidity of near-surface air [1]\nwater_pres_ref::Float64: saturation water vapour pressure [Pa]\nlayer_thickness::Float64: layer thickness for the shallow water model [km]\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.EarthOrography","page":"Function and type index","title":"SpeedyWeather.EarthOrography","text":"Earth's orography read from file, with smoothing.\n\npath::String: path to the folder containing the orography file, pkg path default\nfile::String: filename of orography\nfile_Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: Grid the orography file comes on\nscale::Float64: scale orography by a factor\nsmoothing::Bool: smooth the orography field?\nsmoothing_power::Float64: power of Laplacian for smoothing\nsmoothing_strength::Float64: highest degree l is multiplied by\nsmoothing_truncation::Int64: resolution of orography in spectral trunc\norography::SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat: height [m] on grid-point space.\ngeopot_surf::LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat: surface geopotential, height*gravity [m²/s²]\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.EarthOrography-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.EarthOrography","text":"EarthOrography(\n spectral_grid::SpectralGrid;\n kwargs...\n) -> Any\n\n\nGenerator function pulling the resolution information from spectral_grid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.Feedback","page":"Function and type index","title":"SpeedyWeather.Feedback","text":"Feedback() -> Feedback\nFeedback(verbose::Bool) -> Feedback\nFeedback(verbose::Bool, debug::Bool) -> Feedback\n\n\nGenerator function for a Feedback struct.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Feedback-2","page":"Function and type index","title":"SpeedyWeather.Feedback","text":"Feedback struct that contains options and object for command-line feedback like the progress meter.\n\nverbose::Bool: print feedback to REPL?\ndebug::Bool: check for NaRs in the prognostic variables\noutput::Bool: write a progress.txt file? State synced with OutputWriter.output\nid::Union{Int64, String}: identification of run, taken from ::OutputWriter\nrun_path::String: path to run folder, taken from ::OutputWriter\nprogress_meter::ProgressMeter.Progress: struct containing everything progress related\nprogress_txt::Union{Nothing, IOStream}: txt is a Nothing in case of no output\nnars_detected::Bool: did Infs/NaNs occur in the simulation?\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.GPUDevice","page":"Function and type index","title":"SpeedyWeather.GPUDevice","text":"GPUDevice <: AbstractDevice\n\nIndicates that SpeedyWeather.jl runs on a single GPU\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.GenLogisticCoefs","page":"Function and type index","title":"SpeedyWeather.GenLogisticCoefs","text":"Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF.\n\nFollowing the notation of https://en.wikipedia.org/wiki/Generalisedlogisticfunction (Dec 15 2021).\n\nChange default parameters for more/fewer levels in the stratosphere vs troposphere vs boundary layer.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Geometry","page":"Function and type index","title":"SpeedyWeather.Geometry","text":"Construct Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. Pass on SpectralGrid to calculate the following fields\n\nspectral_grid::SpectralGrid: SpectralGrid that defines spectral and grid resolution\nGrid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid of the dynamical core\nnlat_half::Int64: resolution parameter nlat_half of Grid, # of latitudes on one hemisphere (incl Equator)\nnlon_max::Int64: maximum number of longitudes (at/around Equator)\nnlon::Int64: =nlon_max, same (used for compatibility), TODO: still needed?\nnlat::Int64: number of latitude rings\nnlev::Int64: number of vertical levels\nnpoints::Int64: total number of grid points\nradius::AbstractFloat: Planet's radius [m]\nlatd::Vector{Float64}: array of latitudes in degrees (90˚...-90˚)\nlond::Vector{Float64}: array of longitudes in degrees (0...360˚), empty for non-full grids\nlonds::Vector{NF} where NF<:AbstractFloat: longitude (-180˚...180˚) for each grid point in ring order\nlatds::Vector{NF} where NF<:AbstractFloat: latitude (-90˚...˚90) for each grid point in ring order\nsinlat::Vector{NF} where NF<:AbstractFloat: sin of latitudes\ncoslat::Vector{NF} where NF<:AbstractFloat: cos of latitudes\ncoslat⁻¹::Vector{NF} where NF<:AbstractFloat: = 1/cos(lat)\ncoslat²::Vector{NF} where NF<:AbstractFloat: = cos²(lat)\ncoslat⁻²::Vector{NF} where NF<:AbstractFloat: # = 1/cos²(lat)\nσ_levels_half::Vector{NF} where NF<:AbstractFloat: σ at half levels, σ_k+1/2\nσ_levels_full::Vector{NF} where NF<:AbstractFloat: σ at full levels, σₖ\nσ_levels_thick::Vector{NF} where NF<:AbstractFloat: σ level thicknesses, σₖ₊₁ - σₖ\nln_σ_levels_full::Vector{NF} where NF<:AbstractFloat: log of σ at full levels, include surface (σ=1) as last element\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Geometry-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.Geometry","text":"Geometry(spectral_grid::SpectralGrid) -> Any\n\n\nGenerator function for Geometry struct based on spectral_grid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.GridVariables","page":"Function and type index","title":"SpeedyWeather.GridVariables","text":"GridVariables{NF<:AbstractFloat}\n\nStruct holding the prognostic spectral variables of a given layer in grid point space.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.HeldSuarez","page":"Function and type index","title":"SpeedyWeather.HeldSuarez","text":"Struct that defines the temperature relaxation from Held and Suarez, 1996 BAMS\n\nnlat::Int64: number of latitude rings\nnlev::Int64: number of vertical levels\nσb::Float64: sigma coordinate below which faster surface relaxation is applied\nrelax_time_slow::Float64: time scale [hrs] for slow global relaxation\nrelax_time_fast::Float64: time scale [hrs] for faster tropical surface relaxation\nTmin::Float64: minimum equilibrium temperature [K]\nTmax::Float64: maximum equilibrium temperature [K]\nΔTy::Float64: meridional temperature gradient [K]\nΔθz::Float64: vertical temperature gradient [K]\nκ::Base.RefValue{NF} where NF<:AbstractFloat\np₀::Base.RefValue{NF} where NF<:AbstractFloat\ntemp_relax_freq::Matrix{NF} where NF<:AbstractFloat\ntemp_equil_a::Vector{NF} where NF<:AbstractFloat\ntemp_equil_b::Vector{NF} where NF<:AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.HeldSuarez-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.HeldSuarez","text":"HeldSuarez(SG::SpectralGrid; kwargs...) -> Any\n\n\ncreate a HeldSuarez temperature relaxation with arrays allocated given spectral_grid\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.HyperDiffusion","page":"Function and type index","title":"SpeedyWeather.HyperDiffusion","text":"Struct for horizontal hyper diffusion of vor, div, temp; implicitly in spectral space with a power of the Laplacian (default=4) and the strength controlled by time_scale. Options exist to scale the diffusion by resolution, and adaptive depending on the current vorticity maximum to increase diffusion in active layers. Furthermore the power can be decreased above the tapering_σ to power_stratosphere (default 2). For Barotropic, ShallowWater, the default non-adaptive constant-time scale hyper diffusion is used. Options are\n\ntrunc::Int64: spectral resolution\nnlev::Int64: number of vertical levels\npower::Float64: power of Laplacian\ntime_scale::Float64: diffusion time scales [hrs]\nresolution_scaling::Float64: stronger diffusion with resolution? 0: constant with trunc, 1: (inverse) linear with trunc, etc\npower_stratosphere::Float64: different power for tropopause/stratosphere\ntapering_σ::Float64: linearly scale towards power_stratosphere above this σ\nadaptive::Bool: adaptive = higher diffusion for layers with higher vorticity levels.\nvor_max::Float64: above this (absolute) vorticity level [1/s], diffusion is increased\nadaptive_strength::Float64: increase strength above vor_max by this factor times max(abs(vor))/vor_max\n∇²ⁿ_2D::Vector\n∇²ⁿ_2D_implicit::Vector\n∇²ⁿ::Array{Vector{NF}, 1} where NF\n∇²ⁿ_implicit::Array{Vector{NF}, 1} where NF\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.HyperDiffusion-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.HyperDiffusion","text":"HyperDiffusion(\n spectral_grid::SpectralGrid;\n kwargs...\n) -> Any\n\n\nGenerator function based on the resolutin in spectral_grid. Passes on keyword arguments.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.ImplicitPrimitiveEq","page":"Function and type index","title":"SpeedyWeather.ImplicitPrimitiveEq","text":"Struct that holds various precomputed arrays for the semi-implicit correction to prevent gravity waves from amplifying in the primitive equation model.\n\ntrunc::Int64: spectral resolution\nnlev::Int64: number of vertical levels\nα::Float64: time-step coefficient: 0=explicit, 0.5=centred implicit, 1=backward implicit\ntemp_profile::Vector{NF} where NF<:AbstractFloat: vertical temperature profile, obtained from diagn\nξ::Base.RefValue{NF} where NF<:AbstractFloat: time step 2α*Δt packed in RefValue for mutability\nR::Matrix{NF} where NF<:AbstractFloat: divergence: operator for the geopotential calculation\nU::Vector{NF} where NF<:AbstractFloat: divergence: the -RdTₖ∇² term excl the eigenvalues from ∇² for divergence\nL::Matrix{NF} where NF<:AbstractFloat: temperature: operator for the TₖD + κTₖDlnps/Dt term\nW::Vector{NF} where NF<:AbstractFloat: pressure: vertical averaging of the -D̄ term in the log surface pres equation\nL0::Vector{NF} where NF<:AbstractFloat: components to construct L, 1/ 2Δσ\nL1::Matrix{NF} where NF<:AbstractFloat: vert advection term in the temperature equation (below+above)\nL2::Vector{NF} where NF<:AbstractFloat: factor in front of the divsumabove term\nL3::Matrix{NF} where NF<:AbstractFloat: sumabove operator itself\nL4::Vector{NF} where NF<:AbstractFloat: factor in front of div term in Dlnps/Dt\nS::Matrix{NF} where NF<:AbstractFloat: for every l the matrix to be inverted\nS⁻¹::Array{NF, 3} where NF<:AbstractFloat: combined inverted operator: S = 1 - ξ²(RL + UW)\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ImplicitPrimitiveEq-Tuple{SpectralGrid, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.ImplicitPrimitiveEq","text":"ImplicitPrimitiveEq(\n spectral_grid::SpectralGrid,\n kwargs...\n) -> Any\n\n\nGenerator using the resolution from SpectralGrid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.ImplicitShallowWater","page":"Function and type index","title":"SpeedyWeather.ImplicitShallowWater","text":"Struct that holds various precomputed arrays for the semi-implicit correction to prevent gravity waves from amplifying in the shallow water model.\n\ntrunc::Int64\nα::Float64: coefficient for semi-implicit computations to filter gravity waves\nH::Base.RefValue{NF} where NF<:AbstractFloat\nξH::Base.RefValue{NF} where NF<:AbstractFloat\ng∇²::Vector{NF} where NF<:AbstractFloat\nξg∇²::Vector{NF} where NF<:AbstractFloat\nS⁻¹::Vector{NF} where NF<:AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ImplicitShallowWater-Tuple{SpectralGrid, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.ImplicitShallowWater","text":"ImplicitShallowWater(\n spectral_grid::SpectralGrid,\n kwargs...\n) -> Any\n\n\nGenerator using the resolution from spectral_grid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.JablonowskiRelaxation","page":"Function and type index","title":"SpeedyWeather.JablonowskiRelaxation","text":"HeldSuarez-like temperature relaxation, but towards the Jablonowski temperature profile with increasing temperatures in the stratosphere.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.JablonowskiRelaxation-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.JablonowskiRelaxation","text":"JablonowskiRelaxation(SG::SpectralGrid; kwargs...) -> Any\n\n\ncreate a JablonowskiRelaxation temperature relaxation with arrays allocated given spectral_grid\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.Keepbits","page":"Function and type index","title":"SpeedyWeather.Keepbits","text":"Number of mantissa bits to keep for each prognostic variable when compressed for netCDF and .jld2 data output.\n\nu::Int64\nv::Int64\nvor::Int64\ndiv::Int64\ntemp::Int64\npres::Int64\nhumid::Int64\nprecip_cond::Int64\nprecip_conv::Int64\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Leapfrog","page":"Function and type index","title":"SpeedyWeather.Leapfrog","text":"Leapfrog time stepping defined by the following fields\n\ntrunc::Int64: spectral resolution (max degree of spherical harmonics)\nΔt_at_T31::Float64: time step in minutes for T31, scale linearly to trunc\nradius::Any: radius of sphere [m], used for scaling\nrobert_filter::Any: Robert (1966) time filter coefficeint to suppress comput. mode\nwilliams_filter::Any: Williams time filter (Amezcua 2011) coefficient for 3rd order acc\nΔt_sec::Int64: time step Δt [s] at specified resolution\nΔt::Any: time step Δt [s/m] at specified resolution, scaled by 1/radius\nΔt_hrs::Float64: convert time step Δt from minutes to hours\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Leapfrog-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.Leapfrog","text":"Leapfrog(spectral_grid::SpectralGrid; kwargs...) -> Any\n\n\nGenerator function for a Leapfrog struct using spectral_grid for the resolution information.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.LinearDrag","page":"Function and type index","title":"SpeedyWeather.LinearDrag","text":"Linear boundary layer drag Following Held and Suarez, 1996 BAMS\n\nσb::Float64\ntime_scale::Float64\nnlev::Int64\ndrag_coefs::Vector{NF} where NF<:AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.LinearDrag-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.LinearDrag","text":"LinearDrag(SG::SpectralGrid; kwargs...) -> Any\n\n\nGenerator function using nlev from SG::SpectralGrid\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.MagnusCoefs","page":"Function and type index","title":"SpeedyWeather.MagnusCoefs","text":"Parameters for computing saturation vapour pressure using the August-Roche-Magnus formula,\n\neᵢ(T) = e₀ * exp(Cᵢ * (T - T₀) / (T - Tᵢ)),\n\nwhere T is in Kelvin and i = 1,2 for saturation with respect to water and ice, respectively.\n\ne₀::AbstractFloat: Saturation vapour pressure at 0°C [Pa]\nT₀::AbstractFloat: 0°C in Kelvin\nT₁::AbstractFloat\nT₂::AbstractFloat\nC₁::AbstractFloat\nC₂::AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.NoBoundaryLayerDrag","page":"Function and type index","title":"SpeedyWeather.NoBoundaryLayerDrag","text":"Concrete type that disables the boundary layer drag scheme.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.NoOrography","page":"Function and type index","title":"SpeedyWeather.NoOrography","text":"Orography with zero height in orography and zero surface geopotential geopot_surf.\n\norography::SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat: height [m] on grid-point space.\ngeopot_surf::LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat: surface geopotential, height*gravity [m²/s²]\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.NoOrography-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.NoOrography","text":"NoOrography(spectral_grid::SpectralGrid) -> NoOrography\n\n\nGenerator function pulling the resolution information from spectral_grid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.OutputWriter","page":"Function and type index","title":"SpeedyWeather.OutputWriter","text":"NetCDF output writer. Contains all output options and auxiliary fields for output interpolation. To be initialised with OutputWriter(::SpectralGrid,::Type{<:ModelSetup},kwargs...) to pass on the resolution information and the model type which chooses which variables to output. Options include\n\nspectral_grid::SpectralGrid\noutput::Bool\npath::String: [OPTION] path to output folder, run_???? will be created within\nid::String: [OPTION] run identification number/string\nrun_path::String\nfilename::String: [OPTION] name of the output netcdf file\nwrite_restart::Bool: [OPTION] also write restart file if output==true?\npkg_version::VersionNumber\nstartdate::Dates.DateTime\noutput_dt::Float64: [OPTION] output frequency, time step [hrs]\noutput_dt_sec::Int64: actual output time step [sec]\noutput_vars::Vector{Symbol}: [OPTION] which variables to output, u, v, vor, div, pres, temp, humid\nmissing_value::Union{Float32, Float64}: [OPTION] missing value to be used in netcdf output\ncompression_level::Int64: [OPTION] lossless compression level; 1=low but fast, 9=high but slow\nkeepbits::SpeedyWeather.Keepbits: [OPTION] mantissa bits to keep for every variable\noutput_every_n_steps::Int64\ntimestep_counter::Int64\noutput_counter::Int64\nnetcdf_file::Union{Nothing, NetCDF.NcFile}\ninput_Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}\nas_matrix::Bool: [OPTION] sort grid points into a matrix (interpolation-free), for OctahedralClenshawGrid, OctaHEALPixGrid only\nquadrant_rotation::NTuple{4, Int64}\nmatrix_quadrant::NTuple{4, Tuple{Int64, Int64}}\noutput_Grid::Type{<:SpeedyWeather.RingGrids.AbstractFullGrid}: [OPTION] the grid used for output, full grids only\nnlat_half::Int64: [OPTION] the resolution of the output grid, default: same nlat_half as in the dynamical core\nnlon::Int64\nnlat::Int64\nnpoints::Int64\nnlev::Int64\ninterpolator::SpeedyWeather.RingGrids.AbstractInterpolator\nu::Matrix{NF} where NF<:Union{Float32, Float64}\nv::Matrix{NF} where NF<:Union{Float32, Float64}\nvor::Matrix{NF} where NF<:Union{Float32, Float64}\ndiv::Matrix{NF} where NF<:Union{Float32, Float64}\ntemp::Matrix{NF} where NF<:Union{Float32, Float64}\npres::Matrix{NF} where NF<:Union{Float32, Float64}\nhumid::Matrix{NF} where NF<:Union{Float32, Float64}\nprecip_cond::Matrix{NF} where NF<:Union{Float32, Float64}\nprecip_conv::Matrix{NF} where NF<:Union{Float32, Float64}\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.PrimitiveDryModel","page":"Function and type index","title":"SpeedyWeather.PrimitiveDryModel","text":"The PrimitiveDryModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.\n\nspectral_grid::SpectralGrid: dictates resolution for many other components\nplanet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics\natmosphere::SpeedyWeather.AbstractAtmosphere\ninitial_conditions::SpeedyWeather.InitialConditions\norography::SpeedyWeather.AbstractOrography{NF} where NF<:AbstractFloat\nphysics::Bool\nboundary_layer_drag::SpeedyWeather.BoundaryLayerDrag{NF} where NF<:AbstractFloat\ntemperature_relaxation::SpeedyWeather.TemperatureRelaxation{NF} where NF<:AbstractFloat\nstatic_energy_diffusion::SpeedyWeather.VerticalDiffusion{NF} where NF<:AbstractFloat\ntime_stepping::SpeedyWeather.TimeStepper{NF} where NF<:AbstractFloat\nspectral_transform::SpectralTransform\nhorizontal_diffusion::SpeedyWeather.HorizontalDiffusion{NF} where NF<:AbstractFloat\nimplicit::SpeedyWeather.AbstractImplicit{NF} where NF<:AbstractFloat\ngeometry::Geometry\nconstants::DynamicsConstants\ndevice_setup::SpeedyWeather.DeviceSetup\noutput::SpeedyWeather.AbstractOutputWriter\nfeedback::SpeedyWeather.AbstractFeedback\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.PrimitiveWetModel","page":"Function and type index","title":"SpeedyWeather.PrimitiveWetModel","text":"The PrimitiveDryModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.\n\nspectral_grid::SpectralGrid: dictates resolution for many other components\nplanet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics\natmosphere::SpeedyWeather.AbstractAtmosphere\ninitial_conditions::SpeedyWeather.InitialConditions\norography::SpeedyWeather.AbstractOrography{NF} where NF<:AbstractFloat\nphysics::Bool\nthermodynamics::SpeedyWeather.Thermodynamics{NF} where NF<:AbstractFloat\nboundary_layer_drag::SpeedyWeather.BoundaryLayerDrag{NF} where NF<:AbstractFloat\ntemperature_relaxation::SpeedyWeather.TemperatureRelaxation{NF} where NF<:AbstractFloat\nstatic_energy_diffusion::SpeedyWeather.VerticalDiffusion{NF} where NF<:AbstractFloat\nlarge_scale_condensation::SpeedyWeather.AbstractCondensation{NF} where NF<:AbstractFloat\ntime_stepping::SpeedyWeather.TimeStepper{NF} where NF<:AbstractFloat\nspectral_transform::SpectralTransform\nhorizontal_diffusion::SpeedyWeather.HorizontalDiffusion{NF} where NF<:AbstractFloat\nimplicit::SpeedyWeather.AbstractImplicit{NF} where NF<:AbstractFloat\ngeometry::Geometry\nconstants::DynamicsConstants\ndevice_setup::SpeedyWeather.DeviceSetup\noutput::SpeedyWeather.AbstractOutputWriter\nfeedback::SpeedyWeather.AbstractFeedback\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ShallowWaterModel","page":"Function and type index","title":"SpeedyWeather.ShallowWaterModel","text":"The ShallowWaterModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.\n\nspectral_grid::SpectralGrid: dictates resolution for many other components\nplanet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics\natmosphere::SpeedyWeather.AbstractAtmosphere\nforcing::SpeedyWeather.AbstractForcing{NF} where NF<:AbstractFloat\ninitial_conditions::SpeedyWeather.InitialConditions\norography::SpeedyWeather.AbstractOrography{NF} where NF<:AbstractFloat\ntime_stepping::SpeedyWeather.TimeStepper{NF} where NF<:AbstractFloat\nspectral_transform::SpectralTransform\nhorizontal_diffusion::SpeedyWeather.HorizontalDiffusion{NF} where NF<:AbstractFloat\nimplicit::SpeedyWeather.AbstractImplicit{NF} where NF<:AbstractFloat\ngeometry::Geometry\nconstants::DynamicsConstants\ndevice_setup::SpeedyWeather.DeviceSetup\noutput::SpeedyWeather.AbstractOutputWriter\nfeedback::SpeedyWeather.AbstractFeedback\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Simulation","page":"Function and type index","title":"SpeedyWeather.Simulation","text":"Simulation is a container struct to be used with run!(::Simulation). It contains\n\nprognostic_variables::PrognosticVariables: define the current state of the model\ndiagnostic_variables::DiagnosticVariables: contain the tendencies and auxiliary arrays to compute them\nmodel::SpeedyWeather.ModelSetup: all parameters, constant at runtime\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.SpectralGrid","page":"Function and type index","title":"SpeedyWeather.SpectralGrid","text":"Defines the horizontal spectral resolution and corresponding grid and the vertical coordinate for SpeedyWeather.jl. Options are\n\nNF::Type{<:AbstractFloat}: number format used throughout the model\ntrunc::Int64: horizontal resolution as the maximum degree of spherical harmonics\nGrid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: horizontal grid used for calculations in grid-point space\ndealiasing::Float64: how to match spectral with grid resolution: dealiasing factor, 1=linear, 2=quadratic, 3=cubic grid\nradius::Float64: radius of the sphere [m]\nnlat_half::Int64: number of latitude rings on one hemisphere (Equator incl)\nnpoints::Int64: total number of grid points in the horizontal\nnlev::Int64: number of vertical levels\nvertical_coordinates::SpeedyWeather.VerticalCoordinates: coordinates used to discretize the vertical\n\nnlat_half and npoints should not be chosen but are derived from trunc, Grid and dealiasing.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.SpeedyCondensation","page":"Function and type index","title":"SpeedyWeather.SpeedyCondensation","text":"Large scale condensation as in Fortran SPEEDY with default values from therein.\n\nnlev::Int64: number of vertical levels\nthreshold_boundary_layer::Float64: Relative humidity threshold for boundary layer\nthreshold_range::Float64: Vertical range of relative humidity threshold\nthreshold_max::Float64: Maximum relative humidity threshold [1]\ntime_scale::Float64: Relaxation time for humidity [hrs]\nn_stratosphere_levels::Base.RefValue{Int64}\nhumid_tend_max::Vector{NF} where NF<:AbstractFloat\nrelative_threshold::Vector{NF} where NF<:AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.SpectralTransform-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"SpectralTransform(\n spectral_grid::SpectralGrid;\n recompute_legendre,\n kwargs...\n) -> SpectralTransform\n\n\nGenerator function for a SpectralTransform struct pulling in parameters from a SpectralGrid struct.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.StartFromFile","page":"Function and type index","title":"SpeedyWeather.StartFromFile","text":"Restart from a previous SpeedyWeather.jl simulation via the restart file restart.jld2 Applies interpolation in the horizontal but not in the vertical. restart.jld2 is identified by\n\npath::String: path for restart file\nid::Union{Int64, String}: run_id of restart file in run_????/restart.jld2\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.StartWithRandomVorticity","page":"Function and type index","title":"SpeedyWeather.StartWithRandomVorticity","text":"Start with random vorticity as initial conditions\n\npower::Float64: Power of the spectral distribution k^power\namplitude::Float64: (approximate) amplitude in [1/s], used as standard deviation of spherical harmonic coefficients\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.StaticEnergyDiffusion","page":"Function and type index","title":"SpeedyWeather.StaticEnergyDiffusion","text":"Diffusion of dry static energy: A relaxation towards a reference gradient of static energy wrt to geopotential, see Fortran SPEEDY documentation.\n\ntime_scale::Float64: time scale [hrs] for strength\nstatic_energy_lapse_rate::Float64: [1] ∂SE/∂Φ, vertical gradient of static energy SE with geopotential Φ\nFstar::Base.RefValue{NF} where NF<:AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Tendencies","page":"Function and type index","title":"SpeedyWeather.Tendencies","text":"Tendencies{Grid<:AbstractGrid,NF<:AbstractFloat}\n\nStruct holding the tendencies of the prognostic spectral variables for a given layer.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ZonalJet","page":"Function and type index","title":"SpeedyWeather.ZonalJet","text":"Create a struct that contains all parameters for the Galewsky et al, 2004 zonal jet intitial conditions for the shallow water model. Default values as in Galewsky.\n\nlatitude::Float64: jet latitude [˚N]\nwidth::Float64: jet width [˚], default ≈ 19.29˚\numax::Float64: jet maximum velocity [m/s]\nperturb_lat::Float64: perturbation latitude [˚N], position in jet by default\nperturb_lon::Float64: perturbation longitude [˚E]\nperturb_xwidth::Float64: perturbation zonal extent [˚], default ≈ 19.1˚\nperturb_ywidth::Float64: perturbation meridinoal extent [˚], default ≈ 3.8˚\nperturb_height::Float64: perturbation amplitude [m]\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ZonalRidge","page":"Function and type index","title":"SpeedyWeather.ZonalRidge","text":"Zonal ridge orography after Jablonowski and Williamson, 2006.\n\nη₀::Float64: conversion from σ to Jablonowski's ηᵥ-coordinates\nu₀::Float64: max amplitude of zonal wind [m/s] that scales orography height\norography::SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat: height [m] on grid-point space.\ngeopot_surf::LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat: surface geopotential, height*gravity [m²/s²]\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ZonalRidge-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.ZonalRidge","text":"ZonalRidge(spectral_grid::SpectralGrid; kwargs...) -> Any\n\n\nGenerator function pulling the resolution information from spectral_grid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.ZonalWind","page":"Function and type index","title":"SpeedyWeather.ZonalWind","text":"Create a struct that contains all parameters for the Jablonowski and Williamson, 2006 intitial conditions for the primitive equation model. Default values as in Jablonowski.\n\nη₀::Float64: conversion from σ to Jablonowski's ηᵥ-coordinates\nu₀::Float64: max amplitude of zonal wind [m/s]\nperturb_lat::Float64: perturbation centred at [˚N]\nperturb_lon::Float64: perturbation centred at [˚E]\nperturb_uₚ::Float64: perturbation strength [m/s]\nperturb_radius::Float64: radius of Gaussian perturbation in units of Earth's radius [1]\nΔT::Float64: temperature difference used for stratospheric lapse rate [K], Jablonowski uses ΔT = 4.8e5 [K]\nTmin::Float64: minimum temperature [K] of profile\npressure_on_orography::Bool: initialize pressure given the atmosphere.lapse_rate on orography?\n\n\n\n\n\n","category":"type"},{"location":"functions/#Base.copy!-Tuple{PrognosticVariables, PrognosticVariables}","page":"Function and type index","title":"Base.copy!","text":"copy!(progn_new::PrognosticVariables, progn_old::PrognosticVariables)\n\nCopies entries of progn_old into progn_new. Only copies those variables that are present in the model of both progn_new and progn_old.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.Device-Tuple{}","page":"Function and type index","title":"SpeedyWeather.Device","text":"Device()\n\nReturn default used device for internal purposes, either CPUDevice or GPUDevice if a GPU is available.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.DeviceArray-Tuple{SpeedyWeather.GPUDevice, Any}","page":"Function and type index","title":"SpeedyWeather.DeviceArray","text":"DeviceArray(device::AbstractDevice, x)\n\nAdapts x to a CuArray when device<:GPUDevice is used, otherwise a regular Array. Uses adapt, thus also can return SubArrays etc.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.DeviceArrayNotAdapt-Tuple{SpeedyWeather.GPUDevice, Any}","page":"Function and type index","title":"SpeedyWeather.DeviceArrayNotAdapt","text":"DeviceArrayNotAdapt(device::AbstractDevice, x)\n\nReturns a CuArray when device<:GPUDevice is used, otherwise a regular Array. Doesn't uses adapt, therefore always returns CuArray/Array\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.Device_KernelAbstractions-Tuple{SpeedyWeather.CPUDevice}","page":"Function and type index","title":"SpeedyWeather.Device_KernelAbstractions","text":"Device_KernelAbstractions(::AbstractDevice)\n\nReturn used device for use with KernelAbstractions\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.Device_KernelAbstractions-Tuple{}","page":"Function and type index","title":"SpeedyWeather.Device_KernelAbstractions","text":"Device_KernelAbstractions()\n\nReturn default used device for KernelAbstractions, either CPU or CUDADevice if a GPU is available\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!-Tuple{DiagnosticVariables, PrognosticVariables, Int64, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!(\n diagn::DiagnosticVariables,\n progn::PrognosticVariables,\n lf::Int64,\n model::SpeedyWeather.ModelSetup\n)\n\n\nPropagate the spectral state of progn to diagn using time step/leapfrog index lf. Function barrier that calls gridded! for the respective model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticLayerTimesteps, Int64, Barotropic}","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::Barotropic\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticLayerTimesteps, Int64, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::PrimitiveEquation\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for primitive equation models. Updates grid vorticity, grid divergence, grid temperature, pressure (pres_grid) and the velocities u,v.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticLayerTimesteps, Int64, ShallowWater}","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::ShallowWater\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities u,v.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather._scale_lat!-Union{Tuple{NF}, Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}, AbstractVector}} where NF<:AbstractFloat","page":"Function and type index","title":"SpeedyWeather._scale_lat!","text":"_scale_lat!(\n A::SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n v::AbstractVector\n)\n\n\nGeneric latitude scaling applied to A in-place with latitude-like vector v.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.allocate-Union{Tuple{Model}, Tuple{Type{PrognosticVariables}, SpectralGrid, Type{Model}}} where Model<:SpeedyWeather.ModelSetup","page":"Function and type index","title":"SpeedyWeather.allocate","text":"allocate(\n _::Type{PrognosticVariables},\n spectral_grid::SpectralGrid,\n _::Type{Model<:SpeedyWeather.ModelSetup}\n) -> PrognosticVariables\n\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.bernoulli_potential!-Union{Tuple{NF}, Tuple{SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, SpectralTransform}} where NF","page":"Function and type index","title":"SpeedyWeather.bernoulli_potential!","text":"bernoulli_potential!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},\n S::SpectralTransform\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nComputes the Laplace operator ∇² of the Bernoulli potential B in spectral space.\n\ncomputes the kinetic energy KE = ½(u²+v²) on the grid\ntransforms KE to spectral space\nadds geopotential for the Bernoulli potential in spectral space\ntakes the Laplace operator.\n\nThis version is used for both ShallowWater and PrimitiveEquation, only the geopotential calculation in geopotential! differs.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.boundary_layer_drag!-Tuple{ColumnVariables, LinearDrag}","page":"Function and type index","title":"SpeedyWeather.boundary_layer_drag!","text":"boundary_layer_drag!(\n column::ColumnVariables,\n scheme::LinearDrag\n)\n\n\nCompute tendency for boundary layer drag of a column and add to its tendencies fields\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.boundary_layer_drag!-Tuple{ColumnVariables, SpeedyWeather.NoBoundaryLayerDrag, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.boundary_layer_drag!","text":"NoBoundaryLayer scheme just passes.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.clip_negatives!-Union{Tuple{AbstractArray{T}}, Tuple{T}} where T","page":"Function and type index","title":"SpeedyWeather.clip_negatives!","text":"clip_negatives!(A::AbstractArray)\n\nSet all negative entries a in A to zero.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.create_output_folder-Tuple{String, Union{Int64, String}}","page":"Function and type index","title":"SpeedyWeather.create_output_folder","text":"create_output_folder(\n path::String,\n id::Union{Int64, String}\n) -> String\n\n\nCreates a new folder run_* with the identification id. Also returns the full path run_path of that folder.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.default_sigma_coordinates-Tuple{Integer}","page":"Function and type index","title":"SpeedyWeather.default_sigma_coordinates","text":"default_sigma_coordinates(nlev::Integer) -> Any\n\n\nVertical sigma coordinates defined by their nlev+1 half levels σ_levels_half. Sigma coordinates are fraction of surface pressure (p/p0) and are sorted from top (stratosphere) to bottom (surface). The first half level is at 0 the last at 1. Evaluate a generalised logistic function with coefficients in P for the distribution of values in between. Default coefficients follow the L31 configuration historically used at ECMWF.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.dry_static_energy!-Tuple{ColumnVariables, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.dry_static_energy!","text":"dry_static_energy!(\n column::ColumnVariables,\n constants::DynamicsConstants\n)\n\n\nCompute the dry static energy SE = cₚT + Φ (latent heat times temperature plus geopotential) for the column.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.dynamics_tendencies!","page":"Function and type index","title":"SpeedyWeather.dynamics_tendencies!","text":"dynamics_tendencies!(\n diagn::DiagnosticVariables,\n progn::PrognosticVariables,\n model::PrimitiveEquation\n) -> Any\ndynamics_tendencies!(\n diagn::DiagnosticVariables,\n progn::PrognosticVariables,\n model::PrimitiveEquation,\n lf::Int64\n) -> Any\n\n\nCalculate all tendencies for the PrimitiveEquation model (wet or dry).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.dynamics_tendencies!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, Dates.DateTime, Barotropic}","page":"Function and type index","title":"SpeedyWeather.dynamics_tendencies!","text":"dynamics_tendencies!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n time::Dates.DateTime,\n model::Barotropic\n)\n\n\nCalculate all tendencies for the BarotropicModel.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.dynamics_tendencies!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.SurfaceVariables, LowerTriangularMatrix, Dates.DateTime, ShallowWater}","page":"Function and type index","title":"SpeedyWeather.dynamics_tendencies!","text":"dynamics_tendencies!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surface::SpeedyWeather.SurfaceVariables,\n pres::LowerTriangularMatrix,\n time::Dates.DateTime,\n model::ShallowWater\n)\n\n\nCalculate all tendencies for the ShallowWaterModel.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.first_timesteps!-Tuple{PrognosticVariables, DiagnosticVariables, SpeedyWeather.ModelSetup, SpeedyWeather.AbstractOutputWriter}","page":"Function and type index","title":"SpeedyWeather.first_timesteps!","text":"first_timesteps!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n model::SpeedyWeather.ModelSetup,\n output::SpeedyWeather.AbstractOutputWriter\n) -> typeof(time)\n\n\nPerforms the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.flipsign!-Tuple{AbstractArray}","page":"Function and type index","title":"SpeedyWeather.flipsign!","text":"flipgsign!(A::AbstractArray)\n\nLike -A but in-place.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.flux_divergence!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, SpeedyWeather.RingGrids.AbstractGrid{NF}, SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Geometry{NF}, SpectralTransform{NF}}} where NF","page":"Function and type index","title":"SpeedyWeather.flux_divergence!","text":"flux_divergence!(\n A_tend::LowerTriangularMatrix{Complex{NF}},\n A_grid::SpeedyWeather.RingGrids.AbstractGrid{NF},\n diagn::SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},\n G::Geometry{NF},\n S::SpectralTransform{NF};\n add,\n flipsign\n)\n\n\nComputes ∇⋅((u,v)*A) with the option to add/overwrite A_tend and to flip_sign of the flux divergence by doing so.\n\nA_tend = ∇⋅((u,v)*A) for add=false, flip_sign=false\nA_tend = -∇⋅((u,v)*A) for add=false, flip_sign=true\nA_tend += ∇⋅((u,v)*A) for add=true, flip_sign=false\nA_tend -= ∇⋅((u,v)*A) for add=true, flip_sign=true\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.fluxes_to_tendencies!-Tuple{ColumnVariables, Geometry, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.fluxes_to_tendencies!","text":"fluxes_to_tendencies!(\n column::ColumnVariables,\n geometry::Geometry,\n constants::DynamicsConstants\n)\n\n\nConvert the fluxes on half levels to tendencies on full levels.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.generalised_logistic-Tuple{Any, SpeedyWeather.GenLogisticCoefs}","page":"Function and type index","title":"SpeedyWeather.generalised_logistic","text":"Generalised logistic function based on the coefficients in coefs.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.geopotential!-Tuple{DiagnosticVariables, SpeedyWeather.AbstractOrography, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.geopotential!","text":"geopotential!(\n diagn::DiagnosticVariables,\n O::SpeedyWeather.AbstractOrography,\n C::DynamicsConstants\n)\n\n\nCompute spectral geopotential geopot from spectral temperature temp and spectral surface geopotential geopot_surf (orography*gravity).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.geopotential!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, LowerTriangularMatrix, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.geopotential!","text":"geopotential!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n pres::LowerTriangularMatrix,\n C::DynamicsConstants\n) -> Any\n\n\ncalculates the geopotential in the ShallowWaterModel as g*η, i.e. gravity times the interface displacement (field pres)\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.geopotential!-Tuple{Vector, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.geopotential!","text":"geopotential!(temp::Vector, C::DynamicsConstants) -> Vector\n\n\nCalculate the geopotential based on temp in a single column. This exclues the surface geopotential that would need to be added to the returned vector. Function not used in the dynamical core but for post-processing and analysis.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_column!-Tuple{ColumnVariables, DiagnosticVariables, Int64, Geometry}","page":"Function and type index","title":"SpeedyWeather.get_column!","text":"Recalculate ring index if not provided.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_column!-Tuple{ColumnVariables, DiagnosticVariables, Integer, Integer, Geometry}","page":"Function and type index","title":"SpeedyWeather.get_column!","text":"get_column!(\n C::ColumnVariables,\n D::DiagnosticVariables,\n ij::Integer,\n jring::Integer,\n G::Geometry\n)\n\n\nUpdate C::ColumnVariables by copying the prognostic variables from D::DiagnosticVariables at gridpoint index ij. Provide G::Geometry for coordinate information.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_full_output_file_path-Tuple{OutputWriter}","page":"Function and type index","title":"SpeedyWeather.get_full_output_file_path","text":"get_full_output_file_path(output::OutputWriter) -> String\n\n\nReturns the full path of the output file after it was created.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_run_id-Tuple{String, String}","page":"Function and type index","title":"SpeedyWeather.get_run_id","text":"get_run_id(path::String, id::String) -> String\n\n\nChecks existing run_???? folders in path to determine a 4-digit id number by counting up. E.g. if folder run_0001 exists it will return the string \"0002\". Does not create a folder for the returned run id.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_thermodynamics!-Tuple{ColumnVariables, PrimitiveDry}","page":"Function and type index","title":"SpeedyWeather.get_thermodynamics!","text":"get_thermodynamics!(\n column::ColumnVariables,\n model::PrimitiveDry\n)\n\n\nCalculate the dry static energy for the primitive dry model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_thermodynamics!-Tuple{ColumnVariables, PrimitiveWet}","page":"Function and type index","title":"SpeedyWeather.get_thermodynamics!","text":"get_thermodynamics!(\n column::ColumnVariables,\n model::PrimitiveWet\n)\n\n\nCalculate thermodynamic quantities like saturation vapour pressure, saturation specific humidity, dry static energy, moist static energy and saturation moist static energy from the prognostic column variables.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_var-Tuple{PrognosticVariables, Symbol}","page":"Function and type index","title":"SpeedyWeather.get_var","text":"get_var(progn::PrognosticVariables, var_name::Symbol; lf::Integer=1)\n\nReturns the prognostic variable var_name at leapfrog index lf as a Vector{LowerTriangularMatrices}.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.has-Tuple{Type{<:SpeedyWeather.ModelSetup}, Symbol}","page":"Function and type index","title":"SpeedyWeather.has","text":"has(\n M::Type{<:SpeedyWeather.ModelSetup},\n var_name::Symbol\n) -> Bool\n\n\nReturns true if the model M has a prognostic variable var_name, false otherwise. The default fallback is that all variables are included. \n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.horizontal_diffusion!","page":"Function and type index","title":"SpeedyWeather.horizontal_diffusion!","text":"horizontal_diffusion!(\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::ShallowWater\n)\nhorizontal_diffusion!(\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::ShallowWater,\n lf::Int64\n)\n\n\nApply horizontal diffusion to vorticity and diffusion in the ShallowWater models.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.horizontal_diffusion!-2","page":"Function and type index","title":"SpeedyWeather.horizontal_diffusion!","text":"horizontal_diffusion!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n model::Barotropic\n)\nhorizontal_diffusion!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n model::Barotropic,\n lf::Int64\n)\n\n\nApply horizontal diffusion to vorticity in the Barotropic models.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.horizontal_diffusion!-3","page":"Function and type index","title":"SpeedyWeather.horizontal_diffusion!","text":"horizontal_diffusion!(\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::PrimitiveEquation\n) -> Union{Nothing, Bool}\nhorizontal_diffusion!(\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::PrimitiveEquation,\n lf::Int64\n) -> Union{Nothing, Bool}\n\n\nApply horizontal diffusion applied to vorticity, diffusion and temperature in the PrimitiveEquation models. Uses the constant diffusion for temperature but possibly adaptive diffusion for vorticity and divergence.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.horizontal_diffusion!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, AbstractVector{NF}, AbstractVector{NF}}} where NF<:AbstractFloat","page":"Function and type index","title":"SpeedyWeather.horizontal_diffusion!","text":"horizontal_diffusion!(\n tendency::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n A::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n ∇²ⁿ_expl::AbstractArray{NF<:AbstractFloat, 1},\n ∇²ⁿ_impl::AbstractArray{NF<:AbstractFloat, 1}\n)\n\n\nApply horizontal diffusion to a 2D field A in spectral space by updating its tendency tendency with an implicitly calculated diffusion term. The implicit diffusion of the next time step is split into an explicit part ∇²ⁿ_expl and an implicit part ∇²ⁿ_impl, such that both can be calculated in a single forward step by using A as well as its tendency tendency.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.implicit_correction!-Tuple{DiagnosticVariables, SpeedyWeather.ImplicitPrimitiveEq, PrognosticVariables}","page":"Function and type index","title":"SpeedyWeather.implicit_correction!","text":"implicit_correction!(\n diagn::DiagnosticVariables,\n implicit::SpeedyWeather.ImplicitPrimitiveEq,\n progn::PrognosticVariables\n) -> Any\n\n\nApply the implicit corrections to dampen gravity waves in the primitive equation models.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.implicit_correction!-Union{Tuple{NF}, Tuple{SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, SpeedyWeather.PrognosticLayerTimesteps{NF}, SpeedyWeather.SurfaceVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, SpeedyWeather.PrognosticSurfaceTimesteps{NF}, SpeedyWeather.ImplicitShallowWater}} where NF","page":"Function and type index","title":"SpeedyWeather.implicit_correction!","text":"implicit_correction!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},\n progn::SpeedyWeather.PrognosticLayerTimesteps{NF},\n diagn_surface::SpeedyWeather.SurfaceVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},\n progn_surface::SpeedyWeather.PrognosticSurfaceTimesteps{NF},\n implicit::SpeedyWeather.ImplicitShallowWater\n)\n\n\nApply correction to the tendencies in diagn to prevent the gravity waves from amplifying. The correction is implicitly evaluated using the parameter implicit.α to switch between forward, centered implicit or backward evaluation of the gravity wave terms.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initial_conditions!-Tuple{PrognosticVariables, StartFromFile, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.initial_conditions!","text":"initial_conditions!(\n progn_new::PrognosticVariables,\n initial_conditions::StartFromFile,\n model::SpeedyWeather.ModelSetup\n) -> PrognosticVariables\n\n\nRestart from a previous SpeedyWeather.jl simulation via the restart file restart.jld2 Applies interpolation in the horizontal but not in the vertical.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initial_conditions!-Tuple{PrognosticVariables, ZonalJet, ShallowWater}","page":"Function and type index","title":"SpeedyWeather.initial_conditions!","text":"initial_conditions!(\n progn::PrognosticVariables,\n initial_conditions::ZonalJet,\n model::ShallowWater\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nInitial conditions from Galewsky, 2004, Tellus\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initial_conditions!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, StartWithRandomVorticity, SpeedyWeather.ModelSetup}} where NF","page":"Function and type index","title":"SpeedyWeather.initial_conditions!","text":"initial_conditions!(\n progn::PrognosticVariables{NF},\n initial_conditions::StartWithRandomVorticity,\n model::SpeedyWeather.ModelSetup\n)\n\n\nStart with random vorticity as initial conditions\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initial_conditions!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, ZonalWind, PrimitiveEquation}} where NF","page":"Function and type index","title":"SpeedyWeather.initial_conditions!","text":"initial_conditions!(\n progn::PrognosticVariables{NF},\n initial_conditions::ZonalWind,\n model::PrimitiveEquation\n)\n\n\nInitial conditions from Jablonowski and Williamson, 2006, QJR Meteorol. Soc\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initial_conditions-Tuple{Model} where Model","page":"Function and type index","title":"SpeedyWeather.initial_conditions","text":"initial_conditions(model) -> PrognosticVariables\n\n\nAllocate the prognostic variables and then set to initial conditions.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::HyperDiffusion,\n k::Int64,\n G::Geometry,\n L::SpeedyWeather.TimeStepper\n)\ninitialize!(\n scheme::HyperDiffusion,\n k::Int64,\n G::Geometry,\n L::SpeedyWeather.TimeStepper,\n vor_max::Real\n)\n\n\nPrecomputes the hyper diffusion terms in scheme for layer k based on the model time step in L, the vertical level sigma level in G, and the current (absolute) vorticity maximum level vor_max\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{Barotropic}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(model::Barotropic) -> SpeedyWeather.Simulation\n\n\nCalls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping!.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{EarthOrography, SpeedyWeather.AbstractPlanet, SpectralTransform, Geometry}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n orog::EarthOrography,\n P::SpeedyWeather.AbstractPlanet,\n S::SpectralTransform,\n G::Geometry\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nInitialize the arrays orography,geopot_surf in orog by reading the orography field from file.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{Feedback, SpeedyWeather.Clock, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n feedback::Feedback,\n clock::SpeedyWeather.Clock,\n model::SpeedyWeather.ModelSetup\n) -> Union{Nothing, IOStream}\n\n\nInitializes the a Feedback struct.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{HeldSuarez, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(scheme::HeldSuarez, model::PrimitiveEquation)\n\n\ninitialize the HeldSuarez temperature relaxation by precomputing terms for the equilibrium temperature Teq.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{HyperDiffusion, SpeedyWeather.DiagnosticVariablesLayer, Geometry, SpeedyWeather.TimeStepper}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::HyperDiffusion,\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n G::Geometry,\n L::SpeedyWeather.TimeStepper\n)\n\n\nPre-function to other initialize!(::HyperDiffusion) initialisors that calculates the (absolute) vorticity maximum for the layer of diagn.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{HyperDiffusion, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::HyperDiffusion,\n model::SpeedyWeather.ModelSetup\n)\n\n\nPrecomputes the hyper diffusion terms in scheme based on the model time step, and possibly with a changing strength/power in the vertical.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{HyperDiffusion, SpeedyWeather.TimeStepper}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::HyperDiffusion,\n L::SpeedyWeather.TimeStepper\n)\n\n\nPrecomputes the 2D hyper diffusion terms in scheme based on the model time step.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{JablonowskiRelaxation, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::JablonowskiRelaxation,\n model::PrimitiveEquation\n)\n\n\ninitialize the JablonowskiRelaxation temperature relaxation by precomputing terms for the equilibrium temperature Teq and the frequency (strength of relaxation).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{LinearDrag, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(scheme::LinearDrag, model::PrimitiveEquation)\n\n\nPrecomputes the drag coefficients for this BoundaryLayerDrag scheme.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{NoTemperatureRelaxation, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::NoTemperatureRelaxation,\n model::PrimitiveEquation\n)\n\n\njust passes, does not need any initialization.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{PrimitiveDry}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(model::PrimitiveDry) -> SpeedyWeather.Simulation\n\n\nCalls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{PrimitiveWet}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(model::PrimitiveWet) -> SpeedyWeather.Simulation\n\n\nCalls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{ShallowWater}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(model::ShallowWater) -> SpeedyWeather.Simulation\n\n\nCalls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyCondensation, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::SpeedyCondensation,\n model::PrimitiveEquation\n)\n\n\nInitialize the SpeedyCondensation scheme.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyWeather.Clock, SpeedyWeather.TimeStepper}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n clock::SpeedyWeather.Clock,\n time_stepping::SpeedyWeather.TimeStepper\n) -> SpeedyWeather.Clock\n\n\nInitialize the clock with the time step Δt in the time_stepping.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyWeather.ImplicitPrimitiveEq, Integer, Real, DiagnosticVariables, Geometry, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n implicit::SpeedyWeather.ImplicitPrimitiveEq,\n i::Integer,\n dt::Real,\n diagn::DiagnosticVariables,\n geometry::Geometry,\n constants::DynamicsConstants\n)\n\n\nReinitialize implicit occasionally based on time step i and implicit.recalculate.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyWeather.ImplicitPrimitiveEq, Real, DiagnosticVariables, Geometry, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n implicit::SpeedyWeather.ImplicitPrimitiveEq,\n dt::Real,\n diagn::DiagnosticVariables,\n geometry::Geometry,\n constants::DynamicsConstants\n)\n\n\nInitialize the implicit terms for the PrimitiveEquation models.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyWeather.ImplicitShallowWater, Real, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n implicit::SpeedyWeather.ImplicitShallowWater,\n dt::Real,\n constants::DynamicsConstants\n)\n\n\nUpdate the implicit terms in implicit for the shallow water model as they depend on the time step dt.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyWeather.NoBoundaryLayerDrag, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"NoBoundaryLayer scheme does not need any initialisation.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{ZonalRidge, SpeedyWeather.AbstractPlanet, SpectralTransform, Geometry}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n orog::ZonalRidge,\n P::SpeedyWeather.AbstractPlanet,\n S::SpectralTransform,\n G::Geometry\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nInitialize the arrays orography,geopot_surf in orog following Jablonowski and Williamson, 2006.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Union{Tuple{Model}, Tuple{output_NF}, Tuple{OutputWriter{output_NF, Model}, SpeedyWeather.AbstractFeedback, SpeedyWeather.TimeStepper, DiagnosticVariables, Model}} where {output_NF, Model}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n output::OutputWriter{output_NF, Model},\n feedback::SpeedyWeather.AbstractFeedback,\n time_stepping::SpeedyWeather.TimeStepper,\n diagn::DiagnosticVariables,\n model\n)\n\n\nCreates a netcdf file on disk and the corresponding netcdf_file object preallocated with output variables and dimensions. write_output! then writes consecuitive time steps into this file.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Union{Tuple{NF}, Tuple{SpeedyWeather.StaticEnergyDiffusion{NF}, PrimitiveEquation}} where NF","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::SpeedyWeather.StaticEnergyDiffusion{NF},\n model::PrimitiveEquation\n) -> Any\n\n\nInitialize dry static energy diffusion.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize_geopotential-Tuple{Vector, Vector, Real}","page":"Function and type index","title":"SpeedyWeather.initialize_geopotential","text":"initialize_geopotential(\n σ_levels_full::Vector,\n σ_levels_half::Vector,\n R_dry::Real\n) -> Tuple{Vector{Float64}, Vector{Float64}}\n\n\nPrecomputes constants for the vertical integration of the geopotential, defined as\n\nΦ_{k+1/2} = Φ_{k+1} + R*T_{k+1}*(ln(p_{k+1}) - ln(p_{k+1/2})) (half levels) Φ_k = Φ_{k+1/2} + R*T_k*(ln(p_{k+1/2}) - ln(p_k)) (full levels)\n\nSame formula but k → k-1/2.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.isdecreasing-Tuple{Vector}","page":"Function and type index","title":"SpeedyWeather.isdecreasing","text":"true/false = isdecreasing(v::Vector)\n\nCheck whether elements of a vector v are strictly decreasing.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.isincreasing-Tuple{Vector}","page":"Function and type index","title":"SpeedyWeather.isincreasing","text":"true/false = isincreasing(v::Vector)\n\nCheck whether elements of a vector v are strictly increasing.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.large_scale_condensation!-Tuple{ColumnVariables, PrimitiveDry}","page":"Function and type index","title":"SpeedyWeather.large_scale_condensation!","text":"large_scale_condensation!(\n column::ColumnVariables,\n model::PrimitiveDry\n)\n\n\nNo condensation in a PrimitiveDry model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.large_scale_condensation!-Tuple{ColumnVariables, PrimitiveWet}","page":"Function and type index","title":"SpeedyWeather.large_scale_condensation!","text":"Function barrier only.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.large_scale_condensation!-Union{Tuple{NF}, Tuple{ColumnVariables{NF}, SpeedyCondensation, Geometry, DynamicsConstants, SpeedyWeather.AbstractAtmosphere, SpeedyWeather.TimeStepper}} where NF","page":"Function and type index","title":"SpeedyWeather.large_scale_condensation!","text":"large_scale_condensation!(\n column::ColumnVariables{NF},\n scheme::SpeedyCondensation,\n geometry::Geometry,\n constants::DynamicsConstants,\n atmosphere::SpeedyWeather.AbstractAtmosphere,\n time_stepping::SpeedyWeather.TimeStepper\n)\n\n\nLarge-scale condensation for a column by relaxation back to a reference relative humidity if larger than that. Calculates the tendencies for specific humidity and temperature and integrates the large-scale precipitation vertically for output.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.launch_kernel!-Tuple{SpeedyWeather.DeviceSetup, Any, Any, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.launch_kernel!","text":"launch_kernel!(device_setup::DeviceSetup, kernel!, ndrange, kernel_args...)\n\nLaunches the kernel! on the device_setup with ndrange computations over the kernel and arguments kernel_args\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.leapfrog!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, Real, Int64, Leapfrog{NF}}} where NF<:AbstractFloat","page":"Function and type index","title":"SpeedyWeather.leapfrog!","text":"leapfrog!(\n A_old::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n A_new::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n tendency::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n dt::Real,\n lf::Int64,\n L::Leapfrog{NF<:AbstractFloat}\n)\n\n\nPerforms one leapfrog time step with (lf=2) or without (lf=1) Robert+Williams filter (see Williams (2009), Montly Weather Review, Eq. 7-9).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.linear_pressure_gradient!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticSurfaceTimesteps, Int64, DynamicsConstants, SpeedyWeather.ImplicitPrimitiveEq}","page":"Function and type index","title":"SpeedyWeather.linear_pressure_gradient!","text":"linear_pressure_gradient!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surface::SpeedyWeather.PrognosticSurfaceTimesteps,\n lf::Int64,\n C::DynamicsConstants,\n I::SpeedyWeather.ImplicitPrimitiveEq\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nAdd the linear contribution of the pressure gradient to the geopotential. The pressure gradient in the divergence equation takes the form\n\n-∇⋅(Rd*Tᵥ*∇lnpₛ) = -∇⋅(Rd*Tᵥ'*∇lnpₛ) - ∇²(Rd*Tₖ*lnpₛ)\n\nSo that the second term inside the Laplace operator can be added to the geopotential. Rd is the gas constant, Tᵥ the virtual temperature and Tᵥ' its anomaly wrt to the average or reference temperature Tₖ, lnpₛ is the logarithm of surface pressure.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.linear_virtual_temperature!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticLayerTimesteps, DynamicsConstants, Int64}","page":"Function and type index","title":"SpeedyWeather.linear_virtual_temperature!","text":"linear_virtual_temperature!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n constants::DynamicsConstants,\n lf::Int64\n) -> Any\n\n\nCalculates a linearised virtual temperature Tᵥ as\n\nTᵥ = T + Tₖμq\n\nWith absolute temperature T, layer-average temperarture Tₖ (computed in temperature_average!), specific humidity q and\n\nμ = (1-ξ)/ξ, ξ = R_dry/R_vapour.\n\nin spectral space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.linear_virtual_temperature!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticLayerTimesteps, PrimitiveDry, Integer}","page":"Function and type index","title":"SpeedyWeather.linear_virtual_temperature!","text":"linear_virtual_temperature!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n model::PrimitiveDry,\n lf::Integer\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nLinear virtual temperature for model::PrimitiveDry: Just copy over arrays from temp to temp_virt at timestep lf in spectral space as humidity is zero in this model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.load_trajectory-Tuple{Union{String, Symbol}, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.load_trajectory","text":"load_trajectory(\n var_name::Union{String, Symbol},\n model::SpeedyWeather.ModelSetup\n) -> Any\n\n\nLoads a var_name trajectory of the model M that has been saved in a netCDF file during the time stepping.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.moist_static_energy!-Tuple{ColumnVariables, SpeedyWeather.Thermodynamics}","page":"Function and type index","title":"SpeedyWeather.moist_static_energy!","text":"moist_static_energy!(\n column::ColumnVariables,\n thermodynamics::SpeedyWeather.Thermodynamics\n)\n\n\nCompute the moist static energy\n\nMSE = SE + Lc*Q = cₚT + Φ + Lc*Q\n\nwith the static energy SE, the latent heat of condensation Lc, the geopotential Φ. As well as the saturation moist static energy which replaces Q with Q_sat\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.nans-Tuple","page":"Function and type index","title":"SpeedyWeather.nans","text":"A = nans(dims...)\n\nAllocate A::Array{Float64} with NaNs.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.nans-Union{Tuple{T}, Tuple{Type{T}, Vararg{Any}}} where T","page":"Function and type index","title":"SpeedyWeather.nans","text":"A = nans(T,dims...)\n\nAllocate array A with NaNs of type T. Similar to zeros(T,dims...).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.nar_detection!-Tuple{Feedback, PrognosticVariables}","page":"Function and type index","title":"SpeedyWeather.nar_detection!","text":"nar_detection!(\n feedback::Feedback,\n progn::PrognosticVariables\n) -> Union{Nothing, Bool}\n\n\nDetect NaR (Not-a-Real) in the prognostic variables.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.parameterization_tendencies!-Tuple{DiagnosticVariables, Dates.DateTime, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.parameterization_tendencies!","text":"parameterization_tendencies!(\n diagn::DiagnosticVariables,\n time::Dates.DateTime,\n model::PrimitiveEquation\n) -> Any\n\n\nCompute tendencies for u,v,temp,humid from physical parametrizations. Extract for each vertical atmospheric column the prognostic variables (stored in diagn as they are grid-point transformed), loop over all grid-points, compute all parametrizations on a single-column basis, then write the tendencies back into a horizontal field of tendencies.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.pressure_on_orography!-Tuple{PrognosticVariables, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.pressure_on_orography!","text":"pressure_on_orography!(\n progn::PrognosticVariables,\n model::PrimitiveEquation\n)\n\n\nInitialize surface pressure on orography by integrating the hydrostatic equation with the reference temperature lapse rate.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.progress!-Tuple{Feedback}","page":"Function and type index","title":"SpeedyWeather.progress!","text":"progress!(feedback::Feedback)\n\n\nCalls the progress meter and writes every 5% progress increase to txt.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.progress_finish!-Tuple{Feedback}","page":"Function and type index","title":"SpeedyWeather.progress_finish!","text":"progress_finish!(F::Feedback)\n\n\nFinalises the progress meter and the progress txt file.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.readable_secs-Tuple{Real}","page":"Function and type index","title":"SpeedyWeather.readable_secs","text":"readable_secs(secs::Real) -> Dates.CompoundPeriod\n\n\nReturns Dates.CompoundPeriod rounding to either (days, hours), (hours, minutes), (minutes, seconds), or seconds with 1 decimal place accuracy for >10s and two for less. E.g.\n\njulia> readable_secs(12345)\n3 hours, 26 minutes\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.remaining_time-Tuple{ProgressMeter.Progress}","page":"Function and type index","title":"SpeedyWeather.remaining_time","text":"remaining_time(p::ProgressMeter.Progress) -> String\n\n\nEstimates the remaining time from a ProgresssMeter.Progress. Adapted from ProgressMeter.jl\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.reset_column!-Union{Tuple{ColumnVariables{NF}}, Tuple{NF}} where NF","page":"Function and type index","title":"SpeedyWeather.reset_column!","text":"reset_column!(column::ColumnVariables{NF})\n\n\nSet the accumulators (tendencies but also vertical sums and similar) back to zero for column to be reused at other grid points.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.run!-Tuple{SpeedyWeather.Simulation}","page":"Function and type index","title":"SpeedyWeather.run!","text":"run!(\n simulation::SpeedyWeather.Simulation;\n initialize,\n n_days,\n startdate,\n output\n) -> PrognosticVariables\n\n\nRun a SpeedyWeather.jl simulation. The simulation.model is assumed to be initialized, otherwise use initialize=true as keyword argument.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.saturation_humidity!-Tuple{ColumnVariables, SpeedyWeather.Thermodynamics}","page":"Function and type index","title":"SpeedyWeather.saturation_humidity!","text":"saturation_humidity!(\n column::ColumnVariables,\n thermodynamics::SpeedyWeather.Thermodynamics\n)\n\n\nCompute (1) the saturation vapour pressure as a function of temperature using the August-Roche-Magnus formula,\n\neᵢ(T) = e₀ * exp(Cᵢ * (T - T₀) / (T - Tᵢ)),\n\nwhere T is in Kelvin and i = 1,2 for saturation with respect to water and ice, respectively. And (2) the saturation specific humidity according to the formula,\n\n0.622 * e / (p - (1 - 0.622) * e),\n\nwhere e is the saturation vapour pressure, p is the pressure, and 0.622 is the ratio of the molecular weight of water to dry air.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.scale!-Tuple{PrognosticVariables, Real}","page":"Function and type index","title":"SpeedyWeather.scale!","text":"scale!(progn::PrognosticVariables, scale::Real) -> Real\n\n\nScales the prognostic variables vorticity and divergence with the Earth's radius which is used in the dynamical core.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.scale!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Real}} where NF","page":"Function and type index","title":"SpeedyWeather.scale!","text":"scale!(\n progn::PrognosticVariables{NF},\n var::Symbol,\n scale::Real\n)\n\n\nScale the variable var inside progn with scalar scale.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_divergence!-Tuple{PrognosticVariables, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.set_divergence!","text":"set_divergence!(progn::PrognosticVariables, varargs...; kwargs...)\n\nSee set_var!\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_humidity!-Tuple{PrognosticVariables, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.set_humidity!","text":"set_humidity!(progn::PrognosticVariables, varargs...; kwargs...)\n\nSee set_var!\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_pressure!-Tuple{PrognosticVariables, AbstractMatrix}","page":"Function and type index","title":"SpeedyWeather.set_pressure!","text":"set_pressure!(progn::PrognosticVariables{NF}, \n pressure::AbstractMatrix, \n Grid::Type{<:AbstractGrid}, \n lf::Integer=1) where NF\n\nSets the prognostic variable with the surface pressure in grid space at leapfrog index lf.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_pressure!-Tuple{PrognosticVariables, LowerTriangularMatrix}","page":"Function and type index","title":"SpeedyWeather.set_pressure!","text":"set_pressure!(progn::PrognosticVariables{NF}, \n pressure::LowerTriangularMatrix;\n lf::Integer=1) where NF\n\nSets the prognostic variable with the surface pressure in spectral space at leapfrog index lf.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_pressure!-Tuple{PrognosticVariables, SpeedyWeather.RingGrids.AbstractGrid, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.set_pressure!","text":"set_pressure!(progn::PrognosticVariables{NF}, \n pressure::AbstractGrid, \n M::ModelSetup;\n lf::Integer=1) where NF\n\nSets the prognostic variable with the surface pressure in grid space at leapfrog index lf.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_pressure!-Tuple{PrognosticVariables, SpeedyWeather.RingGrids.AbstractGrid}","page":"Function and type index","title":"SpeedyWeather.set_pressure!","text":"set_pressure!(progn::PrognosticVariables{NF}, \n pressure::AbstractGrid, \n lf::Integer=1) where NF\n\nSets the prognostic variable with the surface pressure in grid space at leapfrog index lf.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_temperature!-Tuple{PrognosticVariables, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.set_temperature!","text":"set_temperature!(progn::PrognosticVariables, varargs...; kwargs...)\n\nSee set_var!\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_var!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Number}} where NF","page":"Function and type index","title":"SpeedyWeather.set_var!","text":"function set_var!(progn::PrognosticVariables{NF}, \n varname::Symbol, \n s::Number;\n lf::Integer=1) where NF\n\nSets all values of prognostic variable varname at leapfrog index lf to the scalar s.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_var!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Vector{<:AbstractMatrix}}, Tuple{PrognosticVariables{NF}, Symbol, Vector{<:AbstractMatrix}, Type{<:SpeedyWeather.RingGrids.AbstractGrid}}} where NF","page":"Function and type index","title":"SpeedyWeather.set_var!","text":"set_var!(progn::PrognosticVariables{NF}, \n varname::Symbol, \n var::Vector{<:AbstractMatrix}, \n Grid::Type{<:AbstractGrid}=FullGaussianGrid;\n lf::Integer=1) where NF\n\nSets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_var!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Vector{<:LowerTriangularMatrix}}} where NF","page":"Function and type index","title":"SpeedyWeather.set_var!","text":"set_var!(progn::PrognosticVariables{NF}, \n varname::Symbol, \n var::Vector{<:LowerTriangularMatrix};\n lf::Integer=1) where NF\n\nSets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in spectral space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_var!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Vector{<:SpeedyWeather.RingGrids.AbstractGrid}, SpeedyWeather.ModelSetup}} where NF","page":"Function and type index","title":"SpeedyWeather.set_var!","text":"set_var!(progn::PrognosticVariables{NF}, \n varname::Symbol, \n var::Vector{<:AbstractGrid}, \n M::ModelSetup;\n lf::Integer=1) where NF\n\nSets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_var!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Vector{<:SpeedyWeather.RingGrids.AbstractGrid}}} where NF","page":"Function and type index","title":"SpeedyWeather.set_var!","text":"set_var!(progn::PrognosticVariables{NF}, \n varname::Symbol, \n var::Vector{<:AbstractGrid};\n lf::Integer=1) where NF\n\nSets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_vorticity!-Tuple{PrognosticVariables, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.set_vorticity!","text":"set_vorticity!(progn::PrognosticVariables, varargs...; kwargs...)\n\nSee set_var!\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.sigma_okay-Tuple{Integer, AbstractVector}","page":"Function and type index","title":"SpeedyWeather.sigma_okay","text":"sigma_okay(nlev::Integer, σ_half::AbstractVector) -> Bool\n\n\nCheck that nlev and σ_half match.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.speedstring-Tuple{Any, Any}","page":"Function and type index","title":"SpeedyWeather.speedstring","text":"speedstring(sec_per_iter, dt_in_sec) -> String\n\n\ndefine a ProgressMeter.speedstring method that also takes a time step dt_in_sec to translate sec/iteration to days/days-like speeds.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.static_energy_diffusion!-Union{Tuple{NF}, Tuple{ColumnVariables{NF}, SpeedyWeather.StaticEnergyDiffusion}} where NF","page":"Function and type index","title":"SpeedyWeather.static_energy_diffusion!","text":"static_energy_diffusion!(\n column::ColumnVariables{NF},\n scheme::SpeedyWeather.StaticEnergyDiffusion\n)\n\n\nApply dry static energy diffusion.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.surface_pressure_tendency!-Tuple{SpeedyWeather.SurfaceVariables, SpectralTransform}","page":"Function and type index","title":"SpeedyWeather.surface_pressure_tendency!","text":"surface_pressure_tendency!( Prog::PrognosticVariables,\n Diag::DiagnosticVariables,\n lf::Int,\n M::PrimitiveEquation)\n\nComputes the tendency of the logarithm of surface pressure as\n\n-(ū*px + v̄*py) - D̄\n\nwith ū,v̄ being the vertically averaged velocities; px, py the gradients of the logarithm of surface pressure ln(p_s) and D̄ the vertically averaged divergence.\n\nCalculate ∇ln(p_s) in spectral space, convert to grid.\nMultiply ū,v̄ with ∇ln(p_s) in grid-point space, convert to spectral.\nD̄ is subtracted in spectral space.\nSet tendency of the l=m=0 mode to 0 for better mass conservation.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_anomaly!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.ImplicitPrimitiveEq}","page":"Function and type index","title":"SpeedyWeather.temperature_anomaly!","text":"Convert absolute and virtual temperature to anomalies wrt to the reference profile\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_average!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, LowerTriangularMatrix, SpectralTransform}","page":"Function and type index","title":"SpeedyWeather.temperature_average!","text":"temperature_average!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n temp::LowerTriangularMatrix,\n S::SpectralTransform\n) -> Any\n\n\nCalculates the average temperature of a layer from the l=m=0 harmonic and stores the result in diagn.temp_average\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_relaxation!-Tuple{ColumnVariables, JablonowskiRelaxation}","page":"Function and type index","title":"SpeedyWeather.temperature_relaxation!","text":"temperature_relaxation!(\n column::ColumnVariables,\n scheme::JablonowskiRelaxation\n)\n\n\nApply HeldSuarez-like temperature relaxation to the Jablonowski and Williamson vertical profile.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_relaxation!-Tuple{ColumnVariables, NoTemperatureRelaxation}","page":"Function and type index","title":"SpeedyWeather.temperature_relaxation!","text":"temperature_relaxation!(\n column::ColumnVariables,\n scheme::NoTemperatureRelaxation\n)\n\n\njust passes.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_relaxation!-Union{Tuple{NF}, Tuple{ColumnVariables{NF}, HeldSuarez}} where NF","page":"Function and type index","title":"SpeedyWeather.temperature_relaxation!","text":"temperature_relaxation!(\n column::ColumnVariables{NF},\n scheme::HeldSuarez\n)\n\n\nApply temperature relaxation following Held and Suarez 1996, BAMS.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_tendency!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, DynamicsConstants, Geometry, SpectralTransform, SpeedyWeather.ImplicitPrimitiveEq}","page":"Function and type index","title":"SpeedyWeather.temperature_tendency!","text":"temperature_tendency!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n C::DynamicsConstants,\n G::Geometry,\n S::SpectralTransform,\n I::SpeedyWeather.ImplicitPrimitiveEq\n)\n\n\nCompute the temperature tendency\n\n∂T/∂t += -∇⋅((u,v)*T') + T'D + κTᵥ*Dlnp/Dt\n\n+= because the tendencies already contain parameterizations and vertical advection. T' is the anomaly with respect to the reference/average temperature. Tᵥ is the virtual temperature used in the adiabatic term κTᵥ*Dlnp/Dt.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_tendency!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.temperature_tendency!","text":"temperature_tendency!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::PrimitiveEquation\n)\n\n\nFunction barrier to unpack model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.time_stepping!-Tuple{PrognosticVariables, DiagnosticVariables, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.time_stepping!","text":"time_stepping!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n model::SpeedyWeather.ModelSetup\n) -> PrognosticVariables\n\n\nMain time loop that that initializes output and feedback, loops over all time steps and calls the output and feedback functions.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.timestep!","page":"Function and type index","title":"SpeedyWeather.timestep!","text":"timestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n dt::Real,\n i::Integer,\n model::Barotropic\n)\ntimestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n dt::Real,\n i::Integer,\n model::Barotropic,\n lf1::Int64\n)\ntimestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n dt::Real,\n i::Integer,\n model::Barotropic,\n lf1::Int64,\n lf2::Int64\n)\n\n\nCalculate a single time step for the model <: Barotropic.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.timestep!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, PrimitiveEquation}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, PrimitiveEquation, Int64}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, PrimitiveEquation, Int64, Int64}} where NF<:AbstractFloat","page":"Function and type index","title":"SpeedyWeather.timestep!","text":"timestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::PrimitiveEquation\n) -> Any\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::PrimitiveEquation,\n lf1::Int64\n) -> Any\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::PrimitiveEquation,\n lf1::Int64,\n lf2::Int64\n) -> Any\n\n\nCalculate a single time step for the model<:PrimitiveEquation\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.timestep!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, ShallowWater}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, ShallowWater, Int64}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, ShallowWater, Int64, Int64}} where NF<:AbstractFloat","page":"Function and type index","title":"SpeedyWeather.timestep!","text":"timestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::ShallowWater\n) -> Union{Nothing, SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat}\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::ShallowWater,\n lf1::Int64\n) -> Union{Nothing, SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat}\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::ShallowWater,\n lf1::Int64,\n lf2::Int64\n) -> Union{Nothing, SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat}\n\n\nCalculate a single time step for the model <: ShallowWater.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.underflow!-Union{Tuple{T}, Tuple{AbstractArray{T}, Real}} where T","page":"Function and type index","title":"SpeedyWeather.underflow!","text":"underflow!(A::AbstractArray,ϵ::Real)\n\nUnderflows element a in A to zero if abs(a) < ϵ.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.unscale!-Tuple{AbstractArray, Real}","page":"Function and type index","title":"SpeedyWeather.unscale!","text":"unscale!(variable::AbstractArray, scale::Real) -> Any\n\n\nUndo the radius-scaling for any variable. Method used for netcdf output.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.unscale!-Tuple{PrognosticVariables}","page":"Function and type index","title":"SpeedyWeather.unscale!","text":"unscale!(progn::PrognosticVariables) -> Int64\n\n\nUndo the radius-scaling of vorticity and divergence from scale!(progn,scale::Real).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vertical_integration!-Union{Tuple{NF}, Tuple{DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, PrognosticVariables{NF}, Int64, Geometry{NF}}} where NF","page":"Function and type index","title":"SpeedyWeather.vertical_integration!","text":"vertical_integration!(Diag::DiagnosticVariables,G::Geometry)\n\nCalculates the vertically averaged (weighted by the thickness of the σ level) velocities (*coslat) and divergence. E.g.\n\nu_mean = ∑_k=1^nlev Δσ_k * u_k\n\nu,v are averaged in grid-point space, divergence in spectral space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.virtual_temperature!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, LowerTriangularMatrix, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.virtual_temperature!","text":"virtual_temperature!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n temp::LowerTriangularMatrix,\n constants::DynamicsConstants\n)\n\n\nCalculates the virtual temperature Tᵥ as\n\nTᵥ = T(1+μq)\n\nWith absolute temperature T, specific humidity q and\n\nμ = (1-ξ)/ξ, ξ = R_dry/R_vapour.\n\nin grid-point space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.virtual_temperature!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, LowerTriangularMatrix, PrimitiveDry}","page":"Function and type index","title":"SpeedyWeather.virtual_temperature!","text":"virtual_temperature!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n temp::LowerTriangularMatrix,\n model::PrimitiveDry\n) -> SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat\n\n\nVirtual temperature in grid-point space: For the PrimitiveDry temperature and virtual temperature are the same (humidity=0). Just copy over the arrays.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.volume_flux_divergence!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.SurfaceVariables, SpeedyWeather.AbstractOrography, DynamicsConstants, Geometry, SpectralTransform}","page":"Function and type index","title":"SpeedyWeather.volume_flux_divergence!","text":"volume_flux_divergence!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surface::SpeedyWeather.SurfaceVariables,\n orog::SpeedyWeather.AbstractOrography,\n constants::DynamicsConstants,\n G::Geometry,\n S::SpectralTransform\n)\n\n\nComputes the (negative) divergence of the volume fluxes uh,vh for the continuity equation, -∇⋅(uh,vh).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vordiv_tendencies!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.SurfaceVariables, DynamicsConstants, Geometry, SpectralTransform}","page":"Function and type index","title":"SpeedyWeather.vordiv_tendencies!","text":"vordiv_tendencies!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surf::SpeedyWeather.SurfaceVariables,\n C::DynamicsConstants,\n G::Geometry,\n S::SpectralTransform\n)\n\n\nTendencies for vorticity and divergence. Excluding Bernoulli potential with geopotential and linear pressure gradient inside the Laplace operator, which are added later in spectral space.\n\nu_tend += v*(f+ζ) - RTᵥ'*∇lnp_x\nv_tend += -u*(f+ζ) - RTᵥ'*∇lnp_y\n\n+= because the tendencies already contain the parameterizations and vertical advection. f is coriolis, ζ relative vorticity, R the gas constant Tᵥ' the virtual temperature anomaly, ∇lnp the gradient of surface pressure and _x and _y its zonal/meridional components. The tendencies are then curled/dived to get the tendencies for vorticity/divergence in spectral space\n\n∂ζ/∂t = ∇×(u_tend,v_tend)\n∂D/∂t = ∇⋅(u_tend,v_tend) + ...\n\n+ ... because there's more terms added later for divergence.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vordiv_tendencies!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.SurfaceVariables, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.vordiv_tendencies!","text":"vordiv_tendencies!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surf::SpeedyWeather.SurfaceVariables,\n model::PrimitiveEquation\n)\n\n\nFunction barrier to unpack model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vorticity_flux!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, Barotropic}","page":"Function and type index","title":"SpeedyWeather.vorticity_flux!","text":"vorticity_flux!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::Barotropic\n)\n\n\nVorticity flux tendency in the barotropic vorticity equation\n\n∂ζ/∂t = ∇×(u_tend,v_tend)\n\nwith\n\nu_tend = Fᵤ + v*(ζ+f) v_tend = Fᵥ - u*(ζ+f)\n\nwith Fᵤ,Fᵥ the forcing from forcing! already in u_tend_grid/v_tend_grid and vorticity ζ, coriolis f.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vorticity_flux!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, ShallowWater}","page":"Function and type index","title":"SpeedyWeather.vorticity_flux!","text":"vorticity_flux!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::ShallowWater\n)\n\n\nVorticity flux tendency in the shallow water equations\n\n∂ζ/∂t = ∇×(u_tend,v_tend) ∂D/∂t = ∇⋅(u_tend,v_tend)\n\nwith\n\nu_tend = Fᵤ + v*(ζ+f) v_tend = Fᵥ - u*(ζ+f)\n\nwith Fᵤ,Fᵥ the forcing from forcing! already in u_tend_grid/v_tend_grid and vorticity ζ, coriolis f.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vorticity_flux_curldiv!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, DynamicsConstants, Geometry, SpectralTransform}","page":"Function and type index","title":"SpeedyWeather.vorticity_flux_curldiv!","text":"vorticity_flux_curldiv!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n C::DynamicsConstants,\n G::Geometry,\n S::SpectralTransform;\n div\n)\n\n\nCompute the vorticity advection as the curl/div of the vorticity fluxes\n\n∂ζ/∂t = ∇×(u_tend,v_tend) ∂D/∂t = ∇⋅(u_tend,v_tend)\n\nwith\n\nu_tend = Fᵤ + v*(ζ+f) v_tend = Fᵥ - u*(ζ+f)\n\nwith Fᵤ,Fᵥ from u_tend_grid/v_tend_grid that are assumed to be alread set in forcing!. Set div=false for the BarotropicModel which doesn't require the divergence tendency.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.workgroup_size-Tuple{SpeedyWeather.AbstractDevice}","page":"Function and type index","title":"SpeedyWeather.workgroup_size","text":"workgroup_size(dev::AbstractDevice)\n\nReturns a workgroup size depending on dev. WIP: Will be expanded in the future to also include grid information. \n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.write_column_tendencies!-Tuple{DiagnosticVariables, ColumnVariables, Int64}","page":"Function and type index","title":"SpeedyWeather.write_column_tendencies!","text":"write_column_tendencies!(\n D::DiagnosticVariables,\n C::ColumnVariables,\n ij::Int64\n)\n\n\nWrite the parametrization tendencies from C::ColumnVariables into the horizontal fields of tendencies stored in D::DiagnosticVariables at gridpoint index ij.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.write_netcdf_time!-Tuple{OutputWriter, Dates.DateTime}","page":"Function and type index","title":"SpeedyWeather.write_netcdf_time!","text":"write_netcdf_time!(\n output::OutputWriter,\n time::Dates.DateTime\n)\n\n\nWrite the current time time::DateTime to the netCDF file in output::OutputWriter.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.write_netcdf_variables!-Union{Tuple{Model}, Tuple{Grid}, Tuple{NF}, Tuple{OutputWriter, DiagnosticVariables{NF, Grid, Model}}} where {NF, Grid, Model}","page":"Function and type index","title":"SpeedyWeather.write_netcdf_variables!","text":"write_netcdf_variables!(\n output::OutputWriter,\n diagn::DiagnosticVariables{NF, Grid, Model}\n)\n\n\nWrite diagnostic variables from diagn to the netCDF file in output::OutputWriter.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.write_output!-Tuple{OutputWriter, Dates.DateTime, DiagnosticVariables}","page":"Function and type index","title":"SpeedyWeather.write_output!","text":"write_output!(\n outputter::OutputWriter,\n time::Dates.DateTime,\n diagn::DiagnosticVariables\n)\n\n\nWrites the variables from diagn of time step i at time time into outputter.netcdf_file. Simply escapes for no netcdf output of if output shouldn't be written on this time step. Interpolates onto output grid and resolution as specified in outputter, converts to output number format, truncates the mantissa for higher compression and applies lossless compression.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.write_restart_file-Tuple{PrognosticVariables, OutputWriter}","page":"Function and type index","title":"SpeedyWeather.write_restart_file","text":"write_restart_file(\n progn::PrognosticVariables,\n output::OutputWriter\n) -> Union{Nothing, String}\n\n\nA restart file restart.jld2 with the prognostic variables is written to the output folder (or current path) that can be used to restart the model. restart.jld2 will then be used as initial conditions. The prognostic variables are bitrounded for compression and the 2nd leapfrog time step is discarded. Variables in restart file are unscaled.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.zero_tendencies!-Tuple{DiagnosticVariables}","page":"Function and type index","title":"SpeedyWeather.zero_tendencies!","text":"zero_tendencies!(diagn::DiagnosticVariables)\n\n\nSet the tendencies in diagn to zero.\n\n\n\n\n\n","category":"method"},{"location":"how_to_run_speedy/#How-to-run-SpeedyWeather.jl","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Creating a SpeedyWeather.jl simulation and running it consists conceptually of 4 steps","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Create a SpectralGrid which defines the grid and spectral resolution\nCreate a model\nInitialize a model to obtain a Simulation.\nRun the simulation.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"In the following we will describe these steps in more detail, but let's start with some examples first.","category":"page"},{"location":"how_to_run_speedy/#Example-1:-2D-turbulence-on-a-non-rotating-sphere","page":"How to run SpeedyWeather.jl","title":"Example 1: 2D turbulence on a non-rotating sphere","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"We want to use the barotropic model to simulate some free-decaying 2D turbulence on the sphere without rotation. We start by defining the SpectralGrid object. To have a resolution of about 100km, we choose a spectral resolution of T127 (see Available horizontal resolutions) and nlev=1 vertical levels. The SpectralGrid object will provide us with some more information","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> spectral_grid = SpectralGrid(trunc=127,nlev=1)\nSpectralGrid:\n Spectral: T127 LowerTriangularMatrix{Complex{Float32}}, radius = 6.371e6 m\n Grid: 40320-element, 192-ring OctahedralGaussianGrid{Float32} (quadratic)\n Resolution: 112km (average)\n Vertical: 1-level SigmaCoordinates","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"We could have specified further options, but let's ignore that for now. Next step we create a planet that's like Earth but not rotating","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> still_earth = Earth(rotation=0)\nMain.SpeedyWeather.Earth\n rotation: Float64 0.0\n gravity: Float64 9.81\n daily_cycle: Bool true\n length_of_day: Float64 24.0\n seasonal_cycle: Bool true\n length_of_year: Float64 365.25\n equinox: Dates.DateTime\n axial_tilt: Float64 23.4","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"There are other options to create a planet but they are irrelevant for the barotropic vorticity equations. We also want to specify the initial conditions, randomly distributed vorticity is already defined","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> initial_conditions = StartWithRandomVorticity()\nStartWithRandomVorticity\n power_law: Float64 -3.0\n amplitude: Float64 1.0e-5","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"By default, the power of vorticity is spectrally distributed with k^-3, k being the horizontal wavenumber, and the amplitude is 10^-5text s^-1.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Now we want to construct a BarotropicModel with these","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> model = BarotropicModel(;spectral_grid, initial_conditions, planet=still_earth);","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The model contains all the parameters, but isn't initialized yet, which we can do with and then run it.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> simulation = initialize!(model);\njulia> run!(simulation,n_days=30)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The run! command will always return the prognostic variables, which, by default, are plotted for surface relative vorticity with a unicode plot. The resolution of the plot is not necessarily representative but it lets us have a quick look at the result","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Barotropic vorticity unicode plot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Woohoo! I can see turbulence! You could pick up where this simulation stopped by simply doing run!(simulation,n_days=50) again. We didn't store any output, which you can do by run!(simulation,output=true), which will switch on NetCDF output with default settings. More options on output in NetCDF output.","category":"page"},{"location":"how_to_run_speedy/#Example-2:-Shallow-water-with-mountains","page":"How to run SpeedyWeather.jl","title":"Example 2: Shallow water with mountains","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"As a second example, let's investigate the Galewsky et al.[1] test case for the shallow water equations with and without mountains. As the shallow water system has also only one level, we can reuse the SpectralGrid from Example 1.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> spectral_grid = SpectralGrid(trunc=127,nlev=1)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Now as a first simulation, we want to disable any orography, so we create a NoOrography","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> orography = NoOrography(spectral_grid)\nNoOrography{Float32, OctahedralGaussianGrid{Float32}}","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Although the orography is zero, you have to pass on spectral_grid so that it can still initialize zero-arrays of the right size and element type. Awesome. This time the initial conditions should be set the the Galewsky et al.[1] zonal jet, which is already defined as","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> initial_conditions = ZonalJet()\nZonalJet\n latitude: Float64 45.0\n width: Float64 19.28571428571429\n umax: Float64 80.0\n perturb_lat: Float64 45.0\n perturb_lon: Float64 270.0\n perturb_xwidth: Float64 19.098593171027442\n perturb_ywidth: Float64 3.819718634205488\n perturb_height: Float64 120.0","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The jet sits at 45˚N with a maximum velocity of 80m/s and a perturbation as described in their paper. Now we construct a model, but this time a ShallowWaterModel","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);\njulia> simulation = initialize!(model);","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet unicode plot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Oh yeah. That looks like the wobbly jet in their paper. Let's run it again for another 6 days but this time also store NetCDF output.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> run!(simulation,n_days=6,output=true)\nWeather is speedy: run 0002 100%|███████████████████████| Time: 0:00:12 (115.37 years/day)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The progress bar tells us that the simulation run got the identification \"0002\", meaning that data is stored in the folder /run_0002, so let's plot that data properly (and not just using UnicodePlots).","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> using PyPlot, NCDatasets\njulia> ds = NCDataset(\"run_0002/output.nc\");\njulia> ds[\"vor\"]\nvor (384 × 192 × 1 × 25)\n Datatype: Float32\n Dimensions: lon × lat × lev × time\n Attributes:\n units = 1/s\n missing_value = NaN\n long_name = relative vorticity\n _FillValue = NaN","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Vorticity vor is stored as a 384x192x1x25 array, we may want to look at the first time step, which is the end of the previous simulation (time=6days) which we didn't store output for.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> vor = ds[\"vor\"][:,:,1,1];\njulia> lat = ds[\"lat\"][:];\njulia> lon = ds[\"lon\"][:];\njulia> pcolormesh(lon,lat,vor')","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Which looks like","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet pyplot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"You see that the unicode plot heavily coarse-grains the simulation, well it's unicode after all! And now the last time step, that means time=12days is","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> vor = ds[\"vor\"][:,:,1,25];\njulia> pcolormesh(lon,lat,vor')","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet pyplot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The jet broke up into many small eddies, but the turbulence is still confined to the northern hemisphere, cool! How this may change when we add mountains (we had NoOrography above!), say Earth's orography, you may ask? Let's try it out! We create an EarthOrography struct like so","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> orography = EarthOrography(spectral_grid)\nEarthOrography{Float32, OctahedralGaussianGrid{Float32}}:\n path::String = SpeedyWeather.jl/input_data\n file::String = orography_F512.nc\n scale::Float64 = 1.0\n smoothing::Bool = true\n smoothing_power::Float64 = 1.0\n smoothing_strength::Float64 = 0.1\n smoothing_truncation::Int64 = 85","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"It will read the orography from file as shown, and there are some smoothing options too, but let's not change them. Same as before, create a model, initialize into a simulation, run. This time directly for 12 days so that we can compare with the last plot","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);\njulia> simulation = initialize!(model);\njulia> run!(simulation,n_days=12,output=true)\nWeather is speedy: run 0003 100%|███████████████████████| Time: 0:00:35 (79.16 years/day)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"This time the run got the id \"0003\", but otherwise we do as before.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet pyplot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Interesting! The initial conditions have zero velocity in the southern hemisphere, but still, one can see some imprint of the orography on vorticity. You can spot the coastline of Antarctica; the Andes and Greenland are somewhat visible too. Mountains also completely changed the flow after 12 days, probably not surprising!","category":"page"},{"location":"how_to_run_speedy/#SpectralGrid","page":"How to run SpeedyWeather.jl","title":"SpectralGrid","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object. We have seen some examples above, now let's look into the details","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"SpectralGrid","category":"page"},{"location":"how_to_run_speedy/#References","page":"How to run SpeedyWeather.jl","title":"References","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"[1] Galewsky, Scott, Polvani, 2004. An initial-value problem for testing numerical models of the global shallow-water equations, Tellus A. DOI: 10.3402/tellusa.v56i5.14436","category":"page"},{"location":"speedytransforms/#SpeedyTransforms","page":"Submodule: SpeedyTransforms","title":"SpeedyTransforms","text":"","category":"section"},{"location":"speedytransforms/","page":"Submodule: SpeedyTransforms","title":"Submodule: SpeedyTransforms","text":"SpeedyTransforms is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"speedytransforms/#Example-transforms","page":"Submodule: SpeedyTransforms","title":"Example transforms","text":"","category":"section"},{"location":"speedytransforms/","page":"Submodule: SpeedyTransforms","title":"Submodule: SpeedyTransforms","text":"julia> using SpeedyWeather\njulia> alms = zeros(ComplexF64,3,3) # spectral coefficients\njulia> alms[2,2] = 1 # only l=1,m=1 harmonic\njulia> map = gridded(alms) # convert to grid space\n8×4 Matrix{Float64}:\n -0.324541 -0.600363 -0.600363 -0.324541\n -0.134429 -0.248678 -0.248678 -0.134429\n 0.134429 0.248678 0.248678 0.134429\n 0.324541 0.600363 0.600363 0.324541\n 0.324541 0.600363 0.600363 0.324541\n 0.134429 0.248678 0.248678 0.134429\n -0.134429 -0.248678 -0.248678 -0.134429\n -0.324541 -0.600363 -0.600363 -0.324541\n \njulia> spectral(map) # back to spectral space\n3×3 Matrix{ComplexF64}:\n 0.0+0.0im 0.0+0.0im 0.0+0.0im\n 0.0+0.0im 1.0+3.60727e-17im 0.0+0.0im\n 0.0+0.0im 0.0+0.0im 0.0+0.0im","category":"page"},{"location":"speedytransforms/","page":"Submodule: SpeedyTransforms","title":"Submodule: SpeedyTransforms","text":"and we have successfully reobtained the l=m=1 spherical harmonic.","category":"page"},{"location":"speedytransforms/#Functions-and-type-index","page":"Submodule: SpeedyTransforms","title":"Functions and type index","text":"","category":"section"},{"location":"speedytransforms/","page":"Submodule: SpeedyTransforms","title":"Submodule: SpeedyTransforms","text":"Modules = [SpeedyWeather.SpeedyTransforms]","category":"page"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.SpectralTransform","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"S = SpectralTransform{NF<:AbstractFloat}(...)\n\nSpectralTransform struct that contains all parameters and preallocated arrays for the spectral transform.\n\n\n\n\n\n","category":"type"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.SpectralTransform-Union{Tuple{AbstractArray{Complex{NF}, 2}}, Tuple{NF}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"S = SpectralTransform( alms::AbstractMatrix{Complex{NF}};\n recompute_legendre::Bool=true,\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID)\n\nGenerator function for a SpectralTransform struct based on the size of the spectral coefficients alms and the grid Grid. Recomputes the Legendre polynomials by default.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.SpectralTransform-Union{Tuple{NF}, Tuple{Type{NF}, Type{<:SpeedyWeather.RingGrids.AbstractGrid}, Int64, Int64}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"SpectralTransform(\n ::Type{NF},\n Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid},\n lmax::Int64,\n mmax::Int64;\n recompute_legendre,\n legendre_shortcut,\n dealiasing\n) -> SpectralTransform\n\n\nGenerator function for a SpectralTransform struct. With NF the number format, Grid the grid type <:AbstractGrid and spectral truncation lmax,mmax this function sets up necessary constants for the spetral transform. Also plans the Fourier transforms, retrieves the colatitudes, and preallocates the Legendre polynomials (if recompute_legendre == false) and quadrature weights.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.SpectralTransform-Union{Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}}, Tuple{NF}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"S = SpectralTransform( map::AbstractGrid;\n recompute_legendre::Bool=true)\n\nGenerator function for a SpectralTransform struct based on the size and grid type of gridded field map. Recomputes the Legendre polynomials by default.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.UV_from_vor!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.UV_from_vor!","text":"UV_from_vor!( U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.UV_from_vordiv!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.UV_from_vordiv!","text":"UV_from_vordiv!(U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n div::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms._divergence!-Union{Tuple{NF}, Tuple{Any, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms._divergence!","text":"_divergence!( kernel,\n div::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGeneric divergence function of vector u,v that writes into the output into div. Generic as it uses the kernel kernel such that curl, div, add or flipsign options are provided through kernel, but otherwise a single function is used.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.curl!-Tuple{LowerTriangularMatrix, LowerTriangularMatrix, LowerTriangularMatrix, SpectralTransform}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.curl!","text":"curl!( curl::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform;\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nCurl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.divergence!-Tuple{LowerTriangularMatrix, LowerTriangularMatrix, LowerTriangularMatrix, SpectralTransform}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.divergence!","text":"divergence!(div::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform{NF};\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nDivergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.get_recursion_factors-Union{Tuple{NF}, Tuple{Type{NF}, Int64, Int64}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.get_recursion_factors","text":"get_recursion_factors( ::Type{NF}, # number format NF\n lmax::Int, # max degree l of spherical harmonics (0-based here)\n mmax::Int # max order m of spherical harmonics\n ) where {NF<:AbstractFloat}\n\nReturns a matrix of recursion factors ϵ up to degree lmax and order mmax of number format NF.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.gridded!-Union{Tuple{NF}, Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!( map::AbstractGrid,\n alms::LowerTriangularMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.gridded-Union{Tuple{AbstractMatrix{T}}, Tuple{T}, Tuple{NF}} where {NF, T<:Complex{NF}}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.gridded","text":"map = gridded( alms::AbstractMatrix;\n recompute_legendre::Bool=true,\n grid::Type{<:AbstractGrid}=DEFAULT_GRID)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.gridded-Union{Tuple{NF}, Tuple{AbstractMatrix, SpectralTransform{NF}}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.gridded","text":"map = gridded( alms::AbstractMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.is_power_2-Tuple{Integer}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.is_power_2","text":"true/false = is_power_2(i::Integer)\n\nChecks whether an integer i is a power of 2, i.e. i = 2^k, with k = 0,1,2,3,....\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.roundup_fft-Union{Tuple{Integer}, Tuple{T}} where T<:Integer","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.roundup_fft","text":"m = roundup_fft(n::Int;\n small_primes::Vector{Int}=[2,3,5])\n\nReturns an integer m >= n with only small prime factors 2, 3 (default, others can be specified with the keyword argument small_primes) to obtain an efficiently fourier-transformable number of longitudes, m = 2^i * 3^j * 5^k >= n, with i,j,k >=0.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, SpeedyWeather.RingGrids.AbstractGrid{NF}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral!","text":"spectral!( alms::LowerTriangularMatrix,\n map::AbstractGrid,\n S::SpectralTransform)\n\nSpectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral-Tuple{AbstractMatrix}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral","text":"alms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral-Union{Tuple{NF}, Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}, SpectralTransform{NF}}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral","text":"alms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nSpectral transform (grid to spectral) map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral-Union{Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}}, Tuple{NF}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral","text":"alms = spectral( map::AbstractGrid;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to Grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_interpolation-Union{Tuple{NF}, Tuple{Type{NF}, LowerTriangularMatrix, Integer, Integer}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_interpolation","text":"alms_interp = spectral_interpolation( ::Type{NF},\n alms::LowerTriangularMatrix,\n ltrunc::Integer,\n mtrunc::Integer\n ) where NF\n\nReturns a spectral coefficient matrix alms_interp that is alms padded with zeros to interpolate in spectral space. If trunc is smaller or equal to the implicit truncation in alms obtained from its size than spectral_truncation is automatically called instead, returning alms_trunc, a coefficient matrix that is smaller than alms, implicitly setting higher degrees and orders to zero.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_smoothing!-Tuple{LowerTriangularMatrix, Real}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_smoothing!","text":"spectral_smoothing!(A::LowerTriangularMatrix,c;power=1)\n\nSmooth the spectral field A following A = (1-(1-c)∇²ⁿ) with power n of a normalised Laplacian so that the highest degree lmax is dampened by multiplication with c. Anti-diffusion for c>1.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_smoothing-Tuple{LowerTriangularMatrix, Real}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_smoothing","text":"A_smooth = spectral_smoothing(A::LowerTriangularMatrix,c;power=1)\n\nSmooth the spectral field A following A_smooth = (1-c*∇²ⁿ)A with power n of a normalised Laplacian so that the highest degree lmax is dampened by multiplication with c. Anti-diffusion for c<0.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_truncation!-Tuple{AbstractMatrix, Int64}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms,trunc)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_truncation!-Tuple{AbstractMatrix}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms)\n\nTruncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_truncation!-Union{Tuple{NF}, Tuple{AbstractMatrix{NF}, Integer, Integer}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms::AbstractMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_truncation!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{NF}, Integer, Integer}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms::LowerTriangularMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_truncation-Union{Tuple{NF}, Tuple{Type{NF}, LowerTriangularMatrix, Integer, Integer}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation","text":"alms_trunc = spectral_truncation(alms,trunc)\n\nReturns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.ϵlm-Tuple{Int64, Int64}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.ϵlm","text":"ϵ = ϵ(l,m)\n\nRecursion factors ϵ as a function of degree l and order m (0-based) of the spherical harmonics. ϵ(l,m) = sqrt((l^2-m^2)/(4*l^2-1)) with default number format Float64.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.ϵlm-Union{Tuple{NF}, Tuple{Type{NF}, Int64, Int64}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.ϵlm","text":"ϵ = ϵ(NF,l,m)\n\nRecursion factors ϵ as a function of degree l and order m (0-based) of the spherical harmonics. ϵ(l,m) = sqrt((l^2-m^2)/(4*l^2-1)) and then converted to number format NF.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.∇²!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.∇²!","text":"∇²!( ∇²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false,\n inverse::Bool=false)\n\nLaplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.\n\nKeyword arguments\n\nadd=true adds the ∇²(alms) to the output\nflipsign=true computes -∇²(alms) instead\ninverse=true computes ∇⁻²(alms) instead\n\nDefault is add=false, flipsign=false, inverse=false. These options can be combined.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.∇⁻²!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.∇⁻²!","text":"∇⁻²!( ∇⁻²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false)\n\nCalls ∇²!(∇⁻²alms, alms, S; add, flipsign, inverse=true).\n\n\n\n\n\n","category":"method"},{"location":"grids/#Grids","page":"Grids","title":"Grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and but let's start but how they can be used","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"julia> spectral_grid = SpectralGrid(Grid = FullGaussianGrid)\nSpectralGrid:\n Spectral: T31 LowerTriangularMatrix{Complex{Float32}}, radius = 6.371e6 m\n Grid: 4608-element, 48-ring FullGaussianGrid{Float32} (quadratic)\n Resolution: 333km (average)\n Vertical: 8-level SigmaCoordinates","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object which defines the resolution in spectral and in grid-point space. The generator SpectralGrid() can take as a keyword argument Grid which can be any of the grids described below. The resolution of the grid, however, is not directly chosen, but determined from the spectral resolution trunc and the dealiasing factor. More in Matching spectral and grid resolution.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: RingGrids is a module too!\nWhile RingGrids is the underlying module that SpeedyWeather.jl uses for data structs on the sphere, the module can also be used independently of SpeedyWeather, for example to interpolate between data on different grids. See RingGrids","category":"page"},{"location":"grids/#Ring-based-equi-longitude-grids","page":"Grids","title":"Ring-based equi-longitude grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"SpeedyWeather.jl's spectral transform supports all ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on these rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: Is the FullClenshawGrid a longitude-latitude grid?\nShort answer: Yes. The FullClenshawGrid is a specific longitude-latitude grid with equi-angle spacing. The most common grids for geoscientific data use regular spacings for 0-360˚E in longitude and 90˚N-90˚S. The FullClenshawGrid does that too, but it does not have a point on the North or South pole, and the central latitude ring sits exactly on the Equator. We name it Clenshaw following the Clenshaw-Curtis quadrature that is used in the Legendre transfrom in the same way as Gaussian refers to the Gaussian quadrature.","category":"page"},{"location":"grids/#Implemented-grids","page":"Grids","title":"Implemented grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Currently the following full grids <: AbstractFullGrid are implemented","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"FullGaussianGrid, a full grid with Gaussian latitudes\nFullClenshawGrid, a full grid with equi-angle latitudes","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron\nOctahedralClenshawGrid, similar but based on equi-angle latitudes\nHEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces\nOctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"An overview of these grids is visualised here","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"(Image: Overview of implemented grids in SpeedyWeather.jl)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.","category":"page"},{"location":"grids/#Grid-resolution","page":"Grids","title":"Grid resolution","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half. ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: HEALPix grids do not use Nside as resolution parameter\nThe original formulation for HEALPix grids use N_side, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use N_side for the documentation or within functions though.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Related: Effective grid resolution and Available horizontal resolutions.","category":"page"},{"location":"grids/#Matching-spectral-and-grid-resolution","page":"Grids","title":"Matching spectral and grid resolution","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation T with a grid resolution N (=nlat_half) such that T + 1 = N. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at l_max=31 in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid. In SpeedyWeather.jl the choice of the order of truncation is controlled with the dealiasing parameter in the SpectralGrid construction.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Let J be the total number of rings. Then we have","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"T approx J for linear truncation, i.e. dealiasing = 1\nfrac32T approx J for quadratic truncation, i.e. dealiasing = 2\n2T approx J for cubic truncation, , i.e. dealiasing = 3","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and in general fracm+12T approx J for m-th order truncation. So the higher the truncation order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. A quick overview of how the grid resolution changes when dealiasing is passed onto SpectralGrid on the FullGaussianGrid","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"trunc dealiasing FullGaussianGrid size\n31 1 64x32\n31 2 96x48\n31 3 128x64\n42 1 96x48\n42 2 128x64\n42 3 192x96\n... ... ...","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"You will obtain this information every time you create a SpectralGrid(;Grid,trunc,dealiasing).","category":"page"},{"location":"grids/#FullGaussianGrid","page":"Grids","title":"Full Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#FullClenshawGrid","page":"Grids","title":"Full Clenshaw-Curtis grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#Octahedral-Gaussian-grid","page":"Grids","title":"Octahedral Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#HEALPix-grid","page":"Grids","title":"HEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are N_varphi basepixels in zonal direction and N_theta basepixels in meridional direction. For N_varphi = 4 and N_theta = 3 we obtain the classical HEALPix grid with N_varphi N_theta = 12 basepixels shown above in Implemented grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always 2N, so 32 at N=16) and there are polar caps above and below the equatorial zone with the border at cos(theta) = 23 (theta in colatitudes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Following Górski, 2004[1], the z=cos(theta) colatitude of the j-th ring in the north polar cap, j=1N_side with 2N_side = N is ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^23N_side^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and on that ring, the longitude phi of the i-th point (i is the in-ring-index) is at","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i-tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The in-ring index i goes from i=14 for the first (i.e. northern-most) ring, i=18 for the second ring and i = 14j for the j-th ring in the northern polar cap.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"In the north equatorial belt j=N_side2N_side this changes to","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac43 - frac2j3N_side","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and the longitudes change to (i is always i = 14N_side in the equatorial belt meaning the number of longitude points is constant here)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2N_side(i - fracs2) quad s = (j - N_side + 1) mod 2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The cell boundaries are obtained by setting i = k + 12 or i = k + 12 + j (half indices) into the equations above, such that z(phik), a function for the cosine of colatitude z of index k and the longitude phi is obtained. These are then (northern polar cap)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^23N_side^2left(fracpi2phi_tright)^2 quad z = 1 - frack^23N_side^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with phi_t = phi mod tfracpi2 and in the equatorial belt","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac23-frac4k3N_side pm frac8phi3pi","category":"page"},{"location":"grids/#OctaHEALPix-grid","page":"Grids","title":"OctaHEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"While the classic HEALPix grid is based on a dodecahedron, other choices for N_varphi and N_theta in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With N_varphi = 4 and N_theta = 1 we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, 2pi around the Equator versus pi between the poles. N_varphi = 6 N_theta = 2 or N_varphi = 8 N_theta = 3 are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visualizations of these grids.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"We call the N_varphi = 4 N_theta = 1 HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As N_theta = 1 there is no equatorial belt which simplifies the grid. The latitude of the j-th isolatitude ring on the OctaHEALPixGrid is defined by","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^2N^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with j=1N, and similarly for the southern hemisphere by symmetry. On this grid N_side = N where N= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index i = 14j are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i - tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and again, the southern hemisphere grid points are obtained by symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries-2","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Similar to the grid cell boundaries for the HEALPix grid, the OctaHEALPix grid's boundaries are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^2N^2left(fracpi2phi_tright)^2 quad z = 1 - frack^2N^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The 3N_side^2 in the denominator of the HEALPix grid came simply N^2 for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).","category":"page"},{"location":"grids/#References","page":"Grids","title":"References","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"[1]: Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976","category":"page"},{"location":"primitiveequation/#Primitive-equation-model","page":"Primitive equation model","title":"Primitive equation model","text":"","category":"section"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"The primitive equations are a hydrostatic approximation of the compressible Navier-Stokes equations for an ideal gas on a rotating sphere. We largely follow the idealised spectral dynamical core developed by GFDL[1] and documented therein[2].","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"The primitive equations solved by SpeedyWeather.jl for relative vorticity zeta, divergence mathcalD, logarithm of surface pressure ln p_s, temperature T and specific humidity q are","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"beginaligned\nfracpartial zetapartial t = nabla times (mathbfmathcalP_mathbfu\n+ (f+zeta)mathbfu_perp - W(mathbfu) - R_dT_vnabla ln p_s) \nfracpartial mathcalDpartial t = nabla cdot (mathcalP_mathbfu\n+ (f+zeta)mathbfu_perp - W(mathbfu) - R_dT_vnabla ln p_s) - nabla^2(frac12(u^2 + v^2) + Phi) \nfracpartial ln p_spartial t = -frac1p_s nabla cdot int_0^p_s mathbfudp \nfracpartial Tpartial t = mathcalP_T -nablacdot(mathbfuT) + TmathcalD - W(T) + kappa T_v fracD ln pDt \nfracpartial qpartial t = mathcalP_q -nablacdot(mathbfuq) + qmathcalD - W(q)\nendaligned","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"with velocity mathbfu = (uv), rotated velocity mathbfu_perp = (v-u), Coriolis parameter f, W the vertical advection operator, dry air gas constant R_d, virtual temperature T_v, geopotential Phi, pressure p, thermodynamic kappa = R_dc_p with c_p the heat capacity at constant pressure. Horizontal hyper diffusion of the form (-1)^n+1nunabla^2n with coefficient nu and power n is added for every variable that is advected, meaning zeta mathcalD T q, but left out here for clarity, see Horizontal diffusion.","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"The parameterizations for the tendencies of uvTq from physical processes are denoted as mathcalP_mathbfu = (mathcalP_u mathcalP_v) mathcalP_T mathcalP_q and are further described in the corresponding sections, see Parameterizations.","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"SpeedyWeather.jl implements a PrimitiveWet and a PrimitiveDry dynamical core. For a dry atmosphere, we have q = 0 and the virtual temperature T_v = T equals the temperature (often called absolute to distinguish from the virtual temperature). The terms in the primitive equations and their discretizations are discussed in the following sections. ","category":"page"},{"location":"primitiveequation/#Virtual-temperature","page":"Primitive equation model","title":"Virtual temperature","text":"","category":"section"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"info: In short: Virtual temperature\nVirtual temperature is the temperature dry air would need to have to be as light as moist air. It is used in the dynamical core to include the effect of humidity on the density while replacing density through the ideal gas law with temperature.","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"We assume the atmosphere to be composed of two ideal gases: Dry air and water vapour. Given a specific humidity q both gases mix, their pressures p_d, p_w (d for dry, w for water vapour), and densities rho_d rho_w add in a given air parcel that has temperature T. The ideal gas law then holds for both gases","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"beginaligned\np_d = rho_d R_d T \np_w = rho_w R_w T \nendaligned","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"with the respective specific gas constants R_d = Rm_d and R_w = Rm_w obtained from the univeral gas constant R divided by the molecular masses of the gas. The total pressure p in the air parcel is","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"p = p_d + p_w = (rho_d R_d + rho_w R_w)T","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"We ultimately want to replace the density rho = rho_w + rho_d in the dynamical core, using the ideal gas law, with the temperature T, so that we never have to calculate the density explicitly. However, in order to not deal with two densities (dry air and water vapour) we would like to replace temperature with a virtual temperature that includes the effect of humidity on the density. So, whereever we use the ideal gas law to replace density with temperature, we would use the virtual temperature, which is a function of the absolute temperature and specific humidity, instead. A higher specific humidity in an air parcel lowers the density as water vapour is lighter than dry air. Consequently, the virtual temperature of moist air is higher than its absolute temperature because warmer air is lighter too at constant pressure. We therefore think of the virtual temperature as the temperature dry air would need to have to be as light as moist air.","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Starting with the last equation, with some manipulation we can write the ideal gas law as total density rho times a gas constant times the virtual temperature that is supposed to be a function of absolute temperature, humidity and some constants","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"p = (rho R_d + rho_w (R_w - R_d)) T = rho R_d (1 +\nfrac1 - tfracR_dR_wtfracR_dR_w fracrho_wrho_w + rho_d)T","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Now we identify","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"mu = frac1 - tfracR_dR_wtfracR_dR_w","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"as some constant that is positive for water vapour being lighter than dry air (tfracR_dR_w = tfracm_wm_d 1) and","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"q = fracrho_wrho_w + rho_d","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"as the specific humidity. Given temperature T and specific humidity q, we can therefore calculate the virtual temperature T_v as","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"T_v = (1 + mu q)T","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"For completeness we want to mention here that the above product, because it is a product of two variables qT has to be computed in grid-point space, see [Spectral Transform]. To obtain an approximation to the virtual temperature in spectral space without expensive transforms one can linearize","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"T_v = T + mu qbarT","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"With a global constant temperature barT, for example obtained from the l=m=0 mode, barT = T_00frac1sqrt4pi but depending on the normalization of the spherical harmonics that factor needs adjustment.","category":"page"},{"location":"primitiveequation/#Vertical-coordinates","page":"Primitive equation model","title":"Vertical coordinates","text":"","category":"section"},{"location":"primitiveequation/#General","page":"Primitive equation model","title":"General","text":"","category":"section"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Let Psi(xyzt) ","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"SpeedyWeather.jl currently uses sigma coordinates for the vertical. ","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"sigma = fracpp_s","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"p_k = sigma_kp_s","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Delta p_k = p_k+1 - p_k = Delta sigma_k p_s","category":"page"},{"location":"primitiveequation/#Geopotential","page":"Primitive equation model","title":"Geopotential","text":"","category":"section"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"In the hydrostatic approximation the vertical momentum equation becomes","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"fracpartial ppartial z = -rho g","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"meaning that the (negative) vertical pressure gradient is given by the density in that layer times the gravitational acceleration. The heavier the fluid the more the pressure will increase below. Inserting the ideal gas law","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"fracpartial gzpartial p = -fracR_dT_vp","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"with the geopotential Phi = gz we can write this in terms of the logarithm of pressure","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"fracpartial Phipartial ln p = -R_dT_v","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Note that we use the Virtual temperature here as we replaced the density through the ideal gas law with temperature. Given a vertical temperature profile T_v and the (constant) surface geopotential Phi_s = gz_s where z_s is the orography, we can integrate this equation from the surface to the top to obtain Phi_k on every layer k. The surface is at k = N+tfrac12 (see Vertical coordinates) with N vertical levels. We can integrate the geopotential onto half levels as","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Phi_k-tfrac12 = Phi_k+tfrac12 + R_dT^v_k(ln p_k+12 - ln p_k-12)","category":"page"},{"location":"primitiveequation/#Surface-pressure-tendency","page":"Primitive equation model","title":"Surface pressure tendency","text":"","category":"section"},{"location":"primitiveequation/#Vertical-advection","page":"Primitive equation model","title":"Vertical advection","text":"","category":"section"},{"location":"primitiveequation/#Pressure-gradient-force","page":"Primitive equation model","title":"Pressure gradient force","text":"","category":"section"},{"location":"primitiveequation/#Temperature-equation","page":"Primitive equation model","title":"Temperature equation","text":"","category":"section"},{"location":"primitiveequation/#implicit_primitive","page":"Primitive equation model","title":"Semi-implicit time stepping","text":"","category":"section"},{"location":"primitiveequation/#Horizontal-diffusion","page":"Primitive equation model","title":"Horizontal diffusion","text":"","category":"section"},{"location":"primitiveequation/#Algorithm","page":"Primitive equation model","title":"Algorithm","text":"","category":"section"},{"location":"primitiveequation/#Scaled-primitive-equations","page":"Primitive equation model","title":"Scaled primitive equations","text":"","category":"section"},{"location":"primitiveequation/#References","page":"Primitive equation model","title":"References","text":"","category":"section"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"[1]: Geophysical Fluid Dynamics Laboratory, Idealized models with spectral dynamics","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"[2]: Geophysical Fluid Dynamics Laboratory, The Spectral Dynamical Core","category":"page"},{"location":"lowertriangularmatrices/#lowertriangularmatrices","page":"Submodule: LowerTriangularMatrices","title":"LowerTriangularMatrices","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"LowerTriangularMatrices is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"This module defines LowerTriangularMatrix, a lower triangular matrix, which in contrast to LinearAlgebra.LowerTriangular does not store the entries above the diagonal. SpeedyWeather.jl uses LowerTriangularMatrix which is defined as a subtype of AbstractMatrix to store the spherical harmonic coefficients (see Spectral packing). ","category":"page"},{"location":"lowertriangularmatrices/#Creation-of-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Creation of LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"A LowerTriangularMatrix can be created using zeros,ones,rand, or randn","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> using SpeedyWeather.LowerTriangularMatrices\n\njulia> L = rand(LowerTriangularMatrix{Float32},5,5)\n5×5 LowerTriangularMatrix{Float32}:\n 0.912744 0.0 0.0 0.0 0.0\n 0.0737592 0.230592 0.0 0.0 0.0\n 0.799679 0.0765255 0.888098 0.0 0.0\n 0.670835 0.997938 0.505276 0.492966 0.0\n 0.949321 0.193692 0.793623 0.152817 0.357968","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"or the undef initializor LowerTriangularMatrix{Float32}(undef,3,3). The element type is arbitrary though, you can use any type T too.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Alternatively, it can be created through conversion from Matrix, which drops the upper triangle entries and sets them to zero","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> M = rand(Float16,3,3)\n3×3 Matrix{Float16}:\n 0.2222 0.694 0.3452\n 0.2158 0.04443 0.274\n 0.9746 0.793 0.6294\n\njulia> LowerTriangularMatrix(M)\n3×3 LowerTriangularMatrix{Float16}:\n 0.2222 0.0 0.0\n 0.2158 0.04443 0.0\n 0.9746 0.793 0.6294","category":"page"},{"location":"lowertriangularmatrices/#Indexing-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Indexing LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"LowerTriangularMatrix supports two types of indexing: 1) by denoting two indices, column and row [l,m] or 2) by denoting a single index [lm]. The double index works as expected","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L\n3×3 LowerTriangularMatrix{Float16}:\n 0.1499 0.0 0.0\n 0.1177 0.478 0.0\n 0.1709 0.756 0.3223\n\njulia> L[2,2]\nFloat16(0.478)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"But the single index skips the zero entries in the upper triangle, i.e.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L[4]\nFloat16(0.478)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"which, important, is different from single indices of an AbstractMatrix","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> Matrix(L)[4]\nFloat16(0.0)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"In performance-critical code a single index should be used, as this directly maps to the index of the underlying data vector. The double index is somewhat slower as it first has to be converted to the corresponding single index.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Consequently, many loops in SpeedyWeather.jl are build with the following structure","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"n,m = size(L)\nij = 0\nfor j in 1:m\n for i in j:n\n ij += 1\n L[ij] = i+j\n end\nend","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"which loops over all lower triangle entries of L::LowerTriangularMatrix and the single index ij is simply counted up. However, one could also use [i,j] as indices in the loop body or to perform any calculation (i+j here). An iterator over all entries in the lower triangle can be created by","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"for ij in eachindex(L)\n # do something\nend","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"The setindex! functionality of matrixes will throw a BoundsError when trying to write into the upper triangle of a LowerTriangularMatrix, for example","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L[2,1] = 0 # valid index\n0\n\njulia> L[1,2] = 0 # invalid index in the upper triangle\nERROR: BoundsError: attempt to access 3×3 LowerTriangularMatrix{Float32} at index [1, 2]","category":"page"},{"location":"lowertriangularmatrices/#Linear-algebra-with-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Linear algebra with LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"The LowerTriangularMatrices module's main purpose is not linear algebra, and it's implementation may not be efficient, however, many operations work as expected","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L = rand(LowerTriangularMatrix{Float32},3,3)\n3×3 LowerTriangularMatrix{Float32}:\n 0.57649 0.0 0.0\n 0.348685 0.875371 0.0\n 0.881923 0.850552 0.998306\n\njulia> L + L\n3×3 LowerTriangularMatrix{Float32}:\n 1.15298 0.0 0.0\n 0.697371 1.75074 0.0\n 1.76385 1.7011 1.99661\n\njulia> L * L\n3×3 Matrix{Float32}:\n 0.332341 0.0 0.0\n 0.506243 0.766275 0.0\n 1.68542 1.59366 0.996616","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Note, however, that the latter includes a conversion to Matrix, which is true for many operations, including inv or \\. Hence when trying to do more sophisticated linear algebra with LowerTriangularMatrix we quickly leave lower triangular-land and go back to normal matrix-land.","category":"page"},{"location":"lowertriangularmatrices/#Function-and-type-index","page":"Submodule: LowerTriangularMatrices","title":"Function and type index","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Modules = [SpeedyWeather.LowerTriangularMatrices]","category":"page"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix","text":"L = LowerTriangularMatrix{T}(v::Vector{T},m::Int,n::Int)\n\nA lower triangular matrix implementation that only stores the non-zero entries explicitly. L<:AbstractMatrix although in general we have L[i] != Matrix(L)[i], the former skips zero entries, tha latter includes them.\n\n\n\n\n\n","category":"type"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix-Union{Tuple{AbstractMatrix{T}}, Tuple{T}} where T","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix","text":"L = LowerTriangularMatrix(M)\n\nCreate a LowerTriangularMatrix L from Matrix M by copying over the non-zero elements in M.\n\n\n\n\n\n","category":"method"},{"location":"lowertriangularmatrices/#Base.fill!-Union{Tuple{T}, Tuple{LowerTriangularMatrix{T}, Any}} where T","page":"Submodule: LowerTriangularMatrices","title":"Base.fill!","text":"fill!(L::LowerTriangularMatrix,x)\n\nFills the elements of L with x. Faster than fill!(::AbstractArray,x) as only the non-zero elements in L are assigned with x.\n\n\n\n\n\n","category":"method"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.eachharmonic-Tuple{LowerTriangularMatrix}","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.eachharmonic","text":"unit_range = eachharmonic(L::LowerTriangular)\n\ncreates unit_range::UnitRange to loop over all non-zeros in a LowerTriangularMatrix L. Like eachindex but skips the upper triangle with zeros in L.\n\n\n\n\n\n","category":"method"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.eachharmonic-Tuple{Vararg{LowerTriangularMatrix}}","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.eachharmonic","text":"unit_range = eachharmonic(Ls::LowerTriangularMatrix...)\n\ncreates unit_range::UnitRange to loop over all non-zeros in the LowerTriangularMatrices provided as arguments. Checks bounds first. All LowerTriangularMatrix's need to be of the same size. Like eachindex but skips the upper triangle with zeros in L.\n\n\n\n\n\n","category":"method"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.ij2k-Tuple{Integer, Integer, Integer}","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.ij2k","text":"k = ij2k( i::Integer, # row index of matrix\n j::Integer, # column index of matrix\n m::Integer) # number of rows in matrix\n\nConverts the index pair i,j of an mxn LowerTriangularMatrix L to a single index k that indexes the same element in the corresponding vector that stores only the lower triangle (the non-zero entries) of L.\n\n\n\n\n\n","category":"method"},{"location":"conventions/#Style-and-convention-guide","page":"Style and convention guide","title":"Style and convention guide","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"In SpeedyWeather.jl we've been following the several conventions that are documented here.","category":"page"},{"location":"conventions/#Variable-naming","page":"Style and convention guide","title":"Variable naming","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The prognostic variables in spectral space are called","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" vor # Vorticity of horizontal wind field\n div # Divergence of horizontal wind field\n temp # Absolute temperature [K]\n pres_surf # Logarithm of surface pressure [log(Pa)]\n humid # Specific humidity [g/kg]","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" u\n v\n geopot\n ...","category":"page"},{"location":"conventions/#Preallocation","page":"Style and convention guide","title":"Preallocation","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"All arrays representing variables are preallocated and grouped into two structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" Prog::PrognosticVariables\n Diag::DiagnosticVariables","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The Diag struct contains further structs which represent the grid-point transformations of the prognostic variables and their tendencies.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" gridvars::GridVariables\n tendencies::Tendencies\n ...","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Constant arrays are grouped into several structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Boundaries","category":"page"},{"location":"conventions/#Julian-conventions","page":"Style and convention guide","title":"Julian conventions","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"We follow Julia's style guide and highlight here some important aspects of it.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Bang (!) convention. A function func does not change its input arguments, however, func! does.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.","category":"page"},{"location":"shallowwater/#Shallow-water-model","page":"Shallow water model","title":"Shallow water model","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The shallow water model describes the evolution of a 2D flow described by its velocity and an interface height that conceptually represents pressure. A divergent flow affects the interface height which in turn can impose a pressure gradient force onto the flow. The dynamics include advection, forces, dissipation, and continuity.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The following description of the shallow water model largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: The Shallow Water Equations[2].","category":"page"},{"location":"shallowwater/#Shallow-water-equations","page":"Shallow water model","title":"Shallow water equations","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The shallow water equations of velocity mathbfu = (uv) and interface height eta (i.e. the deviation from the fluid's rest height H) are, formulated in terms of relative vorticity zeta = nabla times mathbfu, divergence mathcalD = nabla cdot mathbfu","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\nfracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) =\nnabla times mathbfF + (-1)^n+1nunabla^2nzeta \nfracpartial mathcalDpartial t - nabla times (mathbfu(zeta + f)) =\nnabla cdot mathbfF -nabla^2(tfrac12(u^2 + v^2) + geta) + (-1)^n+1nunabla^2nmathcalD \nfracpartial etapartial t + nabla cdot (mathbfuh) = F_eta\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"We denote timet, Coriolis parameter f, a forcing vector mathbfF = (F_uF_v), hyperdiffusion (-1)^n+1 nu nabla^2n (n is the hyperdiffusion order, see Horizontal diffusion), gravitational acceleration g, dynamic layer thickness h, and a forcing for the interface height F_eta. In the shallow water model the dynamics layer thickness h is","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"h = eta + H - H_b","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"that is, the layer thickness at rest H plus the interface height eta minus orography H_b.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"In the shallow water system the flow can be described through uv or zetamathcalD which are related through the stream function Psi and the velocity potential Phi (which is zero in the Barotropic vorticity equation).","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\nzeta = nabla^2 Psi \nmathcalD = nabla^2 Phi \nmathbfu = nabla^perp Psi + nabla Phi\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"With nabla^perp being the rotated gradient operator, in cartesian coordinates xy: nabla^perp = (-partial_y partial_x). See Derivatives in spherical coordinates for further details. Especially because the inversion of the Laplacian and the gradients of Psi Phi can be computed in a single pass, see U,V from vorticity and divergence.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The divergence/curl of the vorticity flux mathbfu(zeta + f) are combined with the divergence/curl of the forcing vector mathbfF, as","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\n- nabla cdot (mathbfu(zeta + f)) + nabla times mathbfF =\nnabla times (mathbfF + mathbfu_perp(zeta + f)) \nnabla times (mathbfu(zeta + f)) + nabla cdot mathbfF =\nnabla cdot (mathbfF + mathbfu_perp(zeta + f))\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"equivalently to how this is done in the Barotropic vorticity equation with mathbfu_perp = (v-u).","category":"page"},{"location":"shallowwater/#Algorithm","page":"Shallow water model","title":"Algorithm","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"0. Start with initial conditions of relative vorticity zeta_lm, divergence D_lm, and interface height eta_lm in spectral space and transform this model state to grid-point space:","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Invert the Laplacian of zeta_lm to obtain the stream function Psi_lm in spectral space\nInvert the Laplacian of D_lm to obtain the velocity potential Phi_lm in spectral space\nobtain velocities U_lm = (cos(theta)u)_lm V_lm = (cos(theta)v)_lm from nabla^perpPsi_lm + nablaPhi_lm\nTransform velocities U_lm, V_lm to grid-point space UV\nUnscale the cos(theta) factor to obtain uv\nTransform zeta_lm, D_lm, eta_lm to zeta D eta in grid-point space","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Now loop over","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Compute the forcing vector mathbfF = (F_uF_v) for u and v\nMultiply uv with zeta+f in grid-point space\nAdd A = F_u + v(zeta + f) and B = F_v - u(zeta + f)\nTransform these vector components to spectral space A_lm, B_lm\nCompute the curl of (AB)_lm in spectral space which is the tendency of zeta_lm\nCompute the divergence of (AB)_lm in spectral space which is the tendency of mathcalD_lm\nCompute the kinetic energy frac12(u^2 + v^2) and transform to spectral space\nAdd to the kinetic energy the \"geopotential\" geta_lm in spectral space to obtain the Bernoulli potential\nTake the Laplacian of the Bernoulli potential and subtract from the divergence tendency\nCompute the volume fluxes uhvh in grid-point space via h = eta + H - H_b\nTransform to spectral space and take the divergence for -nabla cdot (mathbfuh) which is the tendency for eta\nAdd possibly forcing F_eta for eta in spectral space\nCorrect the tendencies following the semi-implicit time integration to prevent fast gravity waves from causing numerical instabilities\nCompute the horizontal diffusion based on the zetamathcalD tendencies\nCompute a leapfrog time step as described in Time integration with a Robert-Asselin and Williams filter\nTransform the new spectral state of zeta_lm, mathcalD_lm, eta_lm to grid-point uvzetamathcalDeta as described in 0.\nPossibly do some output\nRepeat from 1.","category":"page"},{"location":"shallowwater/#implicit_swm","page":"Shallow water model","title":"Semi-implicit time integration","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Probably the biggest advantage of a spectral model is its ability to solve (parts of) the equations implicitly a low computational cost. The reason is that a linear operator can be easily inverted in spectral space, removing the necessity to solve large equation systems. An operation like Psi = nabla^-2zeta in grid-point space is costly because it requires a global communication, coupling all grid points. In spectral space nabla^2 is a diagonal operator, meaning that there is no communication between harmonics and its inversion is therefore easily done on a mode-by-mode basis of the harmonics.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"This can be made use of when facing time stepping constraints with explicit schemes, where ridiculuously small time steps to resolve fast waves would otherwise result in a horribly slow simulation. In the shallow water system there are gravity waves that propagate at a wave speed of sqrtgH (typically 300m/s), which, in order to not violate the CFL criterion for explicit time stepping, would need to be resolved. Therefore, treating the terms that are responsible for gravity waves implicitly would remove that time stepping constraint and allows us to run the simulation at the time step needed to resolve the advective motion of the atmosphere, which is usually one or two orders of magnitude longer than gravity waves.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"In the following we will describe how the semi implicit time integration can be combined with the Leapfrog time stepping and the Robert-Asselin and Williams filter for a large increase in numerical stability with gravity waves. Let V_i be the model state of all prognostic variables at time step i, the leapfrog time stepping is then","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"fracV_i+1 - V_i-12Delta t = N(V_i)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"with the right-hand side operator N evaluated at the current time step i. Now the idea is to split the terms in N into non-linear terms that are evaluated explicitly in N_E and into the linear terms N_I, solved implicitly, that are responsible for the gravity waves. We could already assume to evaluate N_I at i+1, but in fact, we can introduce alpha in 01 so that for alpha=0 we use i-1 (i.e. explicit), for alpha=12 it is centred implicit tfrac12N_I(V_i-1) + tfrac12N_I(V_i+1), and for alpha=1 a fully backwards scheme N_I(V_i+1) evaluated at i+1.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"fracV_i+1 - V_i-12Delta t = N_E(V_i) + alpha N_I(V_i+1) + (1-alpha)N_I(V_i-1)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Let delta V = tfracV_i+1 - V_i-12Delta t be the tendency we need for the Leapfrog time stepping. Introducing xi = 2alphaDelta t we have","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"delta V = N_E(V_i) + N_I(V_i-1) + xi N_I(delta V)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"because N_I is a linear operator. This is done so that we can solve for delta V by inverting N_I, but let us gather the other terms as G first.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"G = N_E(V_i) + N_I(V_i-1) = N(V_i) + N_I(V_i-1 - V_i)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"For the shallow water equations we will only make use of the last formulation, meaning we first evaluate the whole right-hand side N(V_i) at the current time step as we would do with fully explicit time stepping but then add the implicit terms N_I(V_i-1 - V_i) afterwards to move those terms from i to i-1. Note that we could also directly evaluate the implicit terms at i-1 as it is suggested in the previous formulation N_E(V_i) + N_I(V_i-1), the result would be the same. But in general it can be more efficient to do it one or the other way, and in fact it is also possible to combine both ways. This will be discussed in the semi-implicit time stepping for the primitive equations.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"We can now implicitly solve for delta V by","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"delta V = (1-xi N_I)^-1G","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"So what is N_I? In the shallow water system the gravity waves are caused by","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\nfracpartial mathcalDpartial t = -gnabla^2eta \nfracpartial etapartial t = -HmathcalD\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"which is a linearization of the equations around a state of rest with uniform constant layer thickness h = H. The continuity equation with the -nabla(mathbfuh) term, for example, is linearized to -nabla(mathbfuH) = -HmathcalD. The divergence and continuity equations can now be written following the delta V = G + xi N_I(delta V) formulation from above as a coupled system (The vorticity equation is zero for the linear gravity wave equation in the shallow water equations, hence no semi-implicit correction has to be made to the vorticity tendency).","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\ndelta mathcalD = G_mathcalD - xi g nabla^2 delta eta \ndelta eta = G_mathcaleta - xi H deltamathcalD\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"with","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\nG_mathcalD = N_mathcalD - xi g nabla^2 (eta_i-1 - eta_i) \nG_mathcaleta = N_eta - xi H (mathcalD_i-1 - mathcalD_i)\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Inserting the second equation into the first, we can first solve for delta mathcalD, and then for delta eta. Reminder that we do this in spectral space to every harmonic independently, so the Laplace operator nabla^2 = -l(l+1) takes the form of its eigenvalue -l(l+1) (normalized to unit sphere, as are the scaled shallow water equations) and its inversion is therefore just the inversion of this scalar.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"delta D = fracG_mathcalD - xi gnabla^2 G_eta1 - xi^2 H nabla^2 = S^-1(G_mathcalD - xi gnabla^2 G_eta) ","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Where the last formulation just makes it clear that S = 1 - xi^2 H nabla^2 is the operator to be inverted. delta eta is then obtained via insertion as written above. Equivalently, by adding a superscript l for every degree of the spherical harmonics, we have","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"delta mathcalD^l = fracG_mathcalD^l + xi g l(l+1) G_eta^l1 + xi^2 H l(l+1)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The idea of the semi-implicit time stepping is now as follows:","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Evaluate the right-hand side explicitly at time step i to obtain the explicit, preliminary tendencies N_mathcalDN_eta (and N_zeta without a need for semi-implicit correction)\nMove the implicit terms from i to i-1 when calculating G_mathcalD G_eta\nSolve for delta mathcalD, the new, corrected tendency for divergence.\nWith delta mathcalD obtain delta eta, the new, corrected tendency for eta.\nApply horizontal diffusion as a correction to N_zeta delta mathcalD as outlined in Horizontal diffusion.\nLeapfrog with tendencies that have been corrected for both semi-implicit and diffusion.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Some notes on the semi-implicit time stepping","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The inversion of the semi-implicit time stepping depends on delta t, that means every time the time step changes, the inversion has to be recalculated.\nYou may choose alpha = 12 to dampen gravity waves but initialisation shocks still usually kick off many gravity waves that propagate around the sphere for many days.\nWith increasing alpha 12 these waves are also slowed down, such that for alpha = 1 they quickly disappear in several hours.\nUsing the scaled shallow water equations the time step delta t has to be the scaled time step tildeDelta t = delta tR which is divided by the radius R. Then we use the normalized eigenvalues -l(l+1) which also omit the 1R^2 scaling, see scaled shallow water equations for more details.","category":"page"},{"location":"shallowwater/#scaled_swm","page":"Shallow water model","title":"Scaled shallow water equations","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Similar to the scaled barotropic vorticity equations, SpeedyWeather.jl scales in the shallow water equations. The vorticity and the divergence equation are scaled with R^2, the radius of the sphere squared, but the continuity equation is scaled with R. We also combine the vorticity flux and forcing into a single divergence/curl operation as mentioned in Shallow water equations above","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\nfracpartial tildezetapartial tildet =\ntildenabla times (tildemathbfF + mathbfu_perp(tildezeta + tildef)) +\n(-1)^n+1tildenutildenabla^2ntildezeta \nfracpartial tildemathcalDpartial tildet =\ntildenabla cdot (tildemathbfF + mathbfu_perp(tildezeta + tildef)) -\ntildenabla^2left(tfrac12(u^2 + v^2) + geta right) +\n(-1)^n+1tildenutildenabla^2ntildemathcalD \nfracpartial etapartial tildet =\n- tildenabla cdot (mathbfuh) + tildeF_eta\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"As in the scaled barotropic vorticity equations, one needs to scale the time step, the Coriolis force, the forcing and the diffusion coefficient, but then enjoys the luxury of working with dimensionless gradient operators. As before, SpeedyWeather.jl will scale vorticity and divergence just before the model integration starts and unscale them upon completion and for output. In the semi-implicit time integration we solve an equation that also has to be scaled. It is with radius squared scaling (because it is the tendency for the divergence equation which is also scaled with R^2)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"R^2 delta D = R^2fracG_mathcalD - xi gnabla^2 G_eta1 - xi^2 H nabla^2","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"As G_eta is only scaled with R we have","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"tildedelta D = fractildeG_mathcalD - tildexi gtildenabla^2 tildeG_eta1 - tildexi^2 H tildenabla^2","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The R^2 normalizes the Laplace operator in the numerator, but using the scaled G_eta we also scale xi (which is convenient, because the time step within is the one we use anyway). The denominator S does not actually change because xi^2nabla^2 = tildexi^2tildenabla^2 as xi^2 is scaled with 1R^2, but the Laplace operator with R^2. So overall we just have to use the scaled time step tildeDelta t and normalized eigenvalues for tildenabla^2.","category":"page"},{"location":"shallowwater/#References","page":"Shallow water model","title":"References","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"[1]: Geophysical Fluid Dynamics Laboratory, Idealized models with spectral dynamics","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"[2]: Geophysical Fluid Dynamics Laboratory, The Shallow Water Equations.","category":"page"},{"location":"extending/#New-model-setups","page":"Extending SpeedyWeather","title":"New model setups","text":"","category":"section"},{"location":"extending/","page":"Extending SpeedyWeather","title":"Extending SpeedyWeather","text":"more to come...","category":"page"},{"location":"spectral_transform/#Spherical-Harmonic-Transform","page":"Spherical harmonic transform","title":"Spherical Harmonic Transform","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space which can be any of the Implemented grids as defined by RingGrids. This includes the classical full Gaussian grid, a regular longitude-latitude grid called the full Clenshaw grid (FullClenshawGrid), ECMWF's octahedral Gaussian grid[Malardel2016], and HEALPix grids[Gorski2004]. SpeedyWeather.jl's spectral transform module SpeedyTransforms is grid-flexible and can be used with any of these, see Grids.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: SpeedyTransforms is a module too!\nSpeedyTransform is the underlying module that SpeedyWeather imports to transform between spectral and grid-point space, which also implements Derivatives in spherical coordinates. You can use this module independently of SpeedyWeather for spectral transforms, see SpeedyTransforms.","category":"page"},{"location":"spectral_transform/#Inspiration","page":"Spherical harmonic transform","title":"Inspiration","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform implemented by SpeedyWeather.jl follows largely Justin Willmert's CMB.jl and SphericalHarmonicTransforms.jl package and makes use of AssociatedLegendrePolynomials.jl and FFTW.jl for the Fourier transform. Justin described his work in a Blog series [Willmert2020].","category":"page"},{"location":"spectral_transform/#Spherical-harmonics","page":"Spherical harmonic transform","title":"Spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spherical harmonics Y_lm of degree l and order m over the longitude phi = (02pi) and colatitudes theta = (-pi2pi2), are","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Y_lm(phi theta) = lambda_l^m(sintheta) e^imphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with lambda_l^m being the pre-normalized associated Legendre polynomials, and e^imphi are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Latitudes versus colatitudes\nThe implementation of the spectral transforms in SpeedyWeather.jl uses colatitudes theta = (0pi) (0 at the north pole) but the dynamical core uses latitudes theta = (-pi2pi2) (pi2 at the north pole). Note: We may also use latitudes in the spherical harmonic transform in the future for consistency. ","category":"page"},{"location":"spectral_transform/#synthesis","page":"Spherical harmonic transform","title":"Synthesis (spectral to grid)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The synthesis (or inverse transform) takes the spectral coefficients a_lm and transforms them to grid-point values f(phitheta) (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics Y_lm with non-zero coefficients.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"f(phitheta) = sum_l=0^infty sum_m=-l^l a_lm Y_lm(phitheta)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We obtain an approximation with a finite set of a_lm by truncating the series in both degree l and order m somehow. Most commonly, a triangular truncation is applied, such that all degrees after l = l_max are discarded. Triangular because the retained array of the coefficients a_lm looks like a triangle. Other truncations like rhomboidal have been studied[Daley78] but are rarely used since. Choosing l_max also constrains m_max and determines the (horizontal) spectral resolution. In SpeedyWeather.jl this resolution as chosen as trunc when creating the SpectralGrid.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"For f being a real-valued there is a symmetry","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"a_l-m = (-1)^m a^*_l+m","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"meaning that the coefficients at -m and m are the same, but the sign of the real and imaginary component can be flipped, as denoted with the (-1)^m and the complex conjugate a_lm^*. As we are only dealing with real-valued fields anyway, we therefore never have to store the negative orders -m and end up with a lower triangular matrix of size (l_max+1) times (m_max+1) or technically (T+1)^2 where T is the truncation trunc. One is added here because the degree l and order m use 0-based indexing but sizes (and so is Julia's indexing) are 1-based.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"For correctness we want to mention here that vector quantities require one more degree l due to the recurrence relation in the Meridional derivative. Hence for practical reasons all spectral fields are represented as a lower triangular matrix of size (m_max + 2) times (m_max +1). And the scalar quantities would just not make use of that last degree, and its entries would be simply zero. We will, however, for the following sections ignore this and only discuss it again in Meridional derivative.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Another consequence of the symmetry mentioned above is that the zonal harmonics, meaning a_lm=0 have no imaginary component. Because these harmonics are zonally constant, a non-zero imaginary component would rotate them around the Earth's axis, which, well, doesn't actually change a real-valued field. ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Following the notation of [Willmert2020] we can therefore write the truncated synthesis as","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"f(phitheta) = sum_l=0^l_max sum_m=0^l (2-delta_m0) a_lm Y_lm(phitheta)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The (2-delta_m0) factor using the Kronecker delta is used here because of the symmetry we have to count both the m-m order pairs (hence the 2) except for the zonal harmonics which do not have a pair.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Another symmetry arises from the fact that the spherical harmonics are either symmetric or anti-symmetric around the Equator. There is an even/odd combination of degrees and orders so that the sign flips like a checkerboard","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Y_lm(phipi-theta) = (-1)^l+mY_lm(phiphi)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This means that one only has to compute the Legendre polynomials for one hemisphere and the other one follows with this equality.","category":"page"},{"location":"spectral_transform/#analysis","page":"Spherical harmonic transform","title":"Analysis (grid to spectral)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting in grid-point space we can transform a field f(lambdatheta) into the spectral space of the spherical harmonics by","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"a_lm = int_0^2pi int_0^pi f(phitheta) Y_lm(phitheta) sin theta dtheta dphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Note that this notation again uses colatitudes theta, for latitudes the sintheta becomes a costheta and the bounds have to be changed accordingly to (-fracpi2fracpi2). A discretization with N grid points at location (phi_itheta_i), indexed by i can be written as [Willmert2020]","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"hata_lm = sum_i f(phi_itheta_i) Y_lm(phi_itheta_i) sin theta_i Deltatheta Deltaphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The hat on a just means that it is an approximation, or an estimate of the true a_lm approx hata_lm. We can essentially make use of the same symmetries as already discussed in Synthesis. Splitting into the Fourier modes e^imphi and the Legendre polynomials lambda_l^m(costheta) (which are defined over -11 so the costheta argument maps them to colatitudes) we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"hata_lm = sum_j left sum_i f(phi_itheta_j) e^-imphi_i right lambda_lm(theta_j) sin theta_j Deltatheta Deltaphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"So the term in brackets can be separated out as long as the latitude theta_j is constant, which motivates us to restrict the spectral transform to grids with iso-latitude rings, see Grids. Furthermore, this term can be written as a fast Fourier transform, if the phi_i are equally spaced on the latitude ring j. Note that the in-ring index i can depend on the ring index j, so that one can have reduced grids, which have fewer grid points towards the poles, for example. Also the Legendre polynomials only have to be computed for the colatitudes theta_j (and in fact only one hemisphere, due to the north-south symmetry discussed in the Synthesis). It is therefore practical and efficient to design a spectral transform implementation for ring grids, but there is no need to hardcode a specific grid.","category":"page"},{"location":"spectral_transform/#Spectral-packing","page":"Spherical harmonic transform","title":"Spectral packing","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Spectral packing is the way how the coefficients a_lm of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree l and order m as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Every row represents an order l geq 0, starting from l=0 at the top. Every column represents an order m geq 0, starting from m=0 on the left. The coefficients of these spherical harmonics are directly mapped into a matrix a_lm as ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 \n a_10 a_11 \n a_20 a_12 a_22 \n a_30 a_13 a_23 a_33","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal are zero. Note that internally vector fields include an additional degree, such that l_max = m_max + 1 (see Derivatives in spherical coordinates for more information). The harmonics with a_l0 (the first column) are also called zonal harmonics as they are constant with longitude phi. The harmonics with a_ll (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into 2l sectors in longitude phi without a zero-crossing in latitude.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"For correctness it is mentioned here that SpeedyWeather.jl uses a LowerTriangularMatrix type to store the spherical harmonic coefficients. By doing so, the upper triangle is actually not explicitly stored and the data technically unravelled into a vector, but this is hidden as much as possible from the user. For more details see LowerTriangularMatrices.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Array indices\nFor a spectral field a note that due to Julia's 1-based indexing the coefficient a_lm is obtained via a[l+1,m+1]. Alternatively, we may index over 1-based l,m but a comment is usually added for clarification.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Fortran SPEEDY does not use the same spectral packing as SpeedyWeather.jl. The alternative packing lm therein uses l=m and m=l-m as summarized in the following table.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"degree l order m l=m m=l-m\n0 0 0 0\n1 0 0 1\n1 1 1 0\n2 0 0 2\n2 1 1 1\n2 2 2 0\n3 0 0 3\n... ... ... ...","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 a_10 a_20 a_30\n a_11 a_21 a_31 \n a_22 a_32 \n a_33 ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran SPEEDY.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with l leq l_max and m leq m_max are explicitly represented. This is usually described as Tm_max, with l_max = m_max (although in vector quantities require one more degree l in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with l_max = m_max = 31. Note that the degree l and order m are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.","category":"page"},{"location":"spectral_transform/#Available-horizontal-resolutions","page":"Spherical harmonic transform","title":"Available horizontal resolutions","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Technically, SpeedyWeather.jl supports arbitrarily chosen resolution parameter trunc when creating the SpectralGrid that refers to the highest non-zero degree l_max that is resolved in spectral space. SpeedyWeather.jl will always try to choose an easily-Fourier transformable[FFT] size of the grid, but as we use FFTW.jl there is quite some flexibility without performance sacrifice. However, this has traditionally lead to typical resolutions that we also use for testing we therefore recommend to use. They are as follows with more details below","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"trunc nlon nlat Delta x\n31 (default) 96 48 400 km\n42 128 64 312 km\n63 192 96 216 km\n85 256 128 165 km\n127 384 192 112 km\n170 512 256 85 km\n255 768 384 58 km\n341 1024 512 43 km\n511 1536 768 29 km\n682 2048 1024 22 km\n1024 3072 1536 14 km\n1365 4092 2048 11 km","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Some remarks on this table","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This assumes the default quadratic truncation, you can always adapt the grid resolution via the dealiasing option, see Matching spectral and grid resolution\nnlat refers to the total number of latitude rings, see Grids. With non-Gaussian grids, nlat will be one one less, e.g. 47 instead of 48 rings.\nnlon is the number of longitude points on the Full Gaussian Grid, for other grids there will be at most these number of points around the Equator.\nDelta x is the horizontal resolution. For a spectral model there are many ways of estimating this[9]. We use here the square root of the average area a grid cell covers, see Effective grid resolution","category":"page"},{"location":"spectral_transform/#Effective-grid-resolution","page":"Spherical harmonic transform","title":"Effective grid resolution","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"There are many ways to estimate the effective grid resolution of spectral models[9]. Some of them are based on the wavelength a given spectral resolution allows to represent, others on the total number of real variables per area. However, as many atmospheric models do represent a considerable amount of physics on the grid (see Parameterizations) there is also a good argument to include the actual grid resolution into this estimate and not just the spectral resolution. We therefore use the average grid cell area to estimate the resolution","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Delta x = sqrtfrac4pi R^2N","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with N number of grid points over a sphere with radius R. However, we have to acknowledge that this usually gives higher resolution compared to other methods of estimating the effective resolution, see [Randall2021] for a discussion. You may therefore need to be careful to make claims that, e.g. trunc=85 can resolve the atmospheric dynamics at a scale of 165km.","category":"page"},{"location":"spectral_transform/#Derivatives-in-spherical-coordinates","page":"Spherical harmonic transform","title":"Derivatives in spherical coordinates","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Horizontal gradients in spherical coordinates are defined for a scalar field A and the latitudes theta and longitudes lambda as","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla A = left(frac1Rcosthetafracpartial Apartial lambda frac1Rfracpartial Apartial theta right)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"However, the divergence of a vector field mathbfu = (uv) includes additional cos(theta) scalings","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla cdot mathbfu = frac1Rcosthetafracpartial upartial lambda +\nfrac1Rcosthetafracpartial (v costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"and similar for the curl","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla times mathbfu = frac1Rcosthetafracpartial vpartial lambda -\nfrac1Rcosthetafracpartial (u costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The radius of the sphere (i.e. Earth) is R. The zonal gradient scales with 1cos(theta) as the longitudes converge towards the poles (note that theta describes latitudes here, definitions using colatitudes replace the cos with a sin.)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting with a spectral field of vorticity zeta and divergence mathcalD one can obtain stream function Psi and velocity potential Phi by inverting the Laplace operator nabla^2:","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi = nabla^-2zeta quad Phi = nabla^-2mathcalD","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The velocities uv are then obtained from (uv) = nabla^botPsi + nablaPhi following the definition from above and nabla^bot = (-R^-1partial_theta (Rcostheta)^-1partial_lambda)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nu = -frac1Rpartial_thetaPsi + frac1Rcosthetapartial_lambdaPhi \nv = +frac1Rpartial_thetaPhi + frac1Rcosthetapartial_lambdaPsi\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Alternatively, we can use the velocities U = ucostheta V = vcostheta, which we do as the meridional gradient for spherical harmonics is easier implemented with a costheta-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with UV and not uv. From uv we can return to zeta mathcalD via","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nzeta = frac1Rcosthetapartial_lambda v - frac1Rcosthetapartial_theta (u costheta) \nmathcalD = frac1Rcosthetapartial_lambda u + frac1Rcosthetapartial_theta (v costheta)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Equivalently, we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU = -fraccosthetaRpartial_thetaPsi + frac1Rpartial_lambdaPhi \nV = +fraccosthetaRpartial_thetaPhi + frac1Rpartial_lambdaPsi \nzeta = frac1Rpartial_lambda left( fracVcos^2theta right) -\nfraccosthetaRpartial_theta left( fracUcos^2theta right) \nmathcalD = frac1Rpartial_lambda left( fracUcos^2theta right) +\nfraccosthetaRpartial_theta left( fracVcos^2theta right)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is a more convenient formulation because of the way how the Meridional derivative is implemented with a recursion relation, actually computing costheta partial_theta rather than partial_theta directly. The remaining cosine scalings in (UV)*cos^-2theta are done in grid-point space. If one wanted to get back to zeta mathcalD this is how it would be done, but it is often more convenient to unscale UV on the fly in the spectral transform to obtain uv and then divide again by costheta when any gradient (or divergence or curl) is taken. This is because other terms would need that single costheta unscaling too before a gradient is taken. How the operators nabla nabla times nabla cdot can be implemented with spherical harmonics is presented in the following sections.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out in this last formulation too.","category":"page"},{"location":"spectral_transform/#Zonal-derivative","page":"Spherical harmonic transform","title":"Zonal derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The zonal derivative of a scalar field Psi in spectral space is the zonal derivative of all its respective spherical harmonics Psi_lm(phitheta) (now we use phi for longitudes to avoid confusion with the Legendre polynomials lambda_lm)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"v_lm = frac1R cos(theta) fracpartialpartial phi left( lambda_l^m(costheta) e^imphi right) =\nfracimR cos(theta) lambda_l^m(costheta) e^imphi = fracimR cos(theta) Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"So for every spectral harmonic, cos(theta)v_lm is obtained from Psi_lm via a multiplication with imR. Unscaling the cos(theta)-factor is done after transforming the spectral coefficients v_lm into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as tildePsi = R^-1Psi such that the division by radius R in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order im. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number m times imaginary i.","category":"page"},{"location":"spectral_transform/#Meridional-derivative","page":"Spherical harmonic transform","title":"Meridional derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridional derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costheta fracdP_lmdtheta = -lepsilon_l+1mP_l+1m + (l+1)epsilon_lmP_l-1m","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with recursion factors","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"epsilon_lm = sqrtfracl^2-m^24l^2-1","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In the following we use the example of obtaining the zonal velocity u from the stream function Psi, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi(lambdatheta) = sum_lmPsi_lmP_lm(sintheta)e^imlambda","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"we multiply with -R^-1costhetapartial_theta to obtain","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costhetaleft(-frac1Rpartial_thetaPsi right) = -frac1Rsum_lmPsi_lme^imlambdacosthetapartial_theta P_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"at which point the recursion from above can be applied. Collecting terms proportional to P_lm then yields","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"(cos(theta)u)_lm = -frac1R(-(l-1)epsilon_lmPsi_l-1m + (l+2)epsilon_l+1mPsi_l+1m)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"To obtain the coefficient of each spherical harmonic lm of the meridional gradient of a spectral field, two coefficients at l-1m and l+1m have to be combined. This means that the coefficient of a gradient ((costheta) u)_lm is a linear combination of the coefficients of one higher and one lower degree Psi_l+1mPsi_l-1m. As the coefficient Psi_lm with ml are zero, the sectoral harmonics (l=m) of the gradients are obtained from the first off-diagonal only. However, the l=l_max harmonics of the gradients require the l_max-1 as well as the l_max+1 harmonics. As a consequence vector quantities like velocity components uv require one more degree l than scalar quantities like vorticity[Bourke72]. However, for easier compatibility all spectral fields in SpeedyWeather.jl use one more degree l, but scalar quantities should not make use of it. Equivalently, the last degree l is set to zero before the time integration, which only advances scalar quantities.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In SpeedyWeather.jl vector quantities like uv use therefore one more meridional mode than scalar quantities such as vorticity zeta or stream function Psi. The meridional derivative in SpeedyWeather.jl also omits the 1R-scaling as explained for the Zonal derivative and in Radius scaling.","category":"page"},{"location":"spectral_transform/#Divergence-and-curl-in-spherical-harmonics","page":"Spherical harmonic transform","title":"Divergence and curl in spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridional gradient as described above can be applied to scalars, such as Psi and Phi in the conversion to velocities (uv) = nabla^botPsi + nablaPhi, however, the operators curl nabla times and divergence nabla cdot in spherical coordinates involve a costheta scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform of vorticity zeta is","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac12piint_-tfracpi2^tfracpi2int_0^2pi zeta(lambdatheta)\nP_lm(sintheta) e^imlambda dlambda costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Given that Rzeta = cos^-1partial_lambda v - cos^-1partial_theta (u costheta), we therefore have to evaluate a meridional integral of the form","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"int P_lm frac1cos theta partial_theta(u costheta)) cos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which can be solved through integration by parts. As ucostheta = 0 at theta = pm tfracpi2 only the integral","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int partial_theta P_lm (u costheta) dtheta = -int costheta partial_theta P_lm\n(fracucostheta) costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"remains. Inserting the recurrence relation from the Meridional derivative turns this into","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int left(-l epsilon_l+1mP_l+1m + (l+1)epsilon_lm P_l-1m right) (fracucostheta)\ncos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Now we expand (tfracucostheta) but only the lm harmonic will project ontoP_lm. Let u^* = ucos^-1theta v^* = vcos^-1theta we then have in total","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nRzeta_lm = imv^*_lm + (l+1)epsilon_lmu^*_l-1m - lepsilon_l+1mu^*_l+1m \nRD_lm = imu^*_lm - (l+1)epsilon_lmv^*_l-1m + lepsilon_l+1mv^*_l+1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"And the divergence D is similar, but (uv) to (-vu). We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#Laplacian","page":"Spherical harmonic transform","title":"Laplacian","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral Laplacian is easily applied to the coefficients Psi_lm of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator nabla^2 in spherical coordinates with eigenvalues -l(l+1) divided by the radius squared R^2, i.e. nabla^2 Psi becomes tfrac-l(l+1)R^2Psi_lm in spectral space. For example, vorticity zeta and streamfunction Psi are related by zeta = nabla^2Psi in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree l and order m to","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac-l(l+1)R^2Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This can be easily inverted to obtain the stream function Psi from vorticity zeta instead. In order to avoid division by zero, we set Psi_00 here, given that the stream function is only defined up to a constant anyway.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nPsi_lm = fracR^2-l(l+1)zeta_lm quad foralllm 0\nPsi_00 = 0\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"See also Horizontal diffusion and Normalization of diffusion.","category":"page"},{"location":"spectral_transform/#U,V-from-vorticity-and-divergence","page":"Spherical harmonic transform","title":"U,V from vorticity and divergence","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity zeta and divergence D (which are prognostic variables) to U=ucostheta V=vcostheta. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree l (the meridional gradient). It is therefore computationally more efficient to compute UV directly from zetaD instead of calculating stream function and velocity potential first. In total we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU_lm = -fraciml(l+1)(RD)_lm + fracepsilon_l+1ml+1(Rzeta)_l+1m -\nfracepsilon_lml(Rzeta)_l-1m \nV_lm = -fraciml(l+1)(Rzeta)_lm - fracepsilon_l+1ml+1(RD)_l+1m +\nfracepsilon_lml(RD)_l-1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#References","page":"Spherical harmonic transform","title":"References","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Malardel2016]: Malardel S, Wedi N, Deconinck W, Diamantakis M, Kühnlein C, Mozdzynski G, Hamrud M, Smolarkiewicz P. A new grid for the IFS. ECMWF newsletter. 2016;146(23-28):321. doi: 10.21957/zwdu9u5i","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Gorski2004]: Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Willmert2020]: Justin Willmert, 2020. justinwillmert.comIntroduction to Associated Legendre Polynomials (Legendre.jl Series, Part I)\nCalculating Legendre Polynomials (Legendre.jl Series, Part II)\nPre-normalizing Legendre Polynomials (Legendre.jl Series, Part III)\nMaintaining numerical accuracy in the Legendre recurrences (Legendre.jl Series, Part IV)\nIntroducing Legendre.jl (Legendre.jl Series, Part V)\nNumerical Accuracy of the Spherical Harmonic Recurrence Coefficient (Legendre.jl Series Addendum)\nNotes on Calculating the Spherical Harmonics\nMore Notes on Calculating the Spherical Harmonics: Analysis of maps to harmonic coefficients","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Daley78]: Roger Daley & Yvon Bourassa (1978) Rhomboidal versus triangular spherical harmonic truncation: Some verification statistics, Atmosphere-Ocean, 16:2, 187-196, DOI: 10.1080/07055900.1978.9649026","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Randall2021]: David Randall, 2021. An Introduction to Numerical Modeling of the Atmosphere, Chapter 22.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Durran2010]: Dale Durran, 2010. Numerical Methods for Fluid Dynamics, Springer. In particular section 6.2, 6.4.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[GFDL]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[FFT]: Depending on the implementation of the Fast Fourier Transform (Cooley-Tukey algorithm, or or the Bluestein algorithm) easily Fourier-transformable can mean different things: Vectors of the length n that is a power of two, i.e. n = 2^i is certainly easily Fourier-transformable, but for most FFT implementations so are n = 2^i3^j5^k with ijk some positive integers. In fact, FFTW uses O(n log n) algorithms even for prime sizes.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Bourke72]: Bourke, W. An Efficient, One-Level, Primitive-Equation Spectral Model. Mon. Wea. Rev. 100, 683–689 (1972). doi:10.1175/1520-0493(1972)100<0683:AEOPSM>2.3.CO;2","category":"page"},{"location":"ringgrids/#RingGrids","page":"Submodule: RingGrids","title":"RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids defines several iso-latitude grids, which are mathematically described in the section on Grids. In brief, they include the regular latitude-longitude grids (here called FullClenshawGrid) as well as grids which latitudes are shifted to the Gaussian latitudes and reduced grids, meaning that they have a decreasing number of longitudinal points towards the poles to be more equal-area than full grids.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids defines and exports the following grids:","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"full grids: FullClenshawGrid, FullGaussianGrid, FullHEALPix, and FullOctaHEALPix\nreduced grids: OctahedralGaussianGrid, OctahedralClenshawGrid, OctaHEALPixGrid and HEALPixGrid","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"The following explanation of how to use these can be mostly applied to any of them, however, there are certain functions that are not defined, e.g. the full grids can be trivially converted to a Matrix (i.e. they are rectangular grids) but not the OctahedralGaussianGrid.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"note: What is a ring?\nWe use the term ring, short for iso-latitude ring, to refer to a sequence of grid points that all share the same latitude. A latitude-longitude grid is a ring grid, as it organises its grid-points into rings. However, other grids, like the cubed-sphere are not based on iso-latitude rings. SpeedyWeather.jl only works with ring grids because its a requirement for the Spherical Harmonic Transform to be efficient. See Grids.","category":"page"},{"location":"ringgrids/#Creating-data-on-a-RingGrid","page":"Submodule: RingGrids","title":"Creating data on a RingGrid","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Every grid in RingGrids has a grid.data field, which is a vector containing the data on the grid. The grid points are unravelled west to east then north to south, meaning that it starts at 90˚N and 0˚E then walks eastward for 360˚ before jumping on the next latitude ring further south, this way circling around the sphere till reaching the south pole. This may also be called ring order.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Data in a Matrix which follows this ring order can be put on a FullGaussianGrid like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"using SpeedyWeather.RingGrids\nmap = randn(Float32,8,4)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid = FullGaussianGrid(map)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"A full Gaussian grid has always 2N x N grid points, but a FullClenshawGrid has 2N x N-1, if those dimensions don't match, the creation will throw an error. To reobtain the data from a grid, you can access its data field which returns a normal Vector","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid.data","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Which can be reshaped to reobtain map from above. Alternatively you can Matrix(grid) to do this in one step","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"map == Matrix(FullGaussianGrid(map))","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"You can also use zeros,ones,rand,randn to create a grid, whereby nlat_half, i.e. the number of latitude rings on one hemisphere, Equator included, is used as a resolution parameter and here as a second argument.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"nlat_half = 4\ngrid = randn(OctahedralGaussianGrid{Float16},nlat_half)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"and any element type T can be used for OctahedralGaussianGrid{T} and similar for other grid types.","category":"page"},{"location":"ringgrids/#Visualising-RingGrid-data","page":"Submodule: RingGrids","title":"Visualising RingGrid data","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"As only the full grids can be reshaped into a matrix, the underlying data structure of any AbstractGrid is a vector. As shown in the examples above, one can therefore inspect the data as if it was a vector. But as that data has, through its <:AbstractGrid type, all the geometric information available to plot it on a map, RingGrids also exports plot function, based on UnicodePlots' heatmap.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"nlat_half = 24\ngrid = randn(OctahedralGaussianGrid,nlat_half)\nplot(grid)","category":"page"},{"location":"ringgrids/#Indexing-RingGrids","page":"Submodule: RingGrids","title":"Indexing RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"All RingGrids have a single index ij which follows the ring order. While this is obviously not super exciting here are some examples how to make better use of the information that the data sits on a grid.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"We obtain the latitudes of the rings of a grid by calling get_latd (get_lond is only defined for full grids, or use get_latdlonds for latitudes, longitudes per grid point not per ring)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid = randn(OctahedralClenshawGrid,5)\nlatd = get_latd(grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Now we could calculate Coriolis and add it on the grid as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"rotation = 7.29e-5 # angular frequency of Earth's rotation [rad/s]\ncoriolis = 2rotation*sind.(latd) # vector of coriolis parameters per latitude ring\n\nrings = eachring(grid)\nfor (j,ring) in enumerate(rings)\n f = coriolis[j]\n for ij in ring\n grid[ij] += f\n end\nend","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"eachring creates a vector of UnitRange indices, such that we can loop over the ring index j (j=1 being closest to the North pole) pull the coriolis parameter at that latitude and then loop over all in-ring indices i (changing longitudes) to do something on the grid. Something similar can be done to scale/unscale with the cosine of latitude for example. We can always loop over all grid-points like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"for ij in eachgridpoint(grid)\n grid[ij]\nend","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"or use eachindex instead.","category":"page"},{"location":"ringgrids/#Interpolation-on-RingGrids","page":"Submodule: RingGrids","title":"Interpolation on RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"In most cases we will want to use RingGrids so that our data directly comes with the geometric information of where the grid-point is one the sphere. We have seen how to use get_latd, get_lond, ... for that above. This information generally can also be used to interpolate our data from grid to another or to request an interpolated value on some coordinates. Using our data on grid which is an OctahedralGaussianGrid from above we can use the interpolate function to get it onto a FullGaussianGrid (or any other grid for purpose)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid = randn(OctahedralGaussianGrid{Float32},4)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(FullGaussianGrid,grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"By default this will linearly interpolate (it's an Anvil interpolator, see below) onto a grid with the same nlat_half, but we can also coarse-grain or fine-grain by specifying nlat_half directly as 2nd argument","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(FullGaussianGrid,6,grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"So we got from an 8-ring OctahedralGaussianGrid{Float16} to a 12-ring FullGaussianGrid{Float64}, so it did a conversion from Float16 to Float64 on the fly too, because the default precision is Float64 unless specified. interpolate(FullGaussianGrid{Float16},6,grid) would have interpolated onto a grid with element type Float16.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"One can also interpolate onto a given coordinate ˚N, ˚E like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(30.0,10.0,grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"we interpolated the data from grid onto 30˚N, 10˚E. To do this simultaneously for many coordinates they can be packed into a vector too","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate([30.0,40.0,50.0],[10.0,10.0,10.0],grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which returns the data on grid at 30˚N, 40˚N, 50˚N, and 10˚E respectively. Note how the interpolation here retains the element type of grid.","category":"page"},{"location":"ringgrids/#Performance-for-RingGrid-interpolation","page":"Submodule: RingGrids","title":"Performance for RingGrid interpolation","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Every time an interpolation like interpolate(30.0,10.0,grid) is called, several things happen, which are important to understand to know how to get the fastest interpolation out of this module in a given situation. Under the hood an interpolation takes three arguments","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"output vector\ninput grid\ninterpolator","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"The output vector is just an array into which the interpolated data is written, providing this prevents unnecessary allocation of memory in case the destination array of the interpolation already exists. The input grid contains the data which is subject to interpolation, it must come on a ring grid, however, its coordinate information is actually already in the interpolator. The interpolator knows about the geometry of the grid the data is coming on and the coordinates it is supposed to interpolate onto. It has therefore precalculated the indices that are needed to access the right data on the input grid and the weights it needs to apply in the actual interpolation operation. The only thing it does not know is the actual data values of that grid. So in the case you want to interpolate from grid A to grid B many times, you can just reuse the same interpolator. If you want to change the coordinates of the output grid but its total number of points remain constants then you can update the locator inside the interpolator and only else you will need to create a new interpolator. Let's look at this in practice. Say we have two grids an want to interpolate between them","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid_in = rand(HEALPixGrid,4)\ngrid_out = zeros(FullClenshawGrid,6)\ninterp = RingGrids.interpolator(grid_out,grid_in)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Now we have created an interpolator interp which knows about the geometry where to interpolate from and the coordinates there to interpolate to. It is also initialized, meaning it has precomputed the indices to of grid_in that are supposed to be used. It just does not know about the data of grid_in (and neither of grid_out which will be overwritten anyway). We can now do","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate!(grid_out,grid_in,interp)\ngrid_out","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which is identical to interpolate(grid_out,grid_in) but you can reuse interp for other data. The interpolation can also handle various element types (the interpolator interp does not have to be updated for this either)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid_out = zeros(FullClenshawGrid{Float16},6);\ninterpolate!(grid_out,grid_in,interp)\ngrid_out","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"and we have converted data from a HEALPixGrid{Float64} (Float64 is always default if not specified) to a FullClenshawGrid{Float16} including the type conversion Float64-Float16 on the fly. Technically there are three data types and their combinations possible: The input data will come with a type, the output array has an element type and the interpolator has precomputed weights with a given type. Say we want to go from Float16 data on an OctahedralGaussianGrid to Float16 on a FullClenshawGrid but using Float32 precision for the interpolation itself, we would do this by","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid_in = randn(OctahedralGaussianGrid{Float16},24)\ngrid_out = zeros(FullClenshawGrid{Float16},24)\ninterp = RingGrids.interpolator(Float32,grid_out,grid_in)\ninterpolate!(grid_out,grid_in,interp)\ngrid_out","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"As a last example we want to illustrate a situation where we would always want to interpolate onto 10 coordinates, but their locations may change. In order to avoid recreating an interpolator object we would do (AnvilInterpolator is described in Anvil interpolator)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"npoints = 10 # number of coordinates to interpolate onto\ninterp = AnvilInterpolator(Float32,HEALPixGrid,24,npoints)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"with the first argument being the number format used during interpolation, then the input grid type, its resolution in terms of nlat_half and then the number of points to interpolate onto. However, interp is not yet initialized as it does not know about the destination coordinates yet. Let's define them, but note that we already decided there's only 10 of them above.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"latds = collect(0.0:5.0:45.0)\nlonds = collect(-10.0:2.0:8.0)\nnothing # hide","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"now we can update the locator inside our interpolator as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids.update_locator!(interp,latds,londs)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"With data matching the input from above, a nlat_half=24 HEALPixGrid, and allocate 10-element output vector","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"output_vec = zeros(10)\ngrid_input = rand(HEALPixGrid,24)\nnothing # hide","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"we can use the interpolator as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate!(output_vec,grid_input,interp)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which is the approximately the same as doing it directly without creating an interpolator first and updating its locator","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(latds,londs,grid_input)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"but allows for a reuse of the interpolator. Note that the two output arrays are not exactly identical because we manually set our interpolator interp to use Float32 for the interpolation whereas the default is Float64.","category":"page"},{"location":"ringgrids/#Anvil-interpolator","page":"Submodule: RingGrids","title":"Anvil interpolator","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Currently the only interpolator implemented is a 4-point bilinear interpolator, which schematically works as follows. Anvil interpolation is the bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":" 0..............1 # fraction of distance Δab between a,b\n |< Δab >|\n\n0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x\n.Δy |\n. |\n.v x \n. |\n1 c ------ o ---- d\n\n |< Δcd >|\n 0...............1 # fraction of distance Δcd between c,d\n\n^ fraction of distance Δy between a-b and c-d.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"This interpolation is chosen as by definition of the ring grids, a and b share the same latitude, so do c and d, but the longitudes can be different for all four, a,b,c,d.","category":"page"},{"location":"ringgrids/#Function-index","page":"Submodule: RingGrids","title":"Function index","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Modules = [SpeedyWeather.RingGrids]","category":"page"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractFullGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractFullGrid","text":"abstract type AbstractFullGrid{T} <: AbstractGrid{T} end\n\nAn AbstractFullGrid is a horizontal grid with a constant number of longitude points across latitude rings. Different latitudes can be used, Gaussian latitudes, equi-angle latitdes, or others.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractGrid","text":"abstract type AbstractGrid{T} <: AbstractVector{T} end\n\nThe abstract supertype for all spatial grids on the sphere supported by SpeedyWeather.jl. Every new grid has to be of the form\n\nabstract type AbstractGridClass{T} <: AbstractGrid{T} end\nstruct MyNewGrid{T} <: AbstractGridClass{T}\n data::Vector{T} # all grid points unravelled into a vector\n nlat_half::Int # resolution: latitude rings on one hemisphere (Equator incl)\nend\n\nMyNewGrid should belong to a grid class like AbstractFullGrid, AbstractOctahedralGrid or AbstractHEALPixGrid (that already exist but you may introduce a new class of grids) that share certain features such as the number of longitude points per latitude ring and indexing, but may have different latitudes or offset rotations. Each new grid Grid (or grid class) then has to implement the following methods (as an example, see octahedral.jl)\n\nFundamental grid properties getnpoints # total number of grid points nlatodd # does the grid have an odd number of latitude rings? getnlat # total number of latitude rings getnlat_half # number of latitude rings on one hemisphere incl Equator\n\nIndexing getnlonmax # maximum number of longitudes points (at the Equator) getnlonperring # number of longitudes on ring j eachindexinring # a unit range that indexes all longitude points on a ring\n\nCoordinates getcolat # vector of colatitudes (radians) getcolatlon # vectors of colatitudes, longitudes (both radians)\n\nSpectral truncation truncationorder # linear, quadratic, cubic = 1,2,3 for grid gettruncation # spectral truncation given a grid resolution get_resolution # grid resolution given a spectral truncation\n\nQuadrature weights and solid angles getquadratureweights # = sinθ Δθ for grid points on ring j for meridional integration getsolidangle # = sinθ Δθ Δϕ, solid angle of grid points on ring j\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractHEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractHEALPixGrid","text":"abstract type AbstractHEALPixGrid{T} <: AbstractGrid{T} end\n\nAn AbstractHEALPixGrid is a horizontal grid similar to the standard HEALPixGrid, but different latitudes can be used, the default HEALPix latitudes or others.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractInterpolator","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractInterpolator","text":"abstract type AbstractInterpolator{NF,G} end\n\nSupertype for Interpolators. Every Interpolator <: AbstractInterpolator is expected to have two fields,\n\ngeometry, which describes the grid G to interpolate from\nlocator, which locates the indices on G and their weights to interpolate onto a new grid.\n\nNF is the number format used to calculate the interpolation, which can be different from the input data and/or the interpolated data on the new grid.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractLocator","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractLocator","text":"AbstractLocator{NF}\n\nSupertype of every Locator, which locates the indices on a grid to be used to perform an interpolation. E.g. AnvilLocator uses a 4-point stencil for every new coordinate to interpolate onto. Higher order stencils can be implemented by defining OtherLocator <: AbstractLocactor.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractOctaHEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractOctaHEALPixGrid","text":"abstract type AbstractOctaHEALPixGrid{T} <: AbstractGrid{T} end\n\nAn AbstractOctaHEALPixGrid is a horizontal grid similar to the standard OctahedralGrid, but the number of points in the ring closest to the Poles starts from 4 instead of 20, and the longitude of the first point in each ring is shifted as in HEALPixGrid. Also, different latitudes can be used.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractOctahedralGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractOctahedralGrid","text":"abstract type AbstractOctahedralGrid{T} <: AbstractGrid{T} end\n\nAn AbstractOctahedralGrid is a horizontal grid with 16+4i longitude points on the latitude ring i starting with i=1 around the pole. Different latitudes can be used, Gaussian latitudes, equi-angle latitdes, or others.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AnvilLocator","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AnvilLocator","text":"AnvilLocator{NF<:AbstractFloat} <: AbtractLocator\n\nContains arrays that locates grid points of a given field to be uses in an interpolation and their weights. This Locator is a 4-point average in an anvil-shaped grid-point arrangement between two latitude rings.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AnvilLocator-Union{Tuple{Integer}, Tuple{NF}} where NF<:AbstractFloat","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AnvilLocator","text":"L = AnvilLocator( ::Type{NF}, # number format used for the interpolation\n npoints::Integer # number of points to interpolate onto\n ) where {NF<:AbstractFloat}\n\nZero generator function for the 4-point average AnvilLocator. Use update_locator! to update the grid indices used for interpolation and their weights. The number format NF is the format used for the calculations within the interpolation, the input data and/or output data formats may differ.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.FullClenshawGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.FullClenshawGrid","text":"G = FullClenshawGrid{T}\n\nA FullClenshawGrid is a regular latitude-longitude grid with an odd number of nlat equi-spaced latitudes, the central latitude ring is on the Equator. The same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field data that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.FullGaussianGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.FullGaussianGrid","text":"G = FullGaussianGrid{T}\n\nA full Gaussian grid is a regular latitude-longitude grid that uses nlat Gaussian latitudes, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.FullHEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.FullHEALPixGrid","text":"G = FullHEALPixGrid{T}\n\nA full HEALPix grid is a regular latitude-longitude grid that uses nlat latitudes from the HEALPix grid, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.FullOctaHEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.FullOctaHEALPixGrid","text":"G = FullOctaHEALPixGrid{T}\n\nA full OctaHEALPix grid is a regular latitude-longitude grid that uses nlat OctaHEALPix latitudes, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.GridGeometry","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.GridGeometry","text":"GridGeometry{G<:AbstractGrid}\n\ncontains general precomputed arrays describing the grid of G.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.GridGeometry-Tuple{Type{<:SpeedyWeather.RingGrids.AbstractGrid}, Integer}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.GridGeometry","text":"G = GridGeometry( Grid::Type{<:AbstractGrid},\n nlat_half::Integer)\n\nPrecomputed arrays describing the geometry of the Grid with resolution nlat_half. Contains latitudes and longitudes of grid points, their ring index j and their unravelled indices ij.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.HEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.HEALPixGrid","text":"H = HEALPixGrid{T}\n\nA HEALPix grid with 12 faces, each nsidexnside grid points, each covering the same area. The number of latitude rings on one hemisphere (incl Equator) nlat_half is used as resolution parameter. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.OctaHEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.OctaHEALPixGrid","text":"H = OctaHEALPixGrid{T}\n\nA OctaHEALPix grid with 4 base faces, each nlat_halfxnlat_half grid points, each covering the same area. The values of all grid points are stored in a vector field data that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.OctahedralClenshawGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.OctahedralClenshawGrid","text":"G = OctahedralClenshawGrid{T}\n\nAn Octahedral Clenshaw grid that uses nlat equi-spaced latitudes. Like FullClenshawGrid, the central latitude ring is on the Equator. Like OctahedralGaussianGrid, the number of longitude points per latitude ring decreases towards the poles. Starting with 20 equi-spaced longitude points (starting at 0˚E) on the rings around the poles, each latitude ring towards the equator has consecuitively 4 more points, one for each face of the octahedron. E.g. 20,24,28,32,...nlon-4,nlon,nlon,nlon-4,...,32,28,24,20. The maximum number of longitue points is nlon. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.OctahedralGaussianGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.OctahedralGaussianGrid","text":"G = OctahedralGaussianGrid{T}\n\nAn Octahedral Gaussian grid that uses nlat Gaussian latitudes, but a decreasing number of longitude points per latitude ring towards the poles. Starting with 20 equi-spaced longitude points (starting at 0˚E) on the rings around the poles, each latitude ring towards the equator has consecuitively 4 more points, one for each face of the octahedron. E.g. 20,24,28,32,...nlon-4,nlon,nlon,nlon-4,...,32,28,24,20. The maximum number of longitue points is nlon. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.Matrix!-Tuple{AbstractMatrix, OctaHEALPixGrid}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.Matrix!","text":"Matrix!(M::AbstractMatrix,\n G::OctaHEALPixGrid;\n quadrant_rotation=(0,1,2,3),\n matrix_quadrant=((2,2),(1,2),(1,1),(2,1)),\n )\n\nSorts the gridpoints in G into the matrix M without interpolation. Every quadrant of the grid G is rotated as specified in quadrant_rotation, 0 is no rotation, 1 is 90˚ clockwise, 2 is 180˚ etc. Grid quadrants are counted eastward starting from 0˚E. The grid quadrants are moved into the matrix quadrant (i,j) as specified. Defaults are equivalent to centered at 0˚E and a rotation such that the North Pole is at M's midpoint.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.Matrix!-Tuple{AbstractMatrix, OctahedralClenshawGrid}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.Matrix!","text":"Matrix!(M::AbstractMatrix,\n G::OctahedralClenshawGrid;\n quadrant_rotation=(0,1,2,3),\n matrix_quadrant=((2,2),(1,2),(1,1),(2,1)),\n )\n\nSorts the gridpoints in G into the matrix M without interpolation. Every quadrant of the grid G is rotated as specified in quadrant_rotation, 0 is no rotation, 1 is 90˚ clockwise, 2 is 180˚ etc. Grid quadrants are counted eastward starting from 0˚E. The grid quadrants are moved into the matrix quadrant (i,j) as specified. Defaults are equivalent to centered at 0˚E and a rotation such that the North Pole is at M's midpoint.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.Matrix!-Union{Tuple{Vararg{Tuple{AbstractMatrix{T}, OctaHEALPixGrid}}}, Tuple{T}} where T","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.Matrix!","text":"Matrix!(MGs::Tuple{AbstractMatrix{T},OctaHEALPixGrid}...;kwargs...)\n\nLike Matrix!(::AbstractMatrix,::OctaHEALPixGrid) but for simultaneous processing of tuples ((M1,G1),(M2,G2),...) with matrices Mi and grids Gi. All matrices and grids have to be of the same size respectively.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.Matrix!-Union{Tuple{Vararg{Tuple{AbstractMatrix{T}, OctahedralClenshawGrid}}}, Tuple{T}} where T","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.Matrix!","text":"Matrix!(MGs::Tuple{AbstractMatrix{T},OctahedralClenshawGrid}...;kwargs...)\n\nLike Matrix!(::AbstractMatrix,::OctahedralClenshawGrid) but for simultaneous processing of tuples ((M1,G1),(M2,G2),...) with matrices Mi and grids Gi. All matrices and grids have to be of the same size respectively.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.anvil_average-NTuple{7, Any}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.anvil_average","text":"anvil_average(a, b, c, d, Δab, Δcd, Δy) -> Any\n\n\nThe bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate. See schematic:\n\n 0..............1 # fraction of distance Δab between a,b\n |< Δab >|\n\n 0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x\n .Δy |\n . |\n .v x \n . |\n 1 c ------ o ---- d\n\n |< Δcd >|\n 0...............1 # fraction of distance Δcd between c,d\n\n^ fraction of distance Δy between a-b and c-d.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.average_on_poles-Union{Tuple{NF}, Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}, Vector{<:UnitRange{<:Integer}}}} where NF<:AbstractFloat","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.average_on_poles","text":"average_on_poles(\n A::SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n rings::Vector{<:UnitRange{<:Integer}}\n) -> Tuple{Any, Any}\n\n\nComputes the average at the North and South pole from a given grid A and it's precomputed ring indices rings. The North pole average is an equally weighted average of all grid points on the northern-most ring. Similar for the South pole.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.average_on_poles-Union{Tuple{NF}, Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}, Vector{<:UnitRange{<:Integer}}}} where NF<:Integer","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.average_on_poles","text":"average_on_poles(\n A::SpeedyWeather.RingGrids.AbstractGrid{NF<:Integer},\n rings::Vector{<:UnitRange{<:Integer}}\n) -> Tuple{Any, Any}\n\n\nMethod for A::Abstract{T<:Integer} which rounds the averaged values to return the same number format NF.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.each_index_in_ring-Union{Tuple{Grid}, Tuple{Grid, Integer}} where Grid<:SpeedyWeather.RingGrids.AbstractGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.each_index_in_ring","text":"i = each_index_in_ring(grid,j)\n\nUnitRange i to access data on grid grid on ring j.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.eachgridpoint-Tuple{SpeedyWeather.RingGrids.AbstractGrid}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.eachgridpoint","text":"ijs = eachgridpoint(grid)\n\nUnitRange ijs to access each grid point on grid grid.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.eachring-Tuple{SpeedyWeather.RingGrids.AbstractGrid}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.eachring","text":"eachring(grid::SpeedyWeather.RingGrids.AbstractGrid) -> Any\n\n\nVector{UnitRange} rings to loop over every ring of grid grid and then each grid point per ring. To be used like\n\nrings = eachring(grid)\nfor ring in rings\n for ij in ring\n grid[ij]\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.eachring-Union{Tuple{Grid}, Tuple{Grid, Vararg{Grid}}} where Grid<:SpeedyWeather.RingGrids.AbstractGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.eachring","text":"eachring(\n grid1::SpeedyWeather.RingGrids.AbstractGrid,\n grids::Grid<:SpeedyWeather.RingGrids.AbstractGrid...\n) -> Any\n\n\nSame as eachring(grid) but performs a bounds check to assess that all grids in grids are of same size.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.extrema_in-Tuple{Vector, Real, Real}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.extrema_in","text":"true/false = extrema_in(v::Vector,a::Real,b::Real)\n\nFor every element vᵢ in v does a<=vi<=b hold?\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.get_nlons-Tuple{Type{<:SpeedyWeather.RingGrids.AbstractGrid}, Integer}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.get_nlons","text":"get_nlons(\n Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid},\n nlat_half::Integer;\n both_hemispheres\n) -> Any\n\n\nReturns a vector nlons for the number of longitude points per latitude ring, north to south. Provide grid Grid and its resolution parameter nlat_half. For both_hemisphere==false only the northern hemisphere (incl Equator) is returned.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.isdecreasing-Tuple{Vector}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.isdecreasing","text":"true/false = isdecreasing(v::Vector)\n\nCheck whether elements of a vector v are strictly decreasing.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.isincreasing-Tuple{Vector}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.isincreasing","text":"true/false = isincreasing(v::Vector)\n\nCheck whether elements of a vector v are strictly increasing.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.rotate_matrix_indices_180-Tuple{Integer, Integer, Integer}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.rotate_matrix_indices_180","text":"i_new,j_new = rotate_matrix_indices_180(i,j,s)\n\nRotate indices i,j of a square matrix of size s x s by 180˚.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.rotate_matrix_indices_270-Tuple{Integer, Integer, Integer}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.rotate_matrix_indices_270","text":"i_new,j_new = rotate_matrix_indices_270(i,j,s)\n\nRotate indices i,j of a square matrix of size s x s anti-clockwise by 270˚.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.rotate_matrix_indices_90-Tuple{Integer, Integer, Integer}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.rotate_matrix_indices_90","text":"i_new,j_new = rotate_matrix_indices_90(i,j,s)\n\nRotate indices i,j of a square matrix of size s x s anti-clockwise by 90˚.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.whichring-Tuple{Integer, Vector{UnitRange{Int64}}}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.whichring","text":"whichring(\n ij::Integer,\n rings::Vector{UnitRange{Int64}}\n) -> Int64\n\n\nObtain ring index j from gridpoint ij and Vector{UnitRange} describing rind indices as obtained from eachring(::Grid)\n\n\n\n\n\n","category":"method"},{"location":"#SpeedyWeather.jl-documentation","page":"Home","title":"SpeedyWeather.jl documentation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.","category":"page"},{"location":"#Overview","page":"Home","title":"Overview","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to simulate the general circulation of the atmosphere. The prognostic variables used are vorticity, divergence, temperature, surface pressure and specific humidity. Simple parameterizations represent various climate processes: Radiation, clouds, precipitation, surface fluxes, among others.","category":"page"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl defines ","category":"page"},{"location":"","page":"Home","title":"Home","text":"BarotropicModel for the 2D barotropic vorticity equation\nShallowWaterModel for the 2D shallow water equations\nPrimitiveDryModel for the 3D primitive equations without humidity\nPrimitiveWetModel for the 3D primitive equations with humidity","category":"page"},{"location":"","page":"Home","title":"Home","text":"and solves these equations in spherical coordinates as described in this documentation.","category":"page"},{"location":"#Manual-outline","page":"Home","title":"Manual outline","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"See the following pages of the documentation for more details","category":"page"},{"location":"","page":"Home","title":"Home","text":"Installation\nHow to run SpeedyWeather.jl\nSpherical harmonic transform\nGrids\nBarotropic model\nShallow water model\nPrimitive equation model\nParameterizations\nExtending SpeedyWeather\nNetCDF output","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the submodules","category":"page"},{"location":"","page":"Home","title":"Home","text":"RingGrids\nLowerTriangularMatrices \nSpeedyTransforms","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the original documentation by Molteni and Kucharski.","category":"page"},{"location":"#Developers","page":"Home","title":"Developers","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The development of SpeedyWeather.jl is lead by Milan Klöwer and current and past contributors include","category":"page"},{"location":"","page":"Home","title":"Home","text":"Tom Kimpson\nAlistair White\nMaximilian Gelbrecht\nDavid Meyer\nDaisuke Hotta\nNavid Constantinou","category":"page"},{"location":"","page":"Home","title":"Home","text":"Any contributions are always welcome!","category":"page"},{"location":"#Funding","page":"Home","title":"Funding","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"MK received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022. Since 2023 this project is also funded by the National Science Foundation NSF.","category":"page"},{"location":"time_integration/#Time-integration","page":"Time integration","title":"Time integration","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"SpeedyWeather.jl uses a leapfrog time scheme with a Robert's and Williams filter to dampen the computational mode and achieve 3rd order accuracy.","category":"page"},{"location":"time_integration/#Oscillation-equation","page":"Time integration","title":"Oscillation equation","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"fracdFdt = iomega F","category":"page"},{"location":"time_integration/#Implementation-details","page":"Time integration","title":"Implementation details","text":"","category":"section"}]
+}
diff --git a/dev/shallowwater/index.html b/dev/shallowwater/index.html
new file mode 100644
index 000000000..56c2d508c
--- /dev/null
+++ b/dev/shallowwater/index.html
@@ -0,0 +1,36 @@
+
+Shallow water model · SpeedyWeather.jl
The shallow water model describes the evolution of a 2D flow described by its velocity and an interface height that conceptually represents pressure. A divergent flow affects the interface height which in turn can impose a pressure gradient force onto the flow. The dynamics include advection, forces, dissipation, and continuity.
The following description of the shallow water model largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: The Shallow Water Equations[2].
The shallow water equations of velocity $\mathbf{u} = (u,v)$ and interface height $\eta$ (i.e. the deviation from the fluid's rest height $H$) are, formulated in terms of relative vorticity $\zeta = \nabla \times \mathbf{u}$, divergence $\mathcal{D} = \nabla \cdot \mathbf{u}$
We denote time$t$, Coriolis parameter $f$, a forcing vector $\mathbf{F} = (F_u,F_v)$, hyperdiffusion $(-1)^{n+1} \nu \nabla^{2n}$ ($n$ is the hyperdiffusion order, see Horizontal diffusion), gravitational acceleration $g$, dynamic layer thickness $h$, and a forcing for the interface height $F_\eta$. In the shallow water model the dynamics layer thickness $h$ is
\[h = \eta + H - H_b\]
that is, the layer thickness at rest $H$ plus the interface height $\eta$ minus orography $H_b$.
In the shallow water system the flow can be described through $u,v$ or $\zeta,\mathcal{D}$ which are related through the stream function $\Psi$ and the velocity potential $\Phi$ (which is zero in the Barotropic vorticity equation).
With $\nabla^\perp$ being the rotated gradient operator, in cartesian coordinates $x,y$: $\nabla^\perp = (-\partial_y, \partial_x)$. See Derivatives in spherical coordinates for further details. Especially because the inversion of the Laplacian and the gradients of $\Psi, \Phi$ can be computed in a single pass, see U,V from vorticity and divergence.
The divergence/curl of the vorticity flux $\mathbf{u}(\zeta + f)$ are combined with the divergence/curl of the forcing vector $\mathbf{F}$, as
0. Start with initial conditions of relative vorticity $\zeta_{lm}$, divergence $D_{lm}$, and interface height $\eta_{lm}$ in spectral space and transform this model state to grid-point space:
Invert the Laplacian of $\zeta_{lm}$ to obtain the stream function $\Psi_{lm}$ in spectral space
Invert the Laplacian of $D_{lm}$ to obtain the velocity potential $\Phi_{lm}$ in spectral space
Probably the biggest advantage of a spectral model is its ability to solve (parts of) the equations implicitly a low computational cost. The reason is that a linear operator can be easily inverted in spectral space, removing the necessity to solve large equation systems. An operation like $\Psi = \nabla^{-2}\zeta$ in grid-point space is costly because it requires a global communication, coupling all grid points. In spectral space $\nabla^2$ is a diagonal operator, meaning that there is no communication between harmonics and its inversion is therefore easily done on a mode-by-mode basis of the harmonics.
This can be made use of when facing time stepping constraints with explicit schemes, where ridiculuously small time steps to resolve fast waves would otherwise result in a horribly slow simulation. In the shallow water system there are gravity waves that propagate at a wave speed of $\sqrt{gH}$ (typically 300m/s), which, in order to not violate the CFL criterion for explicit time stepping, would need to be resolved. Therefore, treating the terms that are responsible for gravity waves implicitly would remove that time stepping constraint and allows us to run the simulation at the time step needed to resolve the advective motion of the atmosphere, which is usually one or two orders of magnitude longer than gravity waves.
In the following we will describe how the semi implicit time integration can be combined with the Leapfrog time stepping and the Robert-Asselin and Williams filter for a large increase in numerical stability with gravity waves. Let $V_i$ be the model state of all prognostic variables at time step $i$, the leapfrog time stepping is then
with the right-hand side operator $N$ evaluated at the current time step $i$. Now the idea is to split the terms in $N$ into non-linear terms that are evaluated explicitly in $N_E$ and into the linear terms $N_I$, solved implicitly, that are responsible for the gravity waves. We could already assume to evaluate $N_I$ at $i+1$, but in fact, we can introduce $\alpha \in [0,1]$ so that for $\alpha=0$ we use $i-1$ (i.e. explicit), for $\alpha=1/2$ it is centred implicit $\tfrac{1}{2}N_I(V_{i-1}) + \tfrac{1}{2}N_I(V_{i+1})$, and for $\alpha=1$ a fully backwards scheme $N_I(V_{i+1})$ evaluated at $i+1$.
Let $\delta V = \tfrac{V_{i+1} - V_{i-1}}{2\Delta t}$ be the tendency we need for the Leapfrog time stepping. Introducing $\xi = 2\alpha\Delta t$ we have
\[\delta V = N_E(V_i) + N_I(V_{i-1}) + \xi N_I(\delta V)\]
because $N_I$ is a linear operator. This is done so that we can solve for $\delta V$ by inverting $N_I$, but let us gather the other terms as $G$ first.
For the shallow water equations we will only make use of the last formulation, meaning we first evaluate the whole right-hand side $N(V_i)$ at the current time step as we would do with fully explicit time stepping but then add the implicit terms $N_I(V_{i-1} - V_i)$ afterwards to move those terms from $i$ to $i-1$. Note that we could also directly evaluate the implicit terms at $i-1$ as it is suggested in the previous formulation $N_E(V_i) + N_I(V_{i-1})$, the result would be the same. But in general it can be more efficient to do it one or the other way, and in fact it is also possible to combine both ways. This will be discussed in the semi-implicit time stepping for the primitive equations.
We can now implicitly solve for $\delta V$ by
\[\delta V = (1-\xi N_I)^{-1}G\]
So what is $N_I$? In the shallow water system the gravity waves are caused by
which is a linearization of the equations around a state of rest with uniform constant layer thickness $h = H$. The continuity equation with the $-\nabla(\mathbf{u}h)$ term, for example, is linearized to $-\nabla(\mathbf{u}H) = -H\mathcal{D}$. The divergence and continuity equations can now be written following the $\delta V = G + \xi N_I(\delta V)$ formulation from above as a coupled system (The vorticity equation is zero for the linear gravity wave equation in the shallow water equations, hence no semi-implicit correction has to be made to the vorticity tendency).
Inserting the second equation into the first, we can first solve for $\delta \mathcal{D}$, and then for $\delta \eta$. Reminder that we do this in spectral space to every harmonic independently, so the Laplace operator $\nabla^2 = -l(l+1)$ takes the form of its eigenvalue $-l(l+1)$ (normalized to unit sphere, as are the scaled shallow water equations) and its inversion is therefore just the inversion of this scalar.
\[\delta D = \frac{G_\mathcal{D} - \xi g\nabla^2 G_\eta}{1 - \xi^2 H \nabla^2} =: S^{-1}(G_\mathcal{D} - \xi g\nabla^2 G_\eta) \]
Where the last formulation just makes it clear that $S = 1 - \xi^2 H \nabla^2$ is the operator to be inverted. $\delta \eta$ is then obtained via insertion as written above. Equivalently, by adding a superscript $l$ for every degree of the spherical harmonics, we have
\[\delta \mathcal{D}^l = \frac{G_\mathcal{D}^l + \xi g l(l+1) G_\eta^l}{1 + \xi^2 H l(l+1)}\]
The idea of the semi-implicit time stepping is now as follows:
Evaluate the right-hand side explicitly at time step $i$ to obtain the explicit, preliminary tendencies $N_\mathcal{D},N_\eta$ (and $N_\zeta$ without a need for semi-implicit correction)
Move the implicit terms from $i$ to $i-1$ when calculating $G_\mathcal{D}, G_\eta$
Solve for $\delta \mathcal{D}$, the new, corrected tendency for divergence.
With $\delta \mathcal{D}$ obtain $\delta \eta$, the new, corrected tendency for $\eta$.
Apply horizontal diffusion as a correction to $N_\zeta, \delta \mathcal{D}$ as outlined in Horizontal diffusion.
Leapfrog with tendencies that have been corrected for both semi-implicit and diffusion.
Some notes on the semi-implicit time stepping
The inversion of the semi-implicit time stepping depends on $\delta t$, that means every time the time step changes, the inversion has to be recalculated.
You may choose $\alpha = 1/2$ to dampen gravity waves but initialisation shocks still usually kick off many gravity waves that propagate around the sphere for many days.
With increasing $\alpha > 1/2$ these waves are also slowed down, such that for $\alpha = 1$ they quickly disappear in several hours.
Using the scaled shallow water equations the time step $\delta t$ has to be the scaled time step $\tilde{\Delta t} = \delta t/R$ which is divided by the radius $R$. Then we use the normalized eigenvalues $-l(l+1)$ which also omit the $1/R^2$ scaling, see scaled shallow water equations for more details.
Similar to the scaled barotropic vorticity equations, SpeedyWeather.jl scales in the shallow water equations. The vorticity and the divergence equation are scaled with $R^2$, the radius of the sphere squared, but the continuity equation is scaled with $R$. We also combine the vorticity flux and forcing into a single divergence/curl operation as mentioned in Shallow water equations above
As in the scaled barotropic vorticity equations, one needs to scale the time step, the Coriolis force, the forcing and the diffusion coefficient, but then enjoys the luxury of working with dimensionless gradient operators. As before, SpeedyWeather.jl will scale vorticity and divergence just before the model integration starts and unscale them upon completion and for output. In the semi-implicit time integration we solve an equation that also has to be scaled. It is with radius squared scaling (because it is the tendency for the divergence equation which is also scaled with $R^2$)
\[R^2 \delta D = R^2\frac{G_\mathcal{D} - \xi g\nabla^2 G_\eta}{1 - \xi^2 H \nabla^2}\]
The $R^2$ normalizes the Laplace operator in the numerator, but using the scaled $G_\eta$ we also scale $\xi$ (which is convenient, because the time step within is the one we use anyway). The denominator $S$ does not actually change because $\xi^2\nabla^2 = \tilde{\xi}^2\tilde{\nabla}^2$ as $\xi^2$ is scaled with $1/R^2$, but the Laplace operator with $R^2$. So overall we just have to use the scaled time step $\tilde{\Delta t}$ and normalized eigenvalues for $\tilde{\nabla}^2$.
The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space which can be any of the Implemented grids as defined by RingGrids. This includes the classical full Gaussian grid, a regular longitude-latitude grid called the full Clenshaw grid (FullClenshawGrid), ECMWF's octahedral Gaussian grid[Malardel2016], and HEALPix grids[Gorski2004]. SpeedyWeather.jl's spectral transform module SpeedyTransforms is grid-flexible and can be used with any of these, see Grids.
SpeedyTransforms is a module too!
SpeedyTransform is the underlying module that SpeedyWeather imports to transform between spectral and grid-point space, which also implements Derivatives in spherical coordinates. You can use this module independently of SpeedyWeather for spectral transforms, see SpeedyTransforms.
with $\lambda_l^m$ being the pre-normalized associated Legendre polynomials, and $e^{im\phi}$ are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.
Latitudes versus colatitudes
The implementation of the spectral transforms in SpeedyWeather.jl uses colatitudes $\theta = (0,\pi)$ (0 at the north pole) but the dynamical core uses latitudes $\theta = (-\pi/2,\pi/2)$ ($\pi/2$ at the north pole). Note: We may also use latitudes in the spherical harmonic transform in the future for consistency.
The synthesis (or inverse transform) takes the spectral coefficients $a_{lm}$ and transforms them to grid-point values $f(\phi,\theta)$ (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics $Y_{lm}$ with non-zero coefficients.
We obtain an approximation with a finite set of $a_{l,m}$ by truncating the series in both degree $l$ and order $m$ somehow. Most commonly, a triangular truncation is applied, such that all degrees after $l = l_{max}$ are discarded. Triangular because the retained array of the coefficients $a_{l,m}$ looks like a triangle. Other truncations like rhomboidal have been studied[Daley78] but are rarely used since. Choosing $l_{max}$ also constrains $m_{max}$ and determines the (horizontal) spectral resolution. In SpeedyWeather.jl this resolution as chosen as trunc when creating the SpectralGrid.
For $f$ being a real-valued there is a symmetry
\[a_{l,-m} = (-1)^m a^*_{l,+m},\]
meaning that the coefficients at $-m$ and $m$ are the same, but the sign of the real and imaginary component can be flipped, as denoted with the $(-1)^m$ and the complex conjugate $a_{l,m}^*$. As we are only dealing with real-valued fields anyway, we therefore never have to store the negative orders $-m$ and end up with a lower triangular matrix of size $(l_{max}+1) \times (m_{max}+1)$ or technically $(T+1)^2$ where $T$ is the truncation trunc. One is added here because the degree $l$ and order $m$ use 0-based indexing but sizes (and so is Julia's indexing) are 1-based.
For correctness we want to mention here that vector quantities require one more degree $l$ due to the recurrence relation in the Meridional derivative. Hence for practical reasons all spectral fields are represented as a lower triangular matrix of size $(m_{max} + 2) \times (m_{max} +1)$. And the scalar quantities would just not make use of that last degree, and its entries would be simply zero. We will, however, for the following sections ignore this and only discuss it again in Meridional derivative.
Another consequence of the symmetry mentioned above is that the zonal harmonics, meaning $a_{l,m=0}$ have no imaginary component. Because these harmonics are zonally constant, a non-zero imaginary component would rotate them around the Earth's axis, which, well, doesn't actually change a real-valued field.
Following the notation of [Willmert2020] we can therefore write the truncated synthesis as
The $(2-\delta_{m0})$ factor using the Kronecker $\delta$ is used here because of the symmetry we have to count both the $m,-m$ order pairs (hence the $2$) except for the zonal harmonics which do not have a pair.
Another symmetry arises from the fact that the spherical harmonics are either symmetric or anti-symmetric around the Equator. There is an even/odd combination of degrees and orders so that the sign flips like a checkerboard
Note that this notation again uses colatitudes $\theta$, for latitudes the $\sin\theta$ becomes a $\cos\theta$ and the bounds have to be changed accordingly to $(-\frac{\pi}{2},\frac{\pi}{2})$. A discretization with $N$ grid points at location $(\phi_i,\theta_i)$, indexed by $i$ can be written as [Willmert2020]
The hat on $a$ just means that it is an approximation, or an estimate of the true $a_{lm} \approx \hat{a}_{lm}$. We can essentially make use of the same symmetries as already discussed in Synthesis. Splitting into the Fourier modes $e^{im\phi}$ and the Legendre polynomials $\lambda_l^m(\cos\theta)$ (which are defined over $[-1,1]$ so the $\cos\theta$ argument maps them to colatitudes) we have
So the term in brackets can be separated out as long as the latitude $\theta_j$ is constant, which motivates us to restrict the spectral transform to grids with iso-latitude rings, see Grids. Furthermore, this term can be written as a fast Fourier transform, if the $\phi_i$ are equally spaced on the latitude ring $j$. Note that the in-ring index $i$ can depend on the ring index $j$, so that one can have reduced grids, which have fewer grid points towards the poles, for example. Also the Legendre polynomials only have to be computed for the colatitudes $\theta_j$ (and in fact only one hemisphere, due to the north-south symmetry discussed in the Synthesis). It is therefore practical and efficient to design a spectral transform implementation for ring grids, but there is no need to hardcode a specific grid.
Spectral packing is the way how the coefficients $a_{lm}$ of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree $l$ and order $m$ as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)
Every row represents an order $l \geq 0$, starting from $l=0$ at the top. Every column represents an order $m \geq 0$, starting from $m=0$ on the left. The coefficients of these spherical harmonics are directly mapped into a matrix $a_{lm}$ as
$m$
$l$
$a_{00}$
$a_{10}$
$a_{11}$
$a_{20}$
$a_{12}$
$a_{22}$
$a_{30}$
$a_{13}$
$a_{23}$
$a_{33}$
which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal are zero. Note that internally vector fields include an additional degree, such that $l_{max} = m_{max} + 1$ (see Derivatives in spherical coordinates for more information). The harmonics with $a_{l0}$ (the first column) are also called zonal harmonics as they are constant with longitude $\phi$. The harmonics with $a_{ll}$ (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into $2l$ sectors in longitude $\phi$ without a zero-crossing in latitude.
For correctness it is mentioned here that SpeedyWeather.jl uses a LowerTriangularMatrix type to store the spherical harmonic coefficients. By doing so, the upper triangle is actually not explicitly stored and the data technically unravelled into a vector, but this is hidden as much as possible from the user. For more details see LowerTriangularMatrices.
Array indices
For a spectral field a note that due to Julia's 1-based indexing the coefficient $a_{lm}$ is obtained via a[l+1,m+1]. Alternatively, we may index over 1-based l,m but a comment is usually added for clarification.
Fortran SPEEDY does not use the same spectral packing as SpeedyWeather.jl. The alternative packing $l',m'$ therein uses $l'=m$ and $m'=l-m$ as summarized in the following table.
degree $l$
order $m$
$l'=m$
$m'=l-m$
0
0
0
0
1
0
0
1
1
1
1
0
2
0
0
2
2
1
1
1
2
2
2
0
3
0
0
3
...
...
...
...
This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices
$m'$
$l'$
$a_{00}$
$a_{10}$
$a_{20}$
$a_{30}$
$a_{11}$
$a_{21}$
$a_{31}$
$a_{22}$
$a_{32}$
$a_{33}$
This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran SPEEDY.
SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with $l \leq l_{max}$ and $|m| \leq m_{max}$ are explicitly represented. This is usually described as $Tm_{max}$, with $l_{max} = m_{max}$ (although in vector quantities require one more degree $l$ in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with $l_{max} = m_{max} = 31$. Note that the degree $l$ and order $m$ are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.
Technically, SpeedyWeather.jl supports arbitrarily chosen resolution parameter trunc when creating the SpectralGrid that refers to the highest non-zero degree $l_{max}$ that is resolved in spectral space. SpeedyWeather.jl will always try to choose an easily-Fourier transformable[FFT] size of the grid, but as we use FFTW.jl there is quite some flexibility without performance sacrifice. However, this has traditionally lead to typical resolutions that we also use for testing we therefore recommend to use. They are as follows with more details below
trunc
nlon
nlat
$\Delta x$
31 (default)
96
48
400 km
42
128
64
312 km
63
192
96
216 km
85
256
128
165 km
127
384
192
112 km
170
512
256
85 km
255
768
384
58 km
341
1024
512
43 km
511
1536
768
29 km
682
2048
1024
22 km
1024
3072
1536
14 km
1365
4092
2048
11 km
Some remarks on this table
This assumes the default quadratic truncation, you can always adapt the grid resolution via the dealiasing option, see Matching spectral and grid resolution
nlat refers to the total number of latitude rings, see Grids. With non-Gaussian grids, nlat will be one one less, e.g. 47 instead of 48 rings.
nlon is the number of longitude points on the Full Gaussian Grid, for other grids there will be at most these number of points around the Equator.
$\Delta x$ is the horizontal resolution. For a spectral model there are many ways of estimating this[9]. We use here the square root of the average area a grid cell covers, see Effective grid resolution
There are many ways to estimate the effective grid resolution of spectral models[9]. Some of them are based on the wavelength a given spectral resolution allows to represent, others on the total number of real variables per area. However, as many atmospheric models do represent a considerable amount of physics on the grid (see Parameterizations) there is also a good argument to include the actual grid resolution into this estimate and not just the spectral resolution. We therefore use the average grid cell area to estimate the resolution
\[\Delta x = \sqrt{\frac{4\pi R^2}{N}}\]
with $N$ number of grid points over a sphere with radius $R$. However, we have to acknowledge that this usually gives higher resolution compared to other methods of estimating the effective resolution, see [Randall2021] for a discussion. You may therefore need to be careful to make claims that, e.g. trunc=85 can resolve the atmospheric dynamics at a scale of 165km.
The radius of the sphere (i.e. Earth) is $R$. The zonal gradient scales with $1/\cos(\theta)$ as the longitudes converge towards the poles (note that $\theta$ describes latitudes here, definitions using colatitudes replace the $\cos$ with a $\sin$.)
Starting with a spectral field of vorticity $\zeta$ and divergence $\mathcal{D}$ one can obtain stream function $\Psi$ and velocity potential $\Phi$ by inverting the Laplace operator $\nabla^2$:
The velocities $u,v$ are then obtained from $(u,v) = \nabla^\bot\Psi + \nabla\Phi$ following the definition from above and $\nabla^\bot = (-R^{-1}\partial_\theta, (R\cos\theta)^{-1}\partial_\lambda)$
Alternatively, we can use the velocities $U = u\cos\theta, V = v\cos\theta$, which we do as the meridional gradient for spherical harmonics is easier implemented with a $\cos\theta$-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with $U,V$ and not $u,v$. From $u,v$ we can return to $\zeta, \mathcal{D}$ via
\[\begin{aligned}
+\zeta &= \frac{1}{R\cos\theta}\partial_\lambda v - \frac{1}{R\cos\theta}\partial_\theta (u \cos\theta) \\
+\mathcal{D} &= \frac{1}{R\cos\theta}\partial_\lambda u + \frac{1}{R\cos\theta}\partial_\theta (v \cos\theta).
+\end{aligned}\]
which is a more convenient formulation because of the way how the Meridional derivative is implemented with a recursion relation, actually computing $\cos\theta \partial_\theta$ rather than $\partial_\theta$ directly. The remaining cosine scalings in $(U,V)*\cos^{-2}\theta$ are done in grid-point space. If one wanted to get back to $\zeta, \mathcal{D}$ this is how it would be done, but it is often more convenient to unscale $U,V$ on the fly in the spectral transform to obtain $u,v$ and then divide again by $\cos\theta$ when any gradient (or divergence or curl) is taken. This is because other terms would need that single $\cos\theta$ unscaling too before a gradient is taken. How the operators $\nabla, \nabla \times, \nabla \cdot$ can be implemented with spherical harmonics is presented in the following sections.
Also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out in this last formulation too.
The zonal derivative of a scalar field $\Psi$ in spectral space is the zonal derivative of all its respective spherical harmonics $\Psi_{lm}(\phi,\theta)$ (now we use $\phi$ for longitudes to avoid confusion with the Legendre polynomials $\lambda_{lm}$)
So for every spectral harmonic, $\cos(\theta)v_{lm}$ is obtained from $\Psi_{lm}$ via a multiplication with $im/R$. Unscaling the $\cos(\theta)$-factor is done after transforming the spectral coefficients $v_{lm}$ into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as $\tilde{\Psi} = R^{-1}\Psi$ such that the division by radius $R$ in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order $im$. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number $m$ times imaginary $i$.
The meridional derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]
In the following we use the example of obtaining the zonal velocity $u$ from the stream function $\Psi$, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion
To obtain the coefficient of each spherical harmonic $l,m$ of the meridional gradient of a spectral field, two coefficients at $l-1,m$ and $l+1,m$ have to be combined. This means that the coefficient of a gradient $((\cos\theta) u)_{lm}$ is a linear combination of the coefficients of one higher and one lower degree $\Psi_{l+1,m},\Psi_{l-1,m}$. As the coefficient $\Psi_{lm}$ with $m<l$ are zero, the sectoral harmonics ($l=m$) of the gradients are obtained from the first off-diagonal only. However, the $l=l_{max}$ harmonics of the gradients require the $l_{max}-1$ as well as the $l_{max}+1$ harmonics. As a consequence vector quantities like velocity components $u,v$ require one more degree $l$ than scalar quantities like vorticity[Bourke72]. However, for easier compatibility all spectral fields in SpeedyWeather.jl use one more degree $l$, but scalar quantities should not make use of it. Equivalently, the last degree $l$ is set to zero before the time integration, which only advances scalar quantities.
In SpeedyWeather.jl vector quantities like $u,v$ use therefore one more meridional mode than scalar quantities such as vorticity $\zeta$ or stream function $\Psi$. The meridional derivative in SpeedyWeather.jl also omits the $1/R$-scaling as explained for the Zonal derivative and in Radius scaling.
The meridional gradient as described above can be applied to scalars, such as $\Psi$ and $\Phi$ in the conversion to velocities $(u,v) = \nabla^\bot\Psi + \nabla\Phi$, however, the operators curl $\nabla \times$ and divergence $\nabla \cdot$ in spherical coordinates involve a $\cos\theta$ scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].
Given that $R\zeta = \cos^{-1}\partial_\lambda v - \cos^{-1}\partial_\theta (u \cos\theta)$, we therefore have to evaluate a meridional integral of the form
Now we expand $(\tfrac{u}{\cos\theta})$ but only the $l,m$ harmonic will project onto$P_{l,m}$. Let $u^* = u\cos^{-1}\theta, v^* = v\cos^{-1}\theta$ we then have in total
And the divergence $D$ is similar, but $(u,v) \to (-v,u)$. We have moved the scaling with the radius $R$ directly into $\zeta,D$ as further described in Radius scaling.
The spectral Laplacian is easily applied to the coefficients $\Psi_{lm}$ of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator $\nabla^2$ in spherical coordinates with eigenvalues $-l(l+1)$ divided by the radius squared $R^2$, i.e. $\nabla^2 \Psi$ becomes $\tfrac{-l(l+1)}{R^2}\Psi_{lm}$ in spectral space. For example, vorticity $\zeta$ and streamfunction $\Psi$ are related by $\zeta = \nabla^2\Psi$ in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree $l$ and order $m$ to
\[\zeta_{l,m} = \frac{-l(l+1)}{R^2}\Psi_{l,m}\]
This can be easily inverted to obtain the stream function $\Psi$ from vorticity $\zeta$ instead. In order to avoid division by zero, we set $\Psi_{0,0}$ here, given that the stream function is only defined up to a constant anyway.
After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity $\zeta$ and divergence $D$ (which are prognostic variables) to $U=u\cos\theta, V=v\cos\theta$. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree $l$ (the meridional gradient). It is therefore computationally more efficient to compute $U,V$ directly from $\zeta,D$ instead of calculating stream function and velocity potential first. In total we have
Malardel2016Malardel S, Wedi N, Deconinck W, Diamantakis M, Kühnlein C, Mozdzynski G, Hamrud M, Smolarkiewicz P. A new grid for the IFS. ECMWF newsletter. 2016;146(23-28):321. doi: 10.21957/zwdu9u5i
Gorski2004Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976
FFTDepending on the implementation of the Fast Fourier Transform (Cooley-Tukey algorithm, or or the Bluestein algorithm) easily Fourier-transformable can mean different things: Vectors of the length $n$ that is a power of two, i.e. $n = 2^i$ is certainly easily Fourier-transformable, but for most FFT implementations so are $n = 2^i3^j5^k$ with $i,j,k$ some positive integers. In fact, FFTW uses $O(n \log n)$ algorithms even for prime sizes.
SpeedyTransforms is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it) and can also be used without running simulations. It is just not put into its own respective repository.
S = SpectralTransform( alms::AbstractMatrix{Complex{NF}};
+ recompute_legendre::Bool=true,
+ Grid::Type{<:AbstractGrid}=DEFAULT_GRID)
Generator function for a SpectralTransform struct based on the size of the spectral coefficients alms and the grid Grid. Recomputes the Legendre polynomials by default.
Generator function for a SpectralTransform struct. With NF the number format, Grid the grid type <:AbstractGrid and spectral truncation lmax,mmax this function sets up necessary constants for the spetral transform. Also plans the Fourier transforms, retrieves the colatitudes, and preallocates the Legendre polynomials (if recompute_legendre == false) and quadrature weights.
S = SpectralTransform( map::AbstractGrid;
+ recompute_legendre::Bool=true)
Generator function for a SpectralTransform struct based on the size and grid type of gridded field map. Recomputes the Legendre polynomials by default.
Get U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.
Get U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.
Generic divergence function of vector u,v that writes into the output into div. Generic as it uses the kernel kernel such that curl, div, add or flipsign options are provided through kernel, but otherwise a single function is used.
Curl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.
Divergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.
get_recursion_factors( ::Type{NF}, # number format NF
+ lmax::Int, # max degree l of spherical harmonics (0-based here)
+ mmax::Int # max order m of spherical harmonics
+ ) where {NF<:AbstractFloat}
Returns a matrix of recursion factors ϵ up to degree lmax and order mmax of number format NF.
Spectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.
m = roundup_fft(n::Int;
+ small_primes::Vector{Int}=[2,3,5])
Returns an integer m >= n with only small prime factors 2, 3 (default, others can be specified with the keyword argument small_primes) to obtain an efficiently fourier-transformable number of longitudes, m = 2^i * 3^j * 5^k >= n, with i,j,k >=0.
Spectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Returns a spectral coefficient matrix alms_interp that is alms padded with zeros to interpolate in spectral space. If trunc is smaller or equal to the implicit truncation in alms obtained from its size than spectral_truncation is automatically called instead, returning alms_trunc, a coefficient matrix that is smaller than alms, implicitly setting higher degrees and orders to zero.
Smooth the spectral field A following A = (1-(1-c)∇²ⁿ) with power n of a normalised Laplacian so that the highest degree lmax is dampened by multiplication with c. Anti-diffusion for c>1.
Smooth the spectral field A following A_smooth = (1-c*∇²ⁿ)A with power n of a normalised Laplacian so that the highest degree lmax is dampened by multiplication with c. Anti-diffusion for c<0.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.
Truncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.
Truncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.
Returns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.
Recursion factors ϵ as a function of degree l and order m (0-based) of the spherical harmonics. ϵ(l,m) = sqrt((l^2-m^2)/(4*l^2-1)) with default number format Float64.
Recursion factors ϵ as a function of degree l and order m (0-based) of the spherical harmonics. ϵ(l,m) = sqrt((l^2-m^2)/(4*l^2-1)) and then converted to number format NF.
Laplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.
Keyword arguments
add=true adds the ∇²(alms) to the output
flipsign=true computes -∇²(alms) instead
inverse=true computes ∇⁻²(alms) instead
Default is add=false, flipsign=false, inverse=false. These options can be combined.
The prognostic variables in spectral space are called
vor # Vorticity of horizontal wind field
+ div # Divergence of horizontal wind field
+ temp # Absolute temperature [K]
+ pres_surf # Logarithm of surface pressure [log(Pa)]
+ humid # Specific humidity [g/kg]
their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are
We follow Julia's style guide and highlight here some important aspects of it.
Bang (!) convention. A function func does not change its input arguments, however, func! does.
Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.
Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup
but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.
Settings
This document was generated with Documenter.jl version 0.27.24 on Wednesday 24 May 2023. Using Julia version 1.8.5.
A mathematical and implementation-specific description of the dynamical core used in SpeedyWeather.jl. We start by describing the barotropic vorticity equations which is one set of equations that SpeedyWeather.jl can solve (see How to run SpeedyWeather.jl) as many details therein also apply to the Shallow water equations and Primitive equations explained thereafter.
The dynamical core presented here largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2], a shallow water model [3] and a primitive equation model[4].
The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity $\zeta$ with advection, Coriolis force and diffusion in a single global layer.
with time $t$, velocity vector $\mathbf{u} = (u, v)$, Coriolis parameter $f$, and hyperdiffusion $(-1)^{n+1} \nu \nabla^{2n} \zeta$ ($n$ is the hyperdiffusion order; see Horizontal diffusion). Starting with some relative vorticity $\zeta$, the Laplacian is inverted to obtain the stream function $\Psi$
\[\Psi = \nabla^{-2}\zeta\]
The zonal velocity $u$ and meridional velocity $v$ are then the (negative) meridional gradient and zonal gradient of $\Psi$
obtain meridional velocity $(\cos(\theta)v)_{lm}$ through a Zonal derivative
Transform zonal and meridional velocity $(\cos(\theta)u)_{lm}$, $(\cos(\theta)v)_{lm}$ to grid-point space and unscale the $\cos(\theta)$ factor to obtain $u,v$.
Multiply $u,v$ with $\zeta+f$ in grid-point space
Transform $u(\zeta + f)$ and $v(\zeta+f)$ to spectral space
Compute the divergence of $(\mathbf{u}(\zeta + f))_{lm}$ in spectral space through a Meridional derivative and Zonal derivative which will be the tendency of $\zeta_{lm}$
In SpeedyWeather.jl we use hyerdiffusion through an $n$-th power Laplacian $(-1)^{n+1}\nabla^{2n}$ (hyper when $n>1$) which can be implemented as a multiplication of the spectral coefficients $\Psi_{lm}$ with $(-l(l+1))^nR^{-2n}$ (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the $(-l(l+1))^nR^{-2n}$ can be precomputed. Note the sign change $(-1)^{n+1}$ here is such that the dissipative nature of the diffusion operator is retained for $n$ odd and even.
In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step $\Delta t$ of variable $\zeta$ to obtain from time steps $i-1$ and $i$, the next time step $i+1$
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t d\zeta,\]
with $d\zeta$ being some tendency evaluated from $\zeta_i$. Now we want to add a diffusion term $(-1)^{n+1}\nu \nabla^{2n}\zeta$ with viscosity $\nu$, wich however, is implicitly calculated from $\zeta_{i+1}$, then
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t (d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i+1})\]
As the application of $(-1)^{n+1}\nu\nabla^{2n}$ is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to
\[\zeta_{i+1} = \frac{\zeta_{i-1} + 2\Delta t d\zeta}{1 - 2\Delta (-1)^{n+1}\nu\nabla^{2n}},\]
and expand the numerator to
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t \frac{d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t (-1)^{n+1}\nu \nabla^{2n}},\]
Hence the diffusion can be applied implicitly by updating the tendency $d\zeta$ as
\[d\zeta \to \frac{d\zeta + (-1)^{n+1}\nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t \nu \nabla^{2n}}\]
which only depends on $\zeta_{i-1}$. Now let $D_\text{explicit} = (-1)^{n+1}\nu\nabla^{2n}$ be the explicit part and $D_\text{implicit} = 1 - (-1)^{n+1} 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are $D_\text{implicit} = 1 - 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic $l,m$ we do
Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power $n$.
In physics, the Laplace operator $\nabla^2$ is often used to represent diffusion due to viscosity in a fluid. In that case, the viscosity coefficient is $\nu$ of units $\text{m}^2\text{s}^{-1}$ and the full operator reads as $\nu \nabla^2$ with units $(\text{m}^2\text{s}^{-1})(\text{m}^{-2}) = \text{s}^{-1}$. This motivates us to normalize the Laplace operator by a constant of units $\text{m}^{-2}$ and the viscosity coefficient by its inverse such that the viscosity coefficient becomes a damping timescale of unit $\text{s}^{-1}$. Given the application in spectral space we decide to normalize by the largest eigenvalue $-l_\text{max}(l_\text{max}+1)$ such that all entries in the discrete spectral Laplace operator are in $[0,1]$. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized viscosity coefficient $\nu^* = l_\text{max}(l_\text{max}+1)\nu$ (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have
In the barotropic voriticity equation model the inversion of the Laplcians in order to obtain $\Psi$ from $\zeta$ therefore becomes
\[\tilde{\zeta} = \tilde{\nabla}^2 \tilde{\Psi}\]
where the dimensionless gradients simply omit the scaling with $1/R$, $\tilde{\nabla} = R\nabla$. The Barotropic vorticity equation scaled with $R^2$ is
Similar to the scaled barotropic vorticity equations, the scaled shallow water equations scale the vorticity and the divergence equation with $R^2$, but the continuity equation with $R$
This document was generated with Documenter.jl version 0.27.24 on Wednesday 24 May 2023. Using Julia version 1.8.5.
diff --git a/previews/PR300/functions/index.html b/previews/PR300/functions/index.html
new file mode 100644
index 000000000..a01dce484
--- /dev/null
+++ b/previews/PR300/functions/index.html
@@ -0,0 +1,103 @@
+
+Function and type index · SpeedyWeather.jl
P = Parameters{M<:ModelSetup}(kwargs...) <: AbstractParameters{M}
A struct to hold all model parameters that may be changed by the user. The struct uses keywords such that default values can be changed at creation. The default values of the keywords define the default model setup.
NF::DataType: number format
trunc::Int64: spectral truncation
Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid in use
mol_mass_dry_air::Any: molar mass of dry air [g/mol]
mol_mass_vapour::Any: molar mass of water vapour [g/mol]
cₚ::Float64: specific heat at constant pressure [J/K/kg]
R_gas::Float64: universal gas constant [J/K/mol]
R_dry::Float64: specific gas constant for dry air [J/kg/K]
R_vapour::Float64: specific gas constant for water vapour [J/kg/K]
alhc::Float64: latent heat of condensation [J/g] for consistency with specific humidity [g/Kg]
alhs::Float64: latent heat of sublimation [?]
sbc::Float64: stefan-Boltzmann constant [W/m²/K⁴]
lapse_rate::Float64: moist adiabatic temperature lapse rate $-dT/dz$ [K/km]
temp_ref::Float64: absolute temperature at surface $z=0$ [K]
temp_top::Float64: absolute temperature in stratosphere [K]
ΔT_stratosphere::Float64: for stratospheric lapse rate [K] after Jablonowski
scale_height::Float64: scale height for pressure [km]
pres_ref::Float64: surface pressure [hPa]
scale_height_humid::Float64: scale height for specific humidity [km]
relhumid_ref::Float64: relative humidity of near-surface air [1]
water_pres_ref::Float64: saturation water vapour pressure [Pa]
layer_thickness::Float64: layer thickness for the shallow water model [km]
GLcoefs::SpeedyWeather.Coefficients: vertical coordinates of the nlev vertical levels, defined by a generalised logistic function, interpolating ECMWF's L31 configuration
σ_tropopause::Float64: σ coordinate where the tropopause starts
σ_levels_half::Vector{Float64}: only used if set manually, otherwise empty
Spectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.
Spectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
gridded!( diagn::DiagnosticVariables{NF}, # all diagnostic variables
+ progn::PrognosticVariables{NF}, # all prognostic variables
+ M::BarotropicModel, # everything that's constant
+ lf::Int=1 # leapfrog index
+ ) where NF
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.
gridded!( diagn::DiagnosticVariables{NF}, # all diagnostic variables
+ progn::PrognosticVariables{NF}, # all prognostic variables
+ lf::Int=1 # leapfrog index
+ M::ShallowWaterModel, # everything that's constant
+ ) where NF
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities U,V (scaled by cos(lat)).
Returns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.
Truncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.
Truncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.
Laplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.
Keyword arguments
add=true adds the ∇²(alms) to the output
flipsign=true computes -∇²(alms) instead
inverse=true computes ∇⁻²(alms) instead
Default is add=false, flipsign=false, inverse=false. These options can be combined.
Divergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.
Curl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.
Get U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.
Get U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.
Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. NF is the number format used for the precomputed constants.
Vertical sigma coordinates defined by their nlev+1 half levels σ_levels_half. Sigma coordinates are fraction of surface pressure (p/p0) and are sorted from top (stratosphere) to bottom (surface). The first half level is at 0 the last at 1. Evaluate a generalised logistic function with coefficients in P for the distribution of values in between. Default coefficients follow the L31 configuration historically used at ECMWF.
Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF. See geometry.jl and function vertical_coordinate for more informaiton.
timestep!( progn::PrognosticVariables{NF}, # all prognostic variables
+ diagn::DiagnosticVariables{NF}, # all pre-allocated diagnostic variables
+ time::DateTime, # time at timestep
+ dt::Real, # time step (mostly =2Δt, but for init steps =Δt,Δt/2)
+ M::ShallowWaterModel, # everything that's constant at runtime
+ lf1::Int=2, # leapfrog index 1 (dis/enables Robert+William's filter)
+ lf2::Int=2 # leapfrog index 2 (time step used for tendencies)
+ ) where {NF<:AbstractFloat}
Calculate a single time step for the shallow water model of SpeedyWeather.jl
timestep!( progn::PrognosticVariables{NF}, # all prognostic variables
+ diagn::DiagnosticVariables{NF}, # all pre-allocated diagnostic variables
+ time::DateTime, # time at timestep
+ dt::Real, # time step (mostly =2Δt, but for init steps =Δt,Δt/2)
+ M::PrimitiveEquation, # everything that's constant at runtime
+ lf1::Int=2, # leapfrog index 1 (dis/enables Robert+William's filter)
+ lf2::Int=2 # leapfrog index 2 (time step used for tendencies)
+ ) where {NF<:AbstractFloat}
Calculate a single time step for the primitive equation model of SpeedyWeather.jl
first_timesteps!( progn::PrognosticVariables, # all prognostic variables
+ diagn::DiagnosticVariables, # all pre-allocated diagnostic variables
+ time::DateTime, # time at timestep
+ M::ModelSetup, # everything that is constant at runtime
+ feedback::AbstractFeedback # feedback struct
+ )
Performs the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.
leapfrog!( A_old::LowerTriangularMatrix{Complex{NF}}, # prognostic variable at t
+ A_new::LowerTriangularMatrix{Complex{NF}}, # prognostic variable at t+dt
+ tendency::LowerTriangularMatrix{Complex{NF}}, # tendency (dynamics+physics) of A
+ dt::Real, # time step (=2Δt, but for init steps =Δt,Δt/2)
+ lf::Int=2, # leapfrog index to dis/enable William's filter
+ C::DynamicsConstants{NF}, # struct with constants used at runtime
+ ) where {NF<:AbstractFloat} # number format NF
Performs one leapfrog time step with (lf=2) or without (lf=1) Robert+William's filter (see William (2009), Montly Weather Review, Eq. 7-9).
function shortwave_radiation!(
+ column::ColumnVariables{NF}, model::PrimitiveEquation
+)
Compute air temperature tendencies from shortwave radiation for an atmospheric column. For more details see http://users.ictp.it/~kucharsk/speedydescription/kmver41_appendixA.pdf
The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and how they can be used.
SpeedyWeather.jl's spectral transform currently only supports ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.
All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).
Currently the following full grids <: AbstractFullGrid are implemented
FullGaussianGrid, a full grid with Gaussian latitudes
FullClenshawGrid, a full grid with equi-angle latitudes
and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are
OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron
OctahedralClenshawGrid, similar but based on equi-angle latitudes
HEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces
OctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.
An overview of these grids is visualised here
Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.
All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half.
HEALPix grids do not use Nside as resolution parameter
The original formulation for HEALPix grids use $N_{side}$, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use $N_{side}$ for the documentation or within functions though.
A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation T with a grid resolution N (=nlat_half) such that T + 1 = N. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at $l_{max}=31$ in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid.
Let J be the total number of rings. Then we have
$T \approx J$ for linear truncation
$\frac{3}{2}T \approx J$ for quadratic truncation
$2T \approx J$ for cubic truncation
and in general $\frac{m+1}{2}T \approx J$ for m-th order truncation. So the higher the truncaction order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. In SpeedyWeather.jl the parameter dealiasing controls this option, = 1 would be linear, = 2 quadratic, = 3 cubic etc.
Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are $N_\varphi$ basepixels in zonal direction and $N_\theta$ basepixels in meridional direction. For $N_\varphi = 4$ and $N_\theta = 3$ we obtain the classical HEALPix grid with $N_\varphi N_\theta = 12$ basepixels shown above in Implemented Grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always $2N$, so 32 at $N=16$) and there are polar caps above and below the equatorial zone with the border at $\cos(\theta) = 2/3$ ($\theta$ in colatitudes).
Following Górski, 2004[1], the $z=cos(\theta)$ colatitude of the $j$-th ring in the north polar cap, $j=1,...,N_{side}$ with $2N_{side} = N$ is
\[z = 1 - \frac{j^2}{3N_{side}^2}\]
and on that ring, the longitude $\phi$ of the $i$-th point ($i$ is the in-ring-index) is at
\[\phi = \frac{\pi}{2j}(i-\tfrac{1}{2})\]
The in-ring index $i$ goes from $i=1,...,4$ for the first (i.e. northern-most) ring, $i=1,...,8$ for the second ring and $i = 1,...,4j$ for the $j$-th ring in the northern polar cap.
In the north equatorial belt $j=N_{side},...,2N_{side}$ this changes to
\[z = \frac{4}{3} - \frac{2j}{3N_{side}}\]
and the longitudes change to ($i$ is always $i = 1,...,4N_{side}$ in the equatorial belt meaning the number of longitude points is constant here)
The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.
The cell boundaries are obtained by setting $i = k + 1/2$ or $i = k + 1/2 + j$ (half indices) into the equations above, such that $z(\phi,k)$, a function for the cosine of colatitude $z$ of index $k$ and the longitude $\phi$ is obtained. These are then (northern polar cap)
While the classic HEALPix grid is based on a dodecahedron, other choices for $N_\varphi$ and $N_\theta$ in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With $N_\varphi = 4$ and $N_\theta = 1$ we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, $2\pi$ around the Equator versus $\pi$ between the poles. $N_\varphi = 6, N_\theta = 2$ or $N_\varphi = 8, N_\theta = 3$ are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visulations of these grids.
We call the $N_\varphi = 4, N_\theta = 1$ HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As $N_\theta = 1$ there is no equatorial belt which simplifies the grid. The latitude of the $j$-th isolatitude ring on the OctaHEALPixGrid is defined by
\[z = 1 - \frac{j^2}{N^2},\]
with $j=1,...,N$, and similarly for the southern hemisphere by symmetry. On this grid $N_{side} = N$ where $N$= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index $i = 1,...,4j$ are
\[\phi = \frac{\pi}{2j}(i - \tfrac{1}{2})\]
and again, the southern hemisphere grid points are obtained by symmetry.
The $3N_{side}^2$ in the denominator of the HEALPix grid came simply $N^2$ for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).
[1] Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976
Settings
This document was generated with Documenter.jl version 0.27.24 on Wednesday 24 May 2023. Using Julia version 1.8.5.
diff --git a/previews/PR300/how_to_run_speedy/index.html b/previews/PR300/how_to_run_speedy/index.html
new file mode 100644
index 000000000..c69fc3677
--- /dev/null
+++ b/previews/PR300/how_to_run_speedy/index.html
@@ -0,0 +1,7 @@
+
+How to run SpeedyWeather.jl · SpeedyWeather.jl
The simplest way to run SpeedyWeather.jl with default parameters is
using SpeedyWeather
+run_speedy()
Hooray, you have just simulated the Earth's atmosphere. Parameters, their meanings and defaults are documented in Parameters. For example, if you want to run the primitive equation dry core (no humidity) simulation in double precision (Float64), at higher resolution (trunc, the triangular spectral truncation), slow down the rotation of the Earth (rotation in $s^{-1}$), and create some netCDF ouput, do
If provided, the number format has to be the first argument, the model (Barotropic, ShallowWater, PrimitiveDryCore, PrimitiveWetCore are available) the second, and all other arguments are keyword arguments.
progn_vars = run_speedy(NF,Model;kwargs...) or
+progn_vars = run_speedy(NF;kwargs...) or
+progn_vars = run_speedy(Model;kwargs...)
Runs SpeedyWeather.jl with number format NF and the model Model and any additional parameters in the keyword arguments kwargs.... Any unspecified parameters will use the default values as defined in Parameters.
progn_vars, diagn_vars, model_setup = initialize_speedy(NF,Model;kwargs...) or
+progn_vars, diagn_vars, model_setup = initialize_speedy(NF,kwargs...) or
+progn_vars, diagn_vars, model_setup = initialize_speedy(Model,kwargs...)
Initialize the model by returning
progn_vars, the initial conditions of the prognostic variables
diagn_vars, the preallocated the diagnotic variables (initialised to zero)
model_setup, the collected pre-calculated structs that don't change throughout integration.
The keyword arguments kwargs are the same as for run_speedy. The model_setup contains fields that hold the parameters, constants, geometry, spectral transform, boundaries and diffusion.
Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.
SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to perform some calculations in spectral space (time integration, gradients, linear terms) and some in grid-point space (advection, non-linear terms, parameterizations). The prognostic variables used are vorticity, divergence, absolute temperature, logarithm of surface pressure and specific humidity. The time stepping uses a leapfrog scheme with additional filters and a semi-implicit formulation for gravity waves. The default resolution is T31 (96x48 grid points on a regular Gaussian grid, about 400km at the Equator) and 8 vertical levels.
Simple parameterizations are used to represent the physical processes convection, large-scale condensation, clouds, short-wave radiation, long-waves radiation, surface fluxes of momentum and energy, and vertical diffusion.
The focus of SpeedyWeather.jl is to develop a global atmospheric model of intermediate complexity, that can run at various levels of precision (16, 32 and 64-bit) on different architectures (x86 and ARM, GPUs in the future). Additionally, the model is written in an entirely number format-flexible way, such that any custom number format can be used and Julia will compile to the format automatically. Similarly, many model components are written in an abstract way to support modularity and extandability.
SpeedyWeather.jl is a Julia implementation of SPEEDY, which is written in Fortran 77. Sam Hatfield translated SPEEDY to Fortran 90 and started the project to port it to Julia. However, we are making an effort to overhaul the implementation of the mathematical model behind speedy completely and it is unlikely that a single line of code survived.
SpeedyWeather.jl is registered in the Julia Registry. Open Julia's package manager from the REPL with ] and add the github repository to install SpeedyWeather.jl and all dependencies
(@v1.8) pkg> add SpeedyWeather
which will automatically install the latest release. However, you may want to install directly from the main branch with
Contributors received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022.
Settings
This document was generated with Documenter.jl version 0.27.24 on Wednesday 24 May 2023. Using Julia version 1.8.5.
diff --git a/previews/PR300/literated/basic_example/index.html b/previews/PR300/literated/basic_example/index.html
new file mode 100644
index 000000000..6f887f300
--- /dev/null
+++ b/previews/PR300/literated/basic_example/index.html
@@ -0,0 +1,20 @@
+
+Basic example · SpeedyWeather.jl
This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmopshere. Every section is followed by a brief description of implementation details.
This document was generated with Documenter.jl version 0.27.24 on Wednesday 24 May 2023. Using Julia version 1.8.5.
diff --git a/previews/PR300/search_index.js b/previews/PR300/search_index.js
new file mode 100644
index 000000000..533689acf
--- /dev/null
+++ b/previews/PR300/search_index.js
@@ -0,0 +1,3 @@
+var documenterSearchIndex = {"docs":
+[{"location":"development/#Development-notes","page":"Development notes","title":"Development notes","text":"","category":"section"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To run tests, from the path of your local clone of the repository do:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=. -e 'import Pkg; Pkg.test()'","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To install dependencies:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project -e 'import Pkg; Pkg.instantiate()`","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"then opening:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"you are able to using SpeedyWeather.","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To generate the docs:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=docs -e 'import Pkg; Pkg.develop(path=\".\"); Pkg.instantiate()'\njulia --project=docs docs/make.jl","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"If the docs are generated successfully, you view them by opening docs/build/index.html in your favorite browser.","category":"page"},{"location":"functions/#Function-and-type-index","page":"Function and type index","title":"Function and type index","text":"","category":"section"},{"location":"functions/#Parameters-and-constants","page":"Function and type index","title":"Parameters and constants","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Parameters\nSpeedyWeather.Constants","category":"page"},{"location":"functions/#SpeedyWeather.Parameters","page":"Function and type index","title":"SpeedyWeather.Parameters","text":"P = Parameters{M<:ModelSetup}(kwargs...) <: AbstractParameters{M}\n\nA struct to hold all model parameters that may be changed by the user. The struct uses keywords such that default values can be changed at creation. The default values of the keywords define the default model setup.\n\nNF::DataType: number format\ntrunc::Int64: spectral truncation\nGrid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid in use\ndealiasing::Float64: dealiasing factor, 1=linear, 2=quadratic, 3=cubic grid\nplanet::SpeedyWeather.Planet: planet\nmol_mass_dry_air::Any: molar mass of dry air [g/mol]\nmol_mass_vapour::Any: molar mass of water vapour [g/mol]\ncₚ::Float64: specific heat at constant pressure [J/K/kg]\nR_gas::Float64: universal gas constant [J/K/mol]\nR_dry::Float64: specific gas constant for dry air [J/kg/K]\nR_vapour::Float64: specific gas constant for water vapour [J/kg/K]\nalhc::Float64: latent heat of condensation [J/g] for consistency with specific humidity [g/Kg]\nalhs::Float64: latent heat of sublimation [?]\nsbc::Float64: stefan-Boltzmann constant [W/m²/K⁴]\nlapse_rate::Float64: moist adiabatic temperature lapse rate -dTdz [K/km]\ntemp_ref::Float64: absolute temperature at surface z=0 [K]\ntemp_top::Float64: absolute temperature in stratosphere [K]\nΔT_stratosphere::Float64: for stratospheric lapse rate [K] after Jablonowski\nscale_height::Float64: scale height for pressure [km]\npres_ref::Float64: surface pressure [hPa]\nscale_height_humid::Float64: scale height for specific humidity [km]\nrelhumid_ref::Float64: relative humidity of near-surface air [1]\nwater_pres_ref::Float64: saturation water vapour pressure [Pa]\nlayer_thickness::Float64: layer thickness for the shallow water model [km]\nGLcoefs::SpeedyWeather.Coefficients: vertical coordinates of the nlev vertical levels, defined by a generalised logistic function, interpolating ECMWF's L31 configuration\nσ_tropopause::Float64: σ coordinate where the tropopause starts\nσ_levels_half::Vector{Float64}: only used if set manually, otherwise empty\nnlev::Int64: number of vertical levels\ndiffusion::SpeedyWeather.DiffusionParameters: horizontal (hyper)-diffusion\nvertical_diffusion::SpeedyWeather.VerticalDiffusion: vertical diffusion\nstatic_energy_diffusion::SpeedyWeather.VerticalDiffusion: static energy diffusion\ninterface_relaxation::Bool: turn on interface relaxation for shallow water?\ninterface_relax_time::Float64: time scale [hrs] of interface relaxation\ninterface_relax_amplitude::Float64: Amplitude [m] of interface relaxation\nphysics::Bool: en/disables the physics parameterizations\nn_shortwave::Int64: Compute shortwave radiation every n steps\nsppt_on::Bool: Turn on SPPT?\nmagnus_coefs::SpeedyWeather.Coefficients: For computing saturation vapour pressure\nk_lsc::Int64: Index of atmospheric level at which large-scale condensation begins\nRH_thresh_pbl_lsc::Float64: Relative humidity threshold for boundary layer\nRH_thresh_range_lsc::Float64: Vertical range of relative humidity threshold\nRH_thresh_max_lsc::Float64: Maximum relative humidity threshold\nhumid_relax_time_lsc::Float64: Relaxation time for humidity (hours)\npres_thresh_cnv::Float64: Minimum (normalised) surface pressure for the occurrence of convection\nRH_thresh_pbl_cnv::Float64: Relative humidity threshold for convection in PBL\nRH_thresh_trop_cnv::Float64: Relative humidity threshold for convection in the troposphere\nhumid_relax_time_cnv::Float64: Relaxation time for PBL humidity (hours)\nmax_entrainment::Float64: Maximum entrainment as a fraction of cloud-base mass flux\nratio_secondary_mass_flux::Float64: Ratio between secondary and primary mass flux at cloud-base\nnband::Int64: Number of bands used to compute fband\nradiation_coefs::SpeedyWeather.Coefficients: radiation coefficients\nboundary_layer::SpeedyWeather.BoundaryLayer{Float64}: boundary layer drag\ntemperature_relaxation::SpeedyWeather.TemperatureRelaxation{Float64}: temperature relaxation\nstartdate::Dates.DateTime: time at which the integration starts\nn_days::Float64: number of days to integrate for\nΔt_at_T31::Float64: time step in minutes for T31, scale linearly to trunc\nrobert_filter::Float64: Robert (1966) time filter coefficeint to suppress comput. mode\nwilliams_filter::Float64: William's time filter (Amezcua 2011) coefficient for 3rd order acc\nimplicit_α::Float64: coefficient for semi-implicit computations to filter gravity waves\nrecalculate_implicit::Int64: recalculate implicit operators on temperature profile every n time steps\nrecompute_legendre::Bool: recomputation is slower but requires less memory\nlegendre_NF::DataType: which format to use to calculate the Legendre polynomials\nlegendre_shortcut::Symbol: :linear, :quadratic, :cubic, :lincub_coslat, :linquad_coslat²\nboundary_path::String: package location is default\norography::SpeedyWeather.AbstractOrography: orography\norography_scale::Float64: scale orography by a factor\norography_path::String: path of orography\norography_file::String: filename of orography\nseed::Int64: random seed for the global random number generator\ninitial_conditions::SpeedyWeather.InitialConditions: initial conditions\npressure_on_orography::Bool: calculate the initial surface pressure from orography\nverbose::Bool: print dialog for feedback\ndebug::Bool: print debug info, NaR detection\noutput::Bool: Store data in netCDF?\noutput_dt::Float64: output time step [hours]\noutput_path::String: path to output folder\nrun_id::Union{Int64, String}: name of the output folder, defaults to 4-digit number counting up from run-0001\noutput_filename::String: name of the output netcdf file\noutput_vars::Vector{Symbol}: variables to output: :u, :v, :vor, :div, :temp, :humid\ncompression_level::Int64: compression level; 1=low but fast, 9=high but slow\nkeepbits::NamedTuple: mantissa bits to keep for every variable\nversion::VersionNumber: SpeedyWeather.jl version number\noutput_NF::DataType: number format used for output\noutput_nlat_half::Int64: 0 = reuse nlat_half from dynamical core\noutput_Grid::Type{<:SpeedyWeather.RingGrids.AbstractFullGrid}: output grid\noutput_Interpolator::Type{<:SpeedyWeather.RingGrids.AbstractInterpolator}: output interpolator\noutput_matrix::Bool: if true sort gridpoints into a matrix\noutput_quadrant_rotation::NTuple{4, Int64}: rotation of output quadrant\noutput_matrix_quadrant::NTuple{4, Tuple{Int64, Int64}}: matrix of output quadrant\nmissing_value::Float64: missing value to be used in netcdf output\nwrite_restart::Bool: also write restart file if output==true?\nrestart_path::String: path for restart file\nrestart_id::Union{Int64, String}: run_id of restart file in run-????/restart.jld2\n\n\n\n\n\n","category":"type"},{"location":"functions/#Boundaries-and-boundary-conditions","page":"Function and type index","title":"Boundaries and boundary conditions","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Boundaries","category":"page"},{"location":"functions/#Spherical-harmonic-transform","page":"Function and type index","title":"Spherical harmonic transform","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.GeoSpectral\nSpeedyWeather.SpectralTransform\nSpeedyWeather.spectral\nSpeedyWeather.spectral!\nSpeedyWeather.gridded\nSpeedyWeather.gridded!\nSpeedyWeather.triangular_truncation\nSpeedyWeather.roundup_fft\nSpeedyWeather.spectral_truncation\nSpeedyWeather.spectral_truncation!\nSpeedyWeather.spectral_interpolation!\nSpeedyWeather.get_legendre_polynomials!\nSpeedyWeather.∇²!\nSpeedyWeather.∇²\nSpeedyWeather.∇⁻²!\nSpeedyWeather.∇⁻²\nSpeedyWeather.gradient_latitude!\nSpeedyWeather.gradient_latitude\nSpeedyWeather.gradient_longitude!\nSpeedyWeather.gradient_longitude\nSpeedyWeather.divergence!\nSpeedyWeather.curl!\nSpeedyWeather._divergence!\nSpeedyWeather.curl_div!\nSpeedyWeather.UV_from_vordiv!\nSpeedyWeather.UV_from_vor!\nSpeedyWeather.ϵlm\nSpeedyWeather.get_recursion_factors","category":"page"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.SpectralTransform","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"S = SpectralTransform{NF<:AbstractFloat}(...)\n\nSpectralTransform struct that contains all parameters and preallocated arrays for the spectral transform.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral","text":"alms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\nalms = spectral( map::AbstractGrid;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to Grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\nalms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nSpectral transform (grid to spectral) map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral!","text":"spectral!( alms::LowerTriangularMatrix,\n map::AbstractGrid,\n S::SpectralTransform)\n\nSpectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded","text":"map = gridded( alms::AbstractMatrix;\n recompute_legendre::Bool=true,\n grid::Type{<:AbstractGrid}=DEFAULT_GRID)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).\n\n\n\n\n\nmap = gridded( alms::AbstractMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!( map::AbstractGrid,\n alms::LowerTriangularMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\ngridded!( diagn::DiagnosticVariables{NF}, # all diagnostic variables\n progn::PrognosticVariables{NF}, # all prognostic variables\n M::BarotropicModel, # everything that's constant\n lf::Int=1 # leapfrog index\n ) where NF\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.\n\n\n\n\n\ngridded!( diagn::DiagnosticVariables{NF}, # all diagnostic variables\n progn::PrognosticVariables{NF}, # all prognostic variables\n lf::Int=1 # leapfrog index\n M::ShallowWaterModel, # everything that's constant\n ) where NF\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities U,V (scaled by cos(lat)).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral_truncation","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation","text":"alms_trunc = spectral_truncation(alms,trunc)\n\nReturns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral_truncation!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms::AbstractMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.\n\n\n\n\n\nspectral_truncation!(alms::LowerTriangularMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.\n\n\n\n\n\nspectral_truncation!(alms,trunc)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.\n\n\n\n\n\nspectral_truncation!(alms)\n\nTruncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.∇²!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.∇²!","text":"∇²!( ∇²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false,\n inverse::Bool=false)\n\nLaplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.\n\nKeyword arguments\n\nadd=true adds the ∇²(alms) to the output\nflipsign=true computes -∇²(alms) instead\ninverse=true computes ∇⁻²(alms) instead\n\nDefault is add=false, flipsign=false, inverse=false. These options can be combined.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.∇⁻²!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.∇⁻²!","text":"∇⁻²!( ∇⁻²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false)\n\nCalls ∇²!(∇⁻²alms, alms, S; add, flipsign, inverse=true).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.divergence!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.divergence!","text":"divergence!(div::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform{NF};\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nDivergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.curl!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.curl!","text":"curl!( curl::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform;\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nCurl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.UV_from_vordiv!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.UV_from_vordiv!","text":"UV_from_vordiv!(U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n div::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.UV_from_vor!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.UV_from_vor!","text":"UV_from_vor!( U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"function"},{"location":"functions/#Dynamics","page":"Function and type index","title":"Dynamics","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.bernoulli_potential!\nSpeedyWeather.volume_flux_divergence!\nSpeedyWeather.vorticity_fluxes!\nSpeedyWeather.vorticity_flux_curl!\nSpeedyWeather.vorticity_flux_divergence!","category":"page"},{"location":"functions/#SpeedyWeather.bernoulli_potential!","page":"Function and type index","title":"SpeedyWeather.bernoulli_potential!","text":"bernoulli_potential!( diagn::DiagnosticVariablesLayer, \n G::Geometry,\n S::SpectralTransform)\n\nComputes the Laplace operator ∇² of the Bernoulli potential B in spectral space.\n\ncomputes the kinetic energy KE = ½(u²+v²) on the grid\ntransforms KE to spectral space\nadds geopotential for the Bernoulli potential in spectral space\ntakes the Laplace operator.\n\nThis version is used for both ShallowWater and PrimitiveEquation, only the geopotential calculation in geopotential! differs.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.volume_flux_divergence!","page":"Function and type index","title":"SpeedyWeather.volume_flux_divergence!","text":"volume_flux_divergence!(diagn::DiagnosticVariablesLayer,\n surface::SurfaceVariables,\n model::ShallowWater)\n\nComputes the (negative) divergence of the volume fluxes uh,vh for the continuity equation, -∇⋅(uh,vh).\n\n\n\n\n\n","category":"function"},{"location":"functions/#Geometry","page":"Function and type index","title":"Geometry","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Geometry\nSpeedyWeather.vertical_coordinates\nSpeedyWeather.GenLogisticCoefs\nSpeedyWeather.generalised_logistic","category":"page"},{"location":"functions/#SpeedyWeather.Geometry","page":"Function and type index","title":"SpeedyWeather.Geometry","text":"Geometry{NF<:AbstractFloat} <: AbstractGeometry\n\nGeometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. NF is the number format used for the precomputed constants.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.vertical_coordinates","page":"Function and type index","title":"SpeedyWeather.vertical_coordinates","text":"σ_levels_half = vertical_coordinates(P::Parameters)\n\nVertical sigma coordinates defined by their nlev+1 half levels σ_levels_half. Sigma coordinates are fraction of surface pressure (p/p0) and are sorted from top (stratosphere) to bottom (surface). The first half level is at 0 the last at 1. Evaluate a generalised logistic function with coefficients in P for the distribution of values in between. Default coefficients follow the L31 configuration historically used at ECMWF.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.GenLogisticCoefs","page":"Function and type index","title":"SpeedyWeather.GenLogisticCoefs","text":"Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF. See geometry.jl and function vertical_coordinate for more informaiton.\n\nFollowing the notation of https://en.wikipedia.org/wiki/Generalisedlogisticfunction (Dec 15 2021).\n\nChange default parameters for more/fewer levels in the stratosphere vs troposphere vs boundary layer.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.generalised_logistic","page":"Function and type index","title":"SpeedyWeather.generalised_logistic","text":"Generalised logistic function based on the coefficients in coefs.\n\n\n\n\n\n","category":"function"},{"location":"functions/#Time-stepping","page":"Function and type index","title":"Time stepping","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.time_stepping!\nSpeedyWeather.timestep!\nSpeedyWeather.first_timesteps!\nSpeedyWeather.leapfrog!","category":"page"},{"location":"functions/#SpeedyWeather.time_stepping!","page":"Function and type index","title":"SpeedyWeather.time_stepping!","text":"time_stepping!( progn::PrognosticVariables, # all prognostic variables\n diagn::DiagnosticVariables, # all pre-allocated diagnostic variables\n model::ModelSetup) # all precalculated structs\n\nMain time loop that that initialises output and feedback, loops over all time steps and calls the output and feedback functions.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.timestep!","page":"Function and type index","title":"SpeedyWeather.timestep!","text":"timestep!( progn::PrognosticVariables, # all prognostic variables\n diagn::DiagnosticVariables, # all pre-allocated diagnostic variables\n time::DateTime, # time at timestep\n dt::Real, # time step (mostly =2Δt, but for init steps =Δt,Δt/2)\n lf1::Int=2, # leapfrog index 1 (dis/enables Robert+William's filter)\n lf2::Int=2, # leapfrog index 2 (time step used for tendencies)\n M::BarotropicModel, # everything that's constant at runtime\n )\n\nCalculate a single time step for the barotropic vorticity equation model of SpeedyWeather.jl \n\n\n\n\n\ntimestep!( progn::PrognosticVariables{NF}, # all prognostic variables\n diagn::DiagnosticVariables{NF}, # all pre-allocated diagnostic variables\n time::DateTime, # time at timestep\n dt::Real, # time step (mostly =2Δt, but for init steps =Δt,Δt/2)\n M::ShallowWaterModel, # everything that's constant at runtime\n lf1::Int=2, # leapfrog index 1 (dis/enables Robert+William's filter)\n lf2::Int=2 # leapfrog index 2 (time step used for tendencies)\n ) where {NF<:AbstractFloat}\n\nCalculate a single time step for the shallow water model of SpeedyWeather.jl \n\n\n\n\n\ntimestep!( progn::PrognosticVariables{NF}, # all prognostic variables\n diagn::DiagnosticVariables{NF}, # all pre-allocated diagnostic variables\n time::DateTime, # time at timestep\n dt::Real, # time step (mostly =2Δt, but for init steps =Δt,Δt/2)\n M::PrimitiveEquation, # everything that's constant at runtime\n lf1::Int=2, # leapfrog index 1 (dis/enables Robert+William's filter)\n lf2::Int=2 # leapfrog index 2 (time step used for tendencies)\n ) where {NF<:AbstractFloat}\n\nCalculate a single time step for the primitive equation model of SpeedyWeather.jl \n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.first_timesteps!","page":"Function and type index","title":"SpeedyWeather.first_timesteps!","text":"first_timesteps!( progn::PrognosticVariables, # all prognostic variables\n diagn::DiagnosticVariables, # all pre-allocated diagnostic variables\n time::DateTime, # time at timestep\n M::ModelSetup, # everything that is constant at runtime\n feedback::AbstractFeedback # feedback struct\n )\n\nPerforms the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.leapfrog!","page":"Function and type index","title":"SpeedyWeather.leapfrog!","text":"leapfrog!( A_old::LowerTriangularMatrix{Complex{NF}}, # prognostic variable at t\n A_new::LowerTriangularMatrix{Complex{NF}}, # prognostic variable at t+dt\n tendency::LowerTriangularMatrix{Complex{NF}}, # tendency (dynamics+physics) of A\n dt::Real, # time step (=2Δt, but for init steps =Δt,Δt/2)\n lf::Int=2, # leapfrog index to dis/enable William's filter\n C::DynamicsConstants{NF}, # struct with constants used at runtime\n ) where {NF<:AbstractFloat} # number format NF\n\nPerforms one leapfrog time step with (lf=2) or without (lf=1) Robert+William's filter (see William (2009), Montly Weather Review, Eq. 7-9).\n\n\n\n\n\n","category":"function"},{"location":"functions/#Longwave-radiation","page":"Function and type index","title":"Longwave radiation","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.radset!\nSpeedyWeather.radlw_down!\nSpeedyWeather.compute_bbe!\nSpeedyWeather.radlw_up!","category":"page"},{"location":"functions/#SpeedyWeather.radset!","page":"Function and type index","title":"SpeedyWeather.radset!","text":"function radset!(model::PrimitiveEquation) where {NF<:AbstractFloat}\n\nCompute energy fractions in four longwave bands as a function of temperature.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.radlw_down!","page":"Function and type index","title":"SpeedyWeather.radlw_down!","text":"function radlw_down!(\n column::ColumnVariables{NF}, model::PrimitiveEquation\n) where {NF<:AbstractFloat}\n\nCompute the downward flux of longwave radiation. Inputs variables are temp,wvi,tau2. Output column varables arefsfcd,dfabs,flux,st4a`.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.compute_bbe!","page":"Function and type index","title":"SpeedyWeather.compute_bbe!","text":"function compute_bbe!(\n column::ColumnVariables{NF}, model::PrimitiveEquation\n) where {NF<:AbstractFloat}\n\nComputes black-body (or grey-body) emissions.\n\nInput and output variables are ts and fsfcu, respectively.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.radlw_up!","page":"Function and type index","title":"SpeedyWeather.radlw_up!","text":"function radlw_up!(\n column::ColumnVariables{NF}, model::PrimitiveEquation\n) where {NF<:AbstractFloat}\n\nComputes the upward flux of longwave radiation.\n\nInput variables are nlev, temp, fsfcu, fsfcd, flux, ts, tau2, st4a, dfabs, stratc, σ_levels_thick, n_stratosphere_levels. Output column variables are fsfc and ftop.\n\n\n\n\n\n","category":"function"},{"location":"functions/#Shortwave-radiation","page":"Function and type index","title":"Shortwave radiation","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.shortwave_radiation!\nSpeedyWeather.solar!\nSpeedyWeather.sol_oz!\nSpeedyWeather.cloud!\nSpeedyWeather.radsw!","category":"page"},{"location":"functions/#SpeedyWeather.shortwave_radiation!","page":"Function and type index","title":"SpeedyWeather.shortwave_radiation!","text":"function shortwave_radiation!(\n column::ColumnVariables{NF}, model::PrimitiveEquation\n)\n\nCompute air temperature tendencies from shortwave radiation for an atmospheric column. For more details see http://users.ictp.it/~kucharsk/speedydescription/kmver41_appendixA.pdf\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.solar!","page":"Function and type index","title":"SpeedyWeather.solar!","text":"function solar!(column::ColumnVariables{NF})\n\nCompute average daily flux of solar radiation for an atmospheric column, from Hartmann (1994).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.sol_oz!","page":"Function and type index","title":"SpeedyWeather.sol_oz!","text":"function sol_oz!(\n column::ColumnVariables{NF}, model::PrimitiveEquation\n)\n\nCompute solar radiation parametres for an atmospheric column.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.cloud!","page":"Function and type index","title":"SpeedyWeather.cloud!","text":"function cloud!(\n column::ColumnVariables{NF}, model::PrimitiveEquation\n)\n\nCompute shortwave radiation cloud contibutions for an atmospheric column.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.radsw!","page":"Function and type index","title":"SpeedyWeather.radsw!","text":"function radsw!(\n column::ColumnVariables{NF}, model::PrimitiveEquation\n)\n\nCompute shortwave radiation fluxes for an atmospheric column.\n\n\n\n\n\n","category":"function"},{"location":"parametrizations/#Parameterizations","page":"Parameterizations","title":"Parameterizations","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmopshere. Every section is followed by a brief description of implementation details.","category":"page"},{"location":"parametrizations/#Convection","page":"Parameterizations","title":"Convection","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Large-scale-condensation","page":"Parameterizations","title":"Large-scale condensation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Clouds","page":"Parameterizations","title":"Clouds","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Short-wave-radiation","page":"Parameterizations","title":"Short-wave radiation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Long-wave-radiation","page":"Parameterizations","title":"Long-wave radiation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Surface-fluxes-of-momentum-and-energy","page":"Parameterizations","title":"Surface fluxes of momentum and energy","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Vertical-diffusion","page":"Parameterizations","title":"Vertical diffusion","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"how_to_run_speedy/#How-to-run-SpeedyWeather.jl","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The simplest way to run SpeedyWeather.jl with default parameters is","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"using SpeedyWeather\nrun_speedy()","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Hooray, you have just simulated the Earth's atmosphere. Parameters, their meanings and defaults are documented in Parameters. For example, if you want to run the primitive equation dry core (no humidity) simulation in double precision (Float64), at higher resolution (trunc, the triangular spectral truncation), slow down the rotation of the Earth (rotation in s^-1), and create some netCDF ouput, do","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"run_speedy(Float64,PrimitiveDryCore,trunc=42,planet=Earth(rotation=1e-5),output=true)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"If provided, the number format has to be the first argument, the model (Barotropic, ShallowWater, PrimitiveDryCore, PrimitiveWetCore are available) the second, and all other arguments are keyword arguments.","category":"page"},{"location":"how_to_run_speedy/#The-run_speedy-interface","page":"How to run SpeedyWeather.jl","title":"The run_speedy interface","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"run_speedy","category":"page"},{"location":"how_to_run_speedy/#SpeedyWeather.run_speedy","page":"How to run SpeedyWeather.jl","title":"SpeedyWeather.run_speedy","text":"progn_vars = run_speedy(NF,Model;kwargs...) or\nprogn_vars = run_speedy(NF;kwargs...) or\nprogn_vars = run_speedy(Model;kwargs...)\n\nRuns SpeedyWeather.jl with number format NF and the model Model and any additional parameters in the keyword arguments kwargs.... Any unspecified parameters will use the default values as defined in Parameters.\n\n\n\n\n\n","category":"function"},{"location":"how_to_run_speedy/#The-initialize_speedy-interface","page":"How to run SpeedyWeather.jl","title":"The initialize_speedy interface","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"initialize_speedy","category":"page"},{"location":"how_to_run_speedy/#SpeedyWeather.initialize_speedy","page":"How to run SpeedyWeather.jl","title":"SpeedyWeather.initialize_speedy","text":"progn_vars, diagn_vars, model_setup = initialize_speedy(NF,Model;kwargs...) or\nprogn_vars, diagn_vars, model_setup = initialize_speedy(NF,kwargs...) or\nprogn_vars, diagn_vars, model_setup = initialize_speedy(Model,kwargs...)\n\nInitialize the model by returning\n\nprogn_vars, the initial conditions of the prognostic variables\ndiagn_vars, the preallocated the diagnotic variables (initialised to zero)\nmodel_setup, the collected pre-calculated structs that don't change throughout integration.\n\nThe keyword arguments kwargs are the same as for run_speedy. The model_setup contains fields that hold the parameters, constants, geometry, spectral transform, boundaries and diffusion.\n\n\n\n\n\n","category":"function"},{"location":"grids/#Grids","page":"Grids","title":"Grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and how they can be used.","category":"page"},{"location":"grids/#Ring-based-equi-longitude-grids","page":"Grids","title":"Ring-based equi-longitude grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"SpeedyWeather.jl's spectral transform currently only supports ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.","category":"page"},{"location":"grids/#Implemented-grids","page":"Grids","title":"Implemented grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Currently the following full grids <: AbstractFullGrid are implemented","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"FullGaussianGrid, a full grid with Gaussian latitudes\nFullClenshawGrid, a full grid with equi-angle latitudes","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron\nOctahedralClenshawGrid, similar but based on equi-angle latitudes\nHEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces\nOctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"An overview of these grids is visualised here","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"(Image: Overview of implemented grids in SpeedyWeather.jl)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.","category":"page"},{"location":"grids/#Resolution","page":"Grids","title":"Resolution","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half. ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: HEALPix grids do not use Nside as resolution parameter\nThe original formulation for HEALPix grids use N_side, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use N_side for the documentation or within functions though.","category":"page"},{"location":"grids/#Truncation","page":"Grids","title":"Truncation","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation T with a grid resolution N (=nlat_half) such that T + 1 = N. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at l_max=31 in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Let J be the total number of rings. Then we have","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"T approx J for linear truncation\nfrac32T approx J for quadratic truncation\n2T approx J for cubic truncation","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and in general fracm+12T approx J for m-th order truncation. So the higher the truncaction order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. In SpeedyWeather.jl the parameter dealiasing controls this option, = 1 would be linear, = 2 quadratic, = 3 cubic etc.","category":"page"},{"location":"grids/#Full-Gaussian-grid","page":"Grids","title":"Full Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#Full-Clenshaw-Curtis-grid","page":"Grids","title":"Full Clenshaw-Curtis grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#Octahedral-Gaussian-grid","page":"Grids","title":"Octahedral Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#The-HEALPix-grid","page":"Grids","title":"The HEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are N_varphi basepixels in zonal direction and N_theta basepixels in meridional direction. For N_varphi = 4 and N_theta = 3 we obtain the classical HEALPix grid with N_varphi N_theta = 12 basepixels shown above in Implemented Grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always 2N, so 32 at N=16) and there are polar caps above and below the equatorial zone with the border at cos(theta) = 23 (theta in colatitudes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Following Górski, 2004[1], the z=cos(theta) colatitude of the j-th ring in the north polar cap, j=1N_side with 2N_side = N is ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^23N_side^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and on that ring, the longitude phi of the i-th point (i is the in-ring-index) is at","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i-tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The in-ring index i goes from i=14 for the first (i.e. northern-most) ring, i=18 for the second ring and i = 14j for the j-th ring in the northern polar cap.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"In the north equatorial belt j=N_side2N_side this changes to","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac43 - frac2j3N_side","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and the longitudes change to (i is always i = 14N_side in the equatorial belt meaning the number of longitude points is constant here)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2N_side(i - fracs2) quad s = (j - N_side + 1) mod 2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The cell boundaries are obtained by setting i = k + 12 or i = k + 12 + j (half indices) into the equations above, such that z(phik), a function for the cosine of colatitude z of index k and the longitude phi is obtained. These are then (northern polar cap)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^23N_side^2left(fracpi2phi_tright)^2 quad z = 1 - frack^23N_side^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with phi_t = phi mod tfracpi2 and in the equatorial belt","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac23-frac4k3N_side pm frac8phi3pi","category":"page"},{"location":"grids/#OctaHEALPix-grid","page":"Grids","title":"OctaHEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"While the classic HEALPix grid is based on a dodecahedron, other choices for N_varphi and N_theta in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With N_varphi = 4 and N_theta = 1 we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, 2pi around the Equator versus pi between the poles. N_varphi = 6 N_theta = 2 or N_varphi = 8 N_theta = 3 are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visulations of these grids.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"We call the N_varphi = 4 N_theta = 1 HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As N_theta = 1 there is no equatorial belt which simplifies the grid. The latitude of the j-th isolatitude ring on the OctaHEALPixGrid is defined by","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^2N^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with j=1N, and similarly for the southern hemisphere by symmetry. On this grid N_side = N where N= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index i = 14j are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i - tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and again, the southern hemisphere grid points are obtained by symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries-2","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Similar to the grid cell boundaries for the HEALPix grid, the OctaHEALPix grid's boundaries are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^2N^2left(fracpi2phi_tright)^2 quad z = 1 - frack^2N^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The 3N_side^2 in the denominator of the HEALPix grid came simply N^2 for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).","category":"page"},{"location":"grids/#References","page":"Grids","title":"References","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"[1] Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976","category":"page"},{"location":"boundary_conditions/#Boundary-conditions","page":"Boundary conditions","title":"Boundary conditions","text":"","category":"section"},{"location":"boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"This page describes the formulation of boundary conditions and their implementation.","category":"page"},{"location":"conventions/#Style-and-convention-guide","page":"Style and convention guide","title":"Style and convention guide","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"In SpeedyWeather.jl we've been following the several conventions that are documented here.","category":"page"},{"location":"conventions/#Variable-naming","page":"Style and convention guide","title":"Variable naming","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The prognostic variables in spectral space are called","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" vor # Vorticity of horizontal wind field\n div # Divergence of horizontal wind field\n temp # Absolute temperature [K]\n pres_surf # Logarithm of surface pressure [log(Pa)]\n humid # Specific humidity [g/kg]","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" u\n v\n geopot\n ...","category":"page"},{"location":"conventions/#Preallocation","page":"Style and convention guide","title":"Preallocation","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"All arrays representing variables are preallocated and grouped into two structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" Prog::PrognosticVariables\n Diag::DiagnosticVariables","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The Diag struct contains further structs which represent the grid-point transformations of the prognostic variables and their tendencies.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" gridvars::GridVariables\n tendencies::Tendencies\n ...","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Constant arrays are grouped into several structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Boundaries","category":"page"},{"location":"conventions/#Julian-conventions","page":"Style and convention guide","title":"Julian conventions","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"We follow Julia's style guide and highlight here some important aspects of it.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Bang (!) convention. A function func does not change its input arguments, however, func! does.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.","category":"page"},{"location":"literated/basic_example/","page":"Basic example","title":"Basic example","text":"EditURL = \"https://github.com/SpeedyWeather/SpeedyWeather.jl/blob/main/examples/basic_example.jl\"","category":"page"},{"location":"literated/basic_example/#Basic","page":"Basic example","title":"Basic","text":"","category":"section"},{"location":"literated/basic_example/","page":"Basic example","title":"Basic example","text":"This is the first SpeedyWeather example.","category":"page"},{"location":"literated/basic_example/","page":"Basic example","title":"Basic example","text":"using SpeedyWeather","category":"page"},{"location":"literated/basic_example/","page":"Basic example","title":"Basic example","text":"Now let's run","category":"page"},{"location":"literated/basic_example/","page":"Basic example","title":"Basic example","text":"run_speedy(ShallowWater, n_days=30, trunc=63, Grid=OctahedralGaussianGrid, output=true)","category":"page"},{"location":"literated/basic_example/","page":"Basic example","title":"Basic example","text":"","category":"page"},{"location":"literated/basic_example/","page":"Basic example","title":"Basic example","text":"This page was generated using Literate.jl.","category":"page"},{"location":"new_model_setups/#New-model-setups","page":"New model setups","title":"New model setups","text":"","category":"section"},{"location":"new_model_setups/","page":"New model setups","title":"New model setups","text":"more to come...","category":"page"},{"location":"dynamical_core/#Dynamical-core","page":"Dynamical core","title":"Dynamical core","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"A mathematical and implementation-specific description of the dynamical core used in SpeedyWeather.jl. We start by describing the barotropic vorticity equations which is one set of equations that SpeedyWeather.jl can solve (see How to run SpeedyWeather.jl) as many details therein also apply to the Shallow water equations and Primitive equations explained thereafter.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The dynamical core presented here largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2], a shallow water model [3] and a primitive equation model[4]. ","category":"page"},{"location":"dynamical_core/#Barotropic-vorticity-equation","page":"Dynamical core","title":"Barotropic vorticity equation","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity zeta with advection, Coriolis force and diffusion in a single global layer.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"fracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) = (-1)^n+1nunabla^2nzeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with time t, velocity vector mathbfu = (u v), Coriolis parameter f, and hyperdiffusion (-1)^n+1 nu nabla^2n zeta (n is the hyperdiffusion order; see Horizontal diffusion). Starting with some relative vorticity zeta, the Laplacian is inverted to obtain the stream function Psi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Psi = nabla^-2zeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The zonal velocity u and meridional velocity v are then the (negative) meridional gradient and zonal gradient of Psi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nu = -frac1R fracpartial Psipartial theta \nv = frac1Rcos(theta) fracpartial Psipartial phi \nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"which is described in Derivatives in spherical coordinates.","category":"page"},{"location":"dynamical_core/#Algorithm","page":"Dynamical core","title":"Algorithm","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"We briefly outline the algorithm that SpeedyWeather.jl uses in order to integrate the barotropic vorticity equation","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Start with initial conditions of zeta_lm in spectral space\nUse zeta_lm to\nInvert the Laplacian to obtain the stream function Psi_lm in spectral space\nTransform zeta_lm to zeta in grid-point space\nUse Psi_lm to\nobtain zonal velocity (cos(theta)u)_lm through a Meridional derivative\nobtain meridional velocity (cos(theta)v)_lm through a Zonal derivative\nTransform zonal and meridional velocity (cos(theta)u)_lm, (cos(theta)v)_lm to grid-point space and unscale the cos(theta) factor to obtain uv.\nMultiply uv with zeta+f in grid-point space\nTransform u(zeta + f) and v(zeta+f) to spectral space\nCompute the divergence of (mathbfu(zeta + f))_lm in spectral space through a Meridional derivative and Zonal derivative which will be the tendency of zeta_lm\nCompute the Horizontal diffusion based on that tendency\nCompute a leapfrog time step as described in Time integration\nRepeat from 1.","category":"page"},{"location":"dynamical_core/#Shallow-water-equations","page":"Dynamical core","title":"Shallow water equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nfracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) = (-1)^n+1nunabla^2nzeta \nfracpartial mathcalDpartial t - nabla times (mathbfu(zeta + f)) = -nabla^2(tfrac12(u^2 + v^2) + geta) + (-1)^n+1nunabla^2nmathcalD \nfracpartial etapartial t + nabla cdot (mathbfuh) = 0\nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"more to come","category":"page"},{"location":"dynamical_core/#Primitive-equations","page":"Dynamical core","title":"Primitive equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The primitive equations solved by SpeedyWeather.jl are","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\npartial_t u = \npartial_t v = \npartial_t T = \npartial_t Q = \nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"more to come","category":"page"},{"location":"dynamical_core/#Horizontal-diffusion","page":"Dynamical core","title":"Horizontal diffusion","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In SpeedyWeather.jl we use hyerdiffusion through an n-th power Laplacian (-1)^n+1nabla^2n (hyper when n1) which can be implemented as a multiplication of the spectral coefficients Psi_lm with (-l(l+1))^nR^-2n (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the (-l(l+1))^nR^-2n can be precomputed. Note the sign change (-1)^n+1 here is such that the dissipative nature of the diffusion operator is retained for n odd and even.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step Delta t of variable zeta to obtain from time steps i-1 and i, the next time step i+1","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t dzeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with dzeta being some tendency evaluated from zeta_i. Now we want to add a diffusion term (-1)^n+1nu nabla^2nzeta with viscosity nu, wich however, is implicitly calculated from zeta_i+1, then","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t (dzeta + (-1)^n+1 nunabla^2nzeta_i+1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"As the application of (-1)^n+1nunabla^2n is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = fraczeta_i-1 + 2Delta t dzeta1 - 2Delta (-1)^n+1nunabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and expand the numerator to","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t fracdzeta + (-1)^n+1 nunabla^2nzeta_i-11 - 2Delta t (-1)^n+1nu nabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Hence the diffusion can be applied implicitly by updating the tendency dzeta as","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"dzeta to fracdzeta + (-1)^n+1nunabla^2nzeta_i-11 - 2Delta t nu nabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"which only depends on zeta_i-1. Now let D_textexplicit = (-1)^n+1nunabla^2n be the explicit part and D_textimplicit = 1 - (-1)^n+1 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are D_textimplicit = 1 - 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic lm we do","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"dzeta to D_textimplicit^-1(dzeta + D_textexplicitzeta_i-1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power n.","category":"page"},{"location":"dynamical_core/#Normalization-of-diffusion","page":"Dynamical core","title":"Normalization of diffusion","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In physics, the Laplace operator nabla^2 is often used to represent diffusion due to viscosity in a fluid. In that case, the viscosity coefficient is nu of units textm^2texts^-1 and the full operator reads as nu nabla^2 with units (textm^2texts^-1)(textm^-2) = texts^-1. This motivates us to normalize the Laplace operator by a constant of units textm^-2 and the viscosity coefficient by its inverse such that the viscosity coefficient becomes a damping timescale of unit texts^-1. Given the application in spectral space we decide to normalize by the largest eigenvalue -l_textmax(l_textmax+1) such that all entries in the discrete spectral Laplace operator are in 01. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized viscosity coefficient nu^* = l_textmax(l_textmax+1)nu (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have ","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"D^textexplicit_lm = -nu^* fracl(l+1)l_textmax(l_textmax+1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and the hyper-Laplacian of power n follows as","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"D^textexplicitn_lm = -nu^* left(fracl(l+1)l_textmax(l_textmax+1)right)^n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and the implicit part is accordingly D^textimplicitn_lm = 1 - 2Delta t D^textexplicitn_lm.","category":"page"},{"location":"dynamical_core/#Radius-scaling","page":"Dynamical core","title":"Radius scaling","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"SpeedyWeather.jl uses a scaling for vorticity zeta and stream function Psi that is","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildezeta = zeta R tildePsi = Psi R^-1","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In the barotropic voriticity equation model the inversion of the Laplcians in order to obtain Psi from zeta therefore becomes","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildezeta = tildenabla^2 tildePsi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"where the dimensionless gradients simply omit the scaling with 1R, tildenabla = Rnabla. The Barotropic vorticity equation scaled with R^2 is","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"partial_tildettildezeta + tildenabla cdot (mathbfu(tildezeta + tildef)) = tildenutildenabla^2ntildezeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildet = tR^-1, the scaled time t\nmathbfu = (uv), the velocity vector (no scaling applied)\ntildef = fR, the scaled Coriolis parameter f\ntildenu = nu^* R, the scaled viscosity nu^*, which itself is normalized to a damping time scale, see Normalization of diffusion.","category":"page"},{"location":"dynamical_core/#Scaled-shallow-water-equations","page":"Dynamical core","title":"Scaled shallow water equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Similar to the scaled barotropic vorticity equations, the scaled shallow water equations scale the vorticity and the divergence equation with R^2, but the continuity equation with R","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nfracpartial tildezetapartial tildet + tildenabla cdot (mathbfu(tildezeta + tildef)) =\ntildenutildenabla^2ntildezeta \nfracpartial tildemathcalDpartial tildet - tildenabla times (mathbfu(tildezeta + tildef)) =\n-tildenabla^2left(tfrac12(u^2 + v^2) + geta right) + tildenutildenabla^2ntildemathcalD \nfracpartial etapartial tildet + tildenabla cdot (mathbfuh) = 0\nendaligned","category":"page"},{"location":"dynamical_core/#Time-integration","page":"Dynamical core","title":"Time integration","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"SpeedyWeather.jl uses a leapfrog time scheme with a Robert's and William's filter to dampen the computational mode and achieve 3rd order accuracy.","category":"page"},{"location":"dynamical_core/#Oscillation-equation","page":"Dynamical core","title":"Oscillation equation","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"fracdFdt = iomega F","category":"page"},{"location":"dynamical_core/#References","page":"Dynamical core","title":"References","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[1]: Geophysical Fluid Dynamics Laboratory, Idealized models with spectral dynamics","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[2]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[3]: Geophysical Fluid Dynamics Laboratory, The Shallow Water Equations.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[4]: Geophysical Fluid Dynamics Laboratory, The Spectral Dynamical Core","category":"page"},{"location":"spectral_transform/#Spherical-Harmonic-Transform","page":"Spherical harmonic transform","title":"Spherical Harmonic Transform","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space on a longitude-latitude regular Gaussian grid.","category":"page"},{"location":"spectral_transform/#Inspiration","page":"Spherical harmonic transform","title":"Inspiration","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform implemented by SpeedyWeather.jl follows largely Justin Willmert's CMB.jl package and makes use of AssociatedLegendrePolynomials.jl and FFTW.jl (for Float32/64) or GenericFFT.jl (for generic) for the Fourier transform. Justin described his work in a Blog series [1][2][3][4][5][6][7][8].","category":"page"},{"location":"spectral_transform/#Spherical-harmonics","page":"Spherical harmonic transform","title":"Spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spherical harmonics Y_lm of degree l and order m over the longitude phi = (02pi) and colatitudes theta = (-pi2pi2), are","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Y_lm(phi theta) = lambda_l^m(sintheta) e^imphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with lambda_l^m being the pre-normalized associated Legendre polynomials, and e^imphi are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Latitudes versus colatitudes\nThe implementations of the spherical transforms in SpeedyWeather.jl use colatitudes theta = (0pi) (0 at the north pole) but the dynamical core uses latitudes theta = (-pi2pi2) (pi2 at the north pole). However, all arrays are always sorted north to south such that [i,1] will access the northern-most grid points. Note: We may also use latitudes in the spherical harmonic transform in the future for consistency. ","category":"page"},{"location":"spectral_transform/#Synthesis-(spectral-to-grid)","page":"Spherical harmonic transform","title":"Synthesis (spectral to grid)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The synthesis (or inverse transform) takes the spectral coefficients a_lm and transforms them to grid-point values f(phitheta) (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics Y_lm with non-zero coefficients.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"f(phitheta) = sum_l=0^infty sum_m=-l^l a_lm Y_lm(phitheta)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We obtain an approximation with a finite set of a_lm by truncating the series after l = l_max.","category":"page"},{"location":"spectral_transform/#Analysis-(grid-to-spectral)","page":"Spherical harmonic transform","title":"Analysis (grid to spectral)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting in grid-point space we can transform a field f(lambdatheta) into the spectral space of the spherical harmonics by","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"a_lm = int_0^2pi int_-tfracpi2^tfracpi2 f(lambdatheta) Y_lm(lambdatheta) cos theta dtheta dlambda","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This integral has to be discretized to when grid-point values f(lambda_itheta_i) are used. For more details, see [7],[8].","category":"page"},{"location":"spectral_transform/#Spectral-packing","page":"Spherical harmonic transform","title":"Spectral packing","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Spectral packing is the way how the coefficients a_lm of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree l and order m as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Every row represents an order l geq 0, starting from l=0 at the top. Every column represents an order m geq 0, starting from m=0 on the left. The coefficients of these spherical harmonics are directly mapped into a matrix a_lm as ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 \n a_10 a_11 \n a_20 a_12 a_22 \n a_30 a_13 a_23 a_33","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal explicitly stores zeros. Note that internally vector fields include an additional degree, such that l_max = m_max + 1 (see Gradients in spherical coordinates for more information). The harmonics with a_l0 (the first column) are also called zonal harmonics as they are constant with longitude phi. The harmonics with a_ll (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into 2l sectors in longitude phi without a zero-crossing in latitude.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Array indices\nFor a spectral field alms note that due to Julia's 1-based indexing the coefficient a_lm is obtained via alms[l+1,m+1].","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Fortran speedy does not use the same spectral packing as SpeedyWeather.jl. The alternative packing lm therein uses l=m and m=l-m as summarized in the following table.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"degree l order m l=m m=l-m\n0 0 0 0\n1 0 0 1\n1 1 1 0\n2 0 0 2\n2 1 1 1\n2 2 2 0\n3 0 0 3\n... ... ... ...","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 a_10 a_20 a_30\n a_11 a_21 a_31 \n a_22 a_32 \n a_33 ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran-speedy.","category":"page"},{"location":"spectral_transform/#Example-transforms","page":"Spherical harmonic transform","title":"Example transforms","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"julia> using SpeedyWeather\njulia> alms = zeros(ComplexF64,3,3) # spectral coefficients\njulia> alms[2,2] = 1 # only l=1,m=1 harmonic\njulia> map = gridded(alms) # convert to grid space\n8×4 Matrix{Float64}:\n -0.324541 -0.600363 -0.600363 -0.324541\n -0.134429 -0.248678 -0.248678 -0.134429\n 0.134429 0.248678 0.248678 0.134429\n 0.324541 0.600363 0.600363 0.324541\n 0.324541 0.600363 0.600363 0.324541\n 0.134429 0.248678 0.248678 0.134429\n -0.134429 -0.248678 -0.248678 -0.134429\n -0.324541 -0.600363 -0.600363 -0.324541\n \njulia> spectral(map) # back to spectral space\n3×3 Matrix{ComplexF64}:\n 0.0+0.0im 0.0+0.0im 0.0+0.0im\n 0.0+0.0im 1.0+3.60727e-17im 0.0+0.0im\n 0.0+0.0im 0.0+0.0im 0.0+0.0im","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"and we have successfully reobtained the l=m=1 spherical harmonic.","category":"page"},{"location":"spectral_transform/#Available-horizontal-resolutions","page":"Spherical harmonic transform","title":"Available horizontal resolutions","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with l leq l_max and m leq m_max are explicitly represented. This is usually described as Tm_max, with l_max = m_max (although in vector quantities require one more degree l in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with l_max = m_max = 31. Note that the degree l and order m are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Using triangular truncation[9], there are constraints on the corresponding grid resolution. Let nlon, nlat be the number of longitudes, latitudes on a regular Gaussian grid. Then spectral and grid resolution have to be chosen such that","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nlon geq 3l_max+1\nnlat geq (3l_max+1)2","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In general, we choose nlon = 2nlat, and ideally nlon is easily Fourier-transformable, e.g. nlon = 2^i3^j5^k with some integers ijk geq 0. SpeedyWeather.jl is tested at the following horizontal resolutions, with Delta x = tfrac2pi Rnlon as the approximate grid spacing at the Equator","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"l_max nlon nlat Delta x\n31 (default) 96 48 400 km\n42 128 64 300 km\n85 256 128 160 km\n170 512 256 80 km\n341 1024 512 40 km\n682 2048 1024 20 km","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Choosing trunc as argument in run_speedy will automatically choose nlon,nlat as presented in the table. Other common choices are T63 (192x96), T127 (384x192), T255 (768x384), T511 (1536x768), among others.","category":"page"},{"location":"spectral_transform/#Derivatives-in-spherical-coordinates","page":"Spherical harmonic transform","title":"Derivatives in spherical coordinates","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Horizontal gradients in spherical coordinates are defined for a scalar field A and the latitudes theta and longitudes lambda as","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla A = left(frac1Rcosthetafracpartial Apartial lambda frac1Rfracpartial Apartial theta right)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"However, the divergence of a vector field mathbfu = (uv) includes additional cos(theta) scalings","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla cdot mathbfu = frac1Rcosthetafracpartial upartial lambda +\nfrac1Rcosthetafracpartial (v costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"and similar for the curl","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla times mathbfu = frac1Rcosthetafracpartial vpartial lambda -\nfrac1Rcosthetafracpartial (u costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The radius of the sphere (i.e. Earth) is R. The zonal gradient scales with 1cos(theta) as the longitudes converge towards the poles (note that theta describes latitudes here, defintions using colatitudes replace the cos with a sin.)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting with a spectral field of vorticity zeta and divergence mathcalD one can obtain stream function Psi and velocity potential Phi by inverting the Laplace operator nabla^2:","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi = nabla^-2zeta quad Phi = nabla^-2mathcalD","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The velocities uv are then obtained from (uv) = nabla^botPsi + nablaPhi following the defintion from above and nabla^bot = (-R^-1partial_theta (Rcostheta)^-1partial_lambda)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nu = -frac1Rpartial_thetaPsi + frac1Rcosthetapartial_lambdaPhi \nv = +frac1Rpartial_thetaPhi + frac1Rcosthetapartial_lambdaPsi\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Alternatively, we can use the velocities U = ucostheta V = vcostheta, which we do as the meridional gradient for spherical harmonics is easier implemented with a costheta-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with UV and not uv. From uv we can return to zeta mathcalD via","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nzeta = frac1Rcosthetapartial_lambda v - frac1Rcosthetapartial_theta (u costheta) \nmathcalD = frac1Rcosthetapartial_lambda u + frac1Rcosthetapartial_theta (v costheta)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Equivalently, we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU = -fraccosthetaRpartial_thetaPsi + frac1Rpartial_lambdaPhi \nV = +fraccosthetaRpartial_thetaPhi + frac1Rpartial_lambdaPsi \nzeta = frac1Rpartial_lambda left( fracVcos^2theta right) -\nfraccosthetaRpartial_theta left( fracUcos^2theta right) \nmathcalD = frac1Rpartial_lambda left( fracUcos^2theta right) +\nfraccosthetaRpartial_theta left( fracVcos^2theta right)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is a more convenient formulation as required costheta scalings are reduced to a minimum. The remaining (UV)*cos^-2theta are done in grid-point space and usually in combination with other operations like the computation of the vorticity flux. But also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out too. As described in Meridional derivative, it is more convenient to implement costheta partial_theta via a recursion relation for the Legendre polynomials than partial_theta directly. How the operators nabla nabla times nabla cdot can be implemented with spherical harmonics is presented in the following sections.","category":"page"},{"location":"spectral_transform/#Zonal-derivative","page":"Spherical harmonic transform","title":"Zonal derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The zonal derivative of a scalar field Psi in spectral space is the zonal derivative of all its respective spherical harmonics Psi_lm(phitheta) (now we use phi for longitudes to avoid confusion with the Legendre polynomials lambda_lm)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"v_lm = frac1R cos(theta) fracpartialpartial phi left( lambda_l^m(costheta) e^imphi right) =\nfracimR cos(theta) lambda_l^m(costheta) e^imphi = fracimR cos(theta) Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"So for every spectral harmonic, cos(theta)v_lm is obtained from Psi_lm via a multiplication with imR. Unscaling the cos(theta)-factor is done after transforming the spectral coefficients v_lm into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as tildePsi = R^-1Psi such that the division by radius R in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order im. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number m times imaginary i.","category":"page"},{"location":"spectral_transform/#Meridional-derivative","page":"Spherical harmonic transform","title":"Meridional derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridioinal derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costheta fracdP_lmdtheta = -lepsilon_l+1mP_l+1m + (l+1)epsilon_lmP_l-1m","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with recursion factors","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"epsilon_lm = sqrtfracl^2-m^24l^2-1","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In the following we use the example of obtaining the zonal velocity u from the stream function Psi, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi(lambdatheta) = sum_lmPsi_lmP_lm(sintheta)e^imlambda","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"we multiply with -R^-1costhetapartial_theta to obtain","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costhetaleft(-frac1Rpartial_thetaPsi right) = -frac1Rsum_lmPsi_lme^imlambdacosthetapartial_theta P_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"at which point the recursion from above can be applied. Collecting terms proportional to P_lm then yields","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"(cos(theta)u)_lm = -frac1R(-(l-1)epsilon_lmPsi_l-1m + (l+2)epsilon_l+1mPsi_l+1m)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"To obtain the coefficient of each spherical harmonic lm of the meridional gradient of a spectral field, two coefficients at l-1m and l+1m have to be combined. This means that the coefficient of a gradient ((costheta) u)_lm is a linear combination of the coefficients of one higher and one lower degree Psi_l+1mPsi_l-1m. As the coefficient Psi_lm with ml are zero, the sectoral harmonics (l=m) of the gradients are obtained from the first off-diagonal only. However, the l=l_max harmonics of the gradients require the l_max-1 as well as the l_max+1 harmonics. In SpeedyWeather.jl vector quantitie like uv use therefore one more meridional mode than scalar quantities such as vorticity zeta or stream function Psi. The meridional derivative in SpeedyWeather.jl also omits the 1R-scaling as explained for the Zonal derivative and in Radius scaling.","category":"page"},{"location":"spectral_transform/#Divergence-and-curl-in-spherical-harmonics","page":"Spherical harmonic transform","title":"Divergence and curl in spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridional gradient as described above can be applied to scalars, such as Psi and Phi in the conversion to velocities (uv) = nabla^botPsi + nablaPhi, however, the operators curl nabla times and divergence nabla cdot in spherical coordinates involve a costheta scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform of vorticity zeta is","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac12piint_-tfracpi2^tfracpi2int_0^2pi zeta(lambdatheta) P_lm(sintheta) e^imlambda dlambda costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Given that Rzeta = cos^-1partial_lambda v - cos^-1partial_theta (u costheta), we therefore have to evaluate a meridional integral of the form","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"int P_lm frac1cos theta partial_theta(u costheta)) cos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which can be solved through integration by parts. As ucostheta = 0 at theta = pm tfracpi2 only the integral","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int partial_theta P_lm (u costheta) dtheta = -int costheta partial_theta P_lm (fracucostheta) costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"remains. Inserting the recurrence relation from the Meridional derivative turns this into","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int left(-l epsilon_l+1mP_l+1m + (l+1)epsilon_lm P_l-1m right) (fracucostheta) cos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Now we expand (tfracucostheta) but only the lm harmonic will project ontoP_lm. Let u^* = ucos^-1theta v^* = vcos^-1theta we then have in total","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nRzeta_lm = imv^*_lm + (l+1)epsilon_lmu^*_l-1m - lepsilon_l+1mu^*_l+1m \nRD_lm = imu^*_lm - (l+1)epsilon_lmv^*_l-1m + lepsilon_l+1mv^*_l+1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"And the divergence D is similar, but (uv) to (-vu). We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#Laplacian","page":"Spherical harmonic transform","title":"Laplacian","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral Laplacian is easily applied to the coefficients Psi_lm of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator nabla^2 in spherical coordinates with eigenvalues -l(l+1) divided by the radius squared R^2, i.e. nabla^2 Psi becomes tfrac-l(l+1)R^2Psi_lm in spectral space. For example, vorticity zeta and streamfunction Psi are related by zeta = nabla^2Psi in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree l and order m to","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac-l(l+1)R^2Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This can be easily inverted to obtain the stream function Psi from vorticity zeta instead. In order to avoid division by zero, we set Psi_00 here, given that the stream function is only defined up to a constant anyway.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nPsi_lm = fracR^2-l(l+1)zeta_lm quad foralllm 0\nPsi_00 = 0\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"See also Horizontal diffusion and Normalization of diffusion.","category":"page"},{"location":"spectral_transform/#U,V-from-vorticity-and-divergence","page":"Spherical harmonic transform","title":"U,V from vorticity and divergence","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity zeta and divergence D (which are prognostic variables) to U=ucostheta V=vcostheta. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree l (the meridional gradient). It is therefore computationally more efficient to compute UV directly from zetaD instead of calculating stream function and velocity potential first. In total we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU_lm = -fraciml(l+1)(RD)_lm + fracepsilon_l+1ml+1(Rzeta)_l+1m - fracepsilon_lml(Rzeta)_l-1m \nV_lm = -fraciml(l+1)(Rzeta)_lm - fracepsilon_l+1ml+1(RD)_l+1m + fracepsilon_lml(RD)_l-1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#References","page":"Spherical harmonic transform","title":"References","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[1]: Justin Willmert, 2020. Introduction to Associated Legendre Polynomials (Legendre.jl Series, Part I)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[2]: Justin Willmert, 2020. Calculating Legendre Polynomials (Legendre.jl Series, Part II)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[3]: Justin Willmert, 2020. Pre-normalizing Legendre Polynomials (Legendre.jl Series, Part III)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[4]: Justin Willmert, 2020. Maintaining numerical accuracy in the Legendre recurrences (Legendre.jl Series, Part IV)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[5]: Justin Willmert, 2020. Introducing Legendre.jl (Legendre.jl Series, Part V)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[6]: Justin Willmert, 2020. Numerical Accuracy of the Spherical Harmonic Recurrence Coefficient (Legendre.jl Series Addendum)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[7]: Justin Willmert, 2020. Notes on Calculating the Spherical Harmonics","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[8]: Justin Willmert, 2022. More Notes on Calculating the Spherical Harmonics: Analysis of maps to harmonic coefficients","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[9]: David Randall, 2021. An Introduction to Numerical Modeling of the Atmosphere, Chapter 22.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[10]: Dale Durran, 2010. Numerical Methods for Fluid Dynamics, Springer. In particular section 6.2, 6.4.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[11]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"#SpeedyWeather.jl-documentation","page":"Home","title":"SpeedyWeather.jl documentation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.","category":"page"},{"location":"#Overview","page":"Home","title":"Overview","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to perform some calculations in spectral space (time integration, gradients, linear terms) and some in grid-point space (advection, non-linear terms, parameterizations). The prognostic variables used are vorticity, divergence, absolute temperature, logarithm of surface pressure and specific humidity. The time stepping uses a leapfrog scheme with additional filters and a semi-implicit formulation for gravity waves. The default resolution is T31 (96x48 grid points on a regular Gaussian grid, about 400km at the Equator) and 8 vertical levels.","category":"page"},{"location":"","page":"Home","title":"Home","text":"Simple parameterizations are used to represent the physical processes convection, large-scale condensation, clouds, short-wave radiation, long-waves radiation, surface fluxes of momentum and energy, and vertical diffusion.","category":"page"},{"location":"#Manual-outline","page":"Home","title":"Manual outline","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"See the following pages of the documentation for more details","category":"page"},{"location":"","page":"Home","title":"Home","text":"How to run SpeedyWeather.jl\nSpherical harmonic transform\nGrids\nDynamical core\nParametrizations\nNew model setups\nFunction and type index","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the original documentation by Molteni and Kucharski.","category":"page"},{"location":"#Scope","page":"Home","title":"Scope","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The focus of SpeedyWeather.jl is to develop a global atmospheric model of intermediate complexity, that can run at various levels of precision (16, 32 and 64-bit) on different architectures (x86 and ARM, GPUs in the future). Additionally, the model is written in an entirely number format-flexible way, such that any custom number format can be used and Julia will compile to the format automatically. Similarly, many model components are written in an abstract way to support modularity and extandability.","category":"page"},{"location":"#History","page":"Home","title":"History","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl is a Julia implementation of SPEEDY, which is written in Fortran 77. Sam Hatfield translated SPEEDY to Fortran 90 and started the project to port it to Julia. However, we are making an effort to overhaul the implementation of the mathematical model behind speedy completely and it is unlikely that a single line of code survived.","category":"page"},{"location":"#Installation","page":"Home","title":"Installation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl is registered in the Julia Registry. Open Julia's package manager from the REPL with ] and add the github repository to install SpeedyWeather.jl and all dependencies","category":"page"},{"location":"","page":"Home","title":"Home","text":"(@v1.8) pkg> add SpeedyWeather","category":"page"},{"location":"","page":"Home","title":"Home","text":"which will automatically install the latest release. However, you may want to install directly from the main branch with","category":"page"},{"location":"","page":"Home","title":"Home","text":"(@v1.8) pkg> add https://github.com/SpeedyWeather/SpeedyWeather.jl#main","category":"page"},{"location":"","page":"Home","title":"Home","text":"other branches than #main can be installed by adding #branch_name instead.","category":"page"},{"location":"#Developers","page":"Home","title":"Developers","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The development of SpeedyWeather.jl is lead by Milan Klöwer and current and past contributors include","category":"page"},{"location":"","page":"Home","title":"Home","text":"Tom Kimpson\nAlistair White\nMaximilian Gelbrecht\nDavid Meyer\nDaisuke Hotta","category":"page"},{"location":"","page":"Home","title":"Home","text":"Any contributions are always welcome!","category":"page"},{"location":"#Funding","page":"Home","title":"Funding","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Contributors received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022.","category":"page"},{"location":"time_integration/#Time-integration","page":"Time integration","title":"Time integration","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"SpeedyWeather.jl uses a leapfrog time scheme with a Robert's and William's filter to dampen the computational mode and achieve 3rd order accuracy.","category":"page"},{"location":"time_integration/#Oscillation-equation","page":"Time integration","title":"Oscillation equation","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"fracdFdt = iomega F","category":"page"},{"location":"time_integration/#Implementation-details","page":"Time integration","title":"Implementation details","text":"","category":"section"}]
+}
diff --git a/previews/PR300/siteinfo.js b/previews/PR300/siteinfo.js
new file mode 100644
index 000000000..d56682f73
--- /dev/null
+++ b/previews/PR300/siteinfo.js
@@ -0,0 +1 @@
+var DOCUMENTER_CURRENT_VERSION = "previews/PR300";
diff --git a/previews/PR300/spectral_transform/index.html b/previews/PR300/spectral_transform/index.html
new file mode 100644
index 000000000..b9a4b041e
--- /dev/null
+++ b/previews/PR300/spectral_transform/index.html
@@ -0,0 +1,45 @@
+
+Spherical harmonic transform · SpeedyWeather.jl
The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space on a longitude-latitude regular Gaussian grid.
with $\lambda_l^m$ being the pre-normalized associated Legendre polynomials, and $e^{im\phi}$ are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.
Latitudes versus colatitudes
The implementations of the spherical transforms in SpeedyWeather.jl use colatitudes $\theta = (0,\pi)$ (0 at the north pole) but the dynamical core uses latitudes $\theta = (-\pi/2,\pi/2)$ ($\pi/2$ at the north pole). However, all arrays are always sorted north to south such that [i,1] will access the northern-most grid points. Note: We may also use latitudes in the spherical harmonic transform in the future for consistency.
The synthesis (or inverse transform) takes the spectral coefficients $a_{lm}$ and transforms them to grid-point values $f(\phi,\theta)$ (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics $Y_{lm}$ with non-zero coefficients.
Spectral packing is the way how the coefficients $a_{lm}$ of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree $l$ and order $m$ as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)
Every row represents an order $l \geq 0$, starting from $l=0$ at the top. Every column represents an order $m \geq 0$, starting from $m=0$ on the left. The coefficients of these spherical harmonics are directly mapped into a matrix $a_{lm}$ as
$m$
$l$
$a_{00}$
$a_{10}$
$a_{11}$
$a_{20}$
$a_{12}$
$a_{22}$
$a_{30}$
$a_{13}$
$a_{23}$
$a_{33}$
which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal explicitly stores zeros. Note that internally vector fields include an additional degree, such that $l_{max} = m_{max} + 1$ (see Gradients in spherical coordinates for more information). The harmonics with $a_{l0}$ (the first column) are also called zonal harmonics as they are constant with longitude $\phi$. The harmonics with $a_{ll}$ (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into $2l$ sectors in longitude $\phi$ without a zero-crossing in latitude.
Array indices
For a spectral field alms note that due to Julia's 1-based indexing the coefficient $a_{lm}$ is obtained via alms[l+1,m+1].
Fortran speedy does not use the same spectral packing as SpeedyWeather.jl. The alternative packing $l',m'$ therein uses $l'=m$ and $m'=l-m$ as summarized in the following table.
degree $l$
order $m$
$l'=m$
$m'=l-m$
0
0
0
0
1
0
0
1
1
1
1
0
2
0
0
2
2
1
1
1
2
2
2
0
3
0
0
3
...
...
...
...
This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices
$m'$
$l'$
$a_{00}$
$a_{10}$
$a_{20}$
$a_{30}$
$a_{11}$
$a_{21}$
$a_{31}$
$a_{22}$
$a_{32}$
$a_{33}$
This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran-speedy.
SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with $l \leq l_{max}$ and $|m| \leq m_{max}$ are explicitly represented. This is usually described as $Tm_{max}$, with $l_{max} = m_{max}$ (although in vector quantities require one more degree $l$ in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with $l_{max} = m_{max} = 31$. Note that the degree $l$ and order $m$ are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.
Using triangular truncation[9], there are constraints on the corresponding grid resolution. Let nlon, nlat be the number of longitudes, latitudes on a regular Gaussian grid. Then spectral and grid resolution have to be chosen such that
$nlon \geq 3l_{max}+1$
$nlat \geq (3l_{max}+1)/2$
In general, we choose $nlon = 2nlat$, and ideally $nlon$ is easily Fourier-transformable, e.g. $nlon = 2^i3^j5^k$ with some integers $i,j,k \geq 0$. SpeedyWeather.jl is tested at the following horizontal resolutions, with $\Delta x = \tfrac{2\pi R}{nlon}$ as the approximate grid spacing at the Equator
$l_{max}$
nlon
nlat
$\Delta x$
31 (default)
96
48
400 km
42
128
64
300 km
85
256
128
160 km
170
512
256
80 km
341
1024
512
40 km
682
2048
1024
20 km
Choosing trunc as argument in run_speedy will automatically choose nlon,nlat as presented in the table. Other common choices are T63 (192x96), T127 (384x192), T255 (768x384), T511 (1536x768), among others.
The radius of the sphere (i.e. Earth) is $R$. The zonal gradient scales with $1/\cos(\theta)$ as the longitudes converge towards the poles (note that $\theta$ describes latitudes here, defintions using colatitudes replace the $\cos$ with a $\sin$.)
Starting with a spectral field of vorticity $\zeta$ and divergence $\mathcal{D}$ one can obtain stream function $\Psi$ and velocity potential $\Phi$ by inverting the Laplace operator $\nabla^2$:
The velocities $u,v$ are then obtained from $(u,v) = \nabla^\bot\Psi + \nabla\Phi$ following the defintion from above and $\nabla^\bot = (-R^{-1}\partial_\theta, (R\cos\theta)^{-1}\partial_\lambda)$
Alternatively, we can use the velocities $U = u\cos\theta, V = v\cos\theta$, which we do as the meridional gradient for spherical harmonics is easier implemented with a $\cos\theta$-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with $U,V$ and not $u,v$. From $u,v$ we can return to $\zeta, \mathcal{D}$ via
\[\begin{aligned}
+\zeta &= \frac{1}{R\cos\theta}\partial_\lambda v - \frac{1}{R\cos\theta}\partial_\theta (u \cos\theta) \\
+\mathcal{D} &= \frac{1}{R\cos\theta}\partial_\lambda u + \frac{1}{R\cos\theta}\partial_\theta (v \cos\theta).
+\end{aligned}\]
which is a more convenient formulation as required $\cos\theta$ scalings are reduced to a minimum. The remaining $(U,V)*\cos^{-2}\theta$ are done in grid-point space and usually in combination with other operations like the computation of the vorticity flux. But also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out too. As described in Meridional derivative, it is more convenient to implement $\cos\theta \partial_\theta$ via a recursion relation for the Legendre polynomials than $\partial_\theta$ directly. How the operators $\nabla, \nabla \times, \nabla \cdot$ can be implemented with spherical harmonics is presented in the following sections.
The zonal derivative of a scalar field $\Psi$ in spectral space is the zonal derivative of all its respective spherical harmonics $\Psi_{lm}(\phi,\theta)$ (now we use $\phi$ for longitudes to avoid confusion with the Legendre polynomials $\lambda_{lm}$)
So for every spectral harmonic, $\cos(\theta)v_{lm}$ is obtained from $\Psi_{lm}$ via a multiplication with $im/R$. Unscaling the $\cos(\theta)$-factor is done after transforming the spectral coefficients $v_{lm}$ into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as $\tilde{\Psi} = R^{-1}\Psi$ such that the division by radius $R$ in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order $im$. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number $m$ times imaginary $i$.
The meridioinal derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]
In the following we use the example of obtaining the zonal velocity $u$ from the stream function $\Psi$, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion
To obtain the coefficient of each spherical harmonic $l,m$ of the meridional gradient of a spectral field, two coefficients at $l-1,m$ and $l+1,m$ have to be combined. This means that the coefficient of a gradient $((\cos\theta) u)_{lm}$ is a linear combination of the coefficients of one higher and one lower degree $\Psi_{l+1,m},\Psi_{l-1,m}$. As the coefficient $\Psi_{lm}$ with $m<l$ are zero, the sectoral harmonics ($l=m$) of the gradients are obtained from the first off-diagonal only. However, the $l=l_{max}$ harmonics of the gradients require the $l_{max}-1$ as well as the $l_{max}+1$ harmonics. In SpeedyWeather.jl vector quantitie like $u,v$ use therefore one more meridional mode than scalar quantities such as vorticity $\zeta$ or stream function $\Psi$. The meridional derivative in SpeedyWeather.jl also omits the $1/R$-scaling as explained for the Zonal derivative and in Radius scaling.
The meridional gradient as described above can be applied to scalars, such as $\Psi$ and $\Phi$ in the conversion to velocities $(u,v) = \nabla^\bot\Psi + \nabla\Phi$, however, the operators curl $\nabla \times$ and divergence $\nabla \cdot$ in spherical coordinates involve a $\cos\theta$ scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].
Given that $R\zeta = \cos^{-1}\partial_\lambda v - \cos^{-1}\partial_\theta (u \cos\theta)$, we therefore have to evaluate a meridional integral of the form
Now we expand $(\tfrac{u}{\cos\theta})$ but only the $l,m$ harmonic will project onto$P_{l,m}$. Let $u^* = u\cos^{-1}\theta, v^* = v\cos^{-1}\theta$ we then have in total
And the divergence $D$ is similar, but $(u,v) \to (-v,u)$. We have moved the scaling with the radius $R$ directly into $\zeta,D$ as further described in Radius scaling.
The spectral Laplacian is easily applied to the coefficients $\Psi_{lm}$ of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator $\nabla^2$ in spherical coordinates with eigenvalues $-l(l+1)$ divided by the radius squared $R^2$, i.e. $\nabla^2 \Psi$ becomes $\tfrac{-l(l+1)}{R^2}\Psi_{lm}$ in spectral space. For example, vorticity $\zeta$ and streamfunction $\Psi$ are related by $\zeta = \nabla^2\Psi$ in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree $l$ and order $m$ to
\[\zeta_{l,m} = \frac{-l(l+1)}{R^2}\Psi_{l,m}\]
This can be easily inverted to obtain the stream function $\Psi$ from vorticity $\zeta$ instead. In order to avoid division by zero, we set $\Psi_{0,0}$ here, given that the stream function is only defined up to a constant anyway.
After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity $\zeta$ and divergence $D$ (which are prognostic variables) to $U=u\cos\theta, V=v\cos\theta$. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree $l$ (the meridional gradient). It is therefore computationally more efficient to compute $U,V$ directly from $\zeta,D$ instead of calculating stream function and velocity potential first. In total we have
The prognostic variables in spectral space are called
vor # Vorticity of horizontal wind field
+ div # Divergence of horizontal wind field
+ temp # Absolute temperature [K]
+ pres_surf # Logarithm of surface pressure [log(Pa)]
+ humid # Specific humidity [g/kg]
their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are
We follow Julia's style guide and highlight here some important aspects of it.
Bang (!) convention. A function func does not change its input arguments, however, func! does.
Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.
Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup
but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.
Settings
This document was generated with Documenter.jl version 0.27.24 on Monday 29 May 2023. Using Julia version 1.8.5.
A mathematical and implementation-specific description of the dynamical core used in SpeedyWeather.jl. We start by describing the barotropic vorticity equations which is one set of equations that SpeedyWeather.jl can solve (see How to run SpeedyWeather.jl) as many details therein also apply to the Shallow water equations and Primitive equations explained thereafter.
The dynamical core presented here largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2], a shallow water model [3] and a primitive equation model[4].
The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity $\zeta$ with advection, Coriolis force and diffusion in a single global layer.
with time $t$, velocity vector $\mathbf{u} = (u, v)$, Coriolis parameter $f$, and hyperdiffusion $(-1)^{n+1} \nu \nabla^{2n} \zeta$ ($n$ is the hyperdiffusion order; see Horizontal diffusion). Starting with some relative vorticity $\zeta$, the Laplacian is inverted to obtain the stream function $\Psi$
\[\Psi = \nabla^{-2}\zeta\]
The zonal velocity $u$ and meridional velocity $v$ are then the (negative) meridional gradient and zonal gradient of $\Psi$
obtain meridional velocity $(\cos(\theta)v)_{lm}$ through a Zonal derivative
Transform zonal and meridional velocity $(\cos(\theta)u)_{lm}$, $(\cos(\theta)v)_{lm}$ to grid-point space and unscale the $\cos(\theta)$ factor to obtain $u,v$.
Multiply $u,v$ with $\zeta+f$ in grid-point space
Transform $u(\zeta + f)$ and $v(\zeta+f)$ to spectral space
Compute the divergence of $(\mathbf{u}(\zeta + f))_{lm}$ in spectral space through a Meridional derivative and Zonal derivative which will be the tendency of $\zeta_{lm}$
In SpeedyWeather.jl we use hyerdiffusion through an $n$-th power Laplacian $(-1)^{n+1}\nabla^{2n}$ (hyper when $n>1$) which can be implemented as a multiplication of the spectral coefficients $\Psi_{lm}$ with $(-l(l+1))^nR^{-2n}$ (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the $(-l(l+1))^nR^{-2n}$ can be precomputed. Note the sign change $(-1)^{n+1}$ here is such that the dissipative nature of the diffusion operator is retained for $n$ odd and even.
In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step $\Delta t$ of variable $\zeta$ to obtain from time steps $i-1$ and $i$, the next time step $i+1$
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t d\zeta,\]
with $d\zeta$ being some tendency evaluated from $\zeta_i$. Now we want to add a diffusion term $(-1)^{n+1}\nu \nabla^{2n}\zeta$ with viscosity $\nu$, wich however, is implicitly calculated from $\zeta_{i+1}$, then
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t (d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i+1})\]
As the application of $(-1)^{n+1}\nu\nabla^{2n}$ is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to
\[\zeta_{i+1} = \frac{\zeta_{i-1} + 2\Delta t d\zeta}{1 - 2\Delta (-1)^{n+1}\nu\nabla^{2n}},\]
and expand the numerator to
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t \frac{d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t (-1)^{n+1}\nu \nabla^{2n}},\]
Hence the diffusion can be applied implicitly by updating the tendency $d\zeta$ as
\[d\zeta \to \frac{d\zeta + (-1)^{n+1}\nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t \nu \nabla^{2n}}\]
which only depends on $\zeta_{i-1}$. Now let $D_\text{explicit} = (-1)^{n+1}\nu\nabla^{2n}$ be the explicit part and $D_\text{implicit} = 1 - (-1)^{n+1} 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are $D_\text{implicit} = 1 - 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic $l,m$ we do
Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power $n$.
In physics, the Laplace operator $\nabla^2$ is often used to represent diffusion due to viscosity in a fluid. In that case, the viscosity coefficient is $\nu$ of units $\text{m}^2\text{s}^{-1}$ and the full operator reads as $\nu \nabla^2$ with units $(\text{m}^2\text{s}^{-1})(\text{m}^{-2}) = \text{s}^{-1}$. This motivates us to normalize the Laplace operator by a constant of units $\text{m}^{-2}$ and the viscosity coefficient by its inverse such that the viscosity coefficient becomes a damping timescale of unit $\text{s}^{-1}$. Given the application in spectral space we decide to normalize by the largest eigenvalue $-l_\text{max}(l_\text{max}+1)$ such that all entries in the discrete spectral Laplace operator are in $[0,1]$. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized viscosity coefficient $\nu^* = l_\text{max}(l_\text{max}+1)\nu$ (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have
In the barotropic voriticity equation model the inversion of the Laplcians in order to obtain $\Psi$ from $\zeta$ therefore becomes
\[\tilde{\zeta} = \tilde{\nabla}^2 \tilde{\Psi}\]
where the dimensionless gradients simply omit the scaling with $1/R$, $\tilde{\nabla} = R\nabla$. The Barotropic vorticity equation scaled with $R^2$ is
Similar to the scaled barotropic vorticity equations, the scaled shallow water equations scale the vorticity and the divergence equation with $R^2$, but the continuity equation with $R$
This document was generated with Documenter.jl version 0.27.24 on Monday 29 May 2023. Using Julia version 1.8.5.
diff --git a/previews/PR334/functions/index.html b/previews/PR334/functions/index.html
new file mode 100644
index 000000000..150fd5b90
--- /dev/null
+++ b/previews/PR334/functions/index.html
@@ -0,0 +1,180 @@
+
+Function and type index · SpeedyWeather.jl
Spectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.
Spectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities u,v.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for primitive equation models. Updates grid vorticity, grid divergence, grid temperature, pressure (pres_grid) and the velocities u,v.
Returns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.
Truncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.
Truncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.
Laplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.
Keyword arguments
add=true adds the ∇²(alms) to the output
flipsign=true computes -∇²(alms) instead
inverse=true computes ∇⁻²(alms) instead
Default is add=false, flipsign=false, inverse=false. These options can be combined.
Divergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.
Curl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.
Get U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.
Get U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.
Construct Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. Pass on SpectralGrid to calculate the following fields
spectral_grid::SpectralGrid: SpectralGrid that defines spectral and grid resolution
Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid of the dynamical core Default: spectral_grid.Grid
nlat_half::Int64: resolution parameter nlathalf of Grid, # of latitudes on one hemisphere (incl Equator) Default: spectralgrid.nlat_half
nlon_max::Int64: maximum number of longitudes (at/around Equator) Default: getnlonmax(Grid, nlat_half)
nlon::Int64: =nlonmax, same (used for compatibility), TODO: still needed? Default: nlonmax
nlat::Int64: number of latitude rings Default: getnlat(Grid, nlathalf)
nlev::Int64: number of vertical levels Default: spectral_grid.nlev
npoints::Int64: total number of grid points Default: spectral_grid.npoints
ln_σ_levels_full::Vector{NF} where NF<:AbstractFloat: log of σ at full levels, include surface (σ=1) as last element Default: log.(vcat(σlevelsfull, 1))
Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF.
Performs the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.
The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and how they can be used.
SpeedyWeather.jl's spectral transform currently only supports ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.
All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).
Currently the following full grids <: AbstractFullGrid are implemented
FullGaussianGrid, a full grid with Gaussian latitudes
FullClenshawGrid, a full grid with equi-angle latitudes
and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are
OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron
OctahedralClenshawGrid, similar but based on equi-angle latitudes
HEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces
OctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.
An overview of these grids is visualised here
Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.
All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half.
HEALPix grids do not use Nside as resolution parameter
The original formulation for HEALPix grids use $N_{side}$, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use $N_{side}$ for the documentation or within functions though.
A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation T with a grid resolution N (=nlat_half) such that T + 1 = N. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at $l_{max}=31$ in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid.
Let J be the total number of rings. Then we have
$T \approx J$ for linear truncation
$\frac{3}{2}T \approx J$ for quadratic truncation
$2T \approx J$ for cubic truncation
and in general $\frac{m+1}{2}T \approx J$ for m-th order truncation. So the higher the truncaction order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. In SpeedyWeather.jl the parameter dealiasing controls this option, = 1 would be linear, = 2 quadratic, = 3 cubic etc.
Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are $N_\varphi$ basepixels in zonal direction and $N_\theta$ basepixels in meridional direction. For $N_\varphi = 4$ and $N_\theta = 3$ we obtain the classical HEALPix grid with $N_\varphi N_\theta = 12$ basepixels shown above in Implemented Grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always $2N$, so 32 at $N=16$) and there are polar caps above and below the equatorial zone with the border at $\cos(\theta) = 2/3$ ($\theta$ in colatitudes).
Following Górski, 2004[1], the $z=cos(\theta)$ colatitude of the $j$-th ring in the north polar cap, $j=1,...,N_{side}$ with $2N_{side} = N$ is
\[z = 1 - \frac{j^2}{3N_{side}^2}\]
and on that ring, the longitude $\phi$ of the $i$-th point ($i$ is the in-ring-index) is at
\[\phi = \frac{\pi}{2j}(i-\tfrac{1}{2})\]
The in-ring index $i$ goes from $i=1,...,4$ for the first (i.e. northern-most) ring, $i=1,...,8$ for the second ring and $i = 1,...,4j$ for the $j$-th ring in the northern polar cap.
In the north equatorial belt $j=N_{side},...,2N_{side}$ this changes to
\[z = \frac{4}{3} - \frac{2j}{3N_{side}}\]
and the longitudes change to ($i$ is always $i = 1,...,4N_{side}$ in the equatorial belt meaning the number of longitude points is constant here)
The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.
The cell boundaries are obtained by setting $i = k + 1/2$ or $i = k + 1/2 + j$ (half indices) into the equations above, such that $z(\phi,k)$, a function for the cosine of colatitude $z$ of index $k$ and the longitude $\phi$ is obtained. These are then (northern polar cap)
While the classic HEALPix grid is based on a dodecahedron, other choices for $N_\varphi$ and $N_\theta$ in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With $N_\varphi = 4$ and $N_\theta = 1$ we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, $2\pi$ around the Equator versus $\pi$ between the poles. $N_\varphi = 6, N_\theta = 2$ or $N_\varphi = 8, N_\theta = 3$ are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visulations of these grids.
We call the $N_\varphi = 4, N_\theta = 1$ HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As $N_\theta = 1$ there is no equatorial belt which simplifies the grid. The latitude of the $j$-th isolatitude ring on the OctaHEALPixGrid is defined by
\[z = 1 - \frac{j^2}{N^2},\]
with $j=1,...,N$, and similarly for the southern hemisphere by symmetry. On this grid $N_{side} = N$ where $N$= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index $i = 1,...,4j$ are
\[\phi = \frac{\pi}{2j}(i - \tfrac{1}{2})\]
and again, the southern hemisphere grid points are obtained by symmetry.
The $3N_{side}^2$ in the denominator of the HEALPix grid came simply $N^2$ for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).
[1] Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976
Settings
This document was generated with Documenter.jl version 0.27.24 on Monday 29 May 2023. Using Julia version 1.8.5.
diff --git a/previews/PR334/how_to_run_speedy/index.html b/previews/PR334/how_to_run_speedy/index.html
new file mode 100644
index 000000000..64e9d6213
--- /dev/null
+++ b/previews/PR334/how_to_run_speedy/index.html
@@ -0,0 +1,5 @@
+
+How to run SpeedyWeather.jl · SpeedyWeather.jl
The simplest way to run SpeedyWeather.jl with default parameters is
using SpeedyWeather
+run_speedy()
Hooray, you have just simulated the Earth's atmosphere. Parameters, their meanings and defaults are documented in Parameters. For example, if you want to run the primitive equation dry core (no humidity) simulation in double precision (Float64), at higher resolution (trunc, the triangular spectral truncation), slow down the rotation of the Earth (rotation in $s^{-1}$), and create some netCDF ouput, do
If provided, the number format has to be the first argument, the model (Barotropic, ShallowWater, PrimitiveDry, PrimitiveWet are available) the second, and all other arguments are keyword arguments.
progn_vars = run_speedy(NF,Model;kwargs...) or
+progn_vars = run_speedy(NF;kwargs...) or
+progn_vars = run_speedy(Model;kwargs...)
Runs SpeedyWeather.jl with number format NF and the model Model and any additional parameters in the keyword arguments kwargs.... Unspecified parameters use the default values.
Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.
SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to perform some calculations in spectral space (time integration, gradients, linear terms) and some in grid-point space (advection, non-linear terms, parameterizations). The prognostic variables used are vorticity, divergence, absolute temperature, logarithm of surface pressure and specific humidity. The time stepping uses a leapfrog scheme with additional filters and a semi-implicit formulation for gravity waves. The default resolution is T31 (96x48 grid points on a regular Gaussian grid, about 400km at the Equator) and 8 vertical levels.
Simple parameterizations are used to represent the physical processes convection, large-scale condensation, clouds, short-wave radiation, long-waves radiation, surface fluxes of momentum and energy, and vertical diffusion.
The focus of SpeedyWeather.jl is to develop a global atmospheric model of intermediate complexity, that can run at various levels of precision (16, 32 and 64-bit) on different architectures (x86 and ARM, GPUs in the future). Additionally, the model is written in an entirely number format-flexible way, such that any custom number format can be used and Julia will compile to the format automatically. Similarly, many model components are written in an abstract way to support modularity and extandability.
SpeedyWeather.jl is a Julia implementation of SPEEDY, which is written in Fortran 77. Sam Hatfield translated SPEEDY to Fortran 90 and started the project to port it to Julia. However, we are making an effort to overhaul the implementation of the mathematical model behind speedy completely and it is unlikely that a single line of code survived.
SpeedyWeather.jl is registered in the Julia Registry. Open Julia's package manager from the REPL with ] and add the github repository to install SpeedyWeather.jl and all dependencies
(@v1.8) pkg> add SpeedyWeather
which will automatically install the latest release. However, you may want to install directly from the main branch with
Contributors received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022.
Settings
This document was generated with Documenter.jl version 0.27.24 on Monday 29 May 2023. Using Julia version 1.8.5.
diff --git a/previews/PR334/new_model_setups/index.html b/previews/PR334/new_model_setups/index.html
new file mode 100644
index 000000000..4794d3b63
--- /dev/null
+++ b/previews/PR334/new_model_setups/index.html
@@ -0,0 +1,2 @@
+
+New model setups · SpeedyWeather.jl
This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmopshere. Every section is followed by a brief description of implementation details.
This document was generated with Documenter.jl version 0.27.24 on Monday 29 May 2023. Using Julia version 1.8.5.
diff --git a/previews/PR334/search_index.js b/previews/PR334/search_index.js
new file mode 100644
index 000000000..89446fac3
--- /dev/null
+++ b/previews/PR334/search_index.js
@@ -0,0 +1,3 @@
+var documenterSearchIndex = {"docs":
+[{"location":"development/#Development-notes","page":"Development notes","title":"Development notes","text":"","category":"section"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To run tests, from the path of your local clone of the repository do:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=. -e 'import Pkg; Pkg.test()'","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To install dependencies:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project -e 'import Pkg; Pkg.instantiate()`","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"then opening:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"you are able to using SpeedyWeather.","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To generate the docs:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=docs -e 'import Pkg; Pkg.develop(path=\".\"); Pkg.instantiate()'\njulia --project=docs docs/make.jl","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"If the docs are generated successfully, you view them by opening docs/build/index.html in your favorite browser.","category":"page"},{"location":"functions/#Function-and-type-index","page":"Function and type index","title":"Function and type index","text":"","category":"section"},{"location":"functions/#Parameters-and-constants","page":"Function and type index","title":"Parameters and constants","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Parameters\nSpeedyWeather.Constants","category":"page"},{"location":"functions/#Boundaries-and-boundary-conditions","page":"Function and type index","title":"Boundaries and boundary conditions","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Boundaries","category":"page"},{"location":"functions/#Spherical-harmonic-transform","page":"Function and type index","title":"Spherical harmonic transform","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.GeoSpectral\nSpeedyWeather.SpectralTransform\nSpeedyWeather.spectral\nSpeedyWeather.spectral!\nSpeedyWeather.gridded\nSpeedyWeather.gridded!\nSpeedyWeather.triangular_truncation\nSpeedyWeather.roundup_fft\nSpeedyWeather.spectral_truncation\nSpeedyWeather.spectral_truncation!\nSpeedyWeather.spectral_interpolation!\nSpeedyWeather.get_legendre_polynomials!\nSpeedyWeather.∇²!\nSpeedyWeather.∇²\nSpeedyWeather.∇⁻²!\nSpeedyWeather.∇⁻²\nSpeedyWeather.gradient_latitude!\nSpeedyWeather.gradient_latitude\nSpeedyWeather.gradient_longitude!\nSpeedyWeather.gradient_longitude\nSpeedyWeather.divergence!\nSpeedyWeather.curl!\nSpeedyWeather._divergence!\nSpeedyWeather.curl_div!\nSpeedyWeather.UV_from_vordiv!\nSpeedyWeather.UV_from_vor!\nSpeedyWeather.ϵlm\nSpeedyWeather.get_recursion_factors","category":"page"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.SpectralTransform","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"S = SpectralTransform{NF<:AbstractFloat}(...)\n\nSpectralTransform struct that contains all parameters and preallocated arrays for the spectral transform.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral","text":"alms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\nalms = spectral( map::AbstractGrid;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to Grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\nalms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nSpectral transform (grid to spectral) map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral!","text":"spectral!( alms::LowerTriangularMatrix,\n map::AbstractGrid,\n S::SpectralTransform)\n\nSpectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded","text":"map = gridded( alms::AbstractMatrix;\n recompute_legendre::Bool=true,\n grid::Type{<:AbstractGrid}=DEFAULT_GRID)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).\n\n\n\n\n\nmap = gridded( alms::AbstractMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!( map::AbstractGrid,\n alms::LowerTriangularMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\ngridded!(\n diagn::DiagnosticVariables,\n progn::PrognosticVariables,\n lf::Int64,\n model::SpeedyWeather.ModelSetup\n)\n\n\nPropagate the spectral state of progn to diagn using time step/leapfrog index lf. Function barrier that calls gridded! for the respective model.\n\n\n\n\n\ngridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::Barotropic\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.\n\n\n\n\n\ngridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::ShallowWater\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities u,v.\n\n\n\n\n\ngridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::PrimitiveEquation\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for primitive equation models. Updates grid vorticity, grid divergence, grid temperature, pressure (pres_grid) and the velocities u,v.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral_truncation","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation","text":"alms_trunc = spectral_truncation(alms,trunc)\n\nReturns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral_truncation!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms::AbstractMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.\n\n\n\n\n\nspectral_truncation!(alms::LowerTriangularMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.\n\n\n\n\n\nspectral_truncation!(alms,trunc)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.\n\n\n\n\n\nspectral_truncation!(alms)\n\nTruncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.∇²!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.∇²!","text":"∇²!( ∇²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false,\n inverse::Bool=false)\n\nLaplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.\n\nKeyword arguments\n\nadd=true adds the ∇²(alms) to the output\nflipsign=true computes -∇²(alms) instead\ninverse=true computes ∇⁻²(alms) instead\n\nDefault is add=false, flipsign=false, inverse=false. These options can be combined.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.∇⁻²!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.∇⁻²!","text":"∇⁻²!( ∇⁻²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false)\n\nCalls ∇²!(∇⁻²alms, alms, S; add, flipsign, inverse=true).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.divergence!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.divergence!","text":"divergence!(div::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform{NF};\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nDivergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.curl!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.curl!","text":"curl!( curl::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform;\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nCurl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.UV_from_vordiv!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.UV_from_vordiv!","text":"UV_from_vordiv!(U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n div::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.UV_from_vor!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.UV_from_vor!","text":"UV_from_vor!( U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"function"},{"location":"functions/#Dynamics","page":"Function and type index","title":"Dynamics","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.bernoulli_potential!\nSpeedyWeather.volume_flux_divergence!\nSpeedyWeather.vorticity_fluxes!\nSpeedyWeather.vorticity_flux_curl!\nSpeedyWeather.vorticity_flux_divergence!","category":"page"},{"location":"functions/#SpeedyWeather.bernoulli_potential!","page":"Function and type index","title":"SpeedyWeather.bernoulli_potential!","text":"bernoulli_potential!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},\n S::SpectralTransform\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nComputes the Laplace operator ∇² of the Bernoulli potential B in spectral space.\n\ncomputes the kinetic energy KE = ½(u²+v²) on the grid\ntransforms KE to spectral space\nadds geopotential for the Bernoulli potential in spectral space\ntakes the Laplace operator.\n\nThis version is used for both ShallowWater and PrimitiveEquation, only the geopotential calculation in geopotential! differs.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.volume_flux_divergence!","page":"Function and type index","title":"SpeedyWeather.volume_flux_divergence!","text":"volume_flux_divergence!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surface::SpeedyWeather.SurfaceVariables,\n orog::SpeedyWeather.AbstractOrography,\n constants::DynamicsConstants,\n G::Geometry,\n S::SpectralTransform\n)\n\n\nComputes the (negative) divergence of the volume fluxes uh,vh for the continuity equation, -∇⋅(uh,vh).\n\n\n\n\n\n","category":"function"},{"location":"functions/#Geometry","page":"Function and type index","title":"Geometry","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Geometry\nSpeedyWeather.vertical_coordinates\nSpeedyWeather.GenLogisticCoefs\nSpeedyWeather.generalised_logistic","category":"page"},{"location":"functions/#SpeedyWeather.Geometry","page":"Function and type index","title":"SpeedyWeather.Geometry","text":"Construct Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. Pass on SpectralGrid to calculate the following fields\n\nspectral_grid::SpectralGrid: SpectralGrid that defines spectral and grid resolution\nGrid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid of the dynamical core Default: spectral_grid.Grid\nnlat_half::Int64: resolution parameter nlathalf of Grid, # of latitudes on one hemisphere (incl Equator) Default: spectralgrid.nlat_half\nnlon_max::Int64: maximum number of longitudes (at/around Equator) Default: getnlonmax(Grid, nlat_half)\nnlon::Int64: =nlonmax, same (used for compatibility), TODO: still needed? Default: nlonmax\nnlat::Int64: number of latitude rings Default: getnlat(Grid, nlathalf)\nnlev::Int64: number of vertical levels Default: spectral_grid.nlev\nnpoints::Int64: total number of grid points Default: spectral_grid.npoints\nradius::AbstractFloat: Planet's radius [m] Default: spectral_grid.radius\nlatd::Vector{Float64}: array of latitudes in degrees (90˚...-90˚) Default: getlatd(Grid, nlathalf)\nlond::Vector{Float64}: array of longitudes in degrees (0...360˚), empty for non-full grids Default: getlond(Grid, nlathalf)\nlonds::Vector{NF} where NF<:AbstractFloat: longitude (-180˚...180˚) for each grid point in ring order Default: (getlatdlonds(Grid, nlathalf))[2]\nlatds::Vector{NF} where NF<:AbstractFloat: latitude (-90˚...˚90) for each grid point in ring order Default: (getlatdlonds(Grid, nlathalf))[1]\nsinlat::Vector{NF} where NF<:AbstractFloat: sin of latitudes Default: sind.(latd)\ncoslat::Vector{NF} where NF<:AbstractFloat: cos of latitudes Default: cosd.(latd)\ncoslat⁻¹::Vector{NF} where NF<:AbstractFloat: = 1/cos(lat) Default: 1 ./ coslat\ncoslat²::Vector{NF} where NF<:AbstractFloat: = cos²(lat) Default: coslat .^ 2\ncoslat⁻²::Vector{NF} where NF<:AbstractFloat: # = 1/cos²(lat) Default: 1 ./ coslat²\nσ_levels_half::Vector{NF} where NF<:AbstractFloat: σ at half levels, σk+1/2 Default: spectralgrid.verticalcoordinates.σhalf\nσ_levels_full::Vector{NF} where NF<:AbstractFloat: σ at full levels, σₖ Default: 0.5 * (σlevelshalf[2:end] + σlevelshalf[1:end - 1])\nσ_levels_thick::Vector{NF} where NF<:AbstractFloat: σ level thicknesses, σₖ₊₁ - σₖ Default: σlevelshalf[2:end] - σlevelshalf[1:end - 1]\nln_σ_levels_full::Vector{NF} where NF<:AbstractFloat: log of σ at full levels, include surface (σ=1) as last element Default: log.(vcat(σlevelsfull, 1))\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.GenLogisticCoefs","page":"Function and type index","title":"SpeedyWeather.GenLogisticCoefs","text":"Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF.\n\nFollowing the notation of https://en.wikipedia.org/wiki/Generalisedlogisticfunction (Dec 15 2021).\n\nChange default parameters for more/fewer levels in the stratosphere vs troposphere vs boundary layer.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.generalised_logistic","page":"Function and type index","title":"SpeedyWeather.generalised_logistic","text":"Generalised logistic function based on the coefficients in coefs.\n\n\n\n\n\n","category":"function"},{"location":"functions/#Time-stepping","page":"Function and type index","title":"Time stepping","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.time_stepping!\nSpeedyWeather.timestep!\nSpeedyWeather.first_timesteps!\nSpeedyWeather.leapfrog!","category":"page"},{"location":"functions/#SpeedyWeather.time_stepping!","page":"Function and type index","title":"SpeedyWeather.time_stepping!","text":"time_stepping!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n model::SpeedyWeather.ModelSetup\n) -> PrognosticVariables\n\n\nMain time loop that that initializes output and feedback, loops over all time steps and calls the output and feedback functions.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.timestep!","page":"Function and type index","title":"SpeedyWeather.timestep!","text":"timestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::Barotropic\n)\ntimestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::Barotropic,\n lf1::Int64\n)\ntimestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::Barotropic,\n lf1::Int64,\n lf2::Int64\n)\n\n\nCalculate a single time step for the model <: Barotropic.\n\n\n\n\n\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::ShallowWater\n) -> SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::ShallowWater,\n lf1::Int64\n) -> SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::ShallowWater,\n lf1::Int64,\n lf2::Int64\n) -> SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat\n\n\nCalculate a single time step for the model <: ShallowWater.\n\n\n\n\n\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::PrimitiveEquation\n) -> Any\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::PrimitiveEquation,\n lf1::Int64\n) -> Any\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::PrimitiveEquation,\n lf1::Int64,\n lf2::Int64\n) -> Any\n\n\nCalculate a single time step for the model<:PrimitiveEquation\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.first_timesteps!","page":"Function and type index","title":"SpeedyWeather.first_timesteps!","text":"first_timesteps!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n clock::SpeedyWeather.Clock,\n model::SpeedyWeather.ModelSetup,\n output::SpeedyWeather.AbstractOutputWriter\n) -> typeof(time)\n\n\nPerforms the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.leapfrog!","page":"Function and type index","title":"SpeedyWeather.leapfrog!","text":"leapfrog!(\n A_old::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n A_new::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n tendency::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n dt::Real,\n lf::Int64,\n L::Leapfrog{NF<:AbstractFloat}\n)\n\n\nPerforms one leapfrog time step with (lf=2) or without (lf=1) Robert+William's filter (see William (2009), Montly Weather Review, Eq. 7-9).\n\n\n\n\n\n","category":"function"},{"location":"functions/#Longwave-radiation","page":"Function and type index","title":"Longwave radiation","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.radset!\nSpeedyWeather.radlw_down!\nSpeedyWeather.compute_bbe!\nSpeedyWeather.radlw_up!","category":"page"},{"location":"functions/#Shortwave-radiation","page":"Function and type index","title":"Shortwave radiation","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.shortwave_radiation!\nSpeedyWeather.solar!\nSpeedyWeather.sol_oz!\nSpeedyWeather.cloud!\nSpeedyWeather.radsw!","category":"page"},{"location":"parametrizations/#Parameterizations","page":"Parameterizations","title":"Parameterizations","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmopshere. Every section is followed by a brief description of implementation details.","category":"page"},{"location":"parametrizations/#Convection","page":"Parameterizations","title":"Convection","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Large-scale-condensation","page":"Parameterizations","title":"Large-scale condensation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Clouds","page":"Parameterizations","title":"Clouds","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Short-wave-radiation","page":"Parameterizations","title":"Short-wave radiation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Long-wave-radiation","page":"Parameterizations","title":"Long-wave radiation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Surface-fluxes-of-momentum-and-energy","page":"Parameterizations","title":"Surface fluxes of momentum and energy","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Vertical-diffusion","page":"Parameterizations","title":"Vertical diffusion","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"how_to_run_speedy/#How-to-run-SpeedyWeather.jl","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The simplest way to run SpeedyWeather.jl with default parameters is","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"using SpeedyWeather\nrun_speedy()","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Hooray, you have just simulated the Earth's atmosphere. Parameters, their meanings and defaults are documented in Parameters. For example, if you want to run the primitive equation dry core (no humidity) simulation in double precision (Float64), at higher resolution (trunc, the triangular spectral truncation), slow down the rotation of the Earth (rotation in s^-1), and create some netCDF ouput, do","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"run_speedy(Float64,PrimitiveDry,trunc=42,planet=Earth(rotation=1e-5),output=true)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"If provided, the number format has to be the first argument, the model (Barotropic, ShallowWater, PrimitiveDry, PrimitiveWet are available) the second, and all other arguments are keyword arguments.","category":"page"},{"location":"how_to_run_speedy/#The-run_speedy-interface","page":"How to run SpeedyWeather.jl","title":"The run_speedy interface","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"run_speedy","category":"page"},{"location":"how_to_run_speedy/#SpeedyWeather.run_speedy","page":"How to run SpeedyWeather.jl","title":"SpeedyWeather.run_speedy","text":"progn_vars = run_speedy(NF,Model;kwargs...) or\nprogn_vars = run_speedy(NF;kwargs...) or\nprogn_vars = run_speedy(Model;kwargs...)\n\nRuns SpeedyWeather.jl with number format NF and the model Model and any additional parameters in the keyword arguments kwargs.... Unspecified parameters use the default values.\n\n\n\n\n\n","category":"function"},{"location":"how_to_run_speedy/#The-initialize_speedy-interface","page":"How to run SpeedyWeather.jl","title":"The initialize_speedy interface","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"initialize_speedy","category":"page"},{"location":"grids/#Grids","page":"Grids","title":"Grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and how they can be used.","category":"page"},{"location":"grids/#Ring-based-equi-longitude-grids","page":"Grids","title":"Ring-based equi-longitude grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"SpeedyWeather.jl's spectral transform currently only supports ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.","category":"page"},{"location":"grids/#Implemented-grids","page":"Grids","title":"Implemented grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Currently the following full grids <: AbstractFullGrid are implemented","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"FullGaussianGrid, a full grid with Gaussian latitudes\nFullClenshawGrid, a full grid with equi-angle latitudes","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron\nOctahedralClenshawGrid, similar but based on equi-angle latitudes\nHEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces\nOctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"An overview of these grids is visualised here","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"(Image: Overview of implemented grids in SpeedyWeather.jl)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.","category":"page"},{"location":"grids/#Resolution","page":"Grids","title":"Resolution","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half. ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: HEALPix grids do not use Nside as resolution parameter\nThe original formulation for HEALPix grids use N_side, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use N_side for the documentation or within functions though.","category":"page"},{"location":"grids/#Truncation","page":"Grids","title":"Truncation","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation T with a grid resolution N (=nlat_half) such that T + 1 = N. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at l_max=31 in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Let J be the total number of rings. Then we have","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"T approx J for linear truncation\nfrac32T approx J for quadratic truncation\n2T approx J for cubic truncation","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and in general fracm+12T approx J for m-th order truncation. So the higher the truncaction order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. In SpeedyWeather.jl the parameter dealiasing controls this option, = 1 would be linear, = 2 quadratic, = 3 cubic etc.","category":"page"},{"location":"grids/#Full-Gaussian-grid","page":"Grids","title":"Full Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#Full-Clenshaw-Curtis-grid","page":"Grids","title":"Full Clenshaw-Curtis grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#Octahedral-Gaussian-grid","page":"Grids","title":"Octahedral Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#The-HEALPix-grid","page":"Grids","title":"The HEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are N_varphi basepixels in zonal direction and N_theta basepixels in meridional direction. For N_varphi = 4 and N_theta = 3 we obtain the classical HEALPix grid with N_varphi N_theta = 12 basepixels shown above in Implemented Grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always 2N, so 32 at N=16) and there are polar caps above and below the equatorial zone with the border at cos(theta) = 23 (theta in colatitudes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Following Górski, 2004[1], the z=cos(theta) colatitude of the j-th ring in the north polar cap, j=1N_side with 2N_side = N is ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^23N_side^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and on that ring, the longitude phi of the i-th point (i is the in-ring-index) is at","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i-tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The in-ring index i goes from i=14 for the first (i.e. northern-most) ring, i=18 for the second ring and i = 14j for the j-th ring in the northern polar cap.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"In the north equatorial belt j=N_side2N_side this changes to","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac43 - frac2j3N_side","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and the longitudes change to (i is always i = 14N_side in the equatorial belt meaning the number of longitude points is constant here)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2N_side(i - fracs2) quad s = (j - N_side + 1) mod 2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The cell boundaries are obtained by setting i = k + 12 or i = k + 12 + j (half indices) into the equations above, such that z(phik), a function for the cosine of colatitude z of index k and the longitude phi is obtained. These are then (northern polar cap)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^23N_side^2left(fracpi2phi_tright)^2 quad z = 1 - frack^23N_side^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with phi_t = phi mod tfracpi2 and in the equatorial belt","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac23-frac4k3N_side pm frac8phi3pi","category":"page"},{"location":"grids/#OctaHEALPix-grid","page":"Grids","title":"OctaHEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"While the classic HEALPix grid is based on a dodecahedron, other choices for N_varphi and N_theta in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With N_varphi = 4 and N_theta = 1 we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, 2pi around the Equator versus pi between the poles. N_varphi = 6 N_theta = 2 or N_varphi = 8 N_theta = 3 are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visulations of these grids.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"We call the N_varphi = 4 N_theta = 1 HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As N_theta = 1 there is no equatorial belt which simplifies the grid. The latitude of the j-th isolatitude ring on the OctaHEALPixGrid is defined by","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^2N^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with j=1N, and similarly for the southern hemisphere by symmetry. On this grid N_side = N where N= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index i = 14j are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i - tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and again, the southern hemisphere grid points are obtained by symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries-2","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Similar to the grid cell boundaries for the HEALPix grid, the OctaHEALPix grid's boundaries are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^2N^2left(fracpi2phi_tright)^2 quad z = 1 - frack^2N^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The 3N_side^2 in the denominator of the HEALPix grid came simply N^2 for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).","category":"page"},{"location":"grids/#References","page":"Grids","title":"References","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"[1] Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976","category":"page"},{"location":"boundary_conditions/#Boundary-conditions","page":"Boundary conditions","title":"Boundary conditions","text":"","category":"section"},{"location":"boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"This page describes the formulation of boundary conditions and their implementation.","category":"page"},{"location":"conventions/#Style-and-convention-guide","page":"Style and convention guide","title":"Style and convention guide","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"In SpeedyWeather.jl we've been following the several conventions that are documented here.","category":"page"},{"location":"conventions/#Variable-naming","page":"Style and convention guide","title":"Variable naming","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The prognostic variables in spectral space are called","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" vor # Vorticity of horizontal wind field\n div # Divergence of horizontal wind field\n temp # Absolute temperature [K]\n pres_surf # Logarithm of surface pressure [log(Pa)]\n humid # Specific humidity [g/kg]","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" u\n v\n geopot\n ...","category":"page"},{"location":"conventions/#Preallocation","page":"Style and convention guide","title":"Preallocation","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"All arrays representing variables are preallocated and grouped into two structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" Prog::PrognosticVariables\n Diag::DiagnosticVariables","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The Diag struct contains further structs which represent the grid-point transformations of the prognostic variables and their tendencies.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" gridvars::GridVariables\n tendencies::Tendencies\n ...","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Constant arrays are grouped into several structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Boundaries","category":"page"},{"location":"conventions/#Julian-conventions","page":"Style and convention guide","title":"Julian conventions","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"We follow Julia's style guide and highlight here some important aspects of it.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Bang (!) convention. A function func does not change its input arguments, however, func! does.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.","category":"page"},{"location":"new_model_setups/#New-model-setups","page":"New model setups","title":"New model setups","text":"","category":"section"},{"location":"new_model_setups/","page":"New model setups","title":"New model setups","text":"more to come...","category":"page"},{"location":"dynamical_core/#Dynamical-core","page":"Dynamical core","title":"Dynamical core","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"A mathematical and implementation-specific description of the dynamical core used in SpeedyWeather.jl. We start by describing the barotropic vorticity equations which is one set of equations that SpeedyWeather.jl can solve (see How to run SpeedyWeather.jl) as many details therein also apply to the Shallow water equations and Primitive equations explained thereafter.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The dynamical core presented here largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2], a shallow water model [3] and a primitive equation model[4]. ","category":"page"},{"location":"dynamical_core/#Barotropic-vorticity-equation","page":"Dynamical core","title":"Barotropic vorticity equation","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity zeta with advection, Coriolis force and diffusion in a single global layer.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"fracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) = (-1)^n+1nunabla^2nzeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with time t, velocity vector mathbfu = (u v), Coriolis parameter f, and hyperdiffusion (-1)^n+1 nu nabla^2n zeta (n is the hyperdiffusion order; see Horizontal diffusion). Starting with some relative vorticity zeta, the Laplacian is inverted to obtain the stream function Psi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Psi = nabla^-2zeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The zonal velocity u and meridional velocity v are then the (negative) meridional gradient and zonal gradient of Psi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nu = -frac1R fracpartial Psipartial theta \nv = frac1Rcos(theta) fracpartial Psipartial phi \nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"which is described in Derivatives in spherical coordinates.","category":"page"},{"location":"dynamical_core/#Algorithm","page":"Dynamical core","title":"Algorithm","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"We briefly outline the algorithm that SpeedyWeather.jl uses in order to integrate the barotropic vorticity equation","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Start with initial conditions of zeta_lm in spectral space\nUse zeta_lm to\nInvert the Laplacian to obtain the stream function Psi_lm in spectral space\nTransform zeta_lm to zeta in grid-point space\nUse Psi_lm to\nobtain zonal velocity (cos(theta)u)_lm through a Meridional derivative\nobtain meridional velocity (cos(theta)v)_lm through a Zonal derivative\nTransform zonal and meridional velocity (cos(theta)u)_lm, (cos(theta)v)_lm to grid-point space and unscale the cos(theta) factor to obtain uv.\nMultiply uv with zeta+f in grid-point space\nTransform u(zeta + f) and v(zeta+f) to spectral space\nCompute the divergence of (mathbfu(zeta + f))_lm in spectral space through a Meridional derivative and Zonal derivative which will be the tendency of zeta_lm\nCompute the Horizontal diffusion based on that tendency\nCompute a leapfrog time step as described in Time integration\nRepeat from 1.","category":"page"},{"location":"dynamical_core/#Shallow-water-equations","page":"Dynamical core","title":"Shallow water equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nfracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) = (-1)^n+1nunabla^2nzeta \nfracpartial mathcalDpartial t - nabla times (mathbfu(zeta + f)) = -nabla^2(tfrac12(u^2 + v^2) + geta) + (-1)^n+1nunabla^2nmathcalD \nfracpartial etapartial t + nabla cdot (mathbfuh) = 0\nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"more to come","category":"page"},{"location":"dynamical_core/#Primitive-equations","page":"Dynamical core","title":"Primitive equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The primitive equations solved by SpeedyWeather.jl are","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\npartial_t u = \npartial_t v = \npartial_t T = \npartial_t Q = \nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"more to come","category":"page"},{"location":"dynamical_core/#Horizontal-diffusion","page":"Dynamical core","title":"Horizontal diffusion","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In SpeedyWeather.jl we use hyerdiffusion through an n-th power Laplacian (-1)^n+1nabla^2n (hyper when n1) which can be implemented as a multiplication of the spectral coefficients Psi_lm with (-l(l+1))^nR^-2n (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the (-l(l+1))^nR^-2n can be precomputed. Note the sign change (-1)^n+1 here is such that the dissipative nature of the diffusion operator is retained for n odd and even.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step Delta t of variable zeta to obtain from time steps i-1 and i, the next time step i+1","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t dzeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with dzeta being some tendency evaluated from zeta_i. Now we want to add a diffusion term (-1)^n+1nu nabla^2nzeta with viscosity nu, wich however, is implicitly calculated from zeta_i+1, then","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t (dzeta + (-1)^n+1 nunabla^2nzeta_i+1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"As the application of (-1)^n+1nunabla^2n is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = fraczeta_i-1 + 2Delta t dzeta1 - 2Delta (-1)^n+1nunabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and expand the numerator to","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t fracdzeta + (-1)^n+1 nunabla^2nzeta_i-11 - 2Delta t (-1)^n+1nu nabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Hence the diffusion can be applied implicitly by updating the tendency dzeta as","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"dzeta to fracdzeta + (-1)^n+1nunabla^2nzeta_i-11 - 2Delta t nu nabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"which only depends on zeta_i-1. Now let D_textexplicit = (-1)^n+1nunabla^2n be the explicit part and D_textimplicit = 1 - (-1)^n+1 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are D_textimplicit = 1 - 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic lm we do","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"dzeta to D_textimplicit^-1(dzeta + D_textexplicitzeta_i-1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power n.","category":"page"},{"location":"dynamical_core/#Normalization-of-diffusion","page":"Dynamical core","title":"Normalization of diffusion","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In physics, the Laplace operator nabla^2 is often used to represent diffusion due to viscosity in a fluid. In that case, the viscosity coefficient is nu of units textm^2texts^-1 and the full operator reads as nu nabla^2 with units (textm^2texts^-1)(textm^-2) = texts^-1. This motivates us to normalize the Laplace operator by a constant of units textm^-2 and the viscosity coefficient by its inverse such that the viscosity coefficient becomes a damping timescale of unit texts^-1. Given the application in spectral space we decide to normalize by the largest eigenvalue -l_textmax(l_textmax+1) such that all entries in the discrete spectral Laplace operator are in 01. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized viscosity coefficient nu^* = l_textmax(l_textmax+1)nu (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have ","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"D^textexplicit_lm = -nu^* fracl(l+1)l_textmax(l_textmax+1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and the hyper-Laplacian of power n follows as","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"D^textexplicitn_lm = -nu^* left(fracl(l+1)l_textmax(l_textmax+1)right)^n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and the implicit part is accordingly D^textimplicitn_lm = 1 - 2Delta t D^textexplicitn_lm.","category":"page"},{"location":"dynamical_core/#Radius-scaling","page":"Dynamical core","title":"Radius scaling","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"SpeedyWeather.jl uses a scaling for vorticity zeta and stream function Psi that is","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildezeta = zeta R tildePsi = Psi R^-1","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In the barotropic voriticity equation model the inversion of the Laplcians in order to obtain Psi from zeta therefore becomes","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildezeta = tildenabla^2 tildePsi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"where the dimensionless gradients simply omit the scaling with 1R, tildenabla = Rnabla. The Barotropic vorticity equation scaled with R^2 is","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"partial_tildettildezeta + tildenabla cdot (mathbfu(tildezeta + tildef)) = tildenutildenabla^2ntildezeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildet = tR^-1, the scaled time t\nmathbfu = (uv), the velocity vector (no scaling applied)\ntildef = fR, the scaled Coriolis parameter f\ntildenu = nu^* R, the scaled viscosity nu^*, which itself is normalized to a damping time scale, see Normalization of diffusion.","category":"page"},{"location":"dynamical_core/#Scaled-shallow-water-equations","page":"Dynamical core","title":"Scaled shallow water equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Similar to the scaled barotropic vorticity equations, the scaled shallow water equations scale the vorticity and the divergence equation with R^2, but the continuity equation with R","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nfracpartial tildezetapartial tildet + tildenabla cdot (mathbfu(tildezeta + tildef)) =\ntildenutildenabla^2ntildezeta \nfracpartial tildemathcalDpartial tildet - tildenabla times (mathbfu(tildezeta + tildef)) =\n-tildenabla^2left(tfrac12(u^2 + v^2) + geta right) + tildenutildenabla^2ntildemathcalD \nfracpartial etapartial tildet + tildenabla cdot (mathbfuh) = 0\nendaligned","category":"page"},{"location":"dynamical_core/#Time-integration","page":"Dynamical core","title":"Time integration","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"SpeedyWeather.jl uses a leapfrog time scheme with a Robert's and William's filter to dampen the computational mode and achieve 3rd order accuracy.","category":"page"},{"location":"dynamical_core/#Oscillation-equation","page":"Dynamical core","title":"Oscillation equation","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"fracdFdt = iomega F","category":"page"},{"location":"dynamical_core/#References","page":"Dynamical core","title":"References","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[1]: Geophysical Fluid Dynamics Laboratory, Idealized models with spectral dynamics","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[2]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[3]: Geophysical Fluid Dynamics Laboratory, The Shallow Water Equations.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[4]: Geophysical Fluid Dynamics Laboratory, The Spectral Dynamical Core","category":"page"},{"location":"spectral_transform/#Spherical-Harmonic-Transform","page":"Spherical harmonic transform","title":"Spherical Harmonic Transform","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space on a longitude-latitude regular Gaussian grid.","category":"page"},{"location":"spectral_transform/#Inspiration","page":"Spherical harmonic transform","title":"Inspiration","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform implemented by SpeedyWeather.jl follows largely Justin Willmert's CMB.jl package and makes use of AssociatedLegendrePolynomials.jl and FFTW.jl (for Float32/64) or GenericFFT.jl (for generic) for the Fourier transform. Justin described his work in a Blog series [1][2][3][4][5][6][7][8].","category":"page"},{"location":"spectral_transform/#Spherical-harmonics","page":"Spherical harmonic transform","title":"Spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spherical harmonics Y_lm of degree l and order m over the longitude phi = (02pi) and colatitudes theta = (-pi2pi2), are","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Y_lm(phi theta) = lambda_l^m(sintheta) e^imphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with lambda_l^m being the pre-normalized associated Legendre polynomials, and e^imphi are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Latitudes versus colatitudes\nThe implementations of the spherical transforms in SpeedyWeather.jl use colatitudes theta = (0pi) (0 at the north pole) but the dynamical core uses latitudes theta = (-pi2pi2) (pi2 at the north pole). However, all arrays are always sorted north to south such that [i,1] will access the northern-most grid points. Note: We may also use latitudes in the spherical harmonic transform in the future for consistency. ","category":"page"},{"location":"spectral_transform/#Synthesis-(spectral-to-grid)","page":"Spherical harmonic transform","title":"Synthesis (spectral to grid)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The synthesis (or inverse transform) takes the spectral coefficients a_lm and transforms them to grid-point values f(phitheta) (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics Y_lm with non-zero coefficients.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"f(phitheta) = sum_l=0^infty sum_m=-l^l a_lm Y_lm(phitheta)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We obtain an approximation with a finite set of a_lm by truncating the series after l = l_max.","category":"page"},{"location":"spectral_transform/#Analysis-(grid-to-spectral)","page":"Spherical harmonic transform","title":"Analysis (grid to spectral)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting in grid-point space we can transform a field f(lambdatheta) into the spectral space of the spherical harmonics by","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"a_lm = int_0^2pi int_-tfracpi2^tfracpi2 f(lambdatheta) Y_lm(lambdatheta) cos theta dtheta dlambda","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This integral has to be discretized to when grid-point values f(lambda_itheta_i) are used. For more details, see [7],[8].","category":"page"},{"location":"spectral_transform/#Spectral-packing","page":"Spherical harmonic transform","title":"Spectral packing","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Spectral packing is the way how the coefficients a_lm of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree l and order m as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Every row represents an order l geq 0, starting from l=0 at the top. Every column represents an order m geq 0, starting from m=0 on the left. The coefficients of these spherical harmonics are directly mapped into a matrix a_lm as ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 \n a_10 a_11 \n a_20 a_12 a_22 \n a_30 a_13 a_23 a_33","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal explicitly stores zeros. Note that internally vector fields include an additional degree, such that l_max = m_max + 1 (see Gradients in spherical coordinates for more information). The harmonics with a_l0 (the first column) are also called zonal harmonics as they are constant with longitude phi. The harmonics with a_ll (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into 2l sectors in longitude phi without a zero-crossing in latitude.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Array indices\nFor a spectral field alms note that due to Julia's 1-based indexing the coefficient a_lm is obtained via alms[l+1,m+1].","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Fortran speedy does not use the same spectral packing as SpeedyWeather.jl. The alternative packing lm therein uses l=m and m=l-m as summarized in the following table.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"degree l order m l=m m=l-m\n0 0 0 0\n1 0 0 1\n1 1 1 0\n2 0 0 2\n2 1 1 1\n2 2 2 0\n3 0 0 3\n... ... ... ...","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 a_10 a_20 a_30\n a_11 a_21 a_31 \n a_22 a_32 \n a_33 ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran-speedy.","category":"page"},{"location":"spectral_transform/#Example-transforms","page":"Spherical harmonic transform","title":"Example transforms","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"julia> using SpeedyWeather\njulia> alms = zeros(ComplexF64,3,3) # spectral coefficients\njulia> alms[2,2] = 1 # only l=1,m=1 harmonic\njulia> map = gridded(alms) # convert to grid space\n8×4 Matrix{Float64}:\n -0.324541 -0.600363 -0.600363 -0.324541\n -0.134429 -0.248678 -0.248678 -0.134429\n 0.134429 0.248678 0.248678 0.134429\n 0.324541 0.600363 0.600363 0.324541\n 0.324541 0.600363 0.600363 0.324541\n 0.134429 0.248678 0.248678 0.134429\n -0.134429 -0.248678 -0.248678 -0.134429\n -0.324541 -0.600363 -0.600363 -0.324541\n \njulia> spectral(map) # back to spectral space\n3×3 Matrix{ComplexF64}:\n 0.0+0.0im 0.0+0.0im 0.0+0.0im\n 0.0+0.0im 1.0+3.60727e-17im 0.0+0.0im\n 0.0+0.0im 0.0+0.0im 0.0+0.0im","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"and we have successfully reobtained the l=m=1 spherical harmonic.","category":"page"},{"location":"spectral_transform/#Available-horizontal-resolutions","page":"Spherical harmonic transform","title":"Available horizontal resolutions","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with l leq l_max and m leq m_max are explicitly represented. This is usually described as Tm_max, with l_max = m_max (although in vector quantities require one more degree l in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with l_max = m_max = 31. Note that the degree l and order m are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Using triangular truncation[9], there are constraints on the corresponding grid resolution. Let nlon, nlat be the number of longitudes, latitudes on a regular Gaussian grid. Then spectral and grid resolution have to be chosen such that","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nlon geq 3l_max+1\nnlat geq (3l_max+1)2","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In general, we choose nlon = 2nlat, and ideally nlon is easily Fourier-transformable, e.g. nlon = 2^i3^j5^k with some integers ijk geq 0. SpeedyWeather.jl is tested at the following horizontal resolutions, with Delta x = tfrac2pi Rnlon as the approximate grid spacing at the Equator","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"l_max nlon nlat Delta x\n31 (default) 96 48 400 km\n42 128 64 300 km\n85 256 128 160 km\n170 512 256 80 km\n341 1024 512 40 km\n682 2048 1024 20 km","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Choosing trunc as argument in run_speedy will automatically choose nlon,nlat as presented in the table. Other common choices are T63 (192x96), T127 (384x192), T255 (768x384), T511 (1536x768), among others.","category":"page"},{"location":"spectral_transform/#Derivatives-in-spherical-coordinates","page":"Spherical harmonic transform","title":"Derivatives in spherical coordinates","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Horizontal gradients in spherical coordinates are defined for a scalar field A and the latitudes theta and longitudes lambda as","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla A = left(frac1Rcosthetafracpartial Apartial lambda frac1Rfracpartial Apartial theta right)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"However, the divergence of a vector field mathbfu = (uv) includes additional cos(theta) scalings","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla cdot mathbfu = frac1Rcosthetafracpartial upartial lambda +\nfrac1Rcosthetafracpartial (v costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"and similar for the curl","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla times mathbfu = frac1Rcosthetafracpartial vpartial lambda -\nfrac1Rcosthetafracpartial (u costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The radius of the sphere (i.e. Earth) is R. The zonal gradient scales with 1cos(theta) as the longitudes converge towards the poles (note that theta describes latitudes here, defintions using colatitudes replace the cos with a sin.)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting with a spectral field of vorticity zeta and divergence mathcalD one can obtain stream function Psi and velocity potential Phi by inverting the Laplace operator nabla^2:","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi = nabla^-2zeta quad Phi = nabla^-2mathcalD","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The velocities uv are then obtained from (uv) = nabla^botPsi + nablaPhi following the defintion from above and nabla^bot = (-R^-1partial_theta (Rcostheta)^-1partial_lambda)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nu = -frac1Rpartial_thetaPsi + frac1Rcosthetapartial_lambdaPhi \nv = +frac1Rpartial_thetaPhi + frac1Rcosthetapartial_lambdaPsi\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Alternatively, we can use the velocities U = ucostheta V = vcostheta, which we do as the meridional gradient for spherical harmonics is easier implemented with a costheta-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with UV and not uv. From uv we can return to zeta mathcalD via","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nzeta = frac1Rcosthetapartial_lambda v - frac1Rcosthetapartial_theta (u costheta) \nmathcalD = frac1Rcosthetapartial_lambda u + frac1Rcosthetapartial_theta (v costheta)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Equivalently, we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU = -fraccosthetaRpartial_thetaPsi + frac1Rpartial_lambdaPhi \nV = +fraccosthetaRpartial_thetaPhi + frac1Rpartial_lambdaPsi \nzeta = frac1Rpartial_lambda left( fracVcos^2theta right) -\nfraccosthetaRpartial_theta left( fracUcos^2theta right) \nmathcalD = frac1Rpartial_lambda left( fracUcos^2theta right) +\nfraccosthetaRpartial_theta left( fracVcos^2theta right)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is a more convenient formulation as required costheta scalings are reduced to a minimum. The remaining (UV)*cos^-2theta are done in grid-point space and usually in combination with other operations like the computation of the vorticity flux. But also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out too. As described in Meridional derivative, it is more convenient to implement costheta partial_theta via a recursion relation for the Legendre polynomials than partial_theta directly. How the operators nabla nabla times nabla cdot can be implemented with spherical harmonics is presented in the following sections.","category":"page"},{"location":"spectral_transform/#Zonal-derivative","page":"Spherical harmonic transform","title":"Zonal derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The zonal derivative of a scalar field Psi in spectral space is the zonal derivative of all its respective spherical harmonics Psi_lm(phitheta) (now we use phi for longitudes to avoid confusion with the Legendre polynomials lambda_lm)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"v_lm = frac1R cos(theta) fracpartialpartial phi left( lambda_l^m(costheta) e^imphi right) =\nfracimR cos(theta) lambda_l^m(costheta) e^imphi = fracimR cos(theta) Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"So for every spectral harmonic, cos(theta)v_lm is obtained from Psi_lm via a multiplication with imR. Unscaling the cos(theta)-factor is done after transforming the spectral coefficients v_lm into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as tildePsi = R^-1Psi such that the division by radius R in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order im. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number m times imaginary i.","category":"page"},{"location":"spectral_transform/#Meridional-derivative","page":"Spherical harmonic transform","title":"Meridional derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridioinal derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costheta fracdP_lmdtheta = -lepsilon_l+1mP_l+1m + (l+1)epsilon_lmP_l-1m","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with recursion factors","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"epsilon_lm = sqrtfracl^2-m^24l^2-1","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In the following we use the example of obtaining the zonal velocity u from the stream function Psi, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi(lambdatheta) = sum_lmPsi_lmP_lm(sintheta)e^imlambda","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"we multiply with -R^-1costhetapartial_theta to obtain","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costhetaleft(-frac1Rpartial_thetaPsi right) = -frac1Rsum_lmPsi_lme^imlambdacosthetapartial_theta P_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"at which point the recursion from above can be applied. Collecting terms proportional to P_lm then yields","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"(cos(theta)u)_lm = -frac1R(-(l-1)epsilon_lmPsi_l-1m + (l+2)epsilon_l+1mPsi_l+1m)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"To obtain the coefficient of each spherical harmonic lm of the meridional gradient of a spectral field, two coefficients at l-1m and l+1m have to be combined. This means that the coefficient of a gradient ((costheta) u)_lm is a linear combination of the coefficients of one higher and one lower degree Psi_l+1mPsi_l-1m. As the coefficient Psi_lm with ml are zero, the sectoral harmonics (l=m) of the gradients are obtained from the first off-diagonal only. However, the l=l_max harmonics of the gradients require the l_max-1 as well as the l_max+1 harmonics. In SpeedyWeather.jl vector quantitie like uv use therefore one more meridional mode than scalar quantities such as vorticity zeta or stream function Psi. The meridional derivative in SpeedyWeather.jl also omits the 1R-scaling as explained for the Zonal derivative and in Radius scaling.","category":"page"},{"location":"spectral_transform/#Divergence-and-curl-in-spherical-harmonics","page":"Spherical harmonic transform","title":"Divergence and curl in spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridional gradient as described above can be applied to scalars, such as Psi and Phi in the conversion to velocities (uv) = nabla^botPsi + nablaPhi, however, the operators curl nabla times and divergence nabla cdot in spherical coordinates involve a costheta scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform of vorticity zeta is","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac12piint_-tfracpi2^tfracpi2int_0^2pi zeta(lambdatheta) P_lm(sintheta) e^imlambda dlambda costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Given that Rzeta = cos^-1partial_lambda v - cos^-1partial_theta (u costheta), we therefore have to evaluate a meridional integral of the form","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"int P_lm frac1cos theta partial_theta(u costheta)) cos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which can be solved through integration by parts. As ucostheta = 0 at theta = pm tfracpi2 only the integral","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int partial_theta P_lm (u costheta) dtheta = -int costheta partial_theta P_lm (fracucostheta) costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"remains. Inserting the recurrence relation from the Meridional derivative turns this into","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int left(-l epsilon_l+1mP_l+1m + (l+1)epsilon_lm P_l-1m right) (fracucostheta) cos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Now we expand (tfracucostheta) but only the lm harmonic will project ontoP_lm. Let u^* = ucos^-1theta v^* = vcos^-1theta we then have in total","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nRzeta_lm = imv^*_lm + (l+1)epsilon_lmu^*_l-1m - lepsilon_l+1mu^*_l+1m \nRD_lm = imu^*_lm - (l+1)epsilon_lmv^*_l-1m + lepsilon_l+1mv^*_l+1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"And the divergence D is similar, but (uv) to (-vu). We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#Laplacian","page":"Spherical harmonic transform","title":"Laplacian","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral Laplacian is easily applied to the coefficients Psi_lm of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator nabla^2 in spherical coordinates with eigenvalues -l(l+1) divided by the radius squared R^2, i.e. nabla^2 Psi becomes tfrac-l(l+1)R^2Psi_lm in spectral space. For example, vorticity zeta and streamfunction Psi are related by zeta = nabla^2Psi in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree l and order m to","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac-l(l+1)R^2Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This can be easily inverted to obtain the stream function Psi from vorticity zeta instead. In order to avoid division by zero, we set Psi_00 here, given that the stream function is only defined up to a constant anyway.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nPsi_lm = fracR^2-l(l+1)zeta_lm quad foralllm 0\nPsi_00 = 0\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"See also Horizontal diffusion and Normalization of diffusion.","category":"page"},{"location":"spectral_transform/#U,V-from-vorticity-and-divergence","page":"Spherical harmonic transform","title":"U,V from vorticity and divergence","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity zeta and divergence D (which are prognostic variables) to U=ucostheta V=vcostheta. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree l (the meridional gradient). It is therefore computationally more efficient to compute UV directly from zetaD instead of calculating stream function and velocity potential first. In total we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU_lm = -fraciml(l+1)(RD)_lm + fracepsilon_l+1ml+1(Rzeta)_l+1m - fracepsilon_lml(Rzeta)_l-1m \nV_lm = -fraciml(l+1)(Rzeta)_lm - fracepsilon_l+1ml+1(RD)_l+1m + fracepsilon_lml(RD)_l-1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#References","page":"Spherical harmonic transform","title":"References","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[1]: Justin Willmert, 2020. Introduction to Associated Legendre Polynomials (Legendre.jl Series, Part I)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[2]: Justin Willmert, 2020. Calculating Legendre Polynomials (Legendre.jl Series, Part II)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[3]: Justin Willmert, 2020. Pre-normalizing Legendre Polynomials (Legendre.jl Series, Part III)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[4]: Justin Willmert, 2020. Maintaining numerical accuracy in the Legendre recurrences (Legendre.jl Series, Part IV)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[5]: Justin Willmert, 2020. Introducing Legendre.jl (Legendre.jl Series, Part V)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[6]: Justin Willmert, 2020. Numerical Accuracy of the Spherical Harmonic Recurrence Coefficient (Legendre.jl Series Addendum)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[7]: Justin Willmert, 2020. Notes on Calculating the Spherical Harmonics","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[8]: Justin Willmert, 2022. More Notes on Calculating the Spherical Harmonics: Analysis of maps to harmonic coefficients","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[9]: David Randall, 2021. An Introduction to Numerical Modeling of the Atmosphere, Chapter 22.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[10]: Dale Durran, 2010. Numerical Methods for Fluid Dynamics, Springer. In particular section 6.2, 6.4.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[11]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"#SpeedyWeather.jl-documentation","page":"Home","title":"SpeedyWeather.jl documentation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.","category":"page"},{"location":"#Overview","page":"Home","title":"Overview","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to perform some calculations in spectral space (time integration, gradients, linear terms) and some in grid-point space (advection, non-linear terms, parameterizations). The prognostic variables used are vorticity, divergence, absolute temperature, logarithm of surface pressure and specific humidity. The time stepping uses a leapfrog scheme with additional filters and a semi-implicit formulation for gravity waves. The default resolution is T31 (96x48 grid points on a regular Gaussian grid, about 400km at the Equator) and 8 vertical levels.","category":"page"},{"location":"","page":"Home","title":"Home","text":"Simple parameterizations are used to represent the physical processes convection, large-scale condensation, clouds, short-wave radiation, long-waves radiation, surface fluxes of momentum and energy, and vertical diffusion.","category":"page"},{"location":"#Manual-outline","page":"Home","title":"Manual outline","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"See the following pages of the documentation for more details","category":"page"},{"location":"","page":"Home","title":"Home","text":"How to run SpeedyWeather.jl\nSpherical harmonic transform\nGrids\nDynamical core\nParametrizations\nNew model setups\nFunction and type index","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the original documentation by Molteni and Kucharski.","category":"page"},{"location":"#Scope","page":"Home","title":"Scope","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The focus of SpeedyWeather.jl is to develop a global atmospheric model of intermediate complexity, that can run at various levels of precision (16, 32 and 64-bit) on different architectures (x86 and ARM, GPUs in the future). Additionally, the model is written in an entirely number format-flexible way, such that any custom number format can be used and Julia will compile to the format automatically. Similarly, many model components are written in an abstract way to support modularity and extandability.","category":"page"},{"location":"#History","page":"Home","title":"History","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl is a Julia implementation of SPEEDY, which is written in Fortran 77. Sam Hatfield translated SPEEDY to Fortran 90 and started the project to port it to Julia. However, we are making an effort to overhaul the implementation of the mathematical model behind speedy completely and it is unlikely that a single line of code survived.","category":"page"},{"location":"#Installation","page":"Home","title":"Installation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl is registered in the Julia Registry. Open Julia's package manager from the REPL with ] and add the github repository to install SpeedyWeather.jl and all dependencies","category":"page"},{"location":"","page":"Home","title":"Home","text":"(@v1.8) pkg> add SpeedyWeather","category":"page"},{"location":"","page":"Home","title":"Home","text":"which will automatically install the latest release. However, you may want to install directly from the main branch with","category":"page"},{"location":"","page":"Home","title":"Home","text":"(@v1.8) pkg> add https://github.com/SpeedyWeather/SpeedyWeather.jl#main","category":"page"},{"location":"","page":"Home","title":"Home","text":"other branches than #main can be installed by adding #branch_name instead.","category":"page"},{"location":"#Developers","page":"Home","title":"Developers","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The development of SpeedyWeather.jl is lead by Milan Klöwer and current and past contributors include","category":"page"},{"location":"","page":"Home","title":"Home","text":"Tom Kimpson\nAlistair White\nMaximilian Gelbrecht\nDavid Meyer\nDaisuke Hotta","category":"page"},{"location":"","page":"Home","title":"Home","text":"Any contributions are always welcome!","category":"page"},{"location":"#Funding","page":"Home","title":"Funding","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Contributors received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022.","category":"page"},{"location":"time_integration/#Time-integration","page":"Time integration","title":"Time integration","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"SpeedyWeather.jl uses a leapfrog time scheme with a Robert's and William's filter to dampen the computational mode and achieve 3rd order accuracy.","category":"page"},{"location":"time_integration/#Oscillation-equation","page":"Time integration","title":"Oscillation equation","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"fracdFdt = iomega F","category":"page"},{"location":"time_integration/#Implementation-details","page":"Time integration","title":"Implementation details","text":"","category":"section"}]
+}
diff --git a/previews/PR334/siteinfo.js b/previews/PR334/siteinfo.js
new file mode 100644
index 000000000..52ca05f18
--- /dev/null
+++ b/previews/PR334/siteinfo.js
@@ -0,0 +1 @@
+var DOCUMENTER_CURRENT_VERSION = "previews/PR334";
diff --git a/previews/PR334/spectral_transform/index.html b/previews/PR334/spectral_transform/index.html
new file mode 100644
index 000000000..7c514edd6
--- /dev/null
+++ b/previews/PR334/spectral_transform/index.html
@@ -0,0 +1,45 @@
+
+Spherical harmonic transform · SpeedyWeather.jl
The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space on a longitude-latitude regular Gaussian grid.
with $\lambda_l^m$ being the pre-normalized associated Legendre polynomials, and $e^{im\phi}$ are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.
Latitudes versus colatitudes
The implementations of the spherical transforms in SpeedyWeather.jl use colatitudes $\theta = (0,\pi)$ (0 at the north pole) but the dynamical core uses latitudes $\theta = (-\pi/2,\pi/2)$ ($\pi/2$ at the north pole). However, all arrays are always sorted north to south such that [i,1] will access the northern-most grid points. Note: We may also use latitudes in the spherical harmonic transform in the future for consistency.
The synthesis (or inverse transform) takes the spectral coefficients $a_{lm}$ and transforms them to grid-point values $f(\phi,\theta)$ (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics $Y_{lm}$ with non-zero coefficients.
Spectral packing is the way how the coefficients $a_{lm}$ of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree $l$ and order $m$ as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)
Every row represents an order $l \geq 0$, starting from $l=0$ at the top. Every column represents an order $m \geq 0$, starting from $m=0$ on the left. The coefficients of these spherical harmonics are directly mapped into a matrix $a_{lm}$ as
$m$
$l$
$a_{00}$
$a_{10}$
$a_{11}$
$a_{20}$
$a_{12}$
$a_{22}$
$a_{30}$
$a_{13}$
$a_{23}$
$a_{33}$
which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal explicitly stores zeros. Note that internally vector fields include an additional degree, such that $l_{max} = m_{max} + 1$ (see Gradients in spherical coordinates for more information). The harmonics with $a_{l0}$ (the first column) are also called zonal harmonics as they are constant with longitude $\phi$. The harmonics with $a_{ll}$ (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into $2l$ sectors in longitude $\phi$ without a zero-crossing in latitude.
Array indices
For a spectral field alms note that due to Julia's 1-based indexing the coefficient $a_{lm}$ is obtained via alms[l+1,m+1].
Fortran speedy does not use the same spectral packing as SpeedyWeather.jl. The alternative packing $l',m'$ therein uses $l'=m$ and $m'=l-m$ as summarized in the following table.
degree $l$
order $m$
$l'=m$
$m'=l-m$
0
0
0
0
1
0
0
1
1
1
1
0
2
0
0
2
2
1
1
1
2
2
2
0
3
0
0
3
...
...
...
...
This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices
$m'$
$l'$
$a_{00}$
$a_{10}$
$a_{20}$
$a_{30}$
$a_{11}$
$a_{21}$
$a_{31}$
$a_{22}$
$a_{32}$
$a_{33}$
This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran-speedy.
SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with $l \leq l_{max}$ and $|m| \leq m_{max}$ are explicitly represented. This is usually described as $Tm_{max}$, with $l_{max} = m_{max}$ (although in vector quantities require one more degree $l$ in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with $l_{max} = m_{max} = 31$. Note that the degree $l$ and order $m$ are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.
Using triangular truncation[9], there are constraints on the corresponding grid resolution. Let nlon, nlat be the number of longitudes, latitudes on a regular Gaussian grid. Then spectral and grid resolution have to be chosen such that
$nlon \geq 3l_{max}+1$
$nlat \geq (3l_{max}+1)/2$
In general, we choose $nlon = 2nlat$, and ideally $nlon$ is easily Fourier-transformable, e.g. $nlon = 2^i3^j5^k$ with some integers $i,j,k \geq 0$. SpeedyWeather.jl is tested at the following horizontal resolutions, with $\Delta x = \tfrac{2\pi R}{nlon}$ as the approximate grid spacing at the Equator
$l_{max}$
nlon
nlat
$\Delta x$
31 (default)
96
48
400 km
42
128
64
300 km
85
256
128
160 km
170
512
256
80 km
341
1024
512
40 km
682
2048
1024
20 km
Choosing trunc as argument in run_speedy will automatically choose nlon,nlat as presented in the table. Other common choices are T63 (192x96), T127 (384x192), T255 (768x384), T511 (1536x768), among others.
The radius of the sphere (i.e. Earth) is $R$. The zonal gradient scales with $1/\cos(\theta)$ as the longitudes converge towards the poles (note that $\theta$ describes latitudes here, defintions using colatitudes replace the $\cos$ with a $\sin$.)
Starting with a spectral field of vorticity $\zeta$ and divergence $\mathcal{D}$ one can obtain stream function $\Psi$ and velocity potential $\Phi$ by inverting the Laplace operator $\nabla^2$:
The velocities $u,v$ are then obtained from $(u,v) = \nabla^\bot\Psi + \nabla\Phi$ following the defintion from above and $\nabla^\bot = (-R^{-1}\partial_\theta, (R\cos\theta)^{-1}\partial_\lambda)$
Alternatively, we can use the velocities $U = u\cos\theta, V = v\cos\theta$, which we do as the meridional gradient for spherical harmonics is easier implemented with a $\cos\theta$-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with $U,V$ and not $u,v$. From $u,v$ we can return to $\zeta, \mathcal{D}$ via
\[\begin{aligned}
+\zeta &= \frac{1}{R\cos\theta}\partial_\lambda v - \frac{1}{R\cos\theta}\partial_\theta (u \cos\theta) \\
+\mathcal{D} &= \frac{1}{R\cos\theta}\partial_\lambda u + \frac{1}{R\cos\theta}\partial_\theta (v \cos\theta).
+\end{aligned}\]
which is a more convenient formulation as required $\cos\theta$ scalings are reduced to a minimum. The remaining $(U,V)*\cos^{-2}\theta$ are done in grid-point space and usually in combination with other operations like the computation of the vorticity flux. But also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out too. As described in Meridional derivative, it is more convenient to implement $\cos\theta \partial_\theta$ via a recursion relation for the Legendre polynomials than $\partial_\theta$ directly. How the operators $\nabla, \nabla \times, \nabla \cdot$ can be implemented with spherical harmonics is presented in the following sections.
The zonal derivative of a scalar field $\Psi$ in spectral space is the zonal derivative of all its respective spherical harmonics $\Psi_{lm}(\phi,\theta)$ (now we use $\phi$ for longitudes to avoid confusion with the Legendre polynomials $\lambda_{lm}$)
So for every spectral harmonic, $\cos(\theta)v_{lm}$ is obtained from $\Psi_{lm}$ via a multiplication with $im/R$. Unscaling the $\cos(\theta)$-factor is done after transforming the spectral coefficients $v_{lm}$ into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as $\tilde{\Psi} = R^{-1}\Psi$ such that the division by radius $R$ in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order $im$. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number $m$ times imaginary $i$.
The meridioinal derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]
In the following we use the example of obtaining the zonal velocity $u$ from the stream function $\Psi$, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion
To obtain the coefficient of each spherical harmonic $l,m$ of the meridional gradient of a spectral field, two coefficients at $l-1,m$ and $l+1,m$ have to be combined. This means that the coefficient of a gradient $((\cos\theta) u)_{lm}$ is a linear combination of the coefficients of one higher and one lower degree $\Psi_{l+1,m},\Psi_{l-1,m}$. As the coefficient $\Psi_{lm}$ with $m<l$ are zero, the sectoral harmonics ($l=m$) of the gradients are obtained from the first off-diagonal only. However, the $l=l_{max}$ harmonics of the gradients require the $l_{max}-1$ as well as the $l_{max}+1$ harmonics. In SpeedyWeather.jl vector quantitie like $u,v$ use therefore one more meridional mode than scalar quantities such as vorticity $\zeta$ or stream function $\Psi$. The meridional derivative in SpeedyWeather.jl also omits the $1/R$-scaling as explained for the Zonal derivative and in Radius scaling.
The meridional gradient as described above can be applied to scalars, such as $\Psi$ and $\Phi$ in the conversion to velocities $(u,v) = \nabla^\bot\Psi + \nabla\Phi$, however, the operators curl $\nabla \times$ and divergence $\nabla \cdot$ in spherical coordinates involve a $\cos\theta$ scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].
Given that $R\zeta = \cos^{-1}\partial_\lambda v - \cos^{-1}\partial_\theta (u \cos\theta)$, we therefore have to evaluate a meridional integral of the form
Now we expand $(\tfrac{u}{\cos\theta})$ but only the $l,m$ harmonic will project onto$P_{l,m}$. Let $u^* = u\cos^{-1}\theta, v^* = v\cos^{-1}\theta$ we then have in total
And the divergence $D$ is similar, but $(u,v) \to (-v,u)$. We have moved the scaling with the radius $R$ directly into $\zeta,D$ as further described in Radius scaling.
The spectral Laplacian is easily applied to the coefficients $\Psi_{lm}$ of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator $\nabla^2$ in spherical coordinates with eigenvalues $-l(l+1)$ divided by the radius squared $R^2$, i.e. $\nabla^2 \Psi$ becomes $\tfrac{-l(l+1)}{R^2}\Psi_{lm}$ in spectral space. For example, vorticity $\zeta$ and streamfunction $\Psi$ are related by $\zeta = \nabla^2\Psi$ in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree $l$ and order $m$ to
\[\zeta_{l,m} = \frac{-l(l+1)}{R^2}\Psi_{l,m}\]
This can be easily inverted to obtain the stream function $\Psi$ from vorticity $\zeta$ instead. In order to avoid division by zero, we set $\Psi_{0,0}$ here, given that the stream function is only defined up to a constant anyway.
After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity $\zeta$ and divergence $D$ (which are prognostic variables) to $U=u\cos\theta, V=v\cos\theta$. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree $l$ (the meridional gradient). It is therefore computationally more efficient to compute $U,V$ directly from $\zeta,D$ instead of calculating stream function and velocity potential first. In total we have
The prognostic variables in spectral space are called
vor # Vorticity of horizontal wind field
+ div # Divergence of horizontal wind field
+ temp # Absolute temperature [K]
+ pres_surf # Logarithm of surface pressure [log(Pa)]
+ humid # Specific humidity [g/kg]
their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are
We follow Julia's style guide and highlight here some important aspects of it.
Bang (!) convention. A function func does not change its input arguments, however, func! does.
Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.
Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup
but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.
Settings
This document was generated with Documenter.jl version 0.27.24 on Wednesday 31 May 2023. Using Julia version 1.8.5.
A mathematical and implementation-specific description of the dynamical core used in SpeedyWeather.jl. We start by describing the barotropic vorticity equations which is one set of equations that SpeedyWeather.jl can solve (see How to run SpeedyWeather.jl) as many details therein also apply to the Shallow water equations and Primitive equations explained thereafter.
The dynamical core presented here largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2], a shallow water model [3] and a primitive equation model[4].
The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity $\zeta$ with advection, Coriolis force and diffusion in a single global layer on the sphere.
with time $t$, velocity vector $\mathbf{u} = (u, v)$, Coriolis parameter $f$, and hyperdiffusion $(-1)^{n+1} \nu \nabla^{2n} \zeta$ ($n$ is the hyperdiffusion order; see Horizontal diffusion). We also include a forcing vector $\mathbf{F} = (F_u,F_v)$ which acts on the zonal velocity $u$ and the meridional velocity $v$ and hence its curl $\nabla \times \mathbf{F}$ is a tendency for relative vorticity $\zeta$.
Starting with some relative vorticity $\zeta$, the Laplacian is inverted to obtain the stream function $\Psi$
\[\Psi = \nabla^{-2}\zeta\]
The zonal velocity $u$ and meridional velocity $v$ are then the (negative) meridional gradient and zonal gradient of $\Psi$
In SpeedyWeather.jl we use hyerdiffusion through an $n$-th power Laplacian $(-1)^{n+1}\nabla^{2n}$ (hyper when $n>1$) which can be implemented as a multiplication of the spectral coefficients $\Psi_{lm}$ with $(-l(l+1))^nR^{-2n}$ (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the $(-l(l+1))^nR^{-2n}$ can be precomputed. Note the sign change $(-1)^{n+1}$ here is such that the dissipative nature of the diffusion operator is retained for $n$ odd and even.
In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step $\Delta t$ of variable $\zeta$ to obtain from time steps $i-1$ and $i$, the next time step $i+1$
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t d\zeta,\]
with $d\zeta$ being some tendency evaluated from $\zeta_i$. Now we want to add a diffusion term $(-1)^{n+1}\nu \nabla^{2n}\zeta$ with viscosity $\nu$, wich however, is implicitly calculated from $\zeta_{i+1}$, then
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t (d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i+1})\]
As the application of $(-1)^{n+1}\nu\nabla^{2n}$ is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to
\[\zeta_{i+1} = \frac{\zeta_{i-1} + 2\Delta t d\zeta}{1 - 2\Delta (-1)^{n+1}\nu\nabla^{2n}},\]
and expand the numerator to
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t \frac{d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t (-1)^{n+1}\nu \nabla^{2n}},\]
Hence the diffusion can be applied implicitly by updating the tendency $d\zeta$ as
\[d\zeta \to \frac{d\zeta + (-1)^{n+1}\nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t \nu \nabla^{2n}}\]
which only depends on $\zeta_{i-1}$. Now let $D_\text{explicit} = (-1)^{n+1}\nu\nabla^{2n}$ be the explicit part and $D_\text{implicit} = 1 - (-1)^{n+1} 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are $D_\text{implicit} = 1 - 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic $l,m$ we do
Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power $n$.
In physics, the Laplace operator $\nabla^2$ is often used to represent diffusion due to viscosity in a fluid. In that case, the viscosity coefficient is $\nu$ of units $\text{m}^2\text{s}^{-1}$ and the full operator reads as $\nu \nabla^2$ with units $(\text{m}^2\text{s}^{-1})(\text{m}^{-2}) = \text{s}^{-1}$. This motivates us to normalize the Laplace operator by a constant of units $\text{m}^{-2}$ and the viscosity coefficient by its inverse such that the viscosity coefficient becomes a damping timescale of unit $\text{s}^{-1}$. Given the application in spectral space we decide to normalize by the largest eigenvalue $-l_\text{max}(l_\text{max}+1)$ such that all entries in the discrete spectral Laplace operator are in $[0,1]$. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized viscosity coefficient $\nu^* = l_\text{max}(l_\text{max}+1)\nu$ (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have
In the barotropic voriticity equation model the inversion of the Laplcians in order to obtain $\Psi$ from $\zeta$ therefore becomes
\[\tilde{\zeta} = \tilde{\nabla}^2 \tilde{\Psi}\]
where the dimensionless gradients simply omit the scaling with $1/R$, $\tilde{\nabla} = R\nabla$. The Barotropic vorticity equation scaled with $R^2$ is
Similar to the scaled barotropic vorticity equations, the scaled shallow water equations scale the vorticity and the divergence equation with $R^2$, but the continuity equation with $R$
This document was generated with Documenter.jl version 0.27.24 on Wednesday 31 May 2023. Using Julia version 1.8.5.
diff --git a/previews/PR341/functions/index.html b/previews/PR341/functions/index.html
new file mode 100644
index 000000000..6a2259659
--- /dev/null
+++ b/previews/PR341/functions/index.html
@@ -0,0 +1,180 @@
+
+Function and type index · SpeedyWeather.jl
Spectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities u,v.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for primitive equation models. Updates grid vorticity, grid divergence, grid temperature, pressure (pres_grid) and the velocities u,v.
Spectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Returns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.
Truncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.
Truncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.
Laplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.
Keyword arguments
add=true adds the ∇²(alms) to the output
flipsign=true computes -∇²(alms) instead
inverse=true computes ∇⁻²(alms) instead
Default is add=false, flipsign=false, inverse=false. These options can be combined.
Divergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.
Curl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.
Get U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.
Get U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.
Construct Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. Pass on SpectralGrid to calculate the following fields
spectral_grid::SpectralGrid: SpectralGrid that defines spectral and grid resolution
Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid of the dynamical core Default: spectral_grid.Grid
nlat_half::Int64: resolution parameter nlathalf of Grid, # of latitudes on one hemisphere (incl Equator) Default: spectralgrid.nlat_half
nlon_max::Int64: maximum number of longitudes (at/around Equator) Default: getnlonmax(Grid, nlat_half)
nlon::Int64: =nlonmax, same (used for compatibility), TODO: still needed? Default: nlonmax
nlat::Int64: number of latitude rings Default: getnlat(Grid, nlathalf)
nlev::Int64: number of vertical levels Default: spectral_grid.nlev
npoints::Int64: total number of grid points Default: spectral_grid.npoints
ln_σ_levels_full::Vector{NF} where NF<:AbstractFloat: log of σ at full levels, include surface (σ=1) as last element Default: log.(vcat(σlevelsfull, 1))
Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF.
Performs the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.
The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and but let's start but how they can be used
The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object which defines the resolution in spectral and in grid-point space. The generator SpectralGrid() can take as a keyword argument Grid which can be any of the grids described below. The resolution of the grid, however, is not directly chosen, but determined from the spectral resolution trunc and the dealiasing factor. More in Matching spectral and grid resolution.
RingGrids is a module too!
While RingGrids is the underlying module that SpeedyWeather.jl uses for data structs on the sphere, the module can also be used independently of SpeedyWeather, for example to interpolate between data on different grids. See RingGrids
SpeedyWeather.jl's spectral transform supports all ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on these rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.
All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).
Currently the following full grids <: AbstractFullGrid are implemented
FullGaussianGrid, a full grid with Gaussian latitudes
FullClenshawGrid, a full grid with equi-angle latitudes
and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are
OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron
OctahedralClenshawGrid, similar but based on equi-angle latitudes
HEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces
OctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.
An overview of these grids is visualised here
Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.
All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half.
HEALPix grids do not use Nside as resolution parameter
The original formulation for HEALPix grids use $N_{side}$, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use $N_{side}$ for the documentation or within functions though.
A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation T with a grid resolution N (=nlat_half) such that T + 1 = N. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at $l_{max}=31$ in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid.
Let J be the total number of rings. Then we have
$T \approx J$ for linear truncation
$\frac{3}{2}T \approx J$ for quadratic truncation
$2T \approx J$ for cubic truncation
and in general $\frac{m+1}{2}T \approx J$ for m-th order truncation. So the higher the truncaction order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. In SpeedyWeather.jl the parameter dealiasing controls this option, = 1 would be linear, = 2 quadratic, = 3 cubic etc.
For now just a quick overview of how the grid resolution changes when dealiasing is passed onto SpectralGrid on the FullGaussianGrid
trunc
dealiasing
FullGaussianGrid size
31
1
64x32
31
2
96x48
31
3
128x64
42
1
96x48
42
2
128x64
42
3
192x96
...
...
...
You will obtain this information every time you create a SpectralGrid(;Grid,trunc,dealiasing).
Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are $N_\varphi$ basepixels in zonal direction and $N_\theta$ basepixels in meridional direction. For $N_\varphi = 4$ and $N_\theta = 3$ we obtain the classical HEALPix grid with $N_\varphi N_\theta = 12$ basepixels shown above in Implemented Grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always $2N$, so 32 at $N=16$) and there are polar caps above and below the equatorial zone with the border at $\cos(\theta) = 2/3$ ($\theta$ in colatitudes).
Following Górski, 2004[1], the $z=cos(\theta)$ colatitude of the $j$-th ring in the north polar cap, $j=1,...,N_{side}$ with $2N_{side} = N$ is
\[z = 1 - \frac{j^2}{3N_{side}^2}\]
and on that ring, the longitude $\phi$ of the $i$-th point ($i$ is the in-ring-index) is at
\[\phi = \frac{\pi}{2j}(i-\tfrac{1}{2})\]
The in-ring index $i$ goes from $i=1,...,4$ for the first (i.e. northern-most) ring, $i=1,...,8$ for the second ring and $i = 1,...,4j$ for the $j$-th ring in the northern polar cap.
In the north equatorial belt $j=N_{side},...,2N_{side}$ this changes to
\[z = \frac{4}{3} - \frac{2j}{3N_{side}}\]
and the longitudes change to ($i$ is always $i = 1,...,4N_{side}$ in the equatorial belt meaning the number of longitude points is constant here)
The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.
The cell boundaries are obtained by setting $i = k + 1/2$ or $i = k + 1/2 + j$ (half indices) into the equations above, such that $z(\phi,k)$, a function for the cosine of colatitude $z$ of index $k$ and the longitude $\phi$ is obtained. These are then (northern polar cap)
While the classic HEALPix grid is based on a dodecahedron, other choices for $N_\varphi$ and $N_\theta$ in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With $N_\varphi = 4$ and $N_\theta = 1$ we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, $2\pi$ around the Equator versus $\pi$ between the poles. $N_\varphi = 6, N_\theta = 2$ or $N_\varphi = 8, N_\theta = 3$ are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visulations of these grids.
We call the $N_\varphi = 4, N_\theta = 1$ HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As $N_\theta = 1$ there is no equatorial belt which simplifies the grid. The latitude of the $j$-th isolatitude ring on the OctaHEALPixGrid is defined by
\[z = 1 - \frac{j^2}{N^2},\]
with $j=1,...,N$, and similarly for the southern hemisphere by symmetry. On this grid $N_{side} = N$ where $N$= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index $i = 1,...,4j$ are
\[\phi = \frac{\pi}{2j}(i - \tfrac{1}{2})\]
and again, the southern hemisphere grid points are obtained by symmetry.
The $3N_{side}^2$ in the denominator of the HEALPix grid came simply $N^2$ for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).
[1] Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976
Settings
This document was generated with Documenter.jl version 0.27.24 on Wednesday 31 May 2023. Using Julia version 1.8.5.
diff --git a/previews/PR341/how_to_run_speedy/index.html b/previews/PR341/how_to_run_speedy/index.html
new file mode 100644
index 000000000..1ccb149a5
--- /dev/null
+++ b/previews/PR341/how_to_run_speedy/index.html
@@ -0,0 +1,5 @@
+
+How to run SpeedyWeather.jl · SpeedyWeather.jl
The simplest way to run SpeedyWeather.jl with default parameters is
using SpeedyWeather
+run_speedy()
Hooray, you have just simulated the Earth's atmosphere. Parameters, their meanings and defaults are documented in Parameters. For example, if you want to run the primitive equation dry core (no humidity) simulation in double precision (Float64), at higher resolution (trunc, the triangular spectral truncation), slow down the rotation of the Earth (rotation in $s^{-1}$), and create some netCDF ouput, do
If provided, the number format has to be the first argument, the model (Barotropic, ShallowWater, PrimitiveDry, PrimitiveWet are available) the second, and all other arguments are keyword arguments.
progn_vars = run_speedy(NF,Model;kwargs...) or
+progn_vars = run_speedy(NF;kwargs...) or
+progn_vars = run_speedy(Model;kwargs...)
Runs SpeedyWeather.jl with number format NF and the model Model and any additional parameters in the keyword arguments kwargs.... Unspecified parameters use the default values.
Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.
SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to perform some calculations in spectral space (time integration, gradients, linear terms) and some in grid-point space (advection, non-linear terms, parameterizations). The prognostic variables used are vorticity, divergence, absolute temperature, logarithm of surface pressure and specific humidity. The time stepping uses a leapfrog scheme with additional filters and a semi-implicit formulation for gravity waves. The default resolution is T31 (96x48 grid points on a regular Gaussian grid, about 400km at the Equator) and 8 vertical levels.
Simple parameterizations are used to represent the physical processes convection, large-scale condensation, clouds, short-wave radiation, long-waves radiation, surface fluxes of momentum and energy, and vertical diffusion.
MK received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022. Since 2023 this project is also funded by the National Science Foundation NSF.
Settings
This document was generated with Documenter.jl version 0.27.24 on Wednesday 31 May 2023. Using Julia version 1.8.5.
LowerTriangularMatrices is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.
This module defines LowerTriangularMatrix, a lower triangular matrix, which in contrast to LinearAlgebra.LowerTriangular does not store the entries above the diagonal. SpeedyWeather.jl uses LowerTriangularMatrix which is defined as a subtype of AbstractMatrix to store the spherical harmonic coefficients (see Spectral packing).
LowerTriangularMatrix supports two types of indexing: 1) by denoting two indices, column and row [l,m] or 2) by denoting a single index [lm]. The double index works as expected
But the single index skips the zero entries in the upper triangle, i.e.
julia> L[4]
+Float16(0.478)
which, important, is different from single indices of an AbstractMatrix
julia> Matrix(L)[4]
+Float16(0.0)
In performance-critical code a single index should be used, as this directly maps to the index of the underlying data vector. The double index is somewhat slower as it first has to be converted to the corresponding single index.
Consequently, many loops in SpeedyWeather.jl are build with the following structure
n,m = size(L)
+ij = 0
+for j in 1:m
+ for i in j:n
+ ij += 1
+ L[ij] = i+j
+ end
+end
which loops over all lower triangle entries of L::LowerTriangularMatrix and the single index ij is simply counted up. However, one could also use [i,j] as indices in the loop body or to perform any calculation (i+j here). An iterator over all entries in the lower triangle can be created by
for ij in eachindex(L)
+ # do something
+end
The setindex! functionality of matrixes will throw a BoundsError when trying to write into the upper triangle of a LowerTriangularMatrix, for example
julia> L[2,1] = 0 # valid index
+0
+
+julia> L[1,2] = 0 # invalid index in the upper triangle
+ERROR: BoundsError: attempt to access 3×3 LowerTriangularMatrix{Float32} at index [1, 2]
The LowerTriangularMatrices module's main purpose is not linear algebra, and it's implementation may not be efficient, however, many operations work as expected
Note, however, that the latter includes a conversion to Matrix, which is true for many operations, including inv or \. Hence when trying to do more sophisticated linear algebra with LowerTriangularMatrix we quickly leave lower triangular-land and go back to normal matrix-land.
L = LowerTriangularMatrix{T}(v::Vector{T},m::Int,n::Int)
A lower triangular matrix implementation that only stores the non-zero entries explicitly. L<:AbstractMatrix although in general we have L[i] != Matrix(L)[i], the former skips zero entries, tha latter includes them.
k = ij2k( i::Integer, # row index of matrix
+ j::Integer, # column index of matrix
+ m::Integer) # number of rows in matrix
Converts the index pair i,j of an mxn LowerTriangularMatrix L to a single index k that indexes the same element in the corresponding vector that stores only the lower triangle (the non-zero entries) of L.
creates unit_range::UnitRange to loop over all non-zeros in the LowerTriangularMatrices provided as arguments. Checks bounds first. All LowerTriangularMatrix's need to be of the same size. Like eachindex but skips the upper triangle with zeros in L.
SpeedyWeather.jl uses NetCDF to output the data of a simulation. The following describes the details of this and how to change the way in which the NetCDF output is written. There are many options to this available.
The output writer is a component of every Model, i.e. BarotropicModel, ShallowWaterModel, PrimitiveDryModel and PrimitiveWetModel, hence a non-default output writer can be passed on as a keyword argument to the model constructor
So after we have defined the grid through the SpectralGrid object we can use and change the implemented OutputWriter by passing on the following arguments
the spectral_grid has to be the first argument then the model type (Barotropic, ShallowWater, PrimitiveDry, PrimitiveWet) which helps the output writer to make default choices on which variables to output. However, we can also pass on further keyword arguments. So let's start with an example.
which will now output every hour. It is important to pass on the new output writer my_output_writer to the model constructor, otherwise it will not be part of your model and the default is used instead. Note that output_dt has to be understood as the minimum frequency or maximum output time step. Example, we run the model at a resolution of T85 and the time step is going to be 670s
This means that after 32 time steps 5h 57min and 20s will have passed where output will happen as the next time step would be >6h. The time axis of the NetCDF output will look like
This is so that we don't interpolate in time during output to hit exactly every 6 hours, but at the same time have a constant spacing in time between output time steps.
Say we want to run the model at a given horizontal resolution but want to output on another resolution, the OutputWriter takes as argument output_Grid<:AbstractFullGrid and nlat_half::Int. So for example output_Grid=FullClenshawGrid and nlat_half=48 will always interpolate onto a regular 192x95 longitude-latitude grid of 1.875˚ resolution, regardless the grid and resolution used for the model integration.
Note that by default the output is on the corresponding full of the grid used in the dynamical core so that interpolation only happens at most in the zonal direction as they share the location of the latitude rings. You can check this by
So the corresponding full grid of an OctahedralGaussianGrid is the FullGaussiangrid and the same resolution nlat_half is chosen by default in the output writer (which you can change though as shown above). Overview of the corresponding full grids
Grid
Corresponding full grid
FullGaussianGrid
FullGaussianGrid
FullClenshawGrid
FullClenshawGrid
OctahadralGaussianGrid
FullGaussianGrid
OctahedralClensawhGrid
FullClenshawGrid
HEALPixGrid
FullHEALPixGrid
OctaHEALPixGrid
FullOctaHEALPixGrid
The grids FullHEALPixGrid, FullOctaHEALPixGrid share the same latitude rings as their reduced grids, but have always as many longitude points as they are at most around the equator. These grids are not tested in the dynamical core (but you may use them experimentally) and mostly designed for output purposes.
That's easy by passing on path="/my/favourite/path/" and the folder run_* with * the identification of the run (that's the id keyword, which can be manually set but is also automatically determined as a number counting up depending on which folders already exist) will be created within.
which will be used instead of a 4 digit number like 0001, 0002 which is automatically determined if id is not provided. You will see the id of the run in the progress bar
Weather is speedy: run diffusion_test 100%|███████████████████████| Time: 0:00:12 (19.20 years/day)
and the run folder, here run_diffusion_test, is also named accordingly
Further options are described in the OutputWriter docstring, (also accessible via julia>?OutputWriter for example). Note that some fields are actual options, but others are derived from the options you provided or are arrays/objects the output writer needs, but shouldn't be passed on by the user. The actual options are declared as [OPTION] in the following
NetCDF output writer. Contains all output options and auxiliary fields for output interpolation. To be initialised with OutputWriter(::SpectralGrid,::Type{<:ModelSetup},kwargs...) to pass on the resolution information and the model type which chooses which variables to output. Options include
spectral_grid::SpectralGrid
output::Bool
path::String: [OPTION] path to output folder, run_???? will be created within
id::Union{Int64, String}: [OPTION] run identification number/string
run_path::String
filename::String: [OPTION] name of the output netcdf file
write_restart::Bool: [OPTION] also write restart file if output==true?
pkg_version::VersionNumber
startdate::Dates.DateTime
output_dt::Float64: [OPTION] output frequency, time step [hrs]
output_vars::Vector{Symbol}: [OPTION] which variables to output, u, v, vor, div, pres, temp, humid
missing_value::Union{Float32, Float64}: [OPTION] missing value to be used in netcdf output
compression_level::Int64: [OPTION] lossless compression level; 1=low but fast, 9=high but slow
keepbits::SpeedyWeather.Keepbits: [OPTION] mantissa bits to keep for every variable
This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmopshere. Every section is followed by a brief description of implementation details.
RingGrids is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.
RingGrids defines several iso-latitude grids, which are mathematically described in the section on Grids. In brief, they include the regular latitude-longitude grids (here called FullClenshawGrid) as well as grids which latitudes are shifted to the Gaussian latitudes and reduced grids, meaning that they have a decreasing number of longitudinal points towards the poles to be more equal-area than full grids.
RingGrids defines and exports the following grids:
full grids: FullClenshawGrid, FullGaussianGrid, FullHEALPix, and FullOctaHEALPix
reduced grids: OctahedralGaussianGrid, OctahedralClenshawGrid, OctaHEALPixGrid and HEALPixGrid
The following explanation of how to use these can be mostly applied to any of them, however, there are certain functions that are not defined, e.g. the full grids can be trivially converted to a Matrix but not the OctahedralGaussianGrid.
Every grid in RingGrids has a data field, which is a vector containing the data on the grid. The grid points are unravelled west to east then north to south, meaning that it starts at 90˚N and 0˚E then walks eastward for 360˚ before jumping on the next latitude ring further south, this way circling around the sphere till reaching the south pole. This may also be called ring order.
Data on a 96x48 Matrix which follows this ring order can be put on a FullGaussianGrid like so
A full Gaussian grid has always $2N$ x $N$ grid points, but a FullClenshawGrid has $2N$ x $N-1$, if those dimensions don't match, the creation will throw an error. To reobtain the data from a grid, you can access its data field which returns a normal Vector
Which can be reshaped to reobtain map from above. Alternatively you can Matrix(grid) to do this in one step
julia> map == Matrix(FullGaussianGrid(map))
+true
You can also use zeros,ones,rand,randn to create a grid, whereby nlat_half, i.e. the number of latitude rings on one hemisphere, Equator included, is used as a resolution parameter and here as a second argument.
All RingGrids have a single index ij which follows the ring order. While this is obviously not super exciting here are some examples how to make better use of the information that the data sits on a grid.
We obtain the latitudes of the rings of a grid by calling get_latd (get_lond is only defined for full grids, or use get_latdlonds for latitudes, longitudes per grid point not per ring)
Now we could calculate Coriolis and add it on the grid as follows
rotation = 7.29e-5 # angular frequency of Earth's rotation [rad/s]
+coriolis = 2rotation*sind.(latd) # vector of coriolis parameters per latitude ring
+
+rings = eachring(grid)
+for (j,ring) in enumerate(rings)
+ f = coriolis[j]
+ for ij in ring
+ grid[ij] += f
+ end
+end
eachring creates a vector of UnitRange indices, such that we can loop over the ring index j (j=1 being closest to the North pole) pull the coriolis parameter at that latitude and then loop over all in-ring indices i (changing longitudes) to do something on the grid. Something similar can be done to scale/unscale with the cosine of latitude for example. We can always loop over all grid-points like so
In most cases we will want to use RingGrids so that our data directly comes with the geometric information of where the grid-point is one the sphere. We have seen how to use get_latd, get_lond, ... for that above. This information generally can also be used to interpolate our data from grid to another or to request an interpolated value on some coordinates. Using our data on grid which is an OctahedralGaussianGrid from above we can use the interpolate function to get it onto a FullGaussianGrid (or any other grid for purpose)
By default this will linearly interpolate (it's an anvil interpolator, see below) onto a grid with the same nlat_half, but we can also coarse-grain or fine-grain by specifying nlat_half directly as 2nd argument
So we got from an 8-ring OctahedralGaussianGrid{Float16} to a 12-ring FullGaussianGrid{Float64}, so it did a conversion from Float16 to Float64 on the fly too, because the default precision is Float64 unless specified. interpolate(FullGaussianGrid{Float16},6,grid) would have interpolated onto a grid with element type Float16.
One can also interpolate onto a give cordinate ˚N, ˚E like so
Every time an interpolation like interpolate(30.0,10.0,grid) is called, several things happen, which are important to understand to know how to get the fastest interpolation out of this module in a given situation. Under the hood an interpolation takes three arguments
output vector
input grid
interpolator
The output vector is just an array into which the interpolated data is written, providing this prevents unnecessary allocation of memory in case the destination array of the interpolation already exists. The input grid contains the data which is subject to interpolation, it must come on a ring grid, however, its coordinate information is actually already in the interpolator. The interpolator knows about the geometry of the grid the data is coming on and the coordinates it is supposed to interpolate onto. It has therefore precalculated the indices that are needed to access the right data on the input grid and the weights it needs to apply in the actual interplation operation. The only thing it does not know is the actual data values of that grid. So in the case you want to interpolate from grid A to grid B many times, you can just reuse the same interpolator. If you want to change the coordinates of the output grid but its total number of points remain constants then you can update the locator inside the interpolator and only else you will need to create a new interpolator. Let's look at this in practice. Say we have two grids an want to interpolate between them
Now we have created an interpolator interp which knows about the geometry where to interpolate from and the coordinates there to interpolate to. It is also initialized, meaning it has precomputed the indices to of grid_in that are supposed to be used. It just does not know about the data of grid_in (and neither of grid_out which will be overwritten anyway). We can now do
which is identical to interpolate(grid_out,grid_in) but you can reuse interp with more data. The interpolation can also handle various element types (the interpolator interp does not have to be updated for this either)
and we have converted data from a HEALPixGrid{Float64} (which is always default if not specified) to a FullClenshawGrid{Float16} including the type conversion Float64-Float16 on the fly. Technically there are three data types and their combinations possible: The input data will come with a type, the output array has an element type and the interpolator has precomputed weights with a given type. Say we want to go from Float16 data on an OctahedralGaussianGrid to Float16 on a FullClenshawGrid but using Float32 precision for the interpolation itself, we would do this by
As a last example we want to illustrate a situation where we would always want to interplate onto 10 coordinates, but their locations may change. In order to avoid recreating an interpolator object we would do (AnvilInterpolator is described in Anvil interpolator)
julia> npoints = 10 # number of coordinates to interpolate onto
+julia> interp = AnvilInterpolator(Float32,HEALPixGrid,24,npoints)
with the first argument being the number format used during interpolation, then the input grid type, its resolution in terms of nlat_half and then the number of points to interpolate onto. However, interp is not yet initialized as it does not know about the destination coordinates yet. Let's define them, but note that we already decided there's only 10 of them above.
but allows for a reuse of the interpolator. Note that the two output arrays are not exactly identical because we manually set our interpolator interp to use Float32 for the interplation whereas the default is Float64.
Currently the only interpolator implemented is a 4-point bilinear interpolator, which schematically works as follows. Anvil interpolation is the bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate.
0..............1 # fraction of distance Δab between a,b
+ |< Δab >|
+
+0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x
+.Δy |
+. |
+.v x
+. |
+1 c ------ o ---- d
+
+ |< Δcd >|
+ 0...............1 # fraction of distance Δcd between c,d
+
+^ fraction of distance Δy between a-b and c-d.
This interpolation is chosen as by definiton of the ring grids, a and b share the same latitude, so do c and d, but the longitudes can be different for all four, a,b,c,d.
Returns a vector nlons for the number of longitude points per latitude ring, north to south. Provide grid Grid and its resolution parameter nlat_half. For both_hemisphere==false only the northern hemisphere (incl Equator) is returned.
This document was generated with Documenter.jl version 0.27.24 on Wednesday 31 May 2023. Using Julia version 1.8.5.
diff --git a/previews/PR341/search_index.js b/previews/PR341/search_index.js
new file mode 100644
index 000000000..db37f3802
--- /dev/null
+++ b/previews/PR341/search_index.js
@@ -0,0 +1,3 @@
+var documenterSearchIndex = {"docs":
+[{"location":"development/#Development-notes","page":"Development notes","title":"Development notes","text":"","category":"section"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To run tests, from the path of your local clone of the repository do:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=. -e 'import Pkg; Pkg.test()'","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To install dependencies:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project -e 'import Pkg; Pkg.instantiate()`","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"then opening:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"you are able to using SpeedyWeather.","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To generate the docs:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=docs -e 'import Pkg; Pkg.develop(path=\".\"); Pkg.instantiate()'\njulia --project=docs docs/make.jl","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"If the docs are generated successfully, you view them by opening docs/build/index.html in your favorite browser.","category":"page"},{"location":"installation/#Installation","page":"Installation","title":"Installation","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"SpeedyWeather.jl is registered in the Julia Registry. In most cases just open the Julia REPL and type","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia> using Pkg\njulia> Pkg.add(\"SpeedyWeather\")","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"which will automatically install the latest release and all necessary dependencies. If you run into any troubles please raise an issue","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"However, you may want to make use of the latest features, then install directly from the main branch with","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia> Pkg.add(url=\"https://github.com/SpeedyWeather/SpeedyWeather.jl\",rev=\"main\")","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"other branches than main can be similarly installed.","category":"page"},{"location":"installation/#Compatibility-with-Julia-versions","page":"Installation","title":"Compatibility with Julia versions","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"SpeedyWeather.jl usually lives on the latest minor release and/or its predecessor. At the moment (May 2023) this means ","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"Julia v1.8\nJulia v1.9","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"are supported, but we dropped the support of earlier versions.","category":"page"},{"location":"output/#NetCDF-output","page":"NetCDF output","title":"NetCDF output","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"SpeedyWeather.jl uses NetCDF to output the data of a simulation. The following describes the details of this and how to change the way in which the NetCDF output is written. There are many options to this available.","category":"page"},{"location":"output/#Accessing-the-NetCDF-output-writer","page":"NetCDF output","title":"Accessing the NetCDF output writer","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"The output writer is a component of every Model, i.e. BarotropicModel, ShallowWaterModel, PrimitiveDryModel and PrimitiveWetModel, hence a non-default output writer can be passed on as a keyword argument to the model constructor","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> spectral_grid = SpectralGrid()\njulia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry)\njulia> model = PrimitiveDryModel(;spectral_grid, output=my_output_writer)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"So after we have defined the grid through the SpectralGrid object we can use and change the implemented OutputWriter by passing on the following arguments","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, kwargs...)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"the spectral_grid has to be the first argument then the model type (Barotropic, ShallowWater, PrimitiveDry, PrimitiveWet) which helps the output writer to make default choices on which variables to output. However, we can also pass on further keyword arguments. So let's start with an example.","category":"page"},{"location":"output/#Example-1:-NetCDF-output-every-hour","page":"NetCDF output","title":"Example 1: NetCDF output every hour","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"If we want to increase the frequency of the output we can choose output_dt (default =6 in hours) like so","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, output_dt=1)\njulia> model = PrimitiveDryModel(;spectral_grid, output=my_output_writer)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"which will now output every hour. It is important to pass on the new output writer my_output_writer to the model constructor, otherwise it will not be part of your model and the default is used instead. Note that output_dt has to be understood as the minimum frequency or maximum output time step. Example, we run the model at a resolution of T85 and the time step is going to be 670s","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> spectral_grid = SpectralGrid(trunc=85)\njulia> time_stepper = Leapfrog(spectral_grid)\nLeapfrog{Float32}:\n...\n Δt_sec::Int64 = 670\n...","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This means that after 32 time steps 5h 57min and 20s will have passed where output will happen as the next time step would be >6h. The time axis of the NetCDF output will look like","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> using NCDatasets\njulia> ds = NCDataset(\"run_0001/output.nc\");\njulia> ds[\"time\"][:]\n5-element Vector{Dates.DateTime}:\n 2000-01-01T00:00:00\n 2000-01-01T05:57:20\n 2000-01-01T11:54:40\n 2000-01-01T17:52:00\n 2000-01-01T23:49:20\n\njulia> diff(ds[\"time\"][:])\n4-element Vector{Dates.Millisecond}:\n 21440000 milliseconds\n 21440000 milliseconds\n 21440000 milliseconds\n 21440000 milliseconds","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This is so that we don't interpolate in time during output to hit exactly every 6 hours, but at the same time have a constant spacing in time between output time steps.","category":"page"},{"location":"output/#Example-2:-Output-onto-a-higher/lower-resolution-grid","page":"NetCDF output","title":"Example 2: Output onto a higher/lower resolution grid","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Say we want to run the model at a given horizontal resolution but want to output on another resolution, the OutputWriter takes as argument output_Grid<:AbstractFullGrid and nlat_half::Int. So for example output_Grid=FullClenshawGrid and nlat_half=48 will always interpolate onto a regular 192x95 longitude-latitude grid of 1.875˚ resolution, regardless the grid and resolution used for the model integration.","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, output_Grid=FullClenshawGrid, nlat_half=48)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Note that by default the output is on the corresponding full of the grid used in the dynamical core so that interpolation only happens at most in the zonal direction as they share the location of the latitude rings. You can check this by","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> RingGrids.full_grid(OctahedralGaussianGrid)\nFullGaussianGrid","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"So the corresponding full grid of an OctahedralGaussianGrid is the FullGaussiangrid and the same resolution nlat_half is chosen by default in the output writer (which you can change though as shown above). Overview of the corresponding full grids","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Grid Corresponding full grid\nFullGaussianGrid FullGaussianGrid\nFullClenshawGrid FullClenshawGrid\nOctahadralGaussianGrid FullGaussianGrid\nOctahedralClensawhGrid FullClenshawGrid\nHEALPixGrid FullHEALPixGrid\nOctaHEALPixGrid FullOctaHEALPixGrid","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"The grids FullHEALPixGrid, FullOctaHEALPixGrid share the same latitude rings as their reduced grids, but have always as many longitude points as they are at most around the equator. These grids are not tested in the dynamical core (but you may use them experimentally) and mostly designed for output purposes.","category":"page"},{"location":"output/#Example-3:-Changing-the-output-path-or-identification","page":"NetCDF output","title":"Example 3: Changing the output path or identification","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"That's easy by passing on path=\"/my/favourite/path/\" and the folder run_* with * the identification of the run (that's the id keyword, which can be manually set but is also automatically determined as a number counting up depending on which folders already exist) will be created within.","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> path = pwd()\n\"/Users/milan\"\njulia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, path=path)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This folder must already exist. If you want to give your run a name/identification you can pass on id","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid,PrimitiveDry,id=\"diffusion_test\");","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"which will be used instead of a 4 digit number like 0001, 0002 which is automatically determined if id is not provided. You will see the id of the run in the progress bar","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Weather is speedy: run diffusion_test 100%|███████████████████████| Time: 0:00:12 (19.20 years/day)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"and the run folder, here run_diffusion_test, is also named accordingly","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"shell> ls\n...\nrun_diffusion_test\n...","category":"page"},{"location":"output/#Further-options","page":"NetCDF output","title":"Further options","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Further options are described in the OutputWriter docstring, (also accessible via julia>?OutputWriter for example). Note that some fields are actual options, but others are derived from the options you provided or are arrays/objects the output writer needs, but shouldn't be passed on by the user. The actual options are declared as [OPTION] in the following","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"OutputWriter","category":"page"},{"location":"output/#SpeedyWeather.OutputWriter","page":"NetCDF output","title":"SpeedyWeather.OutputWriter","text":"NetCDF output writer. Contains all output options and auxiliary fields for output interpolation. To be initialised with OutputWriter(::SpectralGrid,::Type{<:ModelSetup},kwargs...) to pass on the resolution information and the model type which chooses which variables to output. Options include\n\nspectral_grid::SpectralGrid\noutput::Bool\npath::String: [OPTION] path to output folder, run_???? will be created within\nid::Union{Int64, String}: [OPTION] run identification number/string\nrun_path::String\nfilename::String: [OPTION] name of the output netcdf file\nwrite_restart::Bool: [OPTION] also write restart file if output==true?\npkg_version::VersionNumber\nstartdate::Dates.DateTime\noutput_dt::Float64: [OPTION] output frequency, time step [hrs]\noutput_vars::Vector{Symbol}: [OPTION] which variables to output, u, v, vor, div, pres, temp, humid\nmissing_value::Union{Float32, Float64}: [OPTION] missing value to be used in netcdf output\ncompression_level::Int64: [OPTION] lossless compression level; 1=low but fast, 9=high but slow\nkeepbits::SpeedyWeather.Keepbits: [OPTION] mantissa bits to keep for every variable\noutput_every_n_steps::Int64\ntimestep_counter::Int64\noutput_counter::Int64\nnetcdf_file::Union{Nothing, NetCDF.NcFile}\ninput_Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}\nas_matrix::Bool: [OPTION] sort grid points into a matrix (interpolation-free), for OctahedralClenshawGrid, OctaHEALPixGrid only\nquadrant_rotation::NTuple{4, Int64}\nmatrix_quadrant::NTuple{4, Tuple{Int64, Int64}}\noutput_Grid::Type{<:SpeedyWeather.RingGrids.AbstractFullGrid}: [OPTION] the grid used for output, full grids only\nnlat_half::Int64: [OPTION] the resolution of the output grid, default: same nlat_half as in the dynamical core\nnlon::Int64\nnlat::Int64\nnpoints::Int64\nnlev::Int64\ninterpolator::SpeedyWeather.RingGrids.AbstractInterpolator\nu::Matrix{NF} where NF<:Union{Float32, Float64}\nv::Matrix{NF} where NF<:Union{Float32, Float64}\nvor::Matrix{NF} where NF<:Union{Float32, Float64}\ndiv::Matrix{NF} where NF<:Union{Float32, Float64}\ntemp::Matrix{NF} where NF<:Union{Float32, Float64}\npres::Matrix{NF} where NF<:Union{Float32, Float64}\nhumid::Matrix{NF} where NF<:Union{Float32, Float64}\n\n\n\n\n\n","category":"type"},{"location":"functions/#Function-and-type-index","page":"Function and type index","title":"Function and type index","text":"","category":"section"},{"location":"functions/#Parameters-and-constants","page":"Function and type index","title":"Parameters and constants","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Parameters\nSpeedyWeather.Constants","category":"page"},{"location":"functions/#Boundaries-and-boundary-conditions","page":"Function and type index","title":"Boundaries and boundary conditions","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Boundaries","category":"page"},{"location":"functions/#Spherical-harmonic-transform","page":"Function and type index","title":"Spherical harmonic transform","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.GeoSpectral\nSpeedyWeather.SpectralTransform\nSpeedyWeather.spectral\nSpeedyWeather.spectral!\nSpeedyWeather.gridded\nSpeedyWeather.gridded!\nSpeedyWeather.triangular_truncation\nSpeedyWeather.roundup_fft\nSpeedyWeather.spectral_truncation\nSpeedyWeather.spectral_truncation!\nSpeedyWeather.spectral_interpolation!\nSpeedyWeather.get_legendre_polynomials!\nSpeedyWeather.∇²!\nSpeedyWeather.∇²\nSpeedyWeather.∇⁻²!\nSpeedyWeather.∇⁻²\nSpeedyWeather.gradient_latitude!\nSpeedyWeather.gradient_latitude\nSpeedyWeather.gradient_longitude!\nSpeedyWeather.gradient_longitude\nSpeedyWeather.divergence!\nSpeedyWeather.curl!\nSpeedyWeather._divergence!\nSpeedyWeather.curl_div!\nSpeedyWeather.UV_from_vordiv!\nSpeedyWeather.UV_from_vor!\nSpeedyWeather.ϵlm\nSpeedyWeather.get_recursion_factors","category":"page"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.SpectralTransform","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"S = SpectralTransform{NF<:AbstractFloat}(...)\n\nSpectralTransform struct that contains all parameters and preallocated arrays for the spectral transform.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral","text":"alms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\nalms = spectral( map::AbstractGrid;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to Grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\nalms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nSpectral transform (grid to spectral) map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral!","text":"spectral!( alms::LowerTriangularMatrix,\n map::AbstractGrid,\n S::SpectralTransform)\n\nSpectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded","text":"map = gridded( alms::AbstractMatrix;\n recompute_legendre::Bool=true,\n grid::Type{<:AbstractGrid}=DEFAULT_GRID)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).\n\n\n\n\n\nmap = gridded( alms::AbstractMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!(\n diagn::DiagnosticVariables,\n progn::PrognosticVariables,\n lf::Int64,\n model::SpeedyWeather.ModelSetup\n)\n\n\nPropagate the spectral state of progn to diagn using time step/leapfrog index lf. Function barrier that calls gridded! for the respective model.\n\n\n\n\n\ngridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::Barotropic\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.\n\n\n\n\n\ngridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::ShallowWater\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities u,v.\n\n\n\n\n\ngridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::PrimitiveEquation\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for primitive equation models. Updates grid vorticity, grid divergence, grid temperature, pressure (pres_grid) and the velocities u,v.\n\n\n\n\n\ngridded!( map::AbstractGrid,\n alms::LowerTriangularMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral_truncation","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation","text":"alms_trunc = spectral_truncation(alms,trunc)\n\nReturns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral_truncation!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms::AbstractMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.\n\n\n\n\n\nspectral_truncation!(alms::LowerTriangularMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.\n\n\n\n\n\nspectral_truncation!(alms,trunc)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.\n\n\n\n\n\nspectral_truncation!(alms)\n\nTruncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.∇²!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.∇²!","text":"∇²!( ∇²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false,\n inverse::Bool=false)\n\nLaplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.\n\nKeyword arguments\n\nadd=true adds the ∇²(alms) to the output\nflipsign=true computes -∇²(alms) instead\ninverse=true computes ∇⁻²(alms) instead\n\nDefault is add=false, flipsign=false, inverse=false. These options can be combined.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.∇⁻²!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.∇⁻²!","text":"∇⁻²!( ∇⁻²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false)\n\nCalls ∇²!(∇⁻²alms, alms, S; add, flipsign, inverse=true).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.divergence!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.divergence!","text":"divergence!(div::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform{NF};\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nDivergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.curl!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.curl!","text":"curl!( curl::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform;\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nCurl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.UV_from_vordiv!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.UV_from_vordiv!","text":"UV_from_vordiv!(U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n div::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.UV_from_vor!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.UV_from_vor!","text":"UV_from_vor!( U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"function"},{"location":"functions/#Dynamics","page":"Function and type index","title":"Dynamics","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.bernoulli_potential!\nSpeedyWeather.volume_flux_divergence!\nSpeedyWeather.vorticity_fluxes!\nSpeedyWeather.vorticity_flux_curl!\nSpeedyWeather.vorticity_flux_divergence!","category":"page"},{"location":"functions/#SpeedyWeather.bernoulli_potential!","page":"Function and type index","title":"SpeedyWeather.bernoulli_potential!","text":"bernoulli_potential!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},\n S::SpectralTransform\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nComputes the Laplace operator ∇² of the Bernoulli potential B in spectral space.\n\ncomputes the kinetic energy KE = ½(u²+v²) on the grid\ntransforms KE to spectral space\nadds geopotential for the Bernoulli potential in spectral space\ntakes the Laplace operator.\n\nThis version is used for both ShallowWater and PrimitiveEquation, only the geopotential calculation in geopotential! differs.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.volume_flux_divergence!","page":"Function and type index","title":"SpeedyWeather.volume_flux_divergence!","text":"volume_flux_divergence!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surface::SpeedyWeather.SurfaceVariables,\n orog::SpeedyWeather.AbstractOrography,\n constants::DynamicsConstants,\n G::Geometry,\n S::SpectralTransform\n)\n\n\nComputes the (negative) divergence of the volume fluxes uh,vh for the continuity equation, -∇⋅(uh,vh).\n\n\n\n\n\n","category":"function"},{"location":"functions/#Geometry","page":"Function and type index","title":"Geometry","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Geometry\nSpeedyWeather.vertical_coordinates\nSpeedyWeather.GenLogisticCoefs\nSpeedyWeather.generalised_logistic","category":"page"},{"location":"functions/#SpeedyWeather.Geometry","page":"Function and type index","title":"SpeedyWeather.Geometry","text":"Construct Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. Pass on SpectralGrid to calculate the following fields\n\nspectral_grid::SpectralGrid: SpectralGrid that defines spectral and grid resolution\nGrid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid of the dynamical core Default: spectral_grid.Grid\nnlat_half::Int64: resolution parameter nlathalf of Grid, # of latitudes on one hemisphere (incl Equator) Default: spectralgrid.nlat_half\nnlon_max::Int64: maximum number of longitudes (at/around Equator) Default: getnlonmax(Grid, nlat_half)\nnlon::Int64: =nlonmax, same (used for compatibility), TODO: still needed? Default: nlonmax\nnlat::Int64: number of latitude rings Default: getnlat(Grid, nlathalf)\nnlev::Int64: number of vertical levels Default: spectral_grid.nlev\nnpoints::Int64: total number of grid points Default: spectral_grid.npoints\nradius::AbstractFloat: Planet's radius [m] Default: spectral_grid.radius\nlatd::Vector{Float64}: array of latitudes in degrees (90˚...-90˚) Default: getlatd(Grid, nlathalf)\nlond::Vector{Float64}: array of longitudes in degrees (0...360˚), empty for non-full grids Default: getlond(Grid, nlathalf)\nlonds::Vector{NF} where NF<:AbstractFloat: longitude (-180˚...180˚) for each grid point in ring order Default: (getlatdlonds(Grid, nlathalf))[2]\nlatds::Vector{NF} where NF<:AbstractFloat: latitude (-90˚...˚90) for each grid point in ring order Default: (getlatdlonds(Grid, nlathalf))[1]\nsinlat::Vector{NF} where NF<:AbstractFloat: sin of latitudes Default: sind.(latd)\ncoslat::Vector{NF} where NF<:AbstractFloat: cos of latitudes Default: cosd.(latd)\ncoslat⁻¹::Vector{NF} where NF<:AbstractFloat: = 1/cos(lat) Default: 1 ./ coslat\ncoslat²::Vector{NF} where NF<:AbstractFloat: = cos²(lat) Default: coslat .^ 2\ncoslat⁻²::Vector{NF} where NF<:AbstractFloat: # = 1/cos²(lat) Default: 1 ./ coslat²\nσ_levels_half::Vector{NF} where NF<:AbstractFloat: σ at half levels, σk+1/2 Default: spectralgrid.verticalcoordinates.σhalf\nσ_levels_full::Vector{NF} where NF<:AbstractFloat: σ at full levels, σₖ Default: 0.5 * (σlevelshalf[2:end] + σlevelshalf[1:end - 1])\nσ_levels_thick::Vector{NF} where NF<:AbstractFloat: σ level thicknesses, σₖ₊₁ - σₖ Default: σlevelshalf[2:end] - σlevelshalf[1:end - 1]\nln_σ_levels_full::Vector{NF} where NF<:AbstractFloat: log of σ at full levels, include surface (σ=1) as last element Default: log.(vcat(σlevelsfull, 1))\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.GenLogisticCoefs","page":"Function and type index","title":"SpeedyWeather.GenLogisticCoefs","text":"Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF.\n\nFollowing the notation of https://en.wikipedia.org/wiki/Generalisedlogisticfunction (Dec 15 2021).\n\nChange default parameters for more/fewer levels in the stratosphere vs troposphere vs boundary layer.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.generalised_logistic","page":"Function and type index","title":"SpeedyWeather.generalised_logistic","text":"Generalised logistic function based on the coefficients in coefs.\n\n\n\n\n\n","category":"function"},{"location":"functions/#Time-stepping","page":"Function and type index","title":"Time stepping","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.time_stepping!\nSpeedyWeather.timestep!\nSpeedyWeather.first_timesteps!\nSpeedyWeather.leapfrog!","category":"page"},{"location":"functions/#SpeedyWeather.time_stepping!","page":"Function and type index","title":"SpeedyWeather.time_stepping!","text":"time_stepping!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n model::SpeedyWeather.ModelSetup\n) -> PrognosticVariables\n\n\nMain time loop that that initializes output and feedback, loops over all time steps and calls the output and feedback functions.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.timestep!","page":"Function and type index","title":"SpeedyWeather.timestep!","text":"timestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::Barotropic\n)\ntimestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::Barotropic,\n lf1::Int64\n)\ntimestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::Barotropic,\n lf1::Int64,\n lf2::Int64\n)\n\n\nCalculate a single time step for the model <: Barotropic.\n\n\n\n\n\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::ShallowWater\n) -> SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::ShallowWater,\n lf1::Int64\n) -> SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::ShallowWater,\n lf1::Int64,\n lf2::Int64\n) -> SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat\n\n\nCalculate a single time step for the model <: ShallowWater.\n\n\n\n\n\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::PrimitiveEquation\n) -> Any\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::PrimitiveEquation,\n lf1::Int64\n) -> Any\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::PrimitiveEquation,\n lf1::Int64,\n lf2::Int64\n) -> Any\n\n\nCalculate a single time step for the model<:PrimitiveEquation\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.first_timesteps!","page":"Function and type index","title":"SpeedyWeather.first_timesteps!","text":"first_timesteps!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n clock::SpeedyWeather.Clock,\n model::SpeedyWeather.ModelSetup,\n output::SpeedyWeather.AbstractOutputWriter\n) -> typeof(time)\n\n\nPerforms the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.leapfrog!","page":"Function and type index","title":"SpeedyWeather.leapfrog!","text":"leapfrog!(\n A_old::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n A_new::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n tendency::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n dt::Real,\n lf::Int64,\n L::Leapfrog{NF<:AbstractFloat}\n)\n\n\nPerforms one leapfrog time step with (lf=2) or without (lf=1) Robert+William's filter (see William (2009), Montly Weather Review, Eq. 7-9).\n\n\n\n\n\n","category":"function"},{"location":"functions/#Longwave-radiation","page":"Function and type index","title":"Longwave radiation","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.radset!\nSpeedyWeather.radlw_down!\nSpeedyWeather.compute_bbe!\nSpeedyWeather.radlw_up!","category":"page"},{"location":"functions/#Shortwave-radiation","page":"Function and type index","title":"Shortwave radiation","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.shortwave_radiation!\nSpeedyWeather.solar!\nSpeedyWeather.sol_oz!\nSpeedyWeather.cloud!\nSpeedyWeather.radsw!","category":"page"},{"location":"parametrizations/#Parameterizations","page":"Parameterizations","title":"Parameterizations","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmopshere. Every section is followed by a brief description of implementation details.","category":"page"},{"location":"parametrizations/#Convection","page":"Parameterizations","title":"Convection","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Large-scale-condensation","page":"Parameterizations","title":"Large-scale condensation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Clouds","page":"Parameterizations","title":"Clouds","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Short-wave-radiation","page":"Parameterizations","title":"Short-wave radiation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Long-wave-radiation","page":"Parameterizations","title":"Long-wave radiation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Surface-fluxes-of-momentum-and-energy","page":"Parameterizations","title":"Surface fluxes of momentum and energy","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Vertical-diffusion","page":"Parameterizations","title":"Vertical diffusion","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"how_to_run_speedy/#How-to-run-SpeedyWeather.jl","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The simplest way to run SpeedyWeather.jl with default parameters is","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"using SpeedyWeather\nrun_speedy()","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Hooray, you have just simulated the Earth's atmosphere. Parameters, their meanings and defaults are documented in Parameters. For example, if you want to run the primitive equation dry core (no humidity) simulation in double precision (Float64), at higher resolution (trunc, the triangular spectral truncation), slow down the rotation of the Earth (rotation in s^-1), and create some netCDF ouput, do","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"run_speedy(Float64,PrimitiveDry,trunc=42,planet=Earth(rotation=1e-5),output=true)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"If provided, the number format has to be the first argument, the model (Barotropic, ShallowWater, PrimitiveDry, PrimitiveWet are available) the second, and all other arguments are keyword arguments.","category":"page"},{"location":"how_to_run_speedy/#The-run_speedy-interface","page":"How to run SpeedyWeather.jl","title":"The run_speedy interface","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"run_speedy","category":"page"},{"location":"how_to_run_speedy/#SpeedyWeather.run_speedy","page":"How to run SpeedyWeather.jl","title":"SpeedyWeather.run_speedy","text":"progn_vars = run_speedy(NF,Model;kwargs...) or\nprogn_vars = run_speedy(NF;kwargs...) or\nprogn_vars = run_speedy(Model;kwargs...)\n\nRuns SpeedyWeather.jl with number format NF and the model Model and any additional parameters in the keyword arguments kwargs.... Unspecified parameters use the default values.\n\n\n\n\n\n","category":"function"},{"location":"how_to_run_speedy/#The-initialize_speedy-interface","page":"How to run SpeedyWeather.jl","title":"The initialize_speedy interface","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"initialize_speedy","category":"page"},{"location":"speedytransforms/#SpeedyTransforms","page":"Submodule: SpeedyTransforms","title":"SpeedyTransforms","text":"","category":"section"},{"location":"speedytransforms/","page":"Submodule: SpeedyTransforms","title":"Submodule: SpeedyTransforms","text":"SpeedyTransforms is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"speedytransforms/#Functions","page":"Submodule: SpeedyTransforms","title":"Functions","text":"","category":"section"},{"location":"grids/#Grids","page":"Grids","title":"Grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and but let's start but how they can be used","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"julia> spectral_grid = SpectralGrid(Grid = FullGaussianGrid)\nSpectralGrid:\n Spectral: T31 LowerTriangularMatrix{Complex{Float32}}, radius = 6.371e6 m\n Grid: 4608-element, 48-ring FullGaussianGrid{Float32} (quadratic)\n Resolution: 333km (average)\n Vertical: 8-level SigmaCoordinates","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object which defines the resolution in spectral and in grid-point space. The generator SpectralGrid() can take as a keyword argument Grid which can be any of the grids described below. The resolution of the grid, however, is not directly chosen, but determined from the spectral resolution trunc and the dealiasing factor. More in Matching spectral and grid resolution.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: RingGrids is a module too!\nWhile RingGrids is the underlying module that SpeedyWeather.jl uses for data structs on the sphere, the module can also be used independently of SpeedyWeather, for example to interpolate between data on different grids. See RingGrids","category":"page"},{"location":"grids/#Ring-based-equi-longitude-grids","page":"Grids","title":"Ring-based equi-longitude grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"SpeedyWeather.jl's spectral transform supports all ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on these rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.","category":"page"},{"location":"grids/#Implemented-grids","page":"Grids","title":"Implemented grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Currently the following full grids <: AbstractFullGrid are implemented","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"FullGaussianGrid, a full grid with Gaussian latitudes\nFullClenshawGrid, a full grid with equi-angle latitudes","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron\nOctahedralClenshawGrid, similar but based on equi-angle latitudes\nHEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces\nOctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"An overview of these grids is visualised here","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"(Image: Overview of implemented grids in SpeedyWeather.jl)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.","category":"page"},{"location":"grids/#Grid-resolution","page":"Grids","title":"Grid resolution","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half. ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: HEALPix grids do not use Nside as resolution parameter\nThe original formulation for HEALPix grids use N_side, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use N_side for the documentation or within functions though.","category":"page"},{"location":"grids/#Matching-spectral-and-grid-resolution","page":"Grids","title":"Matching spectral and grid resolution","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation T with a grid resolution N (=nlat_half) such that T + 1 = N. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at l_max=31 in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Let J be the total number of rings. Then we have","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"T approx J for linear truncation\nfrac32T approx J for quadratic truncation\n2T approx J for cubic truncation","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and in general fracm+12T approx J for m-th order truncation. So the higher the truncaction order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. In SpeedyWeather.jl the parameter dealiasing controls this option, = 1 would be linear, = 2 quadratic, = 3 cubic etc.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"For now just a quick overview of how the grid resolution changes when dealiasing is passed onto SpectralGrid on the FullGaussianGrid","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"trunc dealiasing FullGaussianGrid size\n31 1 64x32\n31 2 96x48\n31 3 128x64\n42 1 96x48\n42 2 128x64\n42 3 192x96\n... ... ...","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"You will obtain this information every time you create a SpectralGrid(;Grid,trunc,dealiasing).","category":"page"},{"location":"grids/#Full-Gaussian-grid","page":"Grids","title":"Full Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#Full-Clenshaw-Curtis-grid","page":"Grids","title":"Full Clenshaw-Curtis grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#Octahedral-Gaussian-grid","page":"Grids","title":"Octahedral Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#The-HEALPix-grid","page":"Grids","title":"The HEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are N_varphi basepixels in zonal direction and N_theta basepixels in meridional direction. For N_varphi = 4 and N_theta = 3 we obtain the classical HEALPix grid with N_varphi N_theta = 12 basepixels shown above in Implemented Grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always 2N, so 32 at N=16) and there are polar caps above and below the equatorial zone with the border at cos(theta) = 23 (theta in colatitudes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Following Górski, 2004[1], the z=cos(theta) colatitude of the j-th ring in the north polar cap, j=1N_side with 2N_side = N is ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^23N_side^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and on that ring, the longitude phi of the i-th point (i is the in-ring-index) is at","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i-tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The in-ring index i goes from i=14 for the first (i.e. northern-most) ring, i=18 for the second ring and i = 14j for the j-th ring in the northern polar cap.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"In the north equatorial belt j=N_side2N_side this changes to","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac43 - frac2j3N_side","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and the longitudes change to (i is always i = 14N_side in the equatorial belt meaning the number of longitude points is constant here)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2N_side(i - fracs2) quad s = (j - N_side + 1) mod 2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The cell boundaries are obtained by setting i = k + 12 or i = k + 12 + j (half indices) into the equations above, such that z(phik), a function for the cosine of colatitude z of index k and the longitude phi is obtained. These are then (northern polar cap)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^23N_side^2left(fracpi2phi_tright)^2 quad z = 1 - frack^23N_side^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with phi_t = phi mod tfracpi2 and in the equatorial belt","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac23-frac4k3N_side pm frac8phi3pi","category":"page"},{"location":"grids/#OctaHEALPix-grid","page":"Grids","title":"OctaHEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"While the classic HEALPix grid is based on a dodecahedron, other choices for N_varphi and N_theta in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With N_varphi = 4 and N_theta = 1 we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, 2pi around the Equator versus pi between the poles. N_varphi = 6 N_theta = 2 or N_varphi = 8 N_theta = 3 are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visulations of these grids.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"We call the N_varphi = 4 N_theta = 1 HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As N_theta = 1 there is no equatorial belt which simplifies the grid. The latitude of the j-th isolatitude ring on the OctaHEALPixGrid is defined by","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^2N^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with j=1N, and similarly for the southern hemisphere by symmetry. On this grid N_side = N where N= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index i = 14j are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i - tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and again, the southern hemisphere grid points are obtained by symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries-2","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Similar to the grid cell boundaries for the HEALPix grid, the OctaHEALPix grid's boundaries are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^2N^2left(fracpi2phi_tright)^2 quad z = 1 - frack^2N^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The 3N_side^2 in the denominator of the HEALPix grid came simply N^2 for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).","category":"page"},{"location":"grids/#References","page":"Grids","title":"References","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"[1] Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976","category":"page"},{"location":"boundary_conditions/#Boundary-conditions","page":"Boundary conditions","title":"Boundary conditions","text":"","category":"section"},{"location":"boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"This page describes the formulation of boundary conditions and their implementation.","category":"page"},{"location":"lowertriangularmatrices/#LowerTriangularMatrices","page":"Submodule: LowerTriangularMatrices","title":"LowerTriangularMatrices","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"LowerTriangularMatrices is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"This module defines LowerTriangularMatrix, a lower triangular matrix, which in contrast to LinearAlgebra.LowerTriangular does not store the entries above the diagonal. SpeedyWeather.jl uses LowerTriangularMatrix which is defined as a subtype of AbstractMatrix to store the spherical harmonic coefficients (see Spectral packing). ","category":"page"},{"location":"lowertriangularmatrices/#Creation-of-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Creation of LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"A LowerTriangularMatrix can be created using zeros,ones,rand, or randn","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> using SpeedyWeather.LowerTriangularMatrices\n\njulia> L = rand(LowerTriangularMatrix{Float32},5,5)\n5×5 LowerTriangularMatrix{Float32}:\n 0.912744 0.0 0.0 0.0 0.0\n 0.0737592 0.230592 0.0 0.0 0.0\n 0.799679 0.0765255 0.888098 0.0 0.0\n 0.670835 0.997938 0.505276 0.492966 0.0\n 0.949321 0.193692 0.793623 0.152817 0.357968","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"or the undef initializor LowerTriangularMatrix{Float32}(undef,3,3). The element type is arbitrary though, you can use any type T too.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Alternatively, it can be created through conversion from Matrix, which drops the upper triangle entries and sets them to zero","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> M = rand(Float16,3,3)\n3×3 Matrix{Float16}:\n 0.2222 0.694 0.3452\n 0.2158 0.04443 0.274\n 0.9746 0.793 0.6294\n\njulia> LowerTriangularMatrix(M)\n3×3 LowerTriangularMatrix{Float16}:\n 0.2222 0.0 0.0\n 0.2158 0.04443 0.0\n 0.9746 0.793 0.6294","category":"page"},{"location":"lowertriangularmatrices/#Indexing-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Indexing LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"LowerTriangularMatrix supports two types of indexing: 1) by denoting two indices, column and row [l,m] or 2) by denoting a single index [lm]. The double index works as expected","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L\n3×3 LowerTriangularMatrix{Float16}:\n 0.1499 0.0 0.0\n 0.1177 0.478 0.0\n 0.1709 0.756 0.3223\n\njulia> L[2,2]\nFloat16(0.478)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"But the single index skips the zero entries in the upper triangle, i.e.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L[4]\nFloat16(0.478)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"which, important, is different from single indices of an AbstractMatrix","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> Matrix(L)[4]\nFloat16(0.0)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"In performance-critical code a single index should be used, as this directly maps to the index of the underlying data vector. The double index is somewhat slower as it first has to be converted to the corresponding single index.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Consequently, many loops in SpeedyWeather.jl are build with the following structure","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"n,m = size(L)\nij = 0\nfor j in 1:m\n for i in j:n\n ij += 1\n L[ij] = i+j\n end\nend","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"which loops over all lower triangle entries of L::LowerTriangularMatrix and the single index ij is simply counted up. However, one could also use [i,j] as indices in the loop body or to perform any calculation (i+j here). An iterator over all entries in the lower triangle can be created by","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"for ij in eachindex(L)\n # do something\nend","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"The setindex! functionality of matrixes will throw a BoundsError when trying to write into the upper triangle of a LowerTriangularMatrix, for example","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L[2,1] = 0 # valid index\n0\n\njulia> L[1,2] = 0 # invalid index in the upper triangle\nERROR: BoundsError: attempt to access 3×3 LowerTriangularMatrix{Float32} at index [1, 2]","category":"page"},{"location":"lowertriangularmatrices/#Linear-algebra-with-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Linear algebra with LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"The LowerTriangularMatrices module's main purpose is not linear algebra, and it's implementation may not be efficient, however, many operations work as expected","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L = rand(LowerTriangularMatrix{Float32},3,3)\n3×3 LowerTriangularMatrix{Float32}:\n 0.57649 0.0 0.0\n 0.348685 0.875371 0.0\n 0.881923 0.850552 0.998306\n\njulia> L + L\n3×3 LowerTriangularMatrix{Float32}:\n 1.15298 0.0 0.0\n 0.697371 1.75074 0.0\n 1.76385 1.7011 1.99661\n\njulia> L * L\n3×3 Matrix{Float32}:\n 0.332341 0.0 0.0\n 0.506243 0.766275 0.0\n 1.68542 1.59366 0.996616","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Note, however, that the latter includes a conversion to Matrix, which is true for many operations, including inv or \\. Hence when trying to do more sophisticated linear algebra with LowerTriangularMatrix we quickly leave lower triangular-land and go back to normal matrix-land.","category":"page"},{"location":"lowertriangularmatrices/#Function-index","page":"Submodule: LowerTriangularMatrices","title":"Function index","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"LowerTriangularMatrix\nLowerTriangularMatrices.ij2k\nBase.fill!(L::LowerTriangularMatrix{T}, x) where T\nLowerTriangularMatrices.eachharmonic","category":"page"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix","text":"L = LowerTriangularMatrix{T}(v::Vector{T},m::Int,n::Int)\n\nA lower triangular matrix implementation that only stores the non-zero entries explicitly. L<:AbstractMatrix although in general we have L[i] != Matrix(L)[i], the former skips zero entries, tha latter includes them.\n\n\n\n\n\n","category":"type"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.ij2k","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.ij2k","text":"k = ij2k( i::Integer, # row index of matrix\n j::Integer, # column index of matrix\n m::Integer) # number of rows in matrix\n\nConverts the index pair i,j of an mxn LowerTriangularMatrix L to a single index k that indexes the same element in the corresponding vector that stores only the lower triangle (the non-zero entries) of L.\n\n\n\n\n\n","category":"function"},{"location":"lowertriangularmatrices/#Base.fill!-Union{Tuple{T}, Tuple{LowerTriangularMatrix{T}, Any}} where T","page":"Submodule: LowerTriangularMatrices","title":"Base.fill!","text":"fill!(L::LowerTriangularMatrix,x)\n\nFills the elements of L with x. Faster than fill!(::AbstractArray,x) as only the non-zero elements in L are assigned with x.\n\n\n\n\n\n","category":"method"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.eachharmonic","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.eachharmonic","text":"unit_range = eachharmonic(L::LowerTriangular)\n\ncreates unit_range::UnitRange to loop over all non-zeros in a LowerTriangularMatrix L. Like eachindex but skips the upper triangle with zeros in L.\n\n\n\n\n\nunit_range = eachharmonic(Ls::LowerTriangularMatrix...)\n\ncreates unit_range::UnitRange to loop over all non-zeros in the LowerTriangularMatrices provided as arguments. Checks bounds first. All LowerTriangularMatrix's need to be of the same size. Like eachindex but skips the upper triangle with zeros in L.\n\n\n\n\n\n","category":"function"},{"location":"conventions/#Style-and-convention-guide","page":"Style and convention guide","title":"Style and convention guide","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"In SpeedyWeather.jl we've been following the several conventions that are documented here.","category":"page"},{"location":"conventions/#Variable-naming","page":"Style and convention guide","title":"Variable naming","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The prognostic variables in spectral space are called","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" vor # Vorticity of horizontal wind field\n div # Divergence of horizontal wind field\n temp # Absolute temperature [K]\n pres_surf # Logarithm of surface pressure [log(Pa)]\n humid # Specific humidity [g/kg]","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" u\n v\n geopot\n ...","category":"page"},{"location":"conventions/#Preallocation","page":"Style and convention guide","title":"Preallocation","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"All arrays representing variables are preallocated and grouped into two structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" Prog::PrognosticVariables\n Diag::DiagnosticVariables","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The Diag struct contains further structs which represent the grid-point transformations of the prognostic variables and their tendencies.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" gridvars::GridVariables\n tendencies::Tendencies\n ...","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Constant arrays are grouped into several structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Boundaries","category":"page"},{"location":"conventions/#Julian-conventions","page":"Style and convention guide","title":"Julian conventions","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"We follow Julia's style guide and highlight here some important aspects of it.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Bang (!) convention. A function func does not change its input arguments, however, func! does.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.","category":"page"},{"location":"extending/#New-model-setups","page":"Extending SpeedyWeather","title":"New model setups","text":"","category":"section"},{"location":"extending/","page":"Extending SpeedyWeather","title":"Extending SpeedyWeather","text":"more to come...","category":"page"},{"location":"dynamical_core/#Dynamical-core","page":"Dynamical core","title":"Dynamical core","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"A mathematical and implementation-specific description of the dynamical core used in SpeedyWeather.jl. We start by describing the barotropic vorticity equations which is one set of equations that SpeedyWeather.jl can solve (see How to run SpeedyWeather.jl) as many details therein also apply to the Shallow water equations and Primitive equations explained thereafter.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The dynamical core presented here largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2], a shallow water model [3] and a primitive equation model[4]. ","category":"page"},{"location":"dynamical_core/#Barotropic-vorticity-equation","page":"Dynamical core","title":"Barotropic vorticity equation","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity zeta with advection, Coriolis force and diffusion in a single global layer on the sphere.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"fracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) =\nnabla times mathbfF + (-1)^n+1nunabla^2nzeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with time t, velocity vector mathbfu = (u v), Coriolis parameter f, and hyperdiffusion (-1)^n+1 nu nabla^2n zeta (n is the hyperdiffusion order; see Horizontal diffusion). We also include a forcing vector mathbfF = (F_uF_v) which acts on the zonal velocity u and the meridional velocity v and hence its curl nabla times mathbfF is a tendency for relative vorticity zeta.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Starting with some relative vorticity zeta, the Laplacian is inverted to obtain the stream function Psi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Psi = nabla^-2zeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The zonal velocity u and meridional velocity v are then the (negative) meridional gradient and zonal gradient of Psi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nu = -frac1R fracpartial Psipartial theta \nv = frac1Rcos(theta) fracpartial Psipartial phi \nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"which is described in Derivatives in spherical coordinates.","category":"page"},{"location":"dynamical_core/#Algorithm","page":"Dynamical core","title":"Algorithm","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"We briefly outline the algorithm that SpeedyWeather.jl uses in order to integrate the barotropic vorticity equation. As an intial step","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"0. Start with initial conditions of zeta_lm in spectral space and transform this model state to grid-point space:","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Invert the Laplacian to obtain the stream function Psi_lm in spectral space\nobtain zonal velocity (cos(theta)u)_lm through a Meridional derivative\nobtain meridional velocity (cos(theta)v)_lm through a Zonal derivative\nTransform zonal and meridional velocity (cos(theta)u)_lm, (cos(theta)v)_lm to grid-point space\nUnscale the cos(theta) factor to obtain uv\nTransform zeta_lm to zeta in grid-point space","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Now loop over","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Compute the forcing vector mathbfF = (F_uF_v) for u and v\nMultiply uv with zeta+f in grid-point space\nAdd A = F_u + v(zeta + f) and B = F_v - u(zeta + f)\nTransform these vector components to spectral space A_lm, B_lm\nCompute the curl of (AB)_lm in spectral space which is the tendency of zeta_lm\nCompute the Horizontal diffusion based on that tendency\nCompute a leapfrog time step as described in Time integration\nTransform the spectral state of zeta_lm to grid-point uvzeta as described in 0.\nPossibly do some output\nRepeat from 1.","category":"page"},{"location":"dynamical_core/#Shallow-water-equations","page":"Dynamical core","title":"Shallow water equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nfracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) = (-1)^n+1nunabla^2nzeta \nfracpartial mathcalDpartial t - nabla times (mathbfu(zeta + f)) = -nabla^2(tfrac12(u^2 + v^2) + geta) + (-1)^n+1nunabla^2nmathcalD \nfracpartial etapartial t + nabla cdot (mathbfuh) = 0\nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"more to come","category":"page"},{"location":"dynamical_core/#Primitive-equations","page":"Dynamical core","title":"Primitive equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The primitive equations solved by SpeedyWeather.jl are","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\npartial_t u = \npartial_t v = \npartial_t T = \npartial_t Q = \nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"more to come","category":"page"},{"location":"dynamical_core/#Horizontal-diffusion","page":"Dynamical core","title":"Horizontal diffusion","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In SpeedyWeather.jl we use hyerdiffusion through an n-th power Laplacian (-1)^n+1nabla^2n (hyper when n1) which can be implemented as a multiplication of the spectral coefficients Psi_lm with (-l(l+1))^nR^-2n (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the (-l(l+1))^nR^-2n can be precomputed. Note the sign change (-1)^n+1 here is such that the dissipative nature of the diffusion operator is retained for n odd and even.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step Delta t of variable zeta to obtain from time steps i-1 and i, the next time step i+1","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t dzeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with dzeta being some tendency evaluated from zeta_i. Now we want to add a diffusion term (-1)^n+1nu nabla^2nzeta with viscosity nu, wich however, is implicitly calculated from zeta_i+1, then","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t (dzeta + (-1)^n+1 nunabla^2nzeta_i+1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"As the application of (-1)^n+1nunabla^2n is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = fraczeta_i-1 + 2Delta t dzeta1 - 2Delta (-1)^n+1nunabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and expand the numerator to","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t fracdzeta + (-1)^n+1 nunabla^2nzeta_i-11 - 2Delta t (-1)^n+1nu nabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Hence the diffusion can be applied implicitly by updating the tendency dzeta as","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"dzeta to fracdzeta + (-1)^n+1nunabla^2nzeta_i-11 - 2Delta t nu nabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"which only depends on zeta_i-1. Now let D_textexplicit = (-1)^n+1nunabla^2n be the explicit part and D_textimplicit = 1 - (-1)^n+1 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are D_textimplicit = 1 - 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic lm we do","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"dzeta to D_textimplicit^-1(dzeta + D_textexplicitzeta_i-1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power n.","category":"page"},{"location":"dynamical_core/#Normalization-of-diffusion","page":"Dynamical core","title":"Normalization of diffusion","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In physics, the Laplace operator nabla^2 is often used to represent diffusion due to viscosity in a fluid. In that case, the viscosity coefficient is nu of units textm^2texts^-1 and the full operator reads as nu nabla^2 with units (textm^2texts^-1)(textm^-2) = texts^-1. This motivates us to normalize the Laplace operator by a constant of units textm^-2 and the viscosity coefficient by its inverse such that the viscosity coefficient becomes a damping timescale of unit texts^-1. Given the application in spectral space we decide to normalize by the largest eigenvalue -l_textmax(l_textmax+1) such that all entries in the discrete spectral Laplace operator are in 01. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized viscosity coefficient nu^* = l_textmax(l_textmax+1)nu (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have ","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"D^textexplicit_lm = -nu^* fracl(l+1)l_textmax(l_textmax+1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and the hyper-Laplacian of power n follows as","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"D^textexplicitn_lm = -nu^* left(fracl(l+1)l_textmax(l_textmax+1)right)^n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and the implicit part is accordingly D^textimplicitn_lm = 1 - 2Delta t D^textexplicitn_lm.","category":"page"},{"location":"dynamical_core/#Radius-scaling","page":"Dynamical core","title":"Radius scaling","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"SpeedyWeather.jl uses a scaling for vorticity zeta and stream function Psi that is","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildezeta = zeta R tildePsi = Psi R^-1","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In the barotropic voriticity equation model the inversion of the Laplcians in order to obtain Psi from zeta therefore becomes","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildezeta = tildenabla^2 tildePsi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"where the dimensionless gradients simply omit the scaling with 1R, tildenabla = Rnabla. The Barotropic vorticity equation scaled with R^2 is","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"partial_tildettildezeta + tildenabla cdot (mathbfu(tildezeta + tildef)) = tildenutildenabla^2ntildezeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildet = tR^-1, the scaled time t\nmathbfu = (uv), the velocity vector (no scaling applied)\ntildef = fR, the scaled Coriolis parameter f\ntildenu = nu^* R, the scaled viscosity nu^*, which itself is normalized to a damping time scale, see Normalization of diffusion.","category":"page"},{"location":"dynamical_core/#Scaled-shallow-water-equations","page":"Dynamical core","title":"Scaled shallow water equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Similar to the scaled barotropic vorticity equations, the scaled shallow water equations scale the vorticity and the divergence equation with R^2, but the continuity equation with R","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nfracpartial tildezetapartial tildet + tildenabla cdot (mathbfu(tildezeta + tildef)) =\ntildenutildenabla^2ntildezeta \nfracpartial tildemathcalDpartial tildet - tildenabla times (mathbfu(tildezeta + tildef)) =\n-tildenabla^2left(tfrac12(u^2 + v^2) + geta right) + tildenutildenabla^2ntildemathcalD \nfracpartial etapartial tildet + tildenabla cdot (mathbfuh) = 0\nendaligned","category":"page"},{"location":"dynamical_core/#Time-integration","page":"Dynamical core","title":"Time integration","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"SpeedyWeather.jl uses a leapfrog time scheme with a Robert's and William's filter to dampen the computational mode and achieve 3rd order accuracy.","category":"page"},{"location":"dynamical_core/#Oscillation-equation","page":"Dynamical core","title":"Oscillation equation","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"fracdFdt = iomega F","category":"page"},{"location":"dynamical_core/#References","page":"Dynamical core","title":"References","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[1]: Geophysical Fluid Dynamics Laboratory, Idealized models with spectral dynamics","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[2]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[3]: Geophysical Fluid Dynamics Laboratory, The Shallow Water Equations.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[4]: Geophysical Fluid Dynamics Laboratory, The Spectral Dynamical Core","category":"page"},{"location":"spectral_transform/#Spherical-Harmonic-Transform","page":"Spherical harmonic transform","title":"Spherical Harmonic Transform","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space on a longitude-latitude regular Gaussian grid.","category":"page"},{"location":"spectral_transform/#Inspiration","page":"Spherical harmonic transform","title":"Inspiration","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform implemented by SpeedyWeather.jl follows largely Justin Willmert's CMB.jl package and makes use of AssociatedLegendrePolynomials.jl and FFTW.jl (for Float32/64) or GenericFFT.jl (for generic) for the Fourier transform. Justin described his work in a Blog series [1][2][3][4][5][6][7][8].","category":"page"},{"location":"spectral_transform/#Spherical-harmonics","page":"Spherical harmonic transform","title":"Spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spherical harmonics Y_lm of degree l and order m over the longitude phi = (02pi) and colatitudes theta = (-pi2pi2), are","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Y_lm(phi theta) = lambda_l^m(sintheta) e^imphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with lambda_l^m being the pre-normalized associated Legendre polynomials, and e^imphi are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Latitudes versus colatitudes\nThe implementations of the spherical transforms in SpeedyWeather.jl use colatitudes theta = (0pi) (0 at the north pole) but the dynamical core uses latitudes theta = (-pi2pi2) (pi2 at the north pole). However, all arrays are always sorted north to south such that [i,1] will access the northern-most grid points. Note: We may also use latitudes in the spherical harmonic transform in the future for consistency. ","category":"page"},{"location":"spectral_transform/#Synthesis-(spectral-to-grid)","page":"Spherical harmonic transform","title":"Synthesis (spectral to grid)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The synthesis (or inverse transform) takes the spectral coefficients a_lm and transforms them to grid-point values f(phitheta) (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics Y_lm with non-zero coefficients.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"f(phitheta) = sum_l=0^infty sum_m=-l^l a_lm Y_lm(phitheta)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We obtain an approximation with a finite set of a_lm by truncating the series after l = l_max.","category":"page"},{"location":"spectral_transform/#Analysis-(grid-to-spectral)","page":"Spherical harmonic transform","title":"Analysis (grid to spectral)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting in grid-point space we can transform a field f(lambdatheta) into the spectral space of the spherical harmonics by","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"a_lm = int_0^2pi int_-tfracpi2^tfracpi2 f(lambdatheta) Y_lm(lambdatheta) cos theta dtheta dlambda","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This integral has to be discretized to when grid-point values f(lambda_itheta_i) are used. For more details, see [7],[8].","category":"page"},{"location":"spectral_transform/#Spectral-packing","page":"Spherical harmonic transform","title":"Spectral packing","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Spectral packing is the way how the coefficients a_lm of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree l and order m as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Every row represents an order l geq 0, starting from l=0 at the top. Every column represents an order m geq 0, starting from m=0 on the left. The coefficients of these spherical harmonics are directly mapped into a matrix a_lm as ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 \n a_10 a_11 \n a_20 a_12 a_22 \n a_30 a_13 a_23 a_33","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal explicitly stores zeros. Note that internally vector fields include an additional degree, such that l_max = m_max + 1 (see Gradients in spherical coordinates for more information). The harmonics with a_l0 (the first column) are also called zonal harmonics as they are constant with longitude phi. The harmonics with a_ll (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into 2l sectors in longitude phi without a zero-crossing in latitude.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Array indices\nFor a spectral field alms note that due to Julia's 1-based indexing the coefficient a_lm is obtained via alms[l+1,m+1].","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Fortran speedy does not use the same spectral packing as SpeedyWeather.jl. The alternative packing lm therein uses l=m and m=l-m as summarized in the following table.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"degree l order m l=m m=l-m\n0 0 0 0\n1 0 0 1\n1 1 1 0\n2 0 0 2\n2 1 1 1\n2 2 2 0\n3 0 0 3\n... ... ... ...","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 a_10 a_20 a_30\n a_11 a_21 a_31 \n a_22 a_32 \n a_33 ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran-speedy.","category":"page"},{"location":"spectral_transform/#Example-transforms","page":"Spherical harmonic transform","title":"Example transforms","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"julia> using SpeedyWeather\njulia> alms = zeros(ComplexF64,3,3) # spectral coefficients\njulia> alms[2,2] = 1 # only l=1,m=1 harmonic\njulia> map = gridded(alms) # convert to grid space\n8×4 Matrix{Float64}:\n -0.324541 -0.600363 -0.600363 -0.324541\n -0.134429 -0.248678 -0.248678 -0.134429\n 0.134429 0.248678 0.248678 0.134429\n 0.324541 0.600363 0.600363 0.324541\n 0.324541 0.600363 0.600363 0.324541\n 0.134429 0.248678 0.248678 0.134429\n -0.134429 -0.248678 -0.248678 -0.134429\n -0.324541 -0.600363 -0.600363 -0.324541\n \njulia> spectral(map) # back to spectral space\n3×3 Matrix{ComplexF64}:\n 0.0+0.0im 0.0+0.0im 0.0+0.0im\n 0.0+0.0im 1.0+3.60727e-17im 0.0+0.0im\n 0.0+0.0im 0.0+0.0im 0.0+0.0im","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"and we have successfully reobtained the l=m=1 spherical harmonic.","category":"page"},{"location":"spectral_transform/#Available-horizontal-resolutions","page":"Spherical harmonic transform","title":"Available horizontal resolutions","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with l leq l_max and m leq m_max are explicitly represented. This is usually described as Tm_max, with l_max = m_max (although in vector quantities require one more degree l in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with l_max = m_max = 31. Note that the degree l and order m are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Using triangular truncation[9], there are constraints on the corresponding grid resolution. Let nlon, nlat be the number of longitudes, latitudes on a regular Gaussian grid. Then spectral and grid resolution have to be chosen such that","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nlon geq 3l_max+1\nnlat geq (3l_max+1)2","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In general, we choose nlon = 2nlat, and ideally nlon is easily Fourier-transformable, e.g. nlon = 2^i3^j5^k with some integers ijk geq 0. SpeedyWeather.jl is tested at the following horizontal resolutions, with Delta x = tfrac2pi Rnlon as the approximate grid spacing at the Equator","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"l_max nlon nlat Delta x\n31 (default) 96 48 400 km\n42 128 64 300 km\n85 256 128 160 km\n170 512 256 80 km\n341 1024 512 40 km\n682 2048 1024 20 km","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Choosing trunc as argument in run_speedy will automatically choose nlon,nlat as presented in the table. Other common choices are T63 (192x96), T127 (384x192), T255 (768x384), T511 (1536x768), among others.","category":"page"},{"location":"spectral_transform/#Derivatives-in-spherical-coordinates","page":"Spherical harmonic transform","title":"Derivatives in spherical coordinates","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Horizontal gradients in spherical coordinates are defined for a scalar field A and the latitudes theta and longitudes lambda as","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla A = left(frac1Rcosthetafracpartial Apartial lambda frac1Rfracpartial Apartial theta right)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"However, the divergence of a vector field mathbfu = (uv) includes additional cos(theta) scalings","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla cdot mathbfu = frac1Rcosthetafracpartial upartial lambda +\nfrac1Rcosthetafracpartial (v costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"and similar for the curl","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla times mathbfu = frac1Rcosthetafracpartial vpartial lambda -\nfrac1Rcosthetafracpartial (u costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The radius of the sphere (i.e. Earth) is R. The zonal gradient scales with 1cos(theta) as the longitudes converge towards the poles (note that theta describes latitudes here, defintions using colatitudes replace the cos with a sin.)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting with a spectral field of vorticity zeta and divergence mathcalD one can obtain stream function Psi and velocity potential Phi by inverting the Laplace operator nabla^2:","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi = nabla^-2zeta quad Phi = nabla^-2mathcalD","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The velocities uv are then obtained from (uv) = nabla^botPsi + nablaPhi following the defintion from above and nabla^bot = (-R^-1partial_theta (Rcostheta)^-1partial_lambda)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nu = -frac1Rpartial_thetaPsi + frac1Rcosthetapartial_lambdaPhi \nv = +frac1Rpartial_thetaPhi + frac1Rcosthetapartial_lambdaPsi\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Alternatively, we can use the velocities U = ucostheta V = vcostheta, which we do as the meridional gradient for spherical harmonics is easier implemented with a costheta-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with UV and not uv. From uv we can return to zeta mathcalD via","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nzeta = frac1Rcosthetapartial_lambda v - frac1Rcosthetapartial_theta (u costheta) \nmathcalD = frac1Rcosthetapartial_lambda u + frac1Rcosthetapartial_theta (v costheta)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Equivalently, we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU = -fraccosthetaRpartial_thetaPsi + frac1Rpartial_lambdaPhi \nV = +fraccosthetaRpartial_thetaPhi + frac1Rpartial_lambdaPsi \nzeta = frac1Rpartial_lambda left( fracVcos^2theta right) -\nfraccosthetaRpartial_theta left( fracUcos^2theta right) \nmathcalD = frac1Rpartial_lambda left( fracUcos^2theta right) +\nfraccosthetaRpartial_theta left( fracVcos^2theta right)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is a more convenient formulation as required costheta scalings are reduced to a minimum. The remaining (UV)*cos^-2theta are done in grid-point space and usually in combination with other operations like the computation of the vorticity flux. But also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out too. As described in Meridional derivative, it is more convenient to implement costheta partial_theta via a recursion relation for the Legendre polynomials than partial_theta directly. How the operators nabla nabla times nabla cdot can be implemented with spherical harmonics is presented in the following sections.","category":"page"},{"location":"spectral_transform/#Zonal-derivative","page":"Spherical harmonic transform","title":"Zonal derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The zonal derivative of a scalar field Psi in spectral space is the zonal derivative of all its respective spherical harmonics Psi_lm(phitheta) (now we use phi for longitudes to avoid confusion with the Legendre polynomials lambda_lm)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"v_lm = frac1R cos(theta) fracpartialpartial phi left( lambda_l^m(costheta) e^imphi right) =\nfracimR cos(theta) lambda_l^m(costheta) e^imphi = fracimR cos(theta) Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"So for every spectral harmonic, cos(theta)v_lm is obtained from Psi_lm via a multiplication with imR. Unscaling the cos(theta)-factor is done after transforming the spectral coefficients v_lm into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as tildePsi = R^-1Psi such that the division by radius R in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order im. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number m times imaginary i.","category":"page"},{"location":"spectral_transform/#Meridional-derivative","page":"Spherical harmonic transform","title":"Meridional derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridioinal derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costheta fracdP_lmdtheta = -lepsilon_l+1mP_l+1m + (l+1)epsilon_lmP_l-1m","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with recursion factors","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"epsilon_lm = sqrtfracl^2-m^24l^2-1","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In the following we use the example of obtaining the zonal velocity u from the stream function Psi, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi(lambdatheta) = sum_lmPsi_lmP_lm(sintheta)e^imlambda","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"we multiply with -R^-1costhetapartial_theta to obtain","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costhetaleft(-frac1Rpartial_thetaPsi right) = -frac1Rsum_lmPsi_lme^imlambdacosthetapartial_theta P_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"at which point the recursion from above can be applied. Collecting terms proportional to P_lm then yields","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"(cos(theta)u)_lm = -frac1R(-(l-1)epsilon_lmPsi_l-1m + (l+2)epsilon_l+1mPsi_l+1m)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"To obtain the coefficient of each spherical harmonic lm of the meridional gradient of a spectral field, two coefficients at l-1m and l+1m have to be combined. This means that the coefficient of a gradient ((costheta) u)_lm is a linear combination of the coefficients of one higher and one lower degree Psi_l+1mPsi_l-1m. As the coefficient Psi_lm with ml are zero, the sectoral harmonics (l=m) of the gradients are obtained from the first off-diagonal only. However, the l=l_max harmonics of the gradients require the l_max-1 as well as the l_max+1 harmonics. In SpeedyWeather.jl vector quantitie like uv use therefore one more meridional mode than scalar quantities such as vorticity zeta or stream function Psi. The meridional derivative in SpeedyWeather.jl also omits the 1R-scaling as explained for the Zonal derivative and in Radius scaling.","category":"page"},{"location":"spectral_transform/#Divergence-and-curl-in-spherical-harmonics","page":"Spherical harmonic transform","title":"Divergence and curl in spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridional gradient as described above can be applied to scalars, such as Psi and Phi in the conversion to velocities (uv) = nabla^botPsi + nablaPhi, however, the operators curl nabla times and divergence nabla cdot in spherical coordinates involve a costheta scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform of vorticity zeta is","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac12piint_-tfracpi2^tfracpi2int_0^2pi zeta(lambdatheta) P_lm(sintheta) e^imlambda dlambda costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Given that Rzeta = cos^-1partial_lambda v - cos^-1partial_theta (u costheta), we therefore have to evaluate a meridional integral of the form","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"int P_lm frac1cos theta partial_theta(u costheta)) cos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which can be solved through integration by parts. As ucostheta = 0 at theta = pm tfracpi2 only the integral","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int partial_theta P_lm (u costheta) dtheta = -int costheta partial_theta P_lm (fracucostheta) costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"remains. Inserting the recurrence relation from the Meridional derivative turns this into","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int left(-l epsilon_l+1mP_l+1m + (l+1)epsilon_lm P_l-1m right) (fracucostheta) cos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Now we expand (tfracucostheta) but only the lm harmonic will project ontoP_lm. Let u^* = ucos^-1theta v^* = vcos^-1theta we then have in total","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nRzeta_lm = imv^*_lm + (l+1)epsilon_lmu^*_l-1m - lepsilon_l+1mu^*_l+1m \nRD_lm = imu^*_lm - (l+1)epsilon_lmv^*_l-1m + lepsilon_l+1mv^*_l+1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"And the divergence D is similar, but (uv) to (-vu). We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#Laplacian","page":"Spherical harmonic transform","title":"Laplacian","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral Laplacian is easily applied to the coefficients Psi_lm of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator nabla^2 in spherical coordinates with eigenvalues -l(l+1) divided by the radius squared R^2, i.e. nabla^2 Psi becomes tfrac-l(l+1)R^2Psi_lm in spectral space. For example, vorticity zeta and streamfunction Psi are related by zeta = nabla^2Psi in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree l and order m to","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac-l(l+1)R^2Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This can be easily inverted to obtain the stream function Psi from vorticity zeta instead. In order to avoid division by zero, we set Psi_00 here, given that the stream function is only defined up to a constant anyway.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nPsi_lm = fracR^2-l(l+1)zeta_lm quad foralllm 0\nPsi_00 = 0\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"See also Horizontal diffusion and Normalization of diffusion.","category":"page"},{"location":"spectral_transform/#U,V-from-vorticity-and-divergence","page":"Spherical harmonic transform","title":"U,V from vorticity and divergence","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity zeta and divergence D (which are prognostic variables) to U=ucostheta V=vcostheta. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree l (the meridional gradient). It is therefore computationally more efficient to compute UV directly from zetaD instead of calculating stream function and velocity potential first. In total we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU_lm = -fraciml(l+1)(RD)_lm + fracepsilon_l+1ml+1(Rzeta)_l+1m - fracepsilon_lml(Rzeta)_l-1m \nV_lm = -fraciml(l+1)(Rzeta)_lm - fracepsilon_l+1ml+1(RD)_l+1m + fracepsilon_lml(RD)_l-1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#References","page":"Spherical harmonic transform","title":"References","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[1]: Justin Willmert, 2020. Introduction to Associated Legendre Polynomials (Legendre.jl Series, Part I)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[2]: Justin Willmert, 2020. Calculating Legendre Polynomials (Legendre.jl Series, Part II)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[3]: Justin Willmert, 2020. Pre-normalizing Legendre Polynomials (Legendre.jl Series, Part III)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[4]: Justin Willmert, 2020. Maintaining numerical accuracy in the Legendre recurrences (Legendre.jl Series, Part IV)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[5]: Justin Willmert, 2020. Introducing Legendre.jl (Legendre.jl Series, Part V)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[6]: Justin Willmert, 2020. Numerical Accuracy of the Spherical Harmonic Recurrence Coefficient (Legendre.jl Series Addendum)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[7]: Justin Willmert, 2020. Notes on Calculating the Spherical Harmonics","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[8]: Justin Willmert, 2022. More Notes on Calculating the Spherical Harmonics: Analysis of maps to harmonic coefficients","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[9]: David Randall, 2021. An Introduction to Numerical Modeling of the Atmosphere, Chapter 22.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[10]: Dale Durran, 2010. Numerical Methods for Fluid Dynamics, Springer. In particular section 6.2, 6.4.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[11]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"ringgrids/#RingGrids","page":"Submodule: RingGrids","title":"RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids defines several iso-latitude grids, which are mathematically described in the section on Grids. In brief, they include the regular latitude-longitude grids (here called FullClenshawGrid) as well as grids which latitudes are shifted to the Gaussian latitudes and reduced grids, meaning that they have a decreasing number of longitudinal points towards the poles to be more equal-area than full grids.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids defines and exports the following grids:","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"full grids: FullClenshawGrid, FullGaussianGrid, FullHEALPix, and FullOctaHEALPix\nreduced grids: OctahedralGaussianGrid, OctahedralClenshawGrid, OctaHEALPixGrid and HEALPixGrid","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"The following explanation of how to use these can be mostly applied to any of them, however, there are certain functions that are not defined, e.g. the full grids can be trivially converted to a Matrix but not the OctahedralGaussianGrid.","category":"page"},{"location":"ringgrids/#Creating-data-on-a-RingGrid","page":"Submodule: RingGrids","title":"Creating data on a RingGrid","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Every grid in RingGrids has a data field, which is a vector containing the data on the grid. The grid points are unravelled west to east then north to south, meaning that it starts at 90˚N and 0˚E then walks eastward for 360˚ before jumping on the next latitude ring further south, this way circling around the sphere till reaching the south pole. This may also be called ring order.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Data on a 96x48 Matrix which follows this ring order can be put on a FullGaussianGrid like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> map\n96×48 Matrix{Float32}:\n -0.219801 -0.519598 1.2066 … 0.81304 -1.16023 1.0353\n -0.55615 -1.05712 -0.227948 -2.06369 1.10353 1.60918\n 0.446913 -0.856431 1.58896 -1.0894 -0.894315 0.632353\n 0.445915 -0.107201 -0.577785 0.574784 -0.825049 1.29677\n 1.194 -0.353374 1.30581 0.465554 0.358457 -0.726567\n 1.28693 1.43997 0.691283 … -0.330544 -0.267588 0.181308\n ⋮ ⋱ ⋮\n -0.432703 0.17233 0.89222 0.888913 1.32787 -0.248779\n -0.404498 0.127172 -0.64237 0.127979 -1.55253 -2.00749\n -0.857746 -0.433251 -0.468293 1.09825 -0.291169 1.07452\n 0.375367 -0.218278 0.492855 -0.287976 0.878996 -1.19745\n -0.0619525 -0.129129 -1.35502 … 0.0824819 0.481736 0.845638\n\njulia> grid = FullGaussianGrid(map)\n4608-element, 48-ring FullGaussianGrid{Float32}:\n -0.21980117\n -0.5561496\n 0.44691312\n 0.4459149\n 1.1940043\n 1.2869298\n ⋮\n -0.24877885\n -2.007495\n 1.0745221\n -1.197454\n 0.84563845","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"A full Gaussian grid has always 2N x N grid points, but a FullClenshawGrid has 2N x N-1, if those dimensions don't match, the creation will throw an error. To reobtain the data from a grid, you can access its data field which returns a normal Vector","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> grid.data\n4608-element Vector{Float32}:\n -0.21980117\n -0.5561496\n 0.44691312\n 0.4459149\n 1.1940043\n 1.2869298\n ⋮\n -0.24877885\n -2.007495\n 1.0745221\n -1.197454\n 0.84563845","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Which can be reshaped to reobtain map from above. Alternatively you can Matrix(grid) to do this in one step","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> map == Matrix(FullGaussianGrid(map))\ntrue","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"You can also use zeros,ones,rand,randn to create a grid, whereby nlat_half, i.e. the number of latitude rings on one hemisphere, Equator included, is used as a resolution parameter and here as a second argument.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> nlat_half = 4\njulia> grid = randn(OctahedralGaussianGrid{Float16},nlat_half)\n208-element, 8-ring OctahedralGaussianGrid{Float16}:\n -1.868\n 0.493\n 0.3142\n 1.871\n 1.349\n 0.623\n ⋮\n 1.064\n 0.4346\n -0.641\n 0.1445\n 0.3909","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"and any element type T can be used for OctahedralGaussianGrid{T} and similar for other grid types.","category":"page"},{"location":"ringgrids/#Indexing-RingGrids","page":"Submodule: RingGrids","title":"Indexing RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"All RingGrids have a single index ij which follows the ring order. While this is obviously not super exciting here are some examples how to make better use of the information that the data sits on a grid.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"We obtain the latitudes of the rings of a grid by calling get_latd (get_lond is only defined for full grids, or use get_latdlonds for latitudes, longitudes per grid point not per ring)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> latd = get_latd(grid)\n8-element Vector{Float64}:\n 73.79921362856324\n 52.81294318999426\n 31.704091745007943\n 10.569882312576093\n -10.569882312576093\n -31.704091745007943\n -52.81294318999426\n -73.79921362856324","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Now we could calculate Coriolis and add it on the grid as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"rotation = 7.29e-5 # angular frequency of Earth's rotation [rad/s]\ncoriolis = 2rotation*sind.(latd) # vector of coriolis parameters per latitude ring\n\nrings = eachring(grid)\nfor (j,ring) in enumerate(rings)\n f = coriolis[j]\n for ij in ring\n grid[ij] += f\n end\nend","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"eachring creates a vector of UnitRange indices, such that we can loop over the ring index j (j=1 being closest to the North pole) pull the coriolis parameter at that latitude and then loop over all in-ring indices i (changing longitudes) to do something on the grid. Something similar can be done to scale/unscale with the cosine of latitude for example. We can always loop over all grid-points like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"for ij in eachgridpoint(grid)\n grid[ij]\nend","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"or use eachindex instead.","category":"page"},{"location":"ringgrids/#Interpolation-on-RingGrids","page":"Submodule: RingGrids","title":"Interpolation on RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"In most cases we will want to use RingGrids so that our data directly comes with the geometric information of where the grid-point is one the sphere. We have seen how to use get_latd, get_lond, ... for that above. This information generally can also be used to interpolate our data from grid to another or to request an interpolated value on some coordinates. Using our data on grid which is an OctahedralGaussianGrid from above we can use the interpolate function to get it onto a FullGaussianGrid (or any other grid for purpose)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> grid\n208-element, 8-ring OctahedralGaussianGrid{Float16}:\n -1.868\n 0.493\n 0.3142\n 1.871\n 1.349\n 0.623\n ⋮\n 1.064\n 0.4346\n -0.641\n 0.1445\n 0.3909\n\njulia> interpolate(FullGaussianGrid,grid)\n128-element, 8-ring FullGaussianGrid{Float64}:\n -1.8681640625\n 0.4482421875\n 1.0927734375\n 1.4794921875\n 0.623046875\n -0.6435546875\n ⋮\n -0.57763671875\n 1.064453125\n 0.16552734375\n -0.248291015625\n 0.329345703125","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"By default this will linearly interpolate (it's an anvil interpolator, see below) onto a grid with the same nlat_half, but we can also coarse-grain or fine-grain by specifying nlat_half directly as 2nd argument","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> interpolate(FullGaussianGrid,6,grid)\n288-element, 12-ring FullGaussianGrid{Float64}:\n -1.248046875\n 0.08984375\n 0.2763671875\n 0.76513671875\n 1.1767578125\n ⋮\n 0.26416015625\n -0.295166015625\n -0.271728515625\n 0.0511474609375\n 0.0814208984375","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"So we got from an 8-ring OctahedralGaussianGrid{Float16} to a 12-ring FullGaussianGrid{Float64}, so it did a conversion from Float16 to Float64 on the fly too, because the default precision is Float64 unless specified. interpolate(FullGaussianGrid{Float16},6,grid) would have interpolated onto a grid with element type Float16.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"One can also interpolate onto a give cordinate ˚N, ˚E like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> interpolate(30.0,10.0,grid)\n1-element Vector{Float16}:\n 0.9297","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"we interpolated the data from grid onto 30˚N, 10˚E. To do this simultaneously for many coordinates they can be packed into a vector too","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> interpolate([30.0,40.0,50.0],[10.0,10.0,10.0],grid)\n3-element Vector{Float16}:\n 0.9297\n 0.08887\n -0.929","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which returns the data on grid at 30˚N, 40˚N, 50˚N, and 10˚E respectively. Note how the interpolation here retains the element type of grid.","category":"page"},{"location":"ringgrids/#Performance-for-RingGrid-interpolation","page":"Submodule: RingGrids","title":"Performance for RingGrid interpolation","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Every time an interpolation like interpolate(30.0,10.0,grid) is called, several things happen, which are important to understand to know how to get the fastest interpolation out of this module in a given situation. Under the hood an interpolation takes three arguments","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"output vector\ninput grid\ninterpolator","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"The output vector is just an array into which the interpolated data is written, providing this prevents unnecessary allocation of memory in case the destination array of the interpolation already exists. The input grid contains the data which is subject to interpolation, it must come on a ring grid, however, its coordinate information is actually already in the interpolator. The interpolator knows about the geometry of the grid the data is coming on and the coordinates it is supposed to interpolate onto. It has therefore precalculated the indices that are needed to access the right data on the input grid and the weights it needs to apply in the actual interplation operation. The only thing it does not know is the actual data values of that grid. So in the case you want to interpolate from grid A to grid B many times, you can just reuse the same interpolator. If you want to change the coordinates of the output grid but its total number of points remain constants then you can update the locator inside the interpolator and only else you will need to create a new interpolator. Let's look at this in practice. Say we have two grids an want to interpolate between them","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> grid_in = rand(HEALPixGrid,4)\njulia> grid_out = zeros(FullClenshawGrid,6)\njulia> interp = RingGrids.interpolator(grid_out,grid_in)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Now we have created an interpolator interp which knows about the geometry where to interpolate from and the coordinates there to interpolate to. It is also initialized, meaning it has precomputed the indices to of grid_in that are supposed to be used. It just does not know about the data of grid_in (and neither of grid_out which will be overwritten anyway). We can now do","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> interpolate!(grid_out,grid_in,interp);\njulia> grid_out\n264-element, 11-ring FullClenshawGrid{Float64}:\n 0.47698810225785837\n 0.49923033302273034\n 0.5214725637876022\n 0.5437147945524742\n ⋮\n 0.6277318221906577\n 0.5934538182075797\n 0.6009226488782581\n 0.6083914795489366","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which is identical to interpolate(grid_out,grid_in) but you can reuse interp with more data. The interpolation can also handle various element types (the interpolator interp does not have to be updated for this either)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> grid_out = zeros(FullClenshawGrid{Float16},6);\njulia> interpolate!(grid_out,grid_in,interp)\njulia> grid_out\n264-element, 11-ring FullClenshawGrid{Float16}:\n 0.477\n 0.4993\n 0.5215\n 0.544\n 0.5493\n 0.555\n ⋮\n 0.662\n 0.628\n 0.5933\n 0.601\n 0.6084","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"and we have converted data from a HEALPixGrid{Float64} (which is always default if not specified) to a FullClenshawGrid{Float16} including the type conversion Float64-Float16 on the fly. Technically there are three data types and their combinations possible: The input data will come with a type, the output array has an element type and the interpolator has precomputed weights with a given type. Say we want to go from Float16 data on an OctahedralGaussianGrid to Float16 on a FullClenshawGrid but using Float32 precision for the interpolation itself, we would do this by","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> grid_in = randn(OctahedralGaussianGrid{Float16},24)\njulia> grid_out = zeros(FullClenshawGrid{Float16},24)\njulia> interp = RingGrids.interpolator(Float32,grid_out,grid_in)\njulia> interpolate!(grid_out,grid_in,interp)\njulia> grid_out\n4512-element, 47-ring FullClenshawGrid{Float16}:\n -0.954\n -0.724\n -0.4941\n -0.264\n -0.03433\n 0.1796\n ⋮\n -0.5703\n -0.3481\n -0.07666\n 0.1958\n 0.467","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"As a last example we want to illustrate a situation where we would always want to interplate onto 10 coordinates, but their locations may change. In order to avoid recreating an interpolator object we would do (AnvilInterpolator is described in Anvil interpolator)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> npoints = 10 # number of coordinates to interpolate onto\njulia> interp = AnvilInterpolator(Float32,HEALPixGrid,24,npoints)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"with the first argument being the number format used during interpolation, then the input grid type, its resolution in terms of nlat_half and then the number of points to interpolate onto. However, interp is not yet initialized as it does not know about the destination coordinates yet. Let's define them, but note that we already decided there's only 10 of them above.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> latds = collect(0.0:5.0:45.0)\n10-element Vector{Float64}:\n 0.0\n 5.0\n ⋮\n 40.0\n 45.0\n\njulia> londs = collect(-10.0:2.0:8.0)\n10-element Vector{Float64}:\n -10.0\n -8.0\n -6.0\n ⋮\n 6.0\n 8.0","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"now we can update the locator inside our interpolator as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> RingGrids.update_locator!(interp,latds,londs)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"With data matching the input from above, a nlat_half=24 HEALPixGrid, and allocate 10-element output vector","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> output_vec = zeros(10)\njulia> grid_input = rand(HEALPixGrid,24)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"we can use the interpolator as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> interpolate!(output_vec,grid_input,interp)\n10-element Vector{Float64}:\n 0.3182548251299291\n 0.7499448926757676\n 0.25733825675836064\n ⋮\n 0.2949249541923441\n 0.6690698461409016\n 0.6159433564856793","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which is the approximately the same as doing it directly without creating an interpolator first and updating its locator","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"julia> interpolate(latds,londs,grid_input)\n10-element Vector{Float64}:\n 0.31825482404891603\n 0.7499448923165136\n 0.25733824520344434\n ⋮\n 0.294924962125593\n 0.6690698486360254\n 0.6159433558779497","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"but allows for a reuse of the interpolator. Note that the two output arrays are not exactly identical because we manually set our interpolator interp to use Float32 for the interplation whereas the default is Float64.","category":"page"},{"location":"ringgrids/#Anvil-interpolator","page":"Submodule: RingGrids","title":"Anvil interpolator","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Currently the only interpolator implemented is a 4-point bilinear interpolator, which schematically works as follows. Anvil interpolation is the bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":" 0..............1 # fraction of distance Δab between a,b\n |< Δab >|\n\n0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x\n.Δy |\n. |\n.v x \n. |\n1 c ------ o ---- d\n\n |< Δcd >|\n 0...............1 # fraction of distance Δcd between c,d\n\n^ fraction of distance Δy between a-b and c-d.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"This interpolation is chosen as by definiton of the ring grids, a and b share the same latitude, so do c and d, but the longitudes can be different for all four, a,b,c,d.","category":"page"},{"location":"ringgrids/#Function-index","page":"Submodule: RingGrids","title":"Function index","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids.each_index_in_ring\nRingGrids.eachgridpoint\nRingGrids.eachring\nRingGrids.whichring\nRingGrids.get_nlons","category":"page"},{"location":"ringgrids/#SpeedyWeather.RingGrids.each_index_in_ring","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.each_index_in_ring","text":"i = each_index_in_ring(grid,j)\n\nUnitRange i to access data on grid grid on ring j.\n\n\n\n\n\n","category":"function"},{"location":"ringgrids/#SpeedyWeather.RingGrids.eachgridpoint","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.eachgridpoint","text":"ijs = eachgridpoint(grid)\n\nUnitRange ijs to access each grid point on grid grid.\n\n\n\n\n\n","category":"function"},{"location":"ringgrids/#SpeedyWeather.RingGrids.eachring","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.eachring","text":"eachring(grid::SpeedyWeather.RingGrids.AbstractGrid) -> Any\n\n\nVector{UnitRange} rings to loop over every ring of grid grid and then each grid point per ring. To be used like\n\nrings = eachring(grid)\nfor ring in rings\n for ij in ring\n grid[ij]\n\n\n\n\n\neachring(\n grid1::SpeedyWeather.RingGrids.AbstractGrid,\n grids::Grid<:SpeedyWeather.RingGrids.AbstractGrid...\n) -> Any\n\n\nSame as eachring(grid) but performs a bounds check to assess that all grids in grids are of same size.\n\n\n\n\n\n","category":"function"},{"location":"ringgrids/#SpeedyWeather.RingGrids.whichring","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.whichring","text":"whichring(\n ij::Integer,\n rings::Vector{UnitRange{Int64}}\n) -> Int64\n\n\nObtain ring index j from gridpoint ij and Vector{UnitRange} describing rind indices as obtained from eachring(::Grid)\n\n\n\n\n\n","category":"function"},{"location":"ringgrids/#SpeedyWeather.RingGrids.get_nlons","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.get_nlons","text":"get_nlons(\n Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid},\n nlat_half::Integer;\n both_hemispheres\n) -> Any\n\n\nReturns a vector nlons for the number of longitude points per latitude ring, north to south. Provide grid Grid and its resolution parameter nlat_half. For both_hemisphere==false only the northern hemisphere (incl Equator) is returned.\n\n\n\n\n\n","category":"function"},{"location":"#SpeedyWeather.jl-documentation","page":"Home","title":"SpeedyWeather.jl documentation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.","category":"page"},{"location":"#Overview","page":"Home","title":"Overview","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to perform some calculations in spectral space (time integration, gradients, linear terms) and some in grid-point space (advection, non-linear terms, parameterizations). The prognostic variables used are vorticity, divergence, absolute temperature, logarithm of surface pressure and specific humidity. The time stepping uses a leapfrog scheme with additional filters and a semi-implicit formulation for gravity waves. The default resolution is T31 (96x48 grid points on a regular Gaussian grid, about 400km at the Equator) and 8 vertical levels.","category":"page"},{"location":"","page":"Home","title":"Home","text":"Simple parameterizations are used to represent the physical processes convection, large-scale condensation, clouds, short-wave radiation, long-waves radiation, surface fluxes of momentum and energy, and vertical diffusion.","category":"page"},{"location":"#Manual-outline","page":"Home","title":"Manual outline","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"See the following pages of the documentation for more details","category":"page"},{"location":"","page":"Home","title":"Home","text":"Installation\nHow to run SpeedyWeather.jl\nSpherical harmonic transform\nGrids\nDynamical core\nParametrizations\nExtending SpeedyWeather","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the submodules","category":"page"},{"location":"","page":"Home","title":"Home","text":"RingGrids and their interpolation \nLowerTriangularMatrices \nSpeedyTransforms","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the original documentation by Molteni and Kucharski.","category":"page"},{"location":"#Developers","page":"Home","title":"Developers","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The development of SpeedyWeather.jl is lead by Milan Klöwer and current and past contributors include","category":"page"},{"location":"","page":"Home","title":"Home","text":"Tom Kimpson\nAlistair White\nMaximilian Gelbrecht\nDavid Meyer\nDaisuke Hotta\nNavid Constantinou","category":"page"},{"location":"","page":"Home","title":"Home","text":"Any contributions are always welcome!","category":"page"},{"location":"#Funding","page":"Home","title":"Funding","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"MK received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022. Since 2023 this project is also funded by the National Science Foundation NSF.","category":"page"},{"location":"time_integration/#Time-integration","page":"Time integration","title":"Time integration","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"SpeedyWeather.jl uses a leapfrog time scheme with a Robert's and William's filter to dampen the computational mode and achieve 3rd order accuracy.","category":"page"},{"location":"time_integration/#Oscillation-equation","page":"Time integration","title":"Oscillation equation","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"fracdFdt = iomega F","category":"page"},{"location":"time_integration/#Implementation-details","page":"Time integration","title":"Implementation details","text":"","category":"section"}]
+}
diff --git a/previews/PR341/siteinfo.js b/previews/PR341/siteinfo.js
new file mode 100644
index 000000000..ccf1fa116
--- /dev/null
+++ b/previews/PR341/siteinfo.js
@@ -0,0 +1 @@
+var DOCUMENTER_CURRENT_VERSION = "previews/PR341";
diff --git a/previews/PR341/spectral_transform/index.html b/previews/PR341/spectral_transform/index.html
new file mode 100644
index 000000000..158eefff6
--- /dev/null
+++ b/previews/PR341/spectral_transform/index.html
@@ -0,0 +1,45 @@
+
+Spherical harmonic transform · SpeedyWeather.jl
The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space on a longitude-latitude regular Gaussian grid.
with $\lambda_l^m$ being the pre-normalized associated Legendre polynomials, and $e^{im\phi}$ are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.
Latitudes versus colatitudes
The implementations of the spherical transforms in SpeedyWeather.jl use colatitudes $\theta = (0,\pi)$ (0 at the north pole) but the dynamical core uses latitudes $\theta = (-\pi/2,\pi/2)$ ($\pi/2$ at the north pole). However, all arrays are always sorted north to south such that [i,1] will access the northern-most grid points. Note: We may also use latitudes in the spherical harmonic transform in the future for consistency.
The synthesis (or inverse transform) takes the spectral coefficients $a_{lm}$ and transforms them to grid-point values $f(\phi,\theta)$ (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics $Y_{lm}$ with non-zero coefficients.
Spectral packing is the way how the coefficients $a_{lm}$ of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree $l$ and order $m$ as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)
Every row represents an order $l \geq 0$, starting from $l=0$ at the top. Every column represents an order $m \geq 0$, starting from $m=0$ on the left. The coefficients of these spherical harmonics are directly mapped into a matrix $a_{lm}$ as
$m$
$l$
$a_{00}$
$a_{10}$
$a_{11}$
$a_{20}$
$a_{12}$
$a_{22}$
$a_{30}$
$a_{13}$
$a_{23}$
$a_{33}$
which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal explicitly stores zeros. Note that internally vector fields include an additional degree, such that $l_{max} = m_{max} + 1$ (see Gradients in spherical coordinates for more information). The harmonics with $a_{l0}$ (the first column) are also called zonal harmonics as they are constant with longitude $\phi$. The harmonics with $a_{ll}$ (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into $2l$ sectors in longitude $\phi$ without a zero-crossing in latitude.
Array indices
For a spectral field alms note that due to Julia's 1-based indexing the coefficient $a_{lm}$ is obtained via alms[l+1,m+1].
Fortran speedy does not use the same spectral packing as SpeedyWeather.jl. The alternative packing $l',m'$ therein uses $l'=m$ and $m'=l-m$ as summarized in the following table.
degree $l$
order $m$
$l'=m$
$m'=l-m$
0
0
0
0
1
0
0
1
1
1
1
0
2
0
0
2
2
1
1
1
2
2
2
0
3
0
0
3
...
...
...
...
This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices
$m'$
$l'$
$a_{00}$
$a_{10}$
$a_{20}$
$a_{30}$
$a_{11}$
$a_{21}$
$a_{31}$
$a_{22}$
$a_{32}$
$a_{33}$
This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran-speedy.
SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with $l \leq l_{max}$ and $|m| \leq m_{max}$ are explicitly represented. This is usually described as $Tm_{max}$, with $l_{max} = m_{max}$ (although in vector quantities require one more degree $l$ in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with $l_{max} = m_{max} = 31$. Note that the degree $l$ and order $m$ are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.
Using triangular truncation[9], there are constraints on the corresponding grid resolution. Let nlon, nlat be the number of longitudes, latitudes on a regular Gaussian grid. Then spectral and grid resolution have to be chosen such that
$nlon \geq 3l_{max}+1$
$nlat \geq (3l_{max}+1)/2$
In general, we choose $nlon = 2nlat$, and ideally $nlon$ is easily Fourier-transformable, e.g. $nlon = 2^i3^j5^k$ with some integers $i,j,k \geq 0$. SpeedyWeather.jl is tested at the following horizontal resolutions, with $\Delta x = \tfrac{2\pi R}{nlon}$ as the approximate grid spacing at the Equator
$l_{max}$
nlon
nlat
$\Delta x$
31 (default)
96
48
400 km
42
128
64
300 km
85
256
128
160 km
170
512
256
80 km
341
1024
512
40 km
682
2048
1024
20 km
Choosing trunc as argument in run_speedy will automatically choose nlon,nlat as presented in the table. Other common choices are T63 (192x96), T127 (384x192), T255 (768x384), T511 (1536x768), among others.
The radius of the sphere (i.e. Earth) is $R$. The zonal gradient scales with $1/\cos(\theta)$ as the longitudes converge towards the poles (note that $\theta$ describes latitudes here, defintions using colatitudes replace the $\cos$ with a $\sin$.)
Starting with a spectral field of vorticity $\zeta$ and divergence $\mathcal{D}$ one can obtain stream function $\Psi$ and velocity potential $\Phi$ by inverting the Laplace operator $\nabla^2$:
The velocities $u,v$ are then obtained from $(u,v) = \nabla^\bot\Psi + \nabla\Phi$ following the defintion from above and $\nabla^\bot = (-R^{-1}\partial_\theta, (R\cos\theta)^{-1}\partial_\lambda)$
Alternatively, we can use the velocities $U = u\cos\theta, V = v\cos\theta$, which we do as the meridional gradient for spherical harmonics is easier implemented with a $\cos\theta$-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with $U,V$ and not $u,v$. From $u,v$ we can return to $\zeta, \mathcal{D}$ via
\[\begin{aligned}
+\zeta &= \frac{1}{R\cos\theta}\partial_\lambda v - \frac{1}{R\cos\theta}\partial_\theta (u \cos\theta) \\
+\mathcal{D} &= \frac{1}{R\cos\theta}\partial_\lambda u + \frac{1}{R\cos\theta}\partial_\theta (v \cos\theta).
+\end{aligned}\]
which is a more convenient formulation as required $\cos\theta$ scalings are reduced to a minimum. The remaining $(U,V)*\cos^{-2}\theta$ are done in grid-point space and usually in combination with other operations like the computation of the vorticity flux. But also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out too. As described in Meridional derivative, it is more convenient to implement $\cos\theta \partial_\theta$ via a recursion relation for the Legendre polynomials than $\partial_\theta$ directly. How the operators $\nabla, \nabla \times, \nabla \cdot$ can be implemented with spherical harmonics is presented in the following sections.
The zonal derivative of a scalar field $\Psi$ in spectral space is the zonal derivative of all its respective spherical harmonics $\Psi_{lm}(\phi,\theta)$ (now we use $\phi$ for longitudes to avoid confusion with the Legendre polynomials $\lambda_{lm}$)
So for every spectral harmonic, $\cos(\theta)v_{lm}$ is obtained from $\Psi_{lm}$ via a multiplication with $im/R$. Unscaling the $\cos(\theta)$-factor is done after transforming the spectral coefficients $v_{lm}$ into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as $\tilde{\Psi} = R^{-1}\Psi$ such that the division by radius $R$ in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order $im$. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number $m$ times imaginary $i$.
The meridioinal derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]
In the following we use the example of obtaining the zonal velocity $u$ from the stream function $\Psi$, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion
To obtain the coefficient of each spherical harmonic $l,m$ of the meridional gradient of a spectral field, two coefficients at $l-1,m$ and $l+1,m$ have to be combined. This means that the coefficient of a gradient $((\cos\theta) u)_{lm}$ is a linear combination of the coefficients of one higher and one lower degree $\Psi_{l+1,m},\Psi_{l-1,m}$. As the coefficient $\Psi_{lm}$ with $m<l$ are zero, the sectoral harmonics ($l=m$) of the gradients are obtained from the first off-diagonal only. However, the $l=l_{max}$ harmonics of the gradients require the $l_{max}-1$ as well as the $l_{max}+1$ harmonics. In SpeedyWeather.jl vector quantitie like $u,v$ use therefore one more meridional mode than scalar quantities such as vorticity $\zeta$ or stream function $\Psi$. The meridional derivative in SpeedyWeather.jl also omits the $1/R$-scaling as explained for the Zonal derivative and in Radius scaling.
The meridional gradient as described above can be applied to scalars, such as $\Psi$ and $\Phi$ in the conversion to velocities $(u,v) = \nabla^\bot\Psi + \nabla\Phi$, however, the operators curl $\nabla \times$ and divergence $\nabla \cdot$ in spherical coordinates involve a $\cos\theta$ scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].
Given that $R\zeta = \cos^{-1}\partial_\lambda v - \cos^{-1}\partial_\theta (u \cos\theta)$, we therefore have to evaluate a meridional integral of the form
Now we expand $(\tfrac{u}{\cos\theta})$ but only the $l,m$ harmonic will project onto$P_{l,m}$. Let $u^* = u\cos^{-1}\theta, v^* = v\cos^{-1}\theta$ we then have in total
And the divergence $D$ is similar, but $(u,v) \to (-v,u)$. We have moved the scaling with the radius $R$ directly into $\zeta,D$ as further described in Radius scaling.
The spectral Laplacian is easily applied to the coefficients $\Psi_{lm}$ of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator $\nabla^2$ in spherical coordinates with eigenvalues $-l(l+1)$ divided by the radius squared $R^2$, i.e. $\nabla^2 \Psi$ becomes $\tfrac{-l(l+1)}{R^2}\Psi_{lm}$ in spectral space. For example, vorticity $\zeta$ and streamfunction $\Psi$ are related by $\zeta = \nabla^2\Psi$ in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree $l$ and order $m$ to
\[\zeta_{l,m} = \frac{-l(l+1)}{R^2}\Psi_{l,m}\]
This can be easily inverted to obtain the stream function $\Psi$ from vorticity $\zeta$ instead. In order to avoid division by zero, we set $\Psi_{0,0}$ here, given that the stream function is only defined up to a constant anyway.
After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity $\zeta$ and divergence $D$ (which are prognostic variables) to $U=u\cos\theta, V=v\cos\theta$. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree $l$ (the meridional gradient). It is therefore computationally more efficient to compute $U,V$ directly from $\zeta,D$ instead of calculating stream function and velocity potential first. In total we have
SpeedyTransforms is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it) and can also be used without running simulations. It is just not put into its own respective repository.
The prognostic variables in spectral space are called
vor # Vorticity of horizontal wind field
+ div # Divergence of horizontal wind field
+ temp # Absolute temperature [K]
+ pres_surf # Logarithm of surface pressure [log(Pa)]
+ humid # Specific humidity [g/kg]
their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are
We follow Julia's style guide and highlight here some important aspects of it.
Bang (!) convention. A function func does not change its input arguments, however, func! does.
Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.
Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup
but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.
Settings
This document was generated with Documenter.jl version 0.27.24 on Monday 5 June 2023. Using Julia version 1.8.5.
A mathematical and implementation-specific description of the dynamical core used in SpeedyWeather.jl. We start by describing the barotropic vorticity equations which is one set of equations that SpeedyWeather.jl can solve (see How to run SpeedyWeather.jl) as many details therein also apply to the Shallow water equations and Primitive equations explained thereafter.
The dynamical core presented here largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2], a shallow water model [3] and a primitive equation model[4].
The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity $\zeta$ with advection, Coriolis force and diffusion in a single global layer on the sphere.
with time $t$, velocity vector $\mathbf{u} = (u, v)$, Coriolis parameter $f$, and hyperdiffusion $(-1)^{n+1} \nu \nabla^{2n} \zeta$ ($n$ is the hyperdiffusion order; see Horizontal diffusion). We also include a forcing vector $\mathbf{F} = (F_u,F_v)$ which acts on the zonal velocity $u$ and the meridional velocity $v$ and hence its curl $\nabla \times \mathbf{F}$ is a tendency for relative vorticity $\zeta$.
Starting with some relative vorticity $\zeta$, the Laplacian is inverted to obtain the stream function $\Psi$
\[\Psi = \nabla^{-2}\zeta\]
The zonal velocity $u$ and meridional velocity $v$ are then the (negative) meridional gradient and zonal gradient of $\Psi$
where $\zeta = \hat{\mathbf{z}} \cdot (\nabla \times \mathbf{u})$ is the relative vorticity, $\mathcal{D} = \nabla \cdot \mathbf{u}$ the divergence, and $\eta$ the deviation from the fluid's rest height.
In SpeedyWeather.jl we use hyerdiffusion through an $n$-th power Laplacian $(-1)^{n+1}\nabla^{2n}$ (hyper when $n>1$) which can be implemented as a multiplication of the spectral coefficients $\Psi_{lm}$ with $(-l(l+1))^nR^{-2n}$ (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the $(-l(l+1))^nR^{-2n}$ can be precomputed. Note the sign change $(-1)^{n+1}$ here is such that the dissipative nature of the diffusion operator is retained for $n$ odd and even.
In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step $\Delta t$ of variable $\zeta$ to obtain from time steps $i-1$ and $i$, the next time step $i+1$
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t d\zeta,\]
with $d\zeta$ being some tendency evaluated from $\zeta_i$. Now we want to add a diffusion term $(-1)^{n+1}\nu \nabla^{2n}\zeta$ with viscosity $\nu$, wich however, is implicitly calculated from $\zeta_{i+1}$, then
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t (d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i+1})\]
As the application of $(-1)^{n+1}\nu\nabla^{2n}$ is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to
\[\zeta_{i+1} = \frac{\zeta_{i-1} + 2\Delta t d\zeta}{1 - 2\Delta (-1)^{n+1}\nu\nabla^{2n}},\]
and expand the numerator to
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t \frac{d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t (-1)^{n+1}\nu \nabla^{2n}},\]
Hence the diffusion can be applied implicitly by updating the tendency $d\zeta$ as
\[d\zeta \to \frac{d\zeta + (-1)^{n+1}\nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t \nu \nabla^{2n}}\]
which only depends on $\zeta_{i-1}$. Now let $D_\text{explicit} = (-1)^{n+1}\nu\nabla^{2n}$ be the explicit part and $D_\text{implicit} = 1 - (-1)^{n+1} 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are $D_\text{implicit} = 1 - 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic $l,m$ we do
Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power $n$.
In physics, the Laplace operator $\nabla^2$ is often used to represent diffusion due to viscosity in a fluid. In that case, the viscosity coefficient is $\nu$ of units $\text{m}^2\text{s}^{-1}$ and the full operator reads as $\nu \nabla^2$ with units $(\text{m}^2\text{s}^{-1})(\text{m}^{-2}) = \text{s}^{-1}$. This motivates us to normalize the Laplace operator by a constant of units $\text{m}^{-2}$ and the viscosity coefficient by its inverse such that the viscosity coefficient becomes a damping timescale of unit $\text{s}^{-1}$. Given the application in spectral space we decide to normalize by the largest eigenvalue $-l_\text{max}(l_\text{max}+1)$ such that all entries in the discrete spectral Laplace operator are in $[0,1]$. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized viscosity coefficient $\nu^* = l_\text{max}(l_\text{max}+1)\nu$ (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have
In the barotropic voriticity equation model the inversion of the Laplcians in order to obtain $\Psi$ from $\zeta$ therefore becomes
\[\tilde{\zeta} = \tilde{\nabla}^2 \tilde{\Psi}\]
where the dimensionless gradients simply omit the scaling with $1/R$, $\tilde{\nabla} = R\nabla$. The Barotropic vorticity equation scaled with $R^2$ is
Similar to the scaled barotropic vorticity equations, the scaled shallow water equations scale the vorticity and the divergence equation with $R^2$, but the continuity equation with $R$
This document was generated with Documenter.jl version 0.27.24 on Monday 5 June 2023. Using Julia version 1.8.5.
diff --git a/previews/PR345/functions/index.html b/previews/PR345/functions/index.html
new file mode 100644
index 000000000..94d822eed
--- /dev/null
+++ b/previews/PR345/functions/index.html
@@ -0,0 +1,180 @@
+
+Function and type index · SpeedyWeather.jl
Spectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities u,v.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for primitive equation models. Updates grid vorticity, grid divergence, grid temperature, pressure (pres_grid) and the velocities u,v.
Spectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Returns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.
Truncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.
Truncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.
Laplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.
Keyword arguments
add=true adds the ∇²(alms) to the output
flipsign=true computes -∇²(alms) instead
inverse=true computes ∇⁻²(alms) instead
Default is add=false, flipsign=false, inverse=false. These options can be combined.
Divergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.
Curl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.
Get U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.
Get U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.
Construct Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. Pass on SpectralGrid to calculate the following fields
spectral_grid::SpectralGrid: SpectralGrid that defines spectral and grid resolution
Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid of the dynamical core
nlat_half::Int64: resolution parameter nlat_half of Grid, # of latitudes on one hemisphere (incl Equator)
nlon_max::Int64: maximum number of longitudes (at/around Equator)
nlon::Int64: =nlon_max, same (used for compatibility), TODO: still needed?
nlat::Int64: number of latitude rings
nlev::Int64: number of vertical levels
npoints::Int64: total number of grid points
radius::AbstractFloat: Planet's radius [m]
latd::Vector{Float64}: array of latitudes in degrees (90˚...-90˚)
lond::Vector{Float64}: array of longitudes in degrees (0...360˚), empty for non-full grids
londs::Vector{NF} where NF<:AbstractFloat: longitude (-180˚...180˚) for each grid point in ring order
latds::Vector{NF} where NF<:AbstractFloat: latitude (-90˚...˚90) for each grid point in ring order
sinlat::Vector{NF} where NF<:AbstractFloat: sin of latitudes
coslat::Vector{NF} where NF<:AbstractFloat: cos of latitudes
coslat⁻¹::Vector{NF} where NF<:AbstractFloat: = 1/cos(lat)
coslat²::Vector{NF} where NF<:AbstractFloat: = cos²(lat)
coslat⁻²::Vector{NF} where NF<:AbstractFloat: # = 1/cos²(lat)
σ_levels_half::Vector{NF} where NF<:AbstractFloat: σ at half levels, σ_k+1/2
σ_levels_full::Vector{NF} where NF<:AbstractFloat: σ at full levels, σₖ
σ_levels_thick::Vector{NF} where NF<:AbstractFloat: σ level thicknesses, σₖ₊₁ - σₖ
ln_σ_levels_full::Vector{NF} where NF<:AbstractFloat: log of σ at full levels, include surface (σ=1) as last element
Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF.
Performs the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.
The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and but let's start but how they can be used
The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object which defines the resolution in spectral and in grid-point space. The generator SpectralGrid() can take as a keyword argument Grid which can be any of the grids described below. The resolution of the grid, however, is not directly chosen, but determined from the spectral resolution trunc and the dealiasing factor. More in Matching spectral and grid resolution.
RingGrids is a module too!
While RingGrids is the underlying module that SpeedyWeather.jl uses for data structs on the sphere, the module can also be used independently of SpeedyWeather, for example to interpolate between data on different grids. See RingGrids
SpeedyWeather.jl's spectral transform supports all ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on these rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.
All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).
Currently the following full grids <: AbstractFullGrid are implemented
FullGaussianGrid, a full grid with Gaussian latitudes
FullClenshawGrid, a full grid with equi-angle latitudes
and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are
OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron
OctahedralClenshawGrid, similar but based on equi-angle latitudes
HEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces
OctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.
An overview of these grids is visualised here
Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.
All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half.
HEALPix grids do not use Nside as resolution parameter
The original formulation for HEALPix grids use $N_{side}$, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use $N_{side}$ for the documentation or within functions though.
A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation T with a grid resolution N (=nlat_half) such that T + 1 = N. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at $l_{max}=31$ in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid.
Let J be the total number of rings. Then we have
$T \approx J$ for linear truncation
$\frac{3}{2}T \approx J$ for quadratic truncation
$2T \approx J$ for cubic truncation
and in general $\frac{m+1}{2}T \approx J$ for m-th order truncation. So the higher the truncaction order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. In SpeedyWeather.jl the parameter dealiasing controls this option, = 1 would be linear, = 2 quadratic, = 3 cubic etc.
For now just a quick overview of how the grid resolution changes when dealiasing is passed onto SpectralGrid on the FullGaussianGrid
trunc
dealiasing
FullGaussianGrid size
31
1
64x32
31
2
96x48
31
3
128x64
42
1
96x48
42
2
128x64
42
3
192x96
...
...
...
You will obtain this information every time you create a SpectralGrid(;Grid,trunc,dealiasing).
Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are $N_\varphi$ basepixels in zonal direction and $N_\theta$ basepixels in meridional direction. For $N_\varphi = 4$ and $N_\theta = 3$ we obtain the classical HEALPix grid with $N_\varphi N_\theta = 12$ basepixels shown above in Implemented Grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always $2N$, so 32 at $N=16$) and there are polar caps above and below the equatorial zone with the border at $\cos(\theta) = 2/3$ ($\theta$ in colatitudes).
Following Górski, 2004[1], the $z=cos(\theta)$ colatitude of the $j$-th ring in the north polar cap, $j=1,...,N_{side}$ with $2N_{side} = N$ is
\[z = 1 - \frac{j^2}{3N_{side}^2}\]
and on that ring, the longitude $\phi$ of the $i$-th point ($i$ is the in-ring-index) is at
\[\phi = \frac{\pi}{2j}(i-\tfrac{1}{2})\]
The in-ring index $i$ goes from $i=1,...,4$ for the first (i.e. northern-most) ring, $i=1,...,8$ for the second ring and $i = 1,...,4j$ for the $j$-th ring in the northern polar cap.
In the north equatorial belt $j=N_{side},...,2N_{side}$ this changes to
\[z = \frac{4}{3} - \frac{2j}{3N_{side}}\]
and the longitudes change to ($i$ is always $i = 1,...,4N_{side}$ in the equatorial belt meaning the number of longitude points is constant here)
The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.
The cell boundaries are obtained by setting $i = k + 1/2$ or $i = k + 1/2 + j$ (half indices) into the equations above, such that $z(\phi,k)$, a function for the cosine of colatitude $z$ of index $k$ and the longitude $\phi$ is obtained. These are then (northern polar cap)
While the classic HEALPix grid is based on a dodecahedron, other choices for $N_\varphi$ and $N_\theta$ in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With $N_\varphi = 4$ and $N_\theta = 1$ we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, $2\pi$ around the Equator versus $\pi$ between the poles. $N_\varphi = 6, N_\theta = 2$ or $N_\varphi = 8, N_\theta = 3$ are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visulations of these grids.
We call the $N_\varphi = 4, N_\theta = 1$ HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As $N_\theta = 1$ there is no equatorial belt which simplifies the grid. The latitude of the $j$-th isolatitude ring on the OctaHEALPixGrid is defined by
\[z = 1 - \frac{j^2}{N^2},\]
with $j=1,...,N$, and similarly for the southern hemisphere by symmetry. On this grid $N_{side} = N$ where $N$= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index $i = 1,...,4j$ are
\[\phi = \frac{\pi}{2j}(i - \tfrac{1}{2})\]
and again, the southern hemisphere grid points are obtained by symmetry.
The $3N_{side}^2$ in the denominator of the HEALPix grid came simply $N^2$ for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).
[1] Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976
Settings
This document was generated with Documenter.jl version 0.27.24 on Monday 5 June 2023. Using Julia version 1.8.5.
diff --git a/previews/PR345/how_to_run_speedy/index.html b/previews/PR345/how_to_run_speedy/index.html
new file mode 100644
index 000000000..abab0becb
--- /dev/null
+++ b/previews/PR345/how_to_run_speedy/index.html
@@ -0,0 +1,293 @@
+
+How to run SpeedyWeather.jl · SpeedyWeather.jl
We want to use the barotropic model to simulate some free-decaying 2D turbulence on the sphere without rotation. We start by defining the SpectralGrid object. To have a resolution of about 100km, we choose a spectral resolution of T127 (see Available resolutions) and nlev=1 vertical levels. The SpectralGrid object will provide us with some more information
using SpeedyWeather
+
+spectral_grid = SpectralGrid(trunc=127,nlev=1)
There are other options to create a planet but they are irrelevant for the barotropic vorticity equations. We also want to specify the initial conditions, randomly distributed vorticity is already defined
By default, the power of vorticity is spectrally distributed with $k^{-3}$, $k$ being the horizontal wavenumber, and the amplitude is $10^{-5}\text{s}^{-1}$.
Now we want to construct a BarotropicModel with these
model = BarotropicModel(;spectral_grid, initial_conditions, planet=still_earth)
The model contains all the parameters, but isn't initialized yet, which we can do with and then run it. The run! command will always return the prognostic variables, which, by default, are plotted for surface relative vorticity with a unicode plot. The resolution of the plot is not necessarily representative but it lets us have a quick look at the result
Woohoo! I can see turbulence! You could pick up where this simulation stopped by simply doing run!(simulation,n_days=50) again. We didn't store any output, which you can do by run!(simulation,output=true), which will switch on NetCDF output with default settings. More options on output in NetCDF output.
As a second example, let's investigate the Galewsky et al.[1] test case for the shallow water equations with and without mountains. As the shallow water system has also only one level, we can reuse the SpectralGrid from Example 1.
Although the orography is zero, you have to pass on spectral_grid so that it can still initialize zero-arrays of the right size and element type. Awesome. This time the initial conditions should be set the the Galewsky et al.[1] zonal jet, which is already defined as
The jet sits at 45˚N with a maximum velocity of 80m/s and a perturbation as described in their paper. Now we construct a model, but this time a ShallowWaterModel
model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);
+
+simulation = initialize!(model);
The progress bar tells us that the simulation run got the identification "0002", meaning that data is stored in the folder /run_0002, so let's plot that data properly (and not just using UnicodePlots).
julia> using PyPlot, NCDatasets
+julia> ds = NCDataset("run_0002/output.nc");
+julia> ds["vor"]
+vor (384 × 192 × 1 × 25)
+ Datatype: Float32
+ Dimensions: lon × lat × lev × time
+ Attributes:
+ units = 1/s
+ missing_value = NaN
+ long_name = relative vorticity
+ _FillValue = NaN
Vorticity vor is stored as a 384x192x1x25 array, we may want to look at the first time step, which is the end of the previous simulation (time=6days) which we didn't store output for.
julia> vor = ds["vor"][:,:,1,1];
+julia> lat = ds["lat"][:];
+julia> lon = ds["lon"][:];
+julia> pcolormesh(lon,lat,vor')
Which looks like
You see that the unicode plot heavily coarse-grains the simulation, well it's unicode after all! And now the last time step, that means time=12days is
julia> vor = ds["vor"][:,:,1,25];
+julia> pcolormesh(lon,lat,vor')
The jet broke up into many small eddies, but the turbulence is still confined to the northern hemisphere, cool! How this may change when we add mountains (we had NoOrography above!), say Earth's orography, you may ask? Let's try it out! We create an EarthOrography struct like so
It will read the orography from file as shown, and there are some smoothing options too, but let's not change them. Same as before, create a model, intialize into a simulation, run. This time directly for 12 days so that we can compare with the last plot
julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);
+julia> simulation = initialize!(model);
+julia> run!(simulation,n_days=12,output=true)
+Weather is speedy: run 0003 100%|███████████████████████| Time: 0:00:35 (79.16 years/day)
This time the run got the id "0003", but otherwise we do as before.
Interesting! The initial conditions have zero velocity in the southern hemisphere, but still, one can see some imprint of the orography on vorticity. You can spot the coastline of Antarctica; the Andes and Greenland are somewhat visible too. Mountains also completely changed the flow after 12 days, probably not surprising!
[1] Galewsky, Scott, Polvani, 2004. An initial-value problem for testing numerical models of the global shallow-water equations, Tellus A. DOI: 10.3402/tellusa.v56i5.14436
Settings
This document was generated with Documenter.jl version 0.27.24 on Monday 5 June 2023. Using Julia version 1.8.5.
Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.
SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to perform some calculations in spectral space (time integration, gradients, linear terms) and some in grid-point space (advection, non-linear terms, parameterizations). The prognostic variables used are vorticity, divergence, absolute temperature, logarithm of surface pressure and specific humidity. The time stepping uses a leapfrog scheme with additional filters and a semi-implicit formulation for gravity waves. The default resolution is T31 (96x48 grid points on a regular Gaussian grid, about 400km at the Equator) and 8 vertical levels.
Simple parameterizations are used to represent the physical processes convection, large-scale condensation, clouds, short-wave radiation, long-waves radiation, surface fluxes of momentum and energy, and vertical diffusion.
MK received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022. Since 2023 this project is also funded by the National Science Foundation NSF.
Settings
This document was generated with Documenter.jl version 0.27.24 on Monday 5 June 2023. Using Julia version 1.8.5.
LowerTriangularMatrices is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.
This module defines LowerTriangularMatrix, a lower triangular matrix, which in contrast to LinearAlgebra.LowerTriangular does not store the entries above the diagonal. SpeedyWeather.jl uses LowerTriangularMatrix which is defined as a subtype of AbstractMatrix to store the spherical harmonic coefficients (see Spectral packing).
LowerTriangularMatrix supports two types of indexing: 1) by denoting two indices, column and row [l,m] or 2) by denoting a single index [lm]. The double index works as expected
But the single index skips the zero entries in the upper triangle, i.e.
julia> L[4]
+Float16(0.478)
which, important, is different from single indices of an AbstractMatrix
julia> Matrix(L)[4]
+Float16(0.0)
In performance-critical code a single index should be used, as this directly maps to the index of the underlying data vector. The double index is somewhat slower as it first has to be converted to the corresponding single index.
Consequently, many loops in SpeedyWeather.jl are build with the following structure
n,m = size(L)
+ij = 0
+for j in 1:m
+ for i in j:n
+ ij += 1
+ L[ij] = i+j
+ end
+end
which loops over all lower triangle entries of L::LowerTriangularMatrix and the single index ij is simply counted up. However, one could also use [i,j] as indices in the loop body or to perform any calculation (i+j here). An iterator over all entries in the lower triangle can be created by
for ij in eachindex(L)
+ # do something
+end
The setindex! functionality of matrixes will throw a BoundsError when trying to write into the upper triangle of a LowerTriangularMatrix, for example
julia> L[2,1] = 0 # valid index
+0
+
+julia> L[1,2] = 0 # invalid index in the upper triangle
+ERROR: BoundsError: attempt to access 3×3 LowerTriangularMatrix{Float32} at index [1, 2]
The LowerTriangularMatrices module's main purpose is not linear algebra, and it's implementation may not be efficient, however, many operations work as expected
Note, however, that the latter includes a conversion to Matrix, which is true for many operations, including inv or \. Hence when trying to do more sophisticated linear algebra with LowerTriangularMatrix we quickly leave lower triangular-land and go back to normal matrix-land.
L = LowerTriangularMatrix{T}(v::Vector{T},m::Int,n::Int)
A lower triangular matrix implementation that only stores the non-zero entries explicitly. L<:AbstractMatrix although in general we have L[i] != Matrix(L)[i], the former skips zero entries, tha latter includes them.
k = ij2k( i::Integer, # row index of matrix
+ j::Integer, # column index of matrix
+ m::Integer) # number of rows in matrix
Converts the index pair i,j of an mxn LowerTriangularMatrix L to a single index k that indexes the same element in the corresponding vector that stores only the lower triangle (the non-zero entries) of L.
creates unit_range::UnitRange to loop over all non-zeros in the LowerTriangularMatrices provided as arguments. Checks bounds first. All LowerTriangularMatrix's need to be of the same size. Like eachindex but skips the upper triangle with zeros in L.
SpeedyWeather.jl uses NetCDF to output the data of a simulation. The following describes the details of this and how to change the way in which the NetCDF output is written. There are many options to this available.
The output writer is a component of every Model, i.e. BarotropicModel, ShallowWaterModel, PrimitiveDryModel and PrimitiveWetModel, hence a non-default output writer can be passed on as a keyword argument to the model constructor
So after we have defined the grid through the SpectralGrid object we can use and change the implemented OutputWriter by passing on the following arguments
the spectral_grid has to be the first argument then the model type (Barotropic, ShallowWater, PrimitiveDry, PrimitiveWet) which helps the output writer to make default choices on which variables to output. However, we can also pass on further keyword arguments. So let's start with an example.
which will now output every hour. It is important to pass on the new output writer my_output_writer to the model constructor, otherwise it will not be part of your model and the default is used instead. Note that output_dt has to be understood as the minimum frequency or maximum output time step. Example, we run the model at a resolution of T85 and the time step is going to be 670s
This means that after 32 time steps 5h 57min and 20s will have passed where output will happen as the next time step would be >6h. The time axis of the NetCDF output will look like
This is so that we don't interpolate in time during output to hit exactly every 6 hours, but at the same time have a constant spacing in time between output time steps.
Say we want to run the model at a given horizontal resolution but want to output on another resolution, the OutputWriter takes as argument output_Grid<:AbstractFullGrid and nlat_half::Int. So for example output_Grid=FullClenshawGrid and nlat_half=48 will always interpolate onto a regular 192x95 longitude-latitude grid of 1.875˚ resolution, regardless the grid and resolution used for the model integration.
Note that by default the output is on the corresponding full of the grid used in the dynamical core so that interpolation only happens at most in the zonal direction as they share the location of the latitude rings. You can check this by
So the corresponding full grid of an OctahedralGaussianGrid is the FullGaussiangrid and the same resolution nlat_half is chosen by default in the output writer (which you can change though as shown above). Overview of the corresponding full grids
Grid
Corresponding full grid
FullGaussianGrid
FullGaussianGrid
FullClenshawGrid
FullClenshawGrid
OctahadralGaussianGrid
FullGaussianGrid
OctahedralClensawhGrid
FullClenshawGrid
HEALPixGrid
FullHEALPixGrid
OctaHEALPixGrid
FullOctaHEALPixGrid
The grids FullHEALPixGrid, FullOctaHEALPixGrid share the same latitude rings as their reduced grids, but have always as many longitude points as they are at most around the equator. These grids are not tested in the dynamical core (but you may use them experimentally) and mostly designed for output purposes.
That's easy by passing on path="/my/favourite/path/" and the folder run_* with * the identification of the run (that's the id keyword, which can be manually set but is also automatically determined as a number counting up depending on which folders already exist) will be created within.
which will be used instead of a 4 digit number like 0001, 0002 which is automatically determined if id is not provided. You will see the id of the run in the progress bar
Weather is speedy: run diffusion_test 100%|███████████████████████| Time: 0:00:12 (19.20 years/day)
and the run folder, here run_diffusion_test, is also named accordingly
Further options are described in the OutputWriter docstring, (also accessible via julia>?OutputWriter for example). Note that some fields are actual options, but others are derived from the options you provided or are arrays/objects the output writer needs, but shouldn't be passed on by the user. The actual options are declared as [OPTION] in the following
NetCDF output writer. Contains all output options and auxiliary fields for output interpolation. To be initialised with OutputWriter(::SpectralGrid,::Type{<:ModelSetup},kwargs...) to pass on the resolution information and the model type which chooses which variables to output. Options include
spectral_grid::SpectralGrid
output::Bool
path::String: [OPTION] path to output folder, run_???? will be created within
id::String: [OPTION] run identification number/string
run_path::String
filename::String: [OPTION] name of the output netcdf file
write_restart::Bool: [OPTION] also write restart file if output==true?
pkg_version::VersionNumber
startdate::Dates.DateTime
output_dt::Float64: [OPTION] output frequency, time step [hrs]
output_dt_sec::Int64: actual output time step [sec]
output_vars::Vector{Symbol}: [OPTION] which variables to output, u, v, vor, div, pres, temp, humid
missing_value::Union{Float32, Float64}: [OPTION] missing value to be used in netcdf output
compression_level::Int64: [OPTION] lossless compression level; 1=low but fast, 9=high but slow
keepbits::SpeedyWeather.Keepbits: [OPTION] mantissa bits to keep for every variable
This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmopshere. Every section is followed by a brief description of implementation details.
RingGrids is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.
RingGrids defines several iso-latitude grids, which are mathematically described in the section on Grids. In brief, they include the regular latitude-longitude grids (here called FullClenshawGrid) as well as grids which latitudes are shifted to the Gaussian latitudes and reduced grids, meaning that they have a decreasing number of longitudinal points towards the poles to be more equal-area than full grids.
RingGrids defines and exports the following grids:
full grids: FullClenshawGrid, FullGaussianGrid, FullHEALPix, and FullOctaHEALPix
reduced grids: OctahedralGaussianGrid, OctahedralClenshawGrid, OctaHEALPixGrid and HEALPixGrid
The following explanation of how to use these can be mostly applied to any of them, however, there are certain functions that are not defined, e.g. the full grids can be trivially converted to a Matrix but not the OctahedralGaussianGrid.
What is a ring?
We use the term ring, short for iso-latitude ring, to refer to a sequence of grid points that all share the same latitude. A latitude-longitude grid is a ring grid, as it organises its grid-points into rings. However, other grids, like the cubed-sphere are not based on iso-latitude rings. SpeedyWeather.jl only works with ring grids because its a requirement for the Spherical Harmonic Transform to be efficient. See Grids.
Every grid in RingGrids has a grid.data field, which is a vector containing the data on the grid. The grid points are unravelled west to east then north to south, meaning that it starts at 90˚N and 0˚E then walks eastward for 360˚ before jumping on the next latitude ring further south, this way circling around the sphere till reaching the south pole. This may also be called ring order.
Data in a Matrix which follows this ring order can be put on a FullGaussianGrid like so
using SpeedyWeather.RingGrids
+map = randn(Float32,8,4)
A full Gaussian grid has always $2N$ x $N$ grid points, but a FullClenshawGrid has $2N$ x $N-1$, if those dimensions don't match, the creation will throw an error. To reobtain the data from a grid, you can access its data field which returns a normal Vector
Which can be reshaped to reobtain map from above. Alternatively you can Matrix(grid) to do this in one step
map == Matrix(FullGaussianGrid(map))
true
You can also use zeros,ones,rand,randn to create a grid, whereby nlat_half, i.e. the number of latitude rings on one hemisphere, Equator included, is used as a resolution parameter and here as a second argument.
As only the full grids can be reshaped into a matrix, the underyling data structure of any AbstractGrid is a vector. As shown in the examples above, one can therefore inspect the data as if it was a vector. But as that data has, through its <:AbstractGrid type, all the geometric information available to plot it on a map, RingGrids also exports plot function, based on UnicodePlots' heatmap.
All RingGrids have a single index ij which follows the ring order. While this is obviously not super exciting here are some examples how to make better use of the information that the data sits on a grid.
We obtain the latitudes of the rings of a grid by calling get_latd (get_lond is only defined for full grids, or use get_latdlonds for latitudes, longitudes per grid point not per ring)
Now we could calculate Coriolis and add it on the grid as follows
rotation = 7.29e-5 # angular frequency of Earth's rotation [rad/s]
+coriolis = 2rotation*sind.(latd) # vector of coriolis parameters per latitude ring
+
+rings = eachring(grid)
+for (j,ring) in enumerate(rings)
+ f = coriolis[j]
+ for ij in ring
+ grid[ij] += f
+ end
+end
eachring creates a vector of UnitRange indices, such that we can loop over the ring index j (j=1 being closest to the North pole) pull the coriolis parameter at that latitude and then loop over all in-ring indices i (changing longitudes) to do something on the grid. Something similar can be done to scale/unscale with the cosine of latitude for example. We can always loop over all grid-points like so
In most cases we will want to use RingGrids so that our data directly comes with the geometric information of where the grid-point is one the sphere. We have seen how to use get_latd, get_lond, ... for that above. This information generally can also be used to interpolate our data from grid to another or to request an interpolated value on some coordinates. Using our data on grid which is an OctahedralGaussianGrid from above we can use the interpolate function to get it onto a FullGaussianGrid (or any other grid for purpose)
By default this will linearly interpolate (it's an Anvil interpolator, see below) onto a grid with the same nlat_half, but we can also coarse-grain or fine-grain by specifying nlat_half directly as 2nd argument
So we got from an 8-ring OctahedralGaussianGrid{Float16} to a 12-ring FullGaussianGrid{Float64}, so it did a conversion from Float16 to Float64 on the fly too, because the default precision is Float64 unless specified. interpolate(FullGaussianGrid{Float16},6,grid) would have interpolated onto a grid with element type Float16.
One can also interpolate onto a give cordinate ˚N, ˚E like so
interpolate(30.0,10.0,grid)
0.71075565f0
we interpolated the data from grid onto 30˚N, 10˚E. To do this simultaneously for many coordinates they can be packed into a vector too
Every time an interpolation like interpolate(30.0,10.0,grid) is called, several things happen, which are important to understand to know how to get the fastest interpolation out of this module in a given situation. Under the hood an interpolation takes three arguments
output vector
input grid
interpolator
The output vector is just an array into which the interpolated data is written, providing this prevents unnecessary allocation of memory in case the destination array of the interpolation already exists. The input grid contains the data which is subject to interpolation, it must come on a ring grid, however, its coordinate information is actually already in the interpolator. The interpolator knows about the geometry of the grid the data is coming on and the coordinates it is supposed to interpolate onto. It has therefore precalculated the indices that are needed to access the right data on the input grid and the weights it needs to apply in the actual interplation operation. The only thing it does not know is the actual data values of that grid. So in the case you want to interpolate from grid A to grid B many times, you can just reuse the same interpolator. If you want to change the coordinates of the output grid but its total number of points remain constants then you can update the locator inside the interpolator and only else you will need to create a new interpolator. Let's look at this in practice. Say we have two grids an want to interpolate between them
Now we have created an interpolator interp which knows about the geometry where to interpolate from and the coordinates there to interpolate to. It is also initialized, meaning it has precomputed the indices to of grid_in that are supposed to be used. It just does not know about the data of grid_in (and neither of grid_out which will be overwritten anyway). We can now do
which is identical to interpolate(grid_out,grid_in) but you can reuse interp for other data. The interpolation can also handle various element types (the interpolator interp does not have to be updated for this either)
and we have converted data from a HEALPixGrid{Float64} (Float64 is always default if not specified) to a FullClenshawGrid{Float16} including the type conversion Float64-Float16 on the fly. Technically there are three data types and their combinations possible: The input data will come with a type, the output array has an element type and the interpolator has precomputed weights with a given type. Say we want to go from Float16 data on an OctahedralGaussianGrid to Float16 on a FullClenshawGrid but using Float32 precision for the interpolation itself, we would do this by
As a last example we want to illustrate a situation where we would always want to interplate onto 10 coordinates, but their locations may change. In order to avoid recreating an interpolator object we would do (AnvilInterpolator is described in Anvil interpolator)
npoints = 10 # number of coordinates to interpolate onto
+interp = AnvilInterpolator(Float32,HEALPixGrid,24,npoints)
with the first argument being the number format used during interpolation, then the input grid type, its resolution in terms of nlat_half and then the number of points to interpolate onto. However, interp is not yet initialized as it does not know about the destination coordinates yet. Let's define them, but note that we already decided there's only 10 of them above.
but allows for a reuse of the interpolator. Note that the two output arrays are not exactly identical because we manually set our interpolator interp to use Float32 for the interplation whereas the default is Float64.
Currently the only interpolator implemented is a 4-point bilinear interpolator, which schematically works as follows. Anvil interpolation is the bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate.
0..............1 # fraction of distance Δab between a,b
+ |< Δab >|
+
+0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x
+.Δy |
+. |
+.v x
+. |
+1 c ------ o ---- d
+
+ |< Δcd >|
+ 0...............1 # fraction of distance Δcd between c,d
+
+^ fraction of distance Δy between a-b and c-d.
This interpolation is chosen as by definiton of the ring grids, a and b share the same latitude, so do c and d, but the longitudes can be different for all four, a,b,c,d.
Returns a vector nlons for the number of longitude points per latitude ring, north to south. Provide grid Grid and its resolution parameter nlat_half. For both_hemisphere==false only the northern hemisphere (incl Equator) is returned.
This document was generated with Documenter.jl version 0.27.24 on Monday 5 June 2023. Using Julia version 1.8.5.
diff --git a/previews/PR345/search_index.js b/previews/PR345/search_index.js
new file mode 100644
index 000000000..83f634f78
--- /dev/null
+++ b/previews/PR345/search_index.js
@@ -0,0 +1,3 @@
+var documenterSearchIndex = {"docs":
+[{"location":"development/#Development-notes","page":"Development notes","title":"Development notes","text":"","category":"section"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To run tests, from the path of your local clone of the repository do:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=. -e 'import Pkg; Pkg.test()'","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To install dependencies:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project -e 'import Pkg; Pkg.instantiate()`","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"then opening:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"you are able to using SpeedyWeather.","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To generate the docs:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=docs -e 'import Pkg; Pkg.develop(path=\".\"); Pkg.instantiate()'\njulia --project=docs docs/make.jl","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"If the docs are generated successfully, you view them by opening docs/build/index.html in your favorite browser.","category":"page"},{"location":"installation/#Installation","page":"Installation","title":"Installation","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"SpeedyWeather.jl is registered in the Julia Registry. In most cases just open the Julia REPL and type","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia> using Pkg\njulia> Pkg.add(\"SpeedyWeather\")","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"which will automatically install the latest release and all necessary dependencies. If you run into any troubles please raise an issue","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"However, you may want to make use of the latest features, then install directly from the main branch with","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia> Pkg.add(url=\"https://github.com/SpeedyWeather/SpeedyWeather.jl\",rev=\"main\")","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"other branches than main can be similarly installed.","category":"page"},{"location":"installation/#Compatibility-with-Julia-versions","page":"Installation","title":"Compatibility with Julia versions","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"SpeedyWeather.jl usually lives on the latest minor release and/or its predecessor. At the moment (May 2023) this means ","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"Julia v1.8\nJulia v1.9","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"are supported, but we dropped the support of earlier versions.","category":"page"},{"location":"output/#NetCDF-output","page":"NetCDF output","title":"NetCDF output","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"SpeedyWeather.jl uses NetCDF to output the data of a simulation. The following describes the details of this and how to change the way in which the NetCDF output is written. There are many options to this available.","category":"page"},{"location":"output/#Accessing-the-NetCDF-output-writer","page":"NetCDF output","title":"Accessing the NetCDF output writer","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"The output writer is a component of every Model, i.e. BarotropicModel, ShallowWaterModel, PrimitiveDryModel and PrimitiveWetModel, hence a non-default output writer can be passed on as a keyword argument to the model constructor","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> spectral_grid = SpectralGrid()\njulia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry)\njulia> model = PrimitiveDryModel(;spectral_grid, output=my_output_writer)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"So after we have defined the grid through the SpectralGrid object we can use and change the implemented OutputWriter by passing on the following arguments","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, kwargs...)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"the spectral_grid has to be the first argument then the model type (Barotropic, ShallowWater, PrimitiveDry, PrimitiveWet) which helps the output writer to make default choices on which variables to output. However, we can also pass on further keyword arguments. So let's start with an example.","category":"page"},{"location":"output/#Example-1:-NetCDF-output-every-hour","page":"NetCDF output","title":"Example 1: NetCDF output every hour","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"If we want to increase the frequency of the output we can choose output_dt (default =6 in hours) like so","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, output_dt=1)\njulia> model = PrimitiveDryModel(;spectral_grid, output=my_output_writer)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"which will now output every hour. It is important to pass on the new output writer my_output_writer to the model constructor, otherwise it will not be part of your model and the default is used instead. Note that output_dt has to be understood as the minimum frequency or maximum output time step. Example, we run the model at a resolution of T85 and the time step is going to be 670s","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> spectral_grid = SpectralGrid(trunc=85)\njulia> time_stepper = Leapfrog(spectral_grid)\nLeapfrog{Float32}:\n...\n Δt_sec::Int64 = 670\n...","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This means that after 32 time steps 5h 57min and 20s will have passed where output will happen as the next time step would be >6h. The time axis of the NetCDF output will look like","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> using NCDatasets\njulia> ds = NCDataset(\"run_0001/output.nc\");\njulia> ds[\"time\"][:]\n5-element Vector{Dates.DateTime}:\n 2000-01-01T00:00:00\n 2000-01-01T05:57:20\n 2000-01-01T11:54:40\n 2000-01-01T17:52:00\n 2000-01-01T23:49:20\n\njulia> diff(ds[\"time\"][:])\n4-element Vector{Dates.Millisecond}:\n 21440000 milliseconds\n 21440000 milliseconds\n 21440000 milliseconds\n 21440000 milliseconds","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This is so that we don't interpolate in time during output to hit exactly every 6 hours, but at the same time have a constant spacing in time between output time steps.","category":"page"},{"location":"output/#Example-2:-Output-onto-a-higher/lower-resolution-grid","page":"NetCDF output","title":"Example 2: Output onto a higher/lower resolution grid","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Say we want to run the model at a given horizontal resolution but want to output on another resolution, the OutputWriter takes as argument output_Grid<:AbstractFullGrid and nlat_half::Int. So for example output_Grid=FullClenshawGrid and nlat_half=48 will always interpolate onto a regular 192x95 longitude-latitude grid of 1.875˚ resolution, regardless the grid and resolution used for the model integration.","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, output_Grid=FullClenshawGrid, nlat_half=48)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Note that by default the output is on the corresponding full of the grid used in the dynamical core so that interpolation only happens at most in the zonal direction as they share the location of the latitude rings. You can check this by","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> RingGrids.full_grid(OctahedralGaussianGrid)\nFullGaussianGrid","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"So the corresponding full grid of an OctahedralGaussianGrid is the FullGaussiangrid and the same resolution nlat_half is chosen by default in the output writer (which you can change though as shown above). Overview of the corresponding full grids","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Grid Corresponding full grid\nFullGaussianGrid FullGaussianGrid\nFullClenshawGrid FullClenshawGrid\nOctahadralGaussianGrid FullGaussianGrid\nOctahedralClensawhGrid FullClenshawGrid\nHEALPixGrid FullHEALPixGrid\nOctaHEALPixGrid FullOctaHEALPixGrid","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"The grids FullHEALPixGrid, FullOctaHEALPixGrid share the same latitude rings as their reduced grids, but have always as many longitude points as they are at most around the equator. These grids are not tested in the dynamical core (but you may use them experimentally) and mostly designed for output purposes.","category":"page"},{"location":"output/#Example-3:-Changing-the-output-path-or-identification","page":"NetCDF output","title":"Example 3: Changing the output path or identification","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"That's easy by passing on path=\"/my/favourite/path/\" and the folder run_* with * the identification of the run (that's the id keyword, which can be manually set but is also automatically determined as a number counting up depending on which folders already exist) will be created within.","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> path = pwd()\n\"/Users/milan\"\njulia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, path=path)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This folder must already exist. If you want to give your run a name/identification you can pass on id","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid,PrimitiveDry,id=\"diffusion_test\");","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"which will be used instead of a 4 digit number like 0001, 0002 which is automatically determined if id is not provided. You will see the id of the run in the progress bar","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Weather is speedy: run diffusion_test 100%|███████████████████████| Time: 0:00:12 (19.20 years/day)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"and the run folder, here run_diffusion_test, is also named accordingly","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"shell> ls\n...\nrun_diffusion_test\n...","category":"page"},{"location":"output/#Further-options","page":"NetCDF output","title":"Further options","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Further options are described in the OutputWriter docstring, (also accessible via julia>?OutputWriter for example). Note that some fields are actual options, but others are derived from the options you provided or are arrays/objects the output writer needs, but shouldn't be passed on by the user. The actual options are declared as [OPTION] in the following","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"OutputWriter","category":"page"},{"location":"output/#SpeedyWeather.OutputWriter","page":"NetCDF output","title":"SpeedyWeather.OutputWriter","text":"NetCDF output writer. Contains all output options and auxiliary fields for output interpolation. To be initialised with OutputWriter(::SpectralGrid,::Type{<:ModelSetup},kwargs...) to pass on the resolution information and the model type which chooses which variables to output. Options include\n\nspectral_grid::SpectralGrid\noutput::Bool\npath::String: [OPTION] path to output folder, run_???? will be created within\nid::String: [OPTION] run identification number/string\nrun_path::String\nfilename::String: [OPTION] name of the output netcdf file\nwrite_restart::Bool: [OPTION] also write restart file if output==true?\npkg_version::VersionNumber\nstartdate::Dates.DateTime\noutput_dt::Float64: [OPTION] output frequency, time step [hrs]\noutput_dt_sec::Int64: actual output time step [sec]\noutput_vars::Vector{Symbol}: [OPTION] which variables to output, u, v, vor, div, pres, temp, humid\nmissing_value::Union{Float32, Float64}: [OPTION] missing value to be used in netcdf output\ncompression_level::Int64: [OPTION] lossless compression level; 1=low but fast, 9=high but slow\nkeepbits::SpeedyWeather.Keepbits: [OPTION] mantissa bits to keep for every variable\noutput_every_n_steps::Int64\ntimestep_counter::Int64\noutput_counter::Int64\nnetcdf_file::Union{Nothing, NetCDF.NcFile}\ninput_Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}\nas_matrix::Bool: [OPTION] sort grid points into a matrix (interpolation-free), for OctahedralClenshawGrid, OctaHEALPixGrid only\nquadrant_rotation::NTuple{4, Int64}\nmatrix_quadrant::NTuple{4, Tuple{Int64, Int64}}\noutput_Grid::Type{<:SpeedyWeather.RingGrids.AbstractFullGrid}: [OPTION] the grid used for output, full grids only\nnlat_half::Int64: [OPTION] the resolution of the output grid, default: same nlat_half as in the dynamical core\nnlon::Int64\nnlat::Int64\nnpoints::Int64\nnlev::Int64\ninterpolator::SpeedyWeather.RingGrids.AbstractInterpolator\nu::Matrix{NF} where NF<:Union{Float32, Float64}\nv::Matrix{NF} where NF<:Union{Float32, Float64}\nvor::Matrix{NF} where NF<:Union{Float32, Float64}\ndiv::Matrix{NF} where NF<:Union{Float32, Float64}\ntemp::Matrix{NF} where NF<:Union{Float32, Float64}\npres::Matrix{NF} where NF<:Union{Float32, Float64}\nhumid::Matrix{NF} where NF<:Union{Float32, Float64}\nprecip_cond::Matrix{NF} where NF<:Union{Float32, Float64}\nprecip_conv::Matrix{NF} where NF<:Union{Float32, Float64}\n\n\n\n\n\n","category":"type"},{"location":"functions/#Function-and-type-index","page":"Function and type index","title":"Function and type index","text":"","category":"section"},{"location":"functions/#Parameters-and-constants","page":"Function and type index","title":"Parameters and constants","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Parameters\nSpeedyWeather.Constants","category":"page"},{"location":"functions/#Boundaries-and-boundary-conditions","page":"Function and type index","title":"Boundaries and boundary conditions","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Boundaries","category":"page"},{"location":"functions/#Spherical-harmonic-transform","page":"Function and type index","title":"Spherical harmonic transform","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.GeoSpectral\nSpeedyWeather.SpectralTransform\nSpeedyWeather.spectral\nSpeedyWeather.spectral!\nSpeedyWeather.gridded\nSpeedyWeather.gridded!\nSpeedyWeather.triangular_truncation\nSpeedyWeather.roundup_fft\nSpeedyWeather.spectral_truncation\nSpeedyWeather.spectral_truncation!\nSpeedyWeather.spectral_interpolation!\nSpeedyWeather.get_legendre_polynomials!\nSpeedyWeather.∇²!\nSpeedyWeather.∇²\nSpeedyWeather.∇⁻²!\nSpeedyWeather.∇⁻²\nSpeedyWeather.gradient_latitude!\nSpeedyWeather.gradient_latitude\nSpeedyWeather.gradient_longitude!\nSpeedyWeather.gradient_longitude\nSpeedyWeather.divergence!\nSpeedyWeather.curl!\nSpeedyWeather._divergence!\nSpeedyWeather.curl_div!\nSpeedyWeather.UV_from_vordiv!\nSpeedyWeather.UV_from_vor!\nSpeedyWeather.ϵlm\nSpeedyWeather.get_recursion_factors","category":"page"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.SpectralTransform","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"S = SpectralTransform{NF<:AbstractFloat}(...)\n\nSpectralTransform struct that contains all parameters and preallocated arrays for the spectral transform.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral","text":"alms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\nalms = spectral( map::AbstractGrid;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to Grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\nalms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nSpectral transform (grid to spectral) map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral!","text":"spectral!( alms::LowerTriangularMatrix,\n map::AbstractGrid,\n S::SpectralTransform)\n\nSpectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded","text":"map = gridded( alms::AbstractMatrix;\n recompute_legendre::Bool=true,\n grid::Type{<:AbstractGrid}=DEFAULT_GRID)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).\n\n\n\n\n\nmap = gridded( alms::AbstractMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!(\n diagn::DiagnosticVariables,\n progn::PrognosticVariables,\n lf::Int64,\n model::SpeedyWeather.ModelSetup\n)\n\n\nPropagate the spectral state of progn to diagn using time step/leapfrog index lf. Function barrier that calls gridded! for the respective model.\n\n\n\n\n\ngridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::Barotropic\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.\n\n\n\n\n\ngridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::ShallowWater\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities u,v.\n\n\n\n\n\ngridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::PrimitiveEquation\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for primitive equation models. Updates grid vorticity, grid divergence, grid temperature, pressure (pres_grid) and the velocities u,v.\n\n\n\n\n\ngridded!( map::AbstractGrid,\n alms::LowerTriangularMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral_truncation","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation","text":"alms_trunc = spectral_truncation(alms,trunc)\n\nReturns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral_truncation!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms::AbstractMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.\n\n\n\n\n\nspectral_truncation!(alms::LowerTriangularMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.\n\n\n\n\n\nspectral_truncation!(alms,trunc)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.\n\n\n\n\n\nspectral_truncation!(alms)\n\nTruncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.∇²!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.∇²!","text":"∇²!( ∇²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false,\n inverse::Bool=false)\n\nLaplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.\n\nKeyword arguments\n\nadd=true adds the ∇²(alms) to the output\nflipsign=true computes -∇²(alms) instead\ninverse=true computes ∇⁻²(alms) instead\n\nDefault is add=false, flipsign=false, inverse=false. These options can be combined.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.∇⁻²!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.∇⁻²!","text":"∇⁻²!( ∇⁻²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false)\n\nCalls ∇²!(∇⁻²alms, alms, S; add, flipsign, inverse=true).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.divergence!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.divergence!","text":"divergence!(div::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform{NF};\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nDivergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.curl!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.curl!","text":"curl!( curl::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform;\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nCurl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.UV_from_vordiv!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.UV_from_vordiv!","text":"UV_from_vordiv!(U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n div::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.UV_from_vor!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.UV_from_vor!","text":"UV_from_vor!( U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"function"},{"location":"functions/#Dynamics","page":"Function and type index","title":"Dynamics","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.bernoulli_potential!\nSpeedyWeather.volume_flux_divergence!\nSpeedyWeather.vorticity_fluxes!\nSpeedyWeather.vorticity_flux_curl!\nSpeedyWeather.vorticity_flux_divergence!","category":"page"},{"location":"functions/#SpeedyWeather.bernoulli_potential!","page":"Function and type index","title":"SpeedyWeather.bernoulli_potential!","text":"bernoulli_potential!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},\n S::SpectralTransform\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nComputes the Laplace operator ∇² of the Bernoulli potential B in spectral space.\n\ncomputes the kinetic energy KE = ½(u²+v²) on the grid\ntransforms KE to spectral space\nadds geopotential for the Bernoulli potential in spectral space\ntakes the Laplace operator.\n\nThis version is used for both ShallowWater and PrimitiveEquation, only the geopotential calculation in geopotential! differs.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.volume_flux_divergence!","page":"Function and type index","title":"SpeedyWeather.volume_flux_divergence!","text":"volume_flux_divergence!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surface::SpeedyWeather.SurfaceVariables,\n orog::SpeedyWeather.AbstractOrography,\n constants::DynamicsConstants,\n G::Geometry,\n S::SpectralTransform\n)\n\n\nComputes the (negative) divergence of the volume fluxes uh,vh for the continuity equation, -∇⋅(uh,vh).\n\n\n\n\n\n","category":"function"},{"location":"functions/#Geometry","page":"Function and type index","title":"Geometry","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Geometry\nSpeedyWeather.vertical_coordinates\nSpeedyWeather.GenLogisticCoefs\nSpeedyWeather.generalised_logistic","category":"page"},{"location":"functions/#SpeedyWeather.Geometry","page":"Function and type index","title":"SpeedyWeather.Geometry","text":"Construct Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. Pass on SpectralGrid to calculate the following fields\n\nspectral_grid::SpectralGrid: SpectralGrid that defines spectral and grid resolution\nGrid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid of the dynamical core\nnlat_half::Int64: resolution parameter nlat_half of Grid, # of latitudes on one hemisphere (incl Equator)\nnlon_max::Int64: maximum number of longitudes (at/around Equator)\nnlon::Int64: =nlon_max, same (used for compatibility), TODO: still needed?\nnlat::Int64: number of latitude rings\nnlev::Int64: number of vertical levels\nnpoints::Int64: total number of grid points\nradius::AbstractFloat: Planet's radius [m]\nlatd::Vector{Float64}: array of latitudes in degrees (90˚...-90˚)\nlond::Vector{Float64}: array of longitudes in degrees (0...360˚), empty for non-full grids\nlonds::Vector{NF} where NF<:AbstractFloat: longitude (-180˚...180˚) for each grid point in ring order\nlatds::Vector{NF} where NF<:AbstractFloat: latitude (-90˚...˚90) for each grid point in ring order\nsinlat::Vector{NF} where NF<:AbstractFloat: sin of latitudes\ncoslat::Vector{NF} where NF<:AbstractFloat: cos of latitudes\ncoslat⁻¹::Vector{NF} where NF<:AbstractFloat: = 1/cos(lat)\ncoslat²::Vector{NF} where NF<:AbstractFloat: = cos²(lat)\ncoslat⁻²::Vector{NF} where NF<:AbstractFloat: # = 1/cos²(lat)\nσ_levels_half::Vector{NF} where NF<:AbstractFloat: σ at half levels, σ_k+1/2\nσ_levels_full::Vector{NF} where NF<:AbstractFloat: σ at full levels, σₖ\nσ_levels_thick::Vector{NF} where NF<:AbstractFloat: σ level thicknesses, σₖ₊₁ - σₖ\nln_σ_levels_full::Vector{NF} where NF<:AbstractFloat: log of σ at full levels, include surface (σ=1) as last element\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.GenLogisticCoefs","page":"Function and type index","title":"SpeedyWeather.GenLogisticCoefs","text":"Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF.\n\nFollowing the notation of https://en.wikipedia.org/wiki/Generalisedlogisticfunction (Dec 15 2021).\n\nChange default parameters for more/fewer levels in the stratosphere vs troposphere vs boundary layer.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.generalised_logistic","page":"Function and type index","title":"SpeedyWeather.generalised_logistic","text":"Generalised logistic function based on the coefficients in coefs.\n\n\n\n\n\n","category":"function"},{"location":"functions/#Time-stepping","page":"Function and type index","title":"Time stepping","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.time_stepping!\nSpeedyWeather.timestep!\nSpeedyWeather.first_timesteps!\nSpeedyWeather.leapfrog!","category":"page"},{"location":"functions/#SpeedyWeather.time_stepping!","page":"Function and type index","title":"SpeedyWeather.time_stepping!","text":"time_stepping!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n model::SpeedyWeather.ModelSetup\n) -> PrognosticVariables\n\n\nMain time loop that that initializes output and feedback, loops over all time steps and calls the output and feedback functions.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.timestep!","page":"Function and type index","title":"SpeedyWeather.timestep!","text":"timestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::Barotropic\n)\ntimestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::Barotropic,\n lf1::Int64\n)\ntimestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::Barotropic,\n lf1::Int64,\n lf2::Int64\n)\n\n\nCalculate a single time step for the model <: Barotropic.\n\n\n\n\n\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::ShallowWater\n) -> SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::ShallowWater,\n lf1::Int64\n) -> SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::ShallowWater,\n lf1::Int64,\n lf2::Int64\n) -> SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat\n\n\nCalculate a single time step for the model <: ShallowWater.\n\n\n\n\n\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::PrimitiveEquation\n) -> Any\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::PrimitiveEquation,\n lf1::Int64\n) -> Any\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::PrimitiveEquation,\n lf1::Int64,\n lf2::Int64\n) -> Any\n\n\nCalculate a single time step for the model<:PrimitiveEquation\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.first_timesteps!","page":"Function and type index","title":"SpeedyWeather.first_timesteps!","text":"first_timesteps!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n clock::SpeedyWeather.Clock,\n model::SpeedyWeather.ModelSetup,\n output::SpeedyWeather.AbstractOutputWriter\n) -> typeof(time)\n\n\nPerforms the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.leapfrog!","page":"Function and type index","title":"SpeedyWeather.leapfrog!","text":"leapfrog!(\n A_old::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n A_new::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n tendency::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n dt::Real,\n lf::Int64,\n L::Leapfrog{NF<:AbstractFloat}\n)\n\n\nPerforms one leapfrog time step with (lf=2) or without (lf=1) Robert+William's filter (see William (2009), Montly Weather Review, Eq. 7-9).\n\n\n\n\n\n","category":"function"},{"location":"functions/#Longwave-radiation","page":"Function and type index","title":"Longwave radiation","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.radset!\nSpeedyWeather.radlw_down!\nSpeedyWeather.compute_bbe!\nSpeedyWeather.radlw_up!","category":"page"},{"location":"functions/#Shortwave-radiation","page":"Function and type index","title":"Shortwave radiation","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.shortwave_radiation!\nSpeedyWeather.solar!\nSpeedyWeather.sol_oz!\nSpeedyWeather.cloud!\nSpeedyWeather.radsw!","category":"page"},{"location":"parametrizations/#Parameterizations","page":"Parameterizations","title":"Parameterizations","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmopshere. Every section is followed by a brief description of implementation details.","category":"page"},{"location":"parametrizations/#Convection","page":"Parameterizations","title":"Convection","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Large-scale-condensation","page":"Parameterizations","title":"Large-scale condensation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Clouds","page":"Parameterizations","title":"Clouds","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Short-wave-radiation","page":"Parameterizations","title":"Short-wave radiation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Long-wave-radiation","page":"Parameterizations","title":"Long-wave radiation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Surface-fluxes-of-momentum-and-energy","page":"Parameterizations","title":"Surface fluxes of momentum and energy","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Vertical-diffusion","page":"Parameterizations","title":"Vertical diffusion","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"how_to_run_speedy/#How-to-run-SpeedyWeather.jl","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Creating a SpeedyWeather.jl simulation and running it consists conceptually of 4 steps","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Create a SpectralGrid which defines the grid and spectral resolution\nCreate a model\nInitialize a model to obtain a Simulation.\nRun the simulation.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"In the following we will describe these steps in more detail, but let's start with some examples first.","category":"page"},{"location":"how_to_run_speedy/#Example-1:-2D-turbulence-on-a-non-rotating-sphere","page":"How to run SpeedyWeather.jl","title":"Example 1: 2D turbulence on a non-rotating sphere","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"We want to use the barotropic model to simulate some free-decaying 2D turbulence on the sphere without rotation. We start by defining the SpectralGrid object. To have a resolution of about 100km, we choose a spectral resolution of T127 (see Available resolutions) and nlev=1 vertical levels. The SpectralGrid object will provide us with some more information","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"using SpeedyWeather\n\nspectral_grid = SpectralGrid(trunc=127,nlev=1)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"We could have specified further options, but let's ignore that for now. Next step we create a planet that's like Earth but not rotating","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"still_earth = Earth(rotation=0)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"There are other options to create a planet but they are irrelevant for the barotropic vorticity equations. We also want to specify the initial conditions, randomly distributed vorticity is already defined","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"initial_conditions = StartWithRandomVorticity()","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"By default, the power of vorticity is spectrally distributed with k^-3, k being the horizontal wavenumber, and the amplitude is 10^-5texts^-1.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Now we want to construct a BarotropicModel with these","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"model = BarotropicModel(;spectral_grid, initial_conditions, planet=still_earth)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The model contains all the parameters, but isn't initialized yet, which we can do with and then run it. The run! command will always return the prognostic variables, which, by default, are plotted for surface relative vorticity with a unicode plot. The resolution of the plot is not necessarily representative but it lets us have a quick look at the result","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"simulation = initialize!(model);\n\nrun!(simulation,n_days=30)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Woohoo! I can see turbulence! You could pick up where this simulation stopped by simply doing run!(simulation,n_days=50) again. We didn't store any output, which you can do by run!(simulation,output=true), which will switch on NetCDF output with default settings. More options on output in NetCDF output.","category":"page"},{"location":"how_to_run_speedy/#Example-2:-Shallow-water-with-mountains","page":"How to run SpeedyWeather.jl","title":"Example 2: Shallow water with mountains","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"As a second example, let's investigate the Galewsky et al.[1] test case for the shallow water equations with and without mountains. As the shallow water system has also only one level, we can reuse the SpectralGrid from Example 1.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"spectral_grid = SpectralGrid(trunc=127,nlev=1)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Now as a first simulation, we want to disable any orography, so we create a NoOrography","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"orography = NoOrography(spectral_grid)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Although the orography is zero, you have to pass on spectral_grid so that it can still initialize zero-arrays of the right size and element type. Awesome. This time the initial conditions should be set the the Galewsky et al.[1] zonal jet, which is already defined as","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"initial_conditions = ZonalJet()","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The jet sits at 45˚N with a maximum velocity of 80m/s and a perturbation as described in their paper. Now we construct a model, but this time a ShallowWaterModel","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);\n\nsimulation = initialize!(model);","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Oh yeah. That looks like the wobbly jet in their paper. Let's run it again for another 6 days but this time also store NetCDF output.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"run!(simulation,n_days=6,output=true)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The progress bar tells us that the simulation run got the identification \"0002\", meaning that data is stored in the folder /run_0002, so let's plot that data properly (and not just using UnicodePlots).","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> using PyPlot, NCDatasets\njulia> ds = NCDataset(\"run_0002/output.nc\");\njulia> ds[\"vor\"]\nvor (384 × 192 × 1 × 25)\n Datatype: Float32\n Dimensions: lon × lat × lev × time\n Attributes:\n units = 1/s\n missing_value = NaN\n long_name = relative vorticity\n _FillValue = NaN","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Vorticity vor is stored as a 384x192x1x25 array, we may want to look at the first time step, which is the end of the previous simulation (time=6days) which we didn't store output for.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> vor = ds[\"vor\"][:,:,1,1];\njulia> lat = ds[\"lat\"][:];\njulia> lon = ds[\"lon\"][:];\njulia> pcolormesh(lon,lat,vor')","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Which looks like","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet pyplot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"You see that the unicode plot heavily coarse-grains the simulation, well it's unicode after all! And now the last time step, that means time=12days is","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> vor = ds[\"vor\"][:,:,1,25];\njulia> pcolormesh(lon,lat,vor')","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet pyplot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The jet broke up into many small eddies, but the turbulence is still confined to the northern hemisphere, cool! How this may change when we add mountains (we had NoOrography above!), say Earth's orography, you may ask? Let's try it out! We create an EarthOrography struct like so","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> orography = EarthOrography(spectral_grid)\nEarthOrography{Float32, OctahedralGaussianGrid{Float32}}:\n path::String = SpeedyWeather.jl/input_data\n file::String = orography_F512.nc\n scale::Float64 = 1.0\n smoothing::Bool = true\n smoothing_power::Float64 = 1.0\n smoothing_strength::Float64 = 0.1\n smoothing_truncation::Int64 = 85","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"It will read the orography from file as shown, and there are some smoothing options too, but let's not change them. Same as before, create a model, intialize into a simulation, run. This time directly for 12 days so that we can compare with the last plot","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);\njulia> simulation = initialize!(model);\njulia> run!(simulation,n_days=12,output=true)\nWeather is speedy: run 0003 100%|███████████████████████| Time: 0:00:35 (79.16 years/day)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"This time the run got the id \"0003\", but otherwise we do as before.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet pyplot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Interesting! The initial conditions have zero velocity in the southern hemisphere, but still, one can see some imprint of the orography on vorticity. You can spot the coastline of Antarctica; the Andes and Greenland are somewhat visible too. Mountains also completely changed the flow after 12 days, probably not surprising!","category":"page"},{"location":"how_to_run_speedy/#SpectralGrid","page":"How to run SpeedyWeather.jl","title":"SpectralGrid","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object. We have seen some examples above, now let's look into the details","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"SpectralGrid","category":"page"},{"location":"how_to_run_speedy/#SpeedyWeather.SpectralGrid","page":"How to run SpeedyWeather.jl","title":"SpeedyWeather.SpectralGrid","text":"Defines the horizontal spectral resolution and corresponding grid and the vertical coordinate for SpeedyWeather.jl. Options are\n\nNF::Type{<:AbstractFloat}: number format used throughout the model\ntrunc::Int64: horizontal resolution as the maximum degree of spherical harmonics\nGrid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: horizontal grid used for calculations in grid-point space\ndealiasing::Float64: how to match spectral with grid resolution: dealiasing factor, 1=linear, 2=quadratic, 3=cubic grid\nradius::Float64: radius of the sphere [m]\nnlat_half::Int64: number of latitude rings on one hemisphere (Equator incl)\nnpoints::Int64: total number of grid points in the horizontal\nnlev::Int64: number of vertical levels\nvertical_coordinates::SpeedyWeather.VerticalCoordinates: coordinates used to discretize the vertical\n\nnlat_half and npoints should not be chosen but are derived from trunc, Grid and dealiasing.\n\n\n\n\n\n","category":"type"},{"location":"how_to_run_speedy/#References","page":"How to run SpeedyWeather.jl","title":"References","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"[1] Galewsky, Scott, Polvani, 2004. An initial-value problem for testing numerical models of the global shallow-water equations, Tellus A. DOI: 10.3402/tellusa.v56i5.14436","category":"page"},{"location":"speedytransforms/#SpeedyTransforms","page":"Submodule: SpeedyTransforms","title":"SpeedyTransforms","text":"","category":"section"},{"location":"speedytransforms/","page":"Submodule: SpeedyTransforms","title":"Submodule: SpeedyTransforms","text":"SpeedyTransforms is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"speedytransforms/#Functions","page":"Submodule: SpeedyTransforms","title":"Functions","text":"","category":"section"},{"location":"grids/#Grids","page":"Grids","title":"Grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and but let's start but how they can be used","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"julia> spectral_grid = SpectralGrid(Grid = FullGaussianGrid)\nSpectralGrid:\n Spectral: T31 LowerTriangularMatrix{Complex{Float32}}, radius = 6.371e6 m\n Grid: 4608-element, 48-ring FullGaussianGrid{Float32} (quadratic)\n Resolution: 333km (average)\n Vertical: 8-level SigmaCoordinates","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object which defines the resolution in spectral and in grid-point space. The generator SpectralGrid() can take as a keyword argument Grid which can be any of the grids described below. The resolution of the grid, however, is not directly chosen, but determined from the spectral resolution trunc and the dealiasing factor. More in Matching spectral and grid resolution.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: RingGrids is a module too!\nWhile RingGrids is the underlying module that SpeedyWeather.jl uses for data structs on the sphere, the module can also be used independently of SpeedyWeather, for example to interpolate between data on different grids. See RingGrids","category":"page"},{"location":"grids/#Ring-based-equi-longitude-grids","page":"Grids","title":"Ring-based equi-longitude grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"SpeedyWeather.jl's spectral transform supports all ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on these rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.","category":"page"},{"location":"grids/#Implemented-grids","page":"Grids","title":"Implemented grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Currently the following full grids <: AbstractFullGrid are implemented","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"FullGaussianGrid, a full grid with Gaussian latitudes\nFullClenshawGrid, a full grid with equi-angle latitudes","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron\nOctahedralClenshawGrid, similar but based on equi-angle latitudes\nHEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces\nOctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"An overview of these grids is visualised here","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"(Image: Overview of implemented grids in SpeedyWeather.jl)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.","category":"page"},{"location":"grids/#Grid-resolution","page":"Grids","title":"Grid resolution","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half. ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: HEALPix grids do not use Nside as resolution parameter\nThe original formulation for HEALPix grids use N_side, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use N_side for the documentation or within functions though.","category":"page"},{"location":"grids/#Matching-spectral-and-grid-resolution","page":"Grids","title":"Matching spectral and grid resolution","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation T with a grid resolution N (=nlat_half) such that T + 1 = N. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at l_max=31 in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Let J be the total number of rings. Then we have","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"T approx J for linear truncation\nfrac32T approx J for quadratic truncation\n2T approx J for cubic truncation","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and in general fracm+12T approx J for m-th order truncation. So the higher the truncaction order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. In SpeedyWeather.jl the parameter dealiasing controls this option, = 1 would be linear, = 2 quadratic, = 3 cubic etc.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"For now just a quick overview of how the grid resolution changes when dealiasing is passed onto SpectralGrid on the FullGaussianGrid","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"trunc dealiasing FullGaussianGrid size\n31 1 64x32\n31 2 96x48\n31 3 128x64\n42 1 96x48\n42 2 128x64\n42 3 192x96\n... ... ...","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"You will obtain this information every time you create a SpectralGrid(;Grid,trunc,dealiasing).","category":"page"},{"location":"grids/#Full-Gaussian-grid","page":"Grids","title":"Full Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#Full-Clenshaw-Curtis-grid","page":"Grids","title":"Full Clenshaw-Curtis grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#Octahedral-Gaussian-grid","page":"Grids","title":"Octahedral Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#The-HEALPix-grid","page":"Grids","title":"The HEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are N_varphi basepixels in zonal direction and N_theta basepixels in meridional direction. For N_varphi = 4 and N_theta = 3 we obtain the classical HEALPix grid with N_varphi N_theta = 12 basepixels shown above in Implemented Grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always 2N, so 32 at N=16) and there are polar caps above and below the equatorial zone with the border at cos(theta) = 23 (theta in colatitudes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Following Górski, 2004[1], the z=cos(theta) colatitude of the j-th ring in the north polar cap, j=1N_side with 2N_side = N is ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^23N_side^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and on that ring, the longitude phi of the i-th point (i is the in-ring-index) is at","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i-tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The in-ring index i goes from i=14 for the first (i.e. northern-most) ring, i=18 for the second ring and i = 14j for the j-th ring in the northern polar cap.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"In the north equatorial belt j=N_side2N_side this changes to","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac43 - frac2j3N_side","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and the longitudes change to (i is always i = 14N_side in the equatorial belt meaning the number of longitude points is constant here)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2N_side(i - fracs2) quad s = (j - N_side + 1) mod 2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The cell boundaries are obtained by setting i = k + 12 or i = k + 12 + j (half indices) into the equations above, such that z(phik), a function for the cosine of colatitude z of index k and the longitude phi is obtained. These are then (northern polar cap)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^23N_side^2left(fracpi2phi_tright)^2 quad z = 1 - frack^23N_side^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with phi_t = phi mod tfracpi2 and in the equatorial belt","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac23-frac4k3N_side pm frac8phi3pi","category":"page"},{"location":"grids/#OctaHEALPix-grid","page":"Grids","title":"OctaHEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"While the classic HEALPix grid is based on a dodecahedron, other choices for N_varphi and N_theta in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With N_varphi = 4 and N_theta = 1 we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, 2pi around the Equator versus pi between the poles. N_varphi = 6 N_theta = 2 or N_varphi = 8 N_theta = 3 are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visulations of these grids.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"We call the N_varphi = 4 N_theta = 1 HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As N_theta = 1 there is no equatorial belt which simplifies the grid. The latitude of the j-th isolatitude ring on the OctaHEALPixGrid is defined by","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^2N^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with j=1N, and similarly for the southern hemisphere by symmetry. On this grid N_side = N where N= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index i = 14j are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i - tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and again, the southern hemisphere grid points are obtained by symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries-2","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Similar to the grid cell boundaries for the HEALPix grid, the OctaHEALPix grid's boundaries are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^2N^2left(fracpi2phi_tright)^2 quad z = 1 - frack^2N^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The 3N_side^2 in the denominator of the HEALPix grid came simply N^2 for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).","category":"page"},{"location":"grids/#References","page":"Grids","title":"References","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"[1] Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976","category":"page"},{"location":"boundary_conditions/#Boundary-conditions","page":"Boundary conditions","title":"Boundary conditions","text":"","category":"section"},{"location":"boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"This page describes the formulation of boundary conditions and their implementation.","category":"page"},{"location":"lowertriangularmatrices/#LowerTriangularMatrices","page":"Submodule: LowerTriangularMatrices","title":"LowerTriangularMatrices","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"LowerTriangularMatrices is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"This module defines LowerTriangularMatrix, a lower triangular matrix, which in contrast to LinearAlgebra.LowerTriangular does not store the entries above the diagonal. SpeedyWeather.jl uses LowerTriangularMatrix which is defined as a subtype of AbstractMatrix to store the spherical harmonic coefficients (see Spectral packing). ","category":"page"},{"location":"lowertriangularmatrices/#Creation-of-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Creation of LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"A LowerTriangularMatrix can be created using zeros,ones,rand, or randn","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> using SpeedyWeather.LowerTriangularMatrices\n\njulia> L = rand(LowerTriangularMatrix{Float32},5,5)\n5×5 LowerTriangularMatrix{Float32}:\n 0.912744 0.0 0.0 0.0 0.0\n 0.0737592 0.230592 0.0 0.0 0.0\n 0.799679 0.0765255 0.888098 0.0 0.0\n 0.670835 0.997938 0.505276 0.492966 0.0\n 0.949321 0.193692 0.793623 0.152817 0.357968","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"or the undef initializor LowerTriangularMatrix{Float32}(undef,3,3). The element type is arbitrary though, you can use any type T too.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Alternatively, it can be created through conversion from Matrix, which drops the upper triangle entries and sets them to zero","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> M = rand(Float16,3,3)\n3×3 Matrix{Float16}:\n 0.2222 0.694 0.3452\n 0.2158 0.04443 0.274\n 0.9746 0.793 0.6294\n\njulia> LowerTriangularMatrix(M)\n3×3 LowerTriangularMatrix{Float16}:\n 0.2222 0.0 0.0\n 0.2158 0.04443 0.0\n 0.9746 0.793 0.6294","category":"page"},{"location":"lowertriangularmatrices/#Indexing-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Indexing LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"LowerTriangularMatrix supports two types of indexing: 1) by denoting two indices, column and row [l,m] or 2) by denoting a single index [lm]. The double index works as expected","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L\n3×3 LowerTriangularMatrix{Float16}:\n 0.1499 0.0 0.0\n 0.1177 0.478 0.0\n 0.1709 0.756 0.3223\n\njulia> L[2,2]\nFloat16(0.478)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"But the single index skips the zero entries in the upper triangle, i.e.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L[4]\nFloat16(0.478)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"which, important, is different from single indices of an AbstractMatrix","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> Matrix(L)[4]\nFloat16(0.0)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"In performance-critical code a single index should be used, as this directly maps to the index of the underlying data vector. The double index is somewhat slower as it first has to be converted to the corresponding single index.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Consequently, many loops in SpeedyWeather.jl are build with the following structure","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"n,m = size(L)\nij = 0\nfor j in 1:m\n for i in j:n\n ij += 1\n L[ij] = i+j\n end\nend","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"which loops over all lower triangle entries of L::LowerTriangularMatrix and the single index ij is simply counted up. However, one could also use [i,j] as indices in the loop body or to perform any calculation (i+j here). An iterator over all entries in the lower triangle can be created by","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"for ij in eachindex(L)\n # do something\nend","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"The setindex! functionality of matrixes will throw a BoundsError when trying to write into the upper triangle of a LowerTriangularMatrix, for example","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L[2,1] = 0 # valid index\n0\n\njulia> L[1,2] = 0 # invalid index in the upper triangle\nERROR: BoundsError: attempt to access 3×3 LowerTriangularMatrix{Float32} at index [1, 2]","category":"page"},{"location":"lowertriangularmatrices/#Linear-algebra-with-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Linear algebra with LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"The LowerTriangularMatrices module's main purpose is not linear algebra, and it's implementation may not be efficient, however, many operations work as expected","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L = rand(LowerTriangularMatrix{Float32},3,3)\n3×3 LowerTriangularMatrix{Float32}:\n 0.57649 0.0 0.0\n 0.348685 0.875371 0.0\n 0.881923 0.850552 0.998306\n\njulia> L + L\n3×3 LowerTriangularMatrix{Float32}:\n 1.15298 0.0 0.0\n 0.697371 1.75074 0.0\n 1.76385 1.7011 1.99661\n\njulia> L * L\n3×3 Matrix{Float32}:\n 0.332341 0.0 0.0\n 0.506243 0.766275 0.0\n 1.68542 1.59366 0.996616","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Note, however, that the latter includes a conversion to Matrix, which is true for many operations, including inv or \\. Hence when trying to do more sophisticated linear algebra with LowerTriangularMatrix we quickly leave lower triangular-land and go back to normal matrix-land.","category":"page"},{"location":"lowertriangularmatrices/#Function-index","page":"Submodule: LowerTriangularMatrices","title":"Function index","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"LowerTriangularMatrix\nLowerTriangularMatrices.ij2k\nBase.fill!(L::LowerTriangularMatrix{T}, x) where T\nLowerTriangularMatrices.eachharmonic","category":"page"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix","text":"L = LowerTriangularMatrix{T}(v::Vector{T},m::Int,n::Int)\n\nA lower triangular matrix implementation that only stores the non-zero entries explicitly. L<:AbstractMatrix although in general we have L[i] != Matrix(L)[i], the former skips zero entries, tha latter includes them.\n\n\n\n\n\n","category":"type"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.ij2k","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.ij2k","text":"k = ij2k( i::Integer, # row index of matrix\n j::Integer, # column index of matrix\n m::Integer) # number of rows in matrix\n\nConverts the index pair i,j of an mxn LowerTriangularMatrix L to a single index k that indexes the same element in the corresponding vector that stores only the lower triangle (the non-zero entries) of L.\n\n\n\n\n\n","category":"function"},{"location":"lowertriangularmatrices/#Base.fill!-Union{Tuple{T}, Tuple{LowerTriangularMatrix{T}, Any}} where T","page":"Submodule: LowerTriangularMatrices","title":"Base.fill!","text":"fill!(L::LowerTriangularMatrix,x)\n\nFills the elements of L with x. Faster than fill!(::AbstractArray,x) as only the non-zero elements in L are assigned with x.\n\n\n\n\n\n","category":"method"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.eachharmonic","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.eachharmonic","text":"unit_range = eachharmonic(L::LowerTriangular)\n\ncreates unit_range::UnitRange to loop over all non-zeros in a LowerTriangularMatrix L. Like eachindex but skips the upper triangle with zeros in L.\n\n\n\n\n\nunit_range = eachharmonic(Ls::LowerTriangularMatrix...)\n\ncreates unit_range::UnitRange to loop over all non-zeros in the LowerTriangularMatrices provided as arguments. Checks bounds first. All LowerTriangularMatrix's need to be of the same size. Like eachindex but skips the upper triangle with zeros in L.\n\n\n\n\n\n","category":"function"},{"location":"conventions/#Style-and-convention-guide","page":"Style and convention guide","title":"Style and convention guide","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"In SpeedyWeather.jl we've been following the several conventions that are documented here.","category":"page"},{"location":"conventions/#Variable-naming","page":"Style and convention guide","title":"Variable naming","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The prognostic variables in spectral space are called","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" vor # Vorticity of horizontal wind field\n div # Divergence of horizontal wind field\n temp # Absolute temperature [K]\n pres_surf # Logarithm of surface pressure [log(Pa)]\n humid # Specific humidity [g/kg]","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" u\n v\n geopot\n ...","category":"page"},{"location":"conventions/#Preallocation","page":"Style and convention guide","title":"Preallocation","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"All arrays representing variables are preallocated and grouped into two structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" Prog::PrognosticVariables\n Diag::DiagnosticVariables","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The Diag struct contains further structs which represent the grid-point transformations of the prognostic variables and their tendencies.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" gridvars::GridVariables\n tendencies::Tendencies\n ...","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Constant arrays are grouped into several structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Boundaries","category":"page"},{"location":"conventions/#Julian-conventions","page":"Style and convention guide","title":"Julian conventions","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"We follow Julia's style guide and highlight here some important aspects of it.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Bang (!) convention. A function func does not change its input arguments, however, func! does.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.","category":"page"},{"location":"extending/#New-model-setups","page":"Extending SpeedyWeather","title":"New model setups","text":"","category":"section"},{"location":"extending/","page":"Extending SpeedyWeather","title":"Extending SpeedyWeather","text":"more to come...","category":"page"},{"location":"dynamical_core/#Dynamical-core","page":"Dynamical core","title":"Dynamical core","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"A mathematical and implementation-specific description of the dynamical core used in SpeedyWeather.jl. We start by describing the barotropic vorticity equations which is one set of equations that SpeedyWeather.jl can solve (see How to run SpeedyWeather.jl) as many details therein also apply to the Shallow water equations and Primitive equations explained thereafter.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The dynamical core presented here largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2], a shallow water model [3] and a primitive equation model[4]. ","category":"page"},{"location":"dynamical_core/#Barotropic-vorticity-equation","page":"Dynamical core","title":"Barotropic vorticity equation","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity zeta with advection, Coriolis force and diffusion in a single global layer on the sphere.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"fracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) =\nnabla times mathbfF + (-1)^n+1nunabla^2nzeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with time t, velocity vector mathbfu = (u v), Coriolis parameter f, and hyperdiffusion (-1)^n+1 nu nabla^2n zeta (n is the hyperdiffusion order; see Horizontal diffusion). We also include a forcing vector mathbfF = (F_uF_v) which acts on the zonal velocity u and the meridional velocity v and hence its curl nabla times mathbfF is a tendency for relative vorticity zeta.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Starting with some relative vorticity zeta, the Laplacian is inverted to obtain the stream function Psi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Psi = nabla^-2zeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The zonal velocity u and meridional velocity v are then the (negative) meridional gradient and zonal gradient of Psi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nu = -frac1R fracpartial Psipartial theta \nv = frac1Rcos(theta) fracpartial Psipartial phi \nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"which is described in Derivatives in spherical coordinates.","category":"page"},{"location":"dynamical_core/#Algorithm","page":"Dynamical core","title":"Algorithm","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"We briefly outline the algorithm that SpeedyWeather.jl uses in order to integrate the barotropic vorticity equation. As an intial step","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"0. Start with initial conditions of zeta_lm in spectral space and transform this model state to grid-point space:","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Invert the Laplacian to obtain the stream function Psi_lm in spectral space\nobtain zonal velocity (cos(theta)u)_lm through a Meridional derivative\nobtain meridional velocity (cos(theta)v)_lm through a Zonal derivative\nTransform zonal and meridional velocity (cos(theta)u)_lm, (cos(theta)v)_lm to grid-point space\nUnscale the cos(theta) factor to obtain uv\nTransform zeta_lm to zeta in grid-point space","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Now loop over","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Compute the forcing vector mathbfF = (F_uF_v) for u and v\nMultiply uv with zeta+f in grid-point space\nAdd A = F_u + v(zeta + f) and B = F_v - u(zeta + f)\nTransform these vector components to spectral space A_lm, B_lm\nCompute the curl of (AB)_lm in spectral space which is the tendency of zeta_lm\nCompute the Horizontal diffusion based on that tendency\nCompute a leapfrog time step as described in Time integration\nTransform the spectral state of zeta_lm to grid-point uvzeta as described in 0.\nPossibly do some output\nRepeat from 1.","category":"page"},{"location":"dynamical_core/#Shallow-water-equations","page":"Dynamical core","title":"Shallow water equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nfracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) = (-1)^n+1nunabla^2nzeta \nfracpartial mathcalDpartial t - nabla times (mathbfu(zeta + f)) = -nabla^2(tfrac12(u^2 + v^2) + geta) + (-1)^n+1nunabla^2nmathcalD \nfracpartial etapartial t + nabla cdot (mathbfuh) = 0\nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"where zeta = hatmathbfz cdot (nabla times mathbfu) is the relative vorticity, mathcalD = nabla cdot mathbfu the divergence, and eta the deviation from the fluid's rest height.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Note: more to come...","category":"page"},{"location":"dynamical_core/#Primitive-equations","page":"Dynamical core","title":"Primitive equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The primitive equations solved by SpeedyWeather.jl are","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\npartial_t u = \npartial_t v = \npartial_t T = \npartial_t Q = \nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Note: more to come...","category":"page"},{"location":"dynamical_core/#Horizontal-diffusion","page":"Dynamical core","title":"Horizontal diffusion","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In SpeedyWeather.jl we use hyerdiffusion through an n-th power Laplacian (-1)^n+1nabla^2n (hyper when n1) which can be implemented as a multiplication of the spectral coefficients Psi_lm with (-l(l+1))^nR^-2n (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the (-l(l+1))^nR^-2n can be precomputed. Note the sign change (-1)^n+1 here is such that the dissipative nature of the diffusion operator is retained for n odd and even.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step Delta t of variable zeta to obtain from time steps i-1 and i, the next time step i+1","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t dzeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with dzeta being some tendency evaluated from zeta_i. Now we want to add a diffusion term (-1)^n+1nu nabla^2nzeta with viscosity nu, wich however, is implicitly calculated from zeta_i+1, then","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t (dzeta + (-1)^n+1 nunabla^2nzeta_i+1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"As the application of (-1)^n+1nunabla^2n is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = fraczeta_i-1 + 2Delta t dzeta1 - 2Delta (-1)^n+1nunabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and expand the numerator to","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t fracdzeta + (-1)^n+1 nunabla^2nzeta_i-11 - 2Delta t (-1)^n+1nu nabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Hence the diffusion can be applied implicitly by updating the tendency dzeta as","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"dzeta to fracdzeta + (-1)^n+1nunabla^2nzeta_i-11 - 2Delta t nu nabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"which only depends on zeta_i-1. Now let D_textexplicit = (-1)^n+1nunabla^2n be the explicit part and D_textimplicit = 1 - (-1)^n+1 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are D_textimplicit = 1 - 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic lm we do","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"dzeta to D_textimplicit^-1(dzeta + D_textexplicitzeta_i-1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power n.","category":"page"},{"location":"dynamical_core/#Normalization-of-diffusion","page":"Dynamical core","title":"Normalization of diffusion","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In physics, the Laplace operator nabla^2 is often used to represent diffusion due to viscosity in a fluid. In that case, the viscosity coefficient is nu of units textm^2texts^-1 and the full operator reads as nu nabla^2 with units (textm^2texts^-1)(textm^-2) = texts^-1. This motivates us to normalize the Laplace operator by a constant of units textm^-2 and the viscosity coefficient by its inverse such that the viscosity coefficient becomes a damping timescale of unit texts^-1. Given the application in spectral space we decide to normalize by the largest eigenvalue -l_textmax(l_textmax+1) such that all entries in the discrete spectral Laplace operator are in 01. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized viscosity coefficient nu^* = l_textmax(l_textmax+1)nu (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have ","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"D^textexplicit_lm = -nu^* fracl(l+1)l_textmax(l_textmax+1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and the hyper-Laplacian of power n follows as","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"D^textexplicitn_lm = -nu^* left(fracl(l+1)l_textmax(l_textmax+1)right)^n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and the implicit part is accordingly D^textimplicitn_lm = 1 - 2Delta t D^textexplicitn_lm.","category":"page"},{"location":"dynamical_core/#Radius-scaling","page":"Dynamical core","title":"Radius scaling","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"SpeedyWeather.jl uses a scaling for vorticity zeta and stream function Psi that is","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildezeta = zeta R tildePsi = Psi R^-1","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In the barotropic voriticity equation model the inversion of the Laplcians in order to obtain Psi from zeta therefore becomes","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildezeta = tildenabla^2 tildePsi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"where the dimensionless gradients simply omit the scaling with 1R, tildenabla = Rnabla. The Barotropic vorticity equation scaled with R^2 is","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"partial_tildettildezeta + tildenabla cdot (mathbfu(tildezeta + tildef)) = tildenutildenabla^2ntildezeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildet = tR^-1, the scaled time t\nmathbfu = (uv), the velocity vector (no scaling applied)\ntildef = fR, the scaled Coriolis parameter f\ntildenu = nu^* R, the scaled viscosity nu^*, which itself is normalized to a damping time scale, see Normalization of diffusion.","category":"page"},{"location":"dynamical_core/#Scaled-shallow-water-equations","page":"Dynamical core","title":"Scaled shallow water equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Similar to the scaled barotropic vorticity equations, the scaled shallow water equations scale the vorticity and the divergence equation with R^2, but the continuity equation with R","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nfracpartial tildezetapartial tildet + tildenabla cdot (mathbfu(tildezeta + tildef)) =\ntildenutildenabla^2ntildezeta \nfracpartial tildemathcalDpartial tildet - tildenabla times (mathbfu(tildezeta + tildef)) =\n-tildenabla^2left(tfrac12(u^2 + v^2) + geta right) + tildenutildenabla^2ntildemathcalD \nfracpartial etapartial tildet + tildenabla cdot (mathbfuh) = 0\nendaligned","category":"page"},{"location":"dynamical_core/#Time-integration","page":"Dynamical core","title":"Time integration","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"SpeedyWeather.jl uses a leapfrog time scheme with a Robert's and William's filter to dampen the computational mode and achieve 3rd order accuracy.","category":"page"},{"location":"dynamical_core/#Oscillation-equation","page":"Dynamical core","title":"Oscillation equation","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"fracdFdt = iomega F","category":"page"},{"location":"dynamical_core/#References","page":"Dynamical core","title":"References","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[1]: Geophysical Fluid Dynamics Laboratory, Idealized models with spectral dynamics","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[2]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[3]: Geophysical Fluid Dynamics Laboratory, The Shallow Water Equations.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[4]: Geophysical Fluid Dynamics Laboratory, The Spectral Dynamical Core","category":"page"},{"location":"spectral_transform/#Spherical-Harmonic-Transform","page":"Spherical harmonic transform","title":"Spherical Harmonic Transform","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space on a longitude-latitude regular Gaussian grid.","category":"page"},{"location":"spectral_transform/#Inspiration","page":"Spherical harmonic transform","title":"Inspiration","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform implemented by SpeedyWeather.jl follows largely Justin Willmert's CMB.jl package and makes use of AssociatedLegendrePolynomials.jl and FFTW.jl (for Float32/64) or GenericFFT.jl (for generic) for the Fourier transform. Justin described his work in a Blog series [1][2][3][4][5][6][7][8].","category":"page"},{"location":"spectral_transform/#Spherical-harmonics","page":"Spherical harmonic transform","title":"Spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spherical harmonics Y_lm of degree l and order m over the longitude phi = (02pi) and colatitudes theta = (-pi2pi2), are","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Y_lm(phi theta) = lambda_l^m(sintheta) e^imphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with lambda_l^m being the pre-normalized associated Legendre polynomials, and e^imphi are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Latitudes versus colatitudes\nThe implementations of the spherical transforms in SpeedyWeather.jl use colatitudes theta = (0pi) (0 at the north pole) but the dynamical core uses latitudes theta = (-pi2pi2) (pi2 at the north pole). However, all arrays are always sorted north to south such that [i,1] will access the northern-most grid points. Note: We may also use latitudes in the spherical harmonic transform in the future for consistency. ","category":"page"},{"location":"spectral_transform/#Synthesis-(spectral-to-grid)","page":"Spherical harmonic transform","title":"Synthesis (spectral to grid)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The synthesis (or inverse transform) takes the spectral coefficients a_lm and transforms them to grid-point values f(phitheta) (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics Y_lm with non-zero coefficients.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"f(phitheta) = sum_l=0^infty sum_m=-l^l a_lm Y_lm(phitheta)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We obtain an approximation with a finite set of a_lm by truncating the series after l = l_max.","category":"page"},{"location":"spectral_transform/#Analysis-(grid-to-spectral)","page":"Spherical harmonic transform","title":"Analysis (grid to spectral)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting in grid-point space we can transform a field f(lambdatheta) into the spectral space of the spherical harmonics by","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"a_lm = int_0^2pi int_-tfracpi2^tfracpi2 f(lambdatheta) Y_lm(lambdatheta) cos theta dtheta dlambda","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This integral has to be discretized to when grid-point values f(lambda_itheta_i) are used. For more details, see [7],[8].","category":"page"},{"location":"spectral_transform/#Spectral-packing","page":"Spherical harmonic transform","title":"Spectral packing","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Spectral packing is the way how the coefficients a_lm of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree l and order m as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Every row represents an order l geq 0, starting from l=0 at the top. Every column represents an order m geq 0, starting from m=0 on the left. The coefficients of these spherical harmonics are directly mapped into a matrix a_lm as ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 \n a_10 a_11 \n a_20 a_12 a_22 \n a_30 a_13 a_23 a_33","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal explicitly stores zeros. Note that internally vector fields include an additional degree, such that l_max = m_max + 1 (see Gradients in spherical coordinates for more information). The harmonics with a_l0 (the first column) are also called zonal harmonics as they are constant with longitude phi. The harmonics with a_ll (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into 2l sectors in longitude phi without a zero-crossing in latitude.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Array indices\nFor a spectral field alms note that due to Julia's 1-based indexing the coefficient a_lm is obtained via alms[l+1,m+1].","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Fortran speedy does not use the same spectral packing as SpeedyWeather.jl. The alternative packing lm therein uses l=m and m=l-m as summarized in the following table.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"degree l order m l=m m=l-m\n0 0 0 0\n1 0 0 1\n1 1 1 0\n2 0 0 2\n2 1 1 1\n2 2 2 0\n3 0 0 3\n... ... ... ...","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 a_10 a_20 a_30\n a_11 a_21 a_31 \n a_22 a_32 \n a_33 ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran-speedy.","category":"page"},{"location":"spectral_transform/#Example-transforms","page":"Spherical harmonic transform","title":"Example transforms","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"julia> using SpeedyWeather\njulia> alms = zeros(ComplexF64,3,3) # spectral coefficients\njulia> alms[2,2] = 1 # only l=1,m=1 harmonic\njulia> map = gridded(alms) # convert to grid space\n8×4 Matrix{Float64}:\n -0.324541 -0.600363 -0.600363 -0.324541\n -0.134429 -0.248678 -0.248678 -0.134429\n 0.134429 0.248678 0.248678 0.134429\n 0.324541 0.600363 0.600363 0.324541\n 0.324541 0.600363 0.600363 0.324541\n 0.134429 0.248678 0.248678 0.134429\n -0.134429 -0.248678 -0.248678 -0.134429\n -0.324541 -0.600363 -0.600363 -0.324541\n \njulia> spectral(map) # back to spectral space\n3×3 Matrix{ComplexF64}:\n 0.0+0.0im 0.0+0.0im 0.0+0.0im\n 0.0+0.0im 1.0+3.60727e-17im 0.0+0.0im\n 0.0+0.0im 0.0+0.0im 0.0+0.0im","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"and we have successfully reobtained the l=m=1 spherical harmonic.","category":"page"},{"location":"spectral_transform/#Available-horizontal-resolutions","page":"Spherical harmonic transform","title":"Available horizontal resolutions","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with l leq l_max and m leq m_max are explicitly represented. This is usually described as Tm_max, with l_max = m_max (although in vector quantities require one more degree l in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with l_max = m_max = 31. Note that the degree l and order m are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Using triangular truncation[9], there are constraints on the corresponding grid resolution. Let nlon, nlat be the number of longitudes, latitudes on a regular Gaussian grid. Then spectral and grid resolution have to be chosen such that","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nlon geq 3l_max+1\nnlat geq (3l_max+1)2","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In general, we choose nlon = 2nlat, and ideally nlon is easily Fourier-transformable, e.g. nlon = 2^i3^j5^k with some integers ijk geq 0. SpeedyWeather.jl is tested at the following horizontal resolutions, with Delta x = tfrac2pi Rnlon as the approximate grid spacing at the Equator","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"l_max nlon nlat Delta x\n31 (default) 96 48 400 km\n42 128 64 300 km\n85 256 128 160 km\n170 512 256 80 km\n341 1024 512 40 km\n682 2048 1024 20 km","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Choosing trunc as argument in run_speedy will automatically choose nlon,nlat as presented in the table. Other common choices are T63 (192x96), T127 (384x192), T255 (768x384), T511 (1536x768), among others.","category":"page"},{"location":"spectral_transform/#Derivatives-in-spherical-coordinates","page":"Spherical harmonic transform","title":"Derivatives in spherical coordinates","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Horizontal gradients in spherical coordinates are defined for a scalar field A and the latitudes theta and longitudes lambda as","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla A = left(frac1Rcosthetafracpartial Apartial lambda frac1Rfracpartial Apartial theta right)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"However, the divergence of a vector field mathbfu = (uv) includes additional cos(theta) scalings","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla cdot mathbfu = frac1Rcosthetafracpartial upartial lambda +\nfrac1Rcosthetafracpartial (v costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"and similar for the curl","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla times mathbfu = frac1Rcosthetafracpartial vpartial lambda -\nfrac1Rcosthetafracpartial (u costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The radius of the sphere (i.e. Earth) is R. The zonal gradient scales with 1cos(theta) as the longitudes converge towards the poles (note that theta describes latitudes here, defintions using colatitudes replace the cos with a sin.)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting with a spectral field of vorticity zeta and divergence mathcalD one can obtain stream function Psi and velocity potential Phi by inverting the Laplace operator nabla^2:","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi = nabla^-2zeta quad Phi = nabla^-2mathcalD","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The velocities uv are then obtained from (uv) = nabla^botPsi + nablaPhi following the defintion from above and nabla^bot = (-R^-1partial_theta (Rcostheta)^-1partial_lambda)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nu = -frac1Rpartial_thetaPsi + frac1Rcosthetapartial_lambdaPhi \nv = +frac1Rpartial_thetaPhi + frac1Rcosthetapartial_lambdaPsi\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Alternatively, we can use the velocities U = ucostheta V = vcostheta, which we do as the meridional gradient for spherical harmonics is easier implemented with a costheta-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with UV and not uv. From uv we can return to zeta mathcalD via","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nzeta = frac1Rcosthetapartial_lambda v - frac1Rcosthetapartial_theta (u costheta) \nmathcalD = frac1Rcosthetapartial_lambda u + frac1Rcosthetapartial_theta (v costheta)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Equivalently, we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU = -fraccosthetaRpartial_thetaPsi + frac1Rpartial_lambdaPhi \nV = +fraccosthetaRpartial_thetaPhi + frac1Rpartial_lambdaPsi \nzeta = frac1Rpartial_lambda left( fracVcos^2theta right) -\nfraccosthetaRpartial_theta left( fracUcos^2theta right) \nmathcalD = frac1Rpartial_lambda left( fracUcos^2theta right) +\nfraccosthetaRpartial_theta left( fracVcos^2theta right)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is a more convenient formulation as required costheta scalings are reduced to a minimum. The remaining (UV)*cos^-2theta are done in grid-point space and usually in combination with other operations like the computation of the vorticity flux. But also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out too. As described in Meridional derivative, it is more convenient to implement costheta partial_theta via a recursion relation for the Legendre polynomials than partial_theta directly. How the operators nabla nabla times nabla cdot can be implemented with spherical harmonics is presented in the following sections.","category":"page"},{"location":"spectral_transform/#Zonal-derivative","page":"Spherical harmonic transform","title":"Zonal derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The zonal derivative of a scalar field Psi in spectral space is the zonal derivative of all its respective spherical harmonics Psi_lm(phitheta) (now we use phi for longitudes to avoid confusion with the Legendre polynomials lambda_lm)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"v_lm = frac1R cos(theta) fracpartialpartial phi left( lambda_l^m(costheta) e^imphi right) =\nfracimR cos(theta) lambda_l^m(costheta) e^imphi = fracimR cos(theta) Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"So for every spectral harmonic, cos(theta)v_lm is obtained from Psi_lm via a multiplication with imR. Unscaling the cos(theta)-factor is done after transforming the spectral coefficients v_lm into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as tildePsi = R^-1Psi such that the division by radius R in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order im. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number m times imaginary i.","category":"page"},{"location":"spectral_transform/#Meridional-derivative","page":"Spherical harmonic transform","title":"Meridional derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridioinal derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costheta fracdP_lmdtheta = -lepsilon_l+1mP_l+1m + (l+1)epsilon_lmP_l-1m","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with recursion factors","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"epsilon_lm = sqrtfracl^2-m^24l^2-1","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In the following we use the example of obtaining the zonal velocity u from the stream function Psi, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi(lambdatheta) = sum_lmPsi_lmP_lm(sintheta)e^imlambda","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"we multiply with -R^-1costhetapartial_theta to obtain","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costhetaleft(-frac1Rpartial_thetaPsi right) = -frac1Rsum_lmPsi_lme^imlambdacosthetapartial_theta P_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"at which point the recursion from above can be applied. Collecting terms proportional to P_lm then yields","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"(cos(theta)u)_lm = -frac1R(-(l-1)epsilon_lmPsi_l-1m + (l+2)epsilon_l+1mPsi_l+1m)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"To obtain the coefficient of each spherical harmonic lm of the meridional gradient of a spectral field, two coefficients at l-1m and l+1m have to be combined. This means that the coefficient of a gradient ((costheta) u)_lm is a linear combination of the coefficients of one higher and one lower degree Psi_l+1mPsi_l-1m. As the coefficient Psi_lm with ml are zero, the sectoral harmonics (l=m) of the gradients are obtained from the first off-diagonal only. However, the l=l_max harmonics of the gradients require the l_max-1 as well as the l_max+1 harmonics. In SpeedyWeather.jl vector quantitie like uv use therefore one more meridional mode than scalar quantities such as vorticity zeta or stream function Psi. The meridional derivative in SpeedyWeather.jl also omits the 1R-scaling as explained for the Zonal derivative and in Radius scaling.","category":"page"},{"location":"spectral_transform/#Divergence-and-curl-in-spherical-harmonics","page":"Spherical harmonic transform","title":"Divergence and curl in spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridional gradient as described above can be applied to scalars, such as Psi and Phi in the conversion to velocities (uv) = nabla^botPsi + nablaPhi, however, the operators curl nabla times and divergence nabla cdot in spherical coordinates involve a costheta scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform of vorticity zeta is","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac12piint_-tfracpi2^tfracpi2int_0^2pi zeta(lambdatheta) P_lm(sintheta) e^imlambda dlambda costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Given that Rzeta = cos^-1partial_lambda v - cos^-1partial_theta (u costheta), we therefore have to evaluate a meridional integral of the form","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"int P_lm frac1cos theta partial_theta(u costheta)) cos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which can be solved through integration by parts. As ucostheta = 0 at theta = pm tfracpi2 only the integral","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int partial_theta P_lm (u costheta) dtheta = -int costheta partial_theta P_lm (fracucostheta) costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"remains. Inserting the recurrence relation from the Meridional derivative turns this into","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int left(-l epsilon_l+1mP_l+1m + (l+1)epsilon_lm P_l-1m right) (fracucostheta) cos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Now we expand (tfracucostheta) but only the lm harmonic will project ontoP_lm. Let u^* = ucos^-1theta v^* = vcos^-1theta we then have in total","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nRzeta_lm = imv^*_lm + (l+1)epsilon_lmu^*_l-1m - lepsilon_l+1mu^*_l+1m \nRD_lm = imu^*_lm - (l+1)epsilon_lmv^*_l-1m + lepsilon_l+1mv^*_l+1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"And the divergence D is similar, but (uv) to (-vu). We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#Laplacian","page":"Spherical harmonic transform","title":"Laplacian","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral Laplacian is easily applied to the coefficients Psi_lm of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator nabla^2 in spherical coordinates with eigenvalues -l(l+1) divided by the radius squared R^2, i.e. nabla^2 Psi becomes tfrac-l(l+1)R^2Psi_lm in spectral space. For example, vorticity zeta and streamfunction Psi are related by zeta = nabla^2Psi in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree l and order m to","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac-l(l+1)R^2Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This can be easily inverted to obtain the stream function Psi from vorticity zeta instead. In order to avoid division by zero, we set Psi_00 here, given that the stream function is only defined up to a constant anyway.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nPsi_lm = fracR^2-l(l+1)zeta_lm quad foralllm 0\nPsi_00 = 0\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"See also Horizontal diffusion and Normalization of diffusion.","category":"page"},{"location":"spectral_transform/#U,V-from-vorticity-and-divergence","page":"Spherical harmonic transform","title":"U,V from vorticity and divergence","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity zeta and divergence D (which are prognostic variables) to U=ucostheta V=vcostheta. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree l (the meridional gradient). It is therefore computationally more efficient to compute UV directly from zetaD instead of calculating stream function and velocity potential first. In total we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU_lm = -fraciml(l+1)(RD)_lm + fracepsilon_l+1ml+1(Rzeta)_l+1m - fracepsilon_lml(Rzeta)_l-1m \nV_lm = -fraciml(l+1)(Rzeta)_lm - fracepsilon_l+1ml+1(RD)_l+1m + fracepsilon_lml(RD)_l-1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#References","page":"Spherical harmonic transform","title":"References","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[1]: Justin Willmert, 2020. Introduction to Associated Legendre Polynomials (Legendre.jl Series, Part I)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[2]: Justin Willmert, 2020. Calculating Legendre Polynomials (Legendre.jl Series, Part II)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[3]: Justin Willmert, 2020. Pre-normalizing Legendre Polynomials (Legendre.jl Series, Part III)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[4]: Justin Willmert, 2020. Maintaining numerical accuracy in the Legendre recurrences (Legendre.jl Series, Part IV)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[5]: Justin Willmert, 2020. Introducing Legendre.jl (Legendre.jl Series, Part V)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[6]: Justin Willmert, 2020. Numerical Accuracy of the Spherical Harmonic Recurrence Coefficient (Legendre.jl Series Addendum)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[7]: Justin Willmert, 2020. Notes on Calculating the Spherical Harmonics","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[8]: Justin Willmert, 2022. More Notes on Calculating the Spherical Harmonics: Analysis of maps to harmonic coefficients","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[9]: David Randall, 2021. An Introduction to Numerical Modeling of the Atmosphere, Chapter 22.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[10]: Dale Durran, 2010. Numerical Methods for Fluid Dynamics, Springer. In particular section 6.2, 6.4.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[11]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"ringgrids/#RingGrids","page":"Submodule: RingGrids","title":"RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids defines several iso-latitude grids, which are mathematically described in the section on Grids. In brief, they include the regular latitude-longitude grids (here called FullClenshawGrid) as well as grids which latitudes are shifted to the Gaussian latitudes and reduced grids, meaning that they have a decreasing number of longitudinal points towards the poles to be more equal-area than full grids.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids defines and exports the following grids:","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"full grids: FullClenshawGrid, FullGaussianGrid, FullHEALPix, and FullOctaHEALPix\nreduced grids: OctahedralGaussianGrid, OctahedralClenshawGrid, OctaHEALPixGrid and HEALPixGrid","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"The following explanation of how to use these can be mostly applied to any of them, however, there are certain functions that are not defined, e.g. the full grids can be trivially converted to a Matrix but not the OctahedralGaussianGrid.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"note: What is a ring?\nWe use the term ring, short for iso-latitude ring, to refer to a sequence of grid points that all share the same latitude. A latitude-longitude grid is a ring grid, as it organises its grid-points into rings. However, other grids, like the cubed-sphere are not based on iso-latitude rings. SpeedyWeather.jl only works with ring grids because its a requirement for the Spherical Harmonic Transform to be efficient. See Grids.","category":"page"},{"location":"ringgrids/#Creating-data-on-a-RingGrid","page":"Submodule: RingGrids","title":"Creating data on a RingGrid","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Every grid in RingGrids has a grid.data field, which is a vector containing the data on the grid. The grid points are unravelled west to east then north to south, meaning that it starts at 90˚N and 0˚E then walks eastward for 360˚ before jumping on the next latitude ring further south, this way circling around the sphere till reaching the south pole. This may also be called ring order.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Data in a Matrix which follows this ring order can be put on a FullGaussianGrid like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"using SpeedyWeather.RingGrids\nmap = randn(Float32,8,4)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid = FullGaussianGrid(map)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"A full Gaussian grid has always 2N x N grid points, but a FullClenshawGrid has 2N x N-1, if those dimensions don't match, the creation will throw an error. To reobtain the data from a grid, you can access its data field which returns a normal Vector","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid.data","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Which can be reshaped to reobtain map from above. Alternatively you can Matrix(grid) to do this in one step","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"map == Matrix(FullGaussianGrid(map))","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"You can also use zeros,ones,rand,randn to create a grid, whereby nlat_half, i.e. the number of latitude rings on one hemisphere, Equator included, is used as a resolution parameter and here as a second argument.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"nlat_half = 4\ngrid = randn(OctahedralGaussianGrid{Float16},nlat_half)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"and any element type T can be used for OctahedralGaussianGrid{T} and similar for other grid types.","category":"page"},{"location":"ringgrids/#Visualising-RingGrid-data","page":"Submodule: RingGrids","title":"Visualising RingGrid data","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"As only the full grids can be reshaped into a matrix, the underyling data structure of any AbstractGrid is a vector. As shown in the examples above, one can therefore inspect the data as if it was a vector. But as that data has, through its <:AbstractGrid type, all the geometric information available to plot it on a map, RingGrids also exports plot function, based on UnicodePlots' heatmap.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"nlat_half = 24\ngrid = randn(OctahedralGaussianGrid,nlat_half)\nplot(grid)","category":"page"},{"location":"ringgrids/#Indexing-RingGrids","page":"Submodule: RingGrids","title":"Indexing RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"All RingGrids have a single index ij which follows the ring order. While this is obviously not super exciting here are some examples how to make better use of the information that the data sits on a grid.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"We obtain the latitudes of the rings of a grid by calling get_latd (get_lond is only defined for full grids, or use get_latdlonds for latitudes, longitudes per grid point not per ring)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid = randn(OctahedralClenshawGrid,5)\nlatd = get_latd(grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Now we could calculate Coriolis and add it on the grid as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"rotation = 7.29e-5 # angular frequency of Earth's rotation [rad/s]\ncoriolis = 2rotation*sind.(latd) # vector of coriolis parameters per latitude ring\n\nrings = eachring(grid)\nfor (j,ring) in enumerate(rings)\n f = coriolis[j]\n for ij in ring\n grid[ij] += f\n end\nend","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"eachring creates a vector of UnitRange indices, such that we can loop over the ring index j (j=1 being closest to the North pole) pull the coriolis parameter at that latitude and then loop over all in-ring indices i (changing longitudes) to do something on the grid. Something similar can be done to scale/unscale with the cosine of latitude for example. We can always loop over all grid-points like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"for ij in eachgridpoint(grid)\n grid[ij]\nend","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"or use eachindex instead.","category":"page"},{"location":"ringgrids/#Interpolation-on-RingGrids","page":"Submodule: RingGrids","title":"Interpolation on RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"In most cases we will want to use RingGrids so that our data directly comes with the geometric information of where the grid-point is one the sphere. We have seen how to use get_latd, get_lond, ... for that above. This information generally can also be used to interpolate our data from grid to another or to request an interpolated value on some coordinates. Using our data on grid which is an OctahedralGaussianGrid from above we can use the interpolate function to get it onto a FullGaussianGrid (or any other grid for purpose)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid = randn(OctahedralGaussianGrid{Float32},4)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(FullGaussianGrid,grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"By default this will linearly interpolate (it's an Anvil interpolator, see below) onto a grid with the same nlat_half, but we can also coarse-grain or fine-grain by specifying nlat_half directly as 2nd argument","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(FullGaussianGrid,6,grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"So we got from an 8-ring OctahedralGaussianGrid{Float16} to a 12-ring FullGaussianGrid{Float64}, so it did a conversion from Float16 to Float64 on the fly too, because the default precision is Float64 unless specified. interpolate(FullGaussianGrid{Float16},6,grid) would have interpolated onto a grid with element type Float16.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"One can also interpolate onto a give cordinate ˚N, ˚E like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(30.0,10.0,grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"we interpolated the data from grid onto 30˚N, 10˚E. To do this simultaneously for many coordinates they can be packed into a vector too","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate([30.0,40.0,50.0],[10.0,10.0,10.0],grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which returns the data on grid at 30˚N, 40˚N, 50˚N, and 10˚E respectively. Note how the interpolation here retains the element type of grid.","category":"page"},{"location":"ringgrids/#Performance-for-RingGrid-interpolation","page":"Submodule: RingGrids","title":"Performance for RingGrid interpolation","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Every time an interpolation like interpolate(30.0,10.0,grid) is called, several things happen, which are important to understand to know how to get the fastest interpolation out of this module in a given situation. Under the hood an interpolation takes three arguments","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"output vector\ninput grid\ninterpolator","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"The output vector is just an array into which the interpolated data is written, providing this prevents unnecessary allocation of memory in case the destination array of the interpolation already exists. The input grid contains the data which is subject to interpolation, it must come on a ring grid, however, its coordinate information is actually already in the interpolator. The interpolator knows about the geometry of the grid the data is coming on and the coordinates it is supposed to interpolate onto. It has therefore precalculated the indices that are needed to access the right data on the input grid and the weights it needs to apply in the actual interplation operation. The only thing it does not know is the actual data values of that grid. So in the case you want to interpolate from grid A to grid B many times, you can just reuse the same interpolator. If you want to change the coordinates of the output grid but its total number of points remain constants then you can update the locator inside the interpolator and only else you will need to create a new interpolator. Let's look at this in practice. Say we have two grids an want to interpolate between them","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid_in = rand(HEALPixGrid,4)\ngrid_out = zeros(FullClenshawGrid,6)\ninterp = RingGrids.interpolator(grid_out,grid_in)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Now we have created an interpolator interp which knows about the geometry where to interpolate from and the coordinates there to interpolate to. It is also initialized, meaning it has precomputed the indices to of grid_in that are supposed to be used. It just does not know about the data of grid_in (and neither of grid_out which will be overwritten anyway). We can now do","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate!(grid_out,grid_in,interp)\ngrid_out","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which is identical to interpolate(grid_out,grid_in) but you can reuse interp for other data. The interpolation can also handle various element types (the interpolator interp does not have to be updated for this either)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid_out = zeros(FullClenshawGrid{Float16},6);\ninterpolate!(grid_out,grid_in,interp)\ngrid_out","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"and we have converted data from a HEALPixGrid{Float64} (Float64 is always default if not specified) to a FullClenshawGrid{Float16} including the type conversion Float64-Float16 on the fly. Technically there are three data types and their combinations possible: The input data will come with a type, the output array has an element type and the interpolator has precomputed weights with a given type. Say we want to go from Float16 data on an OctahedralGaussianGrid to Float16 on a FullClenshawGrid but using Float32 precision for the interpolation itself, we would do this by","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid_in = randn(OctahedralGaussianGrid{Float16},24)\ngrid_out = zeros(FullClenshawGrid{Float16},24)\ninterp = RingGrids.interpolator(Float32,grid_out,grid_in)\ninterpolate!(grid_out,grid_in,interp)\ngrid_out","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"As a last example we want to illustrate a situation where we would always want to interplate onto 10 coordinates, but their locations may change. In order to avoid recreating an interpolator object we would do (AnvilInterpolator is described in Anvil interpolator)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"npoints = 10 # number of coordinates to interpolate onto\ninterp = AnvilInterpolator(Float32,HEALPixGrid,24,npoints)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"with the first argument being the number format used during interpolation, then the input grid type, its resolution in terms of nlat_half and then the number of points to interpolate onto. However, interp is not yet initialized as it does not know about the destination coordinates yet. Let's define them, but note that we already decided there's only 10 of them above.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"latds = collect(0.0:5.0:45.0)\nlonds = collect(-10.0:2.0:8.0)\nnothing # hide","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"now we can update the locator inside our interpolator as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids.update_locator!(interp,latds,londs)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"With data matching the input from above, a nlat_half=24 HEALPixGrid, and allocate 10-element output vector","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"output_vec = zeros(10)\ngrid_input = rand(HEALPixGrid,24)\nnothing # hide","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"we can use the interpolator as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate!(output_vec,grid_input,interp)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which is the approximately the same as doing it directly without creating an interpolator first and updating its locator","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(latds,londs,grid_input)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"but allows for a reuse of the interpolator. Note that the two output arrays are not exactly identical because we manually set our interpolator interp to use Float32 for the interplation whereas the default is Float64.","category":"page"},{"location":"ringgrids/#Anvil-interpolator","page":"Submodule: RingGrids","title":"Anvil interpolator","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Currently the only interpolator implemented is a 4-point bilinear interpolator, which schematically works as follows. Anvil interpolation is the bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":" 0..............1 # fraction of distance Δab between a,b\n |< Δab >|\n\n0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x\n.Δy |\n. |\n.v x \n. |\n1 c ------ o ---- d\n\n |< Δcd >|\n 0...............1 # fraction of distance Δcd between c,d\n\n^ fraction of distance Δy between a-b and c-d.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"This interpolation is chosen as by definiton of the ring grids, a and b share the same latitude, so do c and d, but the longitudes can be different for all four, a,b,c,d.","category":"page"},{"location":"ringgrids/#Function-index","page":"Submodule: RingGrids","title":"Function index","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids.each_index_in_ring\nRingGrids.eachgridpoint\nRingGrids.eachring\nRingGrids.whichring\nRingGrids.get_nlons","category":"page"},{"location":"ringgrids/#SpeedyWeather.RingGrids.each_index_in_ring","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.each_index_in_ring","text":"i = each_index_in_ring(grid,j)\n\nUnitRange i to access data on grid grid on ring j.\n\n\n\n\n\n","category":"function"},{"location":"ringgrids/#SpeedyWeather.RingGrids.eachgridpoint","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.eachgridpoint","text":"ijs = eachgridpoint(grid)\n\nUnitRange ijs to access each grid point on grid grid.\n\n\n\n\n\n","category":"function"},{"location":"ringgrids/#SpeedyWeather.RingGrids.eachring","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.eachring","text":"eachring(grid::SpeedyWeather.RingGrids.AbstractGrid) -> Any\n\n\nVector{UnitRange} rings to loop over every ring of grid grid and then each grid point per ring. To be used like\n\nrings = eachring(grid)\nfor ring in rings\n for ij in ring\n grid[ij]\n\n\n\n\n\neachring(\n grid1::SpeedyWeather.RingGrids.AbstractGrid,\n grids::Grid<:SpeedyWeather.RingGrids.AbstractGrid...\n) -> Any\n\n\nSame as eachring(grid) but performs a bounds check to assess that all grids in grids are of same size.\n\n\n\n\n\n","category":"function"},{"location":"ringgrids/#SpeedyWeather.RingGrids.whichring","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.whichring","text":"whichring(\n ij::Integer,\n rings::Vector{UnitRange{Int64}}\n) -> Int64\n\n\nObtain ring index j from gridpoint ij and Vector{UnitRange} describing rind indices as obtained from eachring(::Grid)\n\n\n\n\n\n","category":"function"},{"location":"ringgrids/#SpeedyWeather.RingGrids.get_nlons","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.get_nlons","text":"get_nlons(\n Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid},\n nlat_half::Integer;\n both_hemispheres\n) -> Any\n\n\nReturns a vector nlons for the number of longitude points per latitude ring, north to south. Provide grid Grid and its resolution parameter nlat_half. For both_hemisphere==false only the northern hemisphere (incl Equator) is returned.\n\n\n\n\n\n","category":"function"},{"location":"#SpeedyWeather.jl-documentation","page":"Home","title":"SpeedyWeather.jl documentation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.","category":"page"},{"location":"#Overview","page":"Home","title":"Overview","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to perform some calculations in spectral space (time integration, gradients, linear terms) and some in grid-point space (advection, non-linear terms, parameterizations). The prognostic variables used are vorticity, divergence, absolute temperature, logarithm of surface pressure and specific humidity. The time stepping uses a leapfrog scheme with additional filters and a semi-implicit formulation for gravity waves. The default resolution is T31 (96x48 grid points on a regular Gaussian grid, about 400km at the Equator) and 8 vertical levels.","category":"page"},{"location":"","page":"Home","title":"Home","text":"Simple parameterizations are used to represent the physical processes convection, large-scale condensation, clouds, short-wave radiation, long-waves radiation, surface fluxes of momentum and energy, and vertical diffusion.","category":"page"},{"location":"#Manual-outline","page":"Home","title":"Manual outline","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"See the following pages of the documentation for more details","category":"page"},{"location":"","page":"Home","title":"Home","text":"Installation\nHow to run SpeedyWeather.jl\nSpherical harmonic transform\nGrids\nDynamical core\nParametrizations\nExtending SpeedyWeather","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the submodules","category":"page"},{"location":"","page":"Home","title":"Home","text":"RingGrids and their interpolation \nLowerTriangularMatrices \nSpeedyTransforms","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the original documentation by Molteni and Kucharski.","category":"page"},{"location":"#Developers","page":"Home","title":"Developers","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The development of SpeedyWeather.jl is lead by Milan Klöwer and current and past contributors include","category":"page"},{"location":"","page":"Home","title":"Home","text":"Tom Kimpson\nAlistair White\nMaximilian Gelbrecht\nDavid Meyer\nDaisuke Hotta\nNavid Constantinou","category":"page"},{"location":"","page":"Home","title":"Home","text":"Any contributions are always welcome!","category":"page"},{"location":"#Funding","page":"Home","title":"Funding","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"MK received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022. Since 2023 this project is also funded by the National Science Foundation NSF.","category":"page"},{"location":"time_integration/#Time-integration","page":"Time integration","title":"Time integration","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"SpeedyWeather.jl uses a leapfrog time scheme with a Robert's and William's filter to dampen the computational mode and achieve 3rd order accuracy.","category":"page"},{"location":"time_integration/#Oscillation-equation","page":"Time integration","title":"Oscillation equation","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"fracdFdt = iomega F","category":"page"},{"location":"time_integration/#Implementation-details","page":"Time integration","title":"Implementation details","text":"","category":"section"}]
+}
diff --git a/previews/PR345/siteinfo.js b/previews/PR345/siteinfo.js
new file mode 100644
index 000000000..c3c85eb9c
--- /dev/null
+++ b/previews/PR345/siteinfo.js
@@ -0,0 +1 @@
+var DOCUMENTER_CURRENT_VERSION = "previews/PR345";
diff --git a/previews/PR345/spectral_transform/index.html b/previews/PR345/spectral_transform/index.html
new file mode 100644
index 000000000..7b06fea06
--- /dev/null
+++ b/previews/PR345/spectral_transform/index.html
@@ -0,0 +1,45 @@
+
+Spherical harmonic transform · SpeedyWeather.jl
The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space on a longitude-latitude regular Gaussian grid.
with $\lambda_l^m$ being the pre-normalized associated Legendre polynomials, and $e^{im\phi}$ are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.
Latitudes versus colatitudes
The implementations of the spherical transforms in SpeedyWeather.jl use colatitudes $\theta = (0,\pi)$ (0 at the north pole) but the dynamical core uses latitudes $\theta = (-\pi/2,\pi/2)$ ($\pi/2$ at the north pole). However, all arrays are always sorted north to south such that [i,1] will access the northern-most grid points. Note: We may also use latitudes in the spherical harmonic transform in the future for consistency.
The synthesis (or inverse transform) takes the spectral coefficients $a_{lm}$ and transforms them to grid-point values $f(\phi,\theta)$ (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics $Y_{lm}$ with non-zero coefficients.
Spectral packing is the way how the coefficients $a_{lm}$ of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree $l$ and order $m$ as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)
Every row represents an order $l \geq 0$, starting from $l=0$ at the top. Every column represents an order $m \geq 0$, starting from $m=0$ on the left. The coefficients of these spherical harmonics are directly mapped into a matrix $a_{lm}$ as
$m$
$l$
$a_{00}$
$a_{10}$
$a_{11}$
$a_{20}$
$a_{12}$
$a_{22}$
$a_{30}$
$a_{13}$
$a_{23}$
$a_{33}$
which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal explicitly stores zeros. Note that internally vector fields include an additional degree, such that $l_{max} = m_{max} + 1$ (see Gradients in spherical coordinates for more information). The harmonics with $a_{l0}$ (the first column) are also called zonal harmonics as they are constant with longitude $\phi$. The harmonics with $a_{ll}$ (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into $2l$ sectors in longitude $\phi$ without a zero-crossing in latitude.
Array indices
For a spectral field alms note that due to Julia's 1-based indexing the coefficient $a_{lm}$ is obtained via alms[l+1,m+1].
Fortran speedy does not use the same spectral packing as SpeedyWeather.jl. The alternative packing $l',m'$ therein uses $l'=m$ and $m'=l-m$ as summarized in the following table.
degree $l$
order $m$
$l'=m$
$m'=l-m$
0
0
0
0
1
0
0
1
1
1
1
0
2
0
0
2
2
1
1
1
2
2
2
0
3
0
0
3
...
...
...
...
This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices
$m'$
$l'$
$a_{00}$
$a_{10}$
$a_{20}$
$a_{30}$
$a_{11}$
$a_{21}$
$a_{31}$
$a_{22}$
$a_{32}$
$a_{33}$
This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran-speedy.
SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with $l \leq l_{max}$ and $|m| \leq m_{max}$ are explicitly represented. This is usually described as $Tm_{max}$, with $l_{max} = m_{max}$ (although in vector quantities require one more degree $l$ in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with $l_{max} = m_{max} = 31$. Note that the degree $l$ and order $m$ are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.
Using triangular truncation[9], there are constraints on the corresponding grid resolution. Let nlon, nlat be the number of longitudes, latitudes on a regular Gaussian grid. Then spectral and grid resolution have to be chosen such that
$nlon \geq 3l_{max}+1$
$nlat \geq (3l_{max}+1)/2$
In general, we choose $nlon = 2nlat$, and ideally $nlon$ is easily Fourier-transformable, e.g. $nlon = 2^i3^j5^k$ with some integers $i,j,k \geq 0$. SpeedyWeather.jl is tested at the following horizontal resolutions, with $\Delta x = \tfrac{2\pi R}{nlon}$ as the approximate grid spacing at the Equator
$l_{max}$
nlon
nlat
$\Delta x$
31 (default)
96
48
400 km
42
128
64
300 km
85
256
128
160 km
170
512
256
80 km
341
1024
512
40 km
682
2048
1024
20 km
Choosing trunc as argument in run_speedy will automatically choose nlon,nlat as presented in the table. Other common choices are T63 (192x96), T127 (384x192), T255 (768x384), T511 (1536x768), among others.
The radius of the sphere (i.e. Earth) is $R$. The zonal gradient scales with $1/\cos(\theta)$ as the longitudes converge towards the poles (note that $\theta$ describes latitudes here, defintions using colatitudes replace the $\cos$ with a $\sin$.)
Starting with a spectral field of vorticity $\zeta$ and divergence $\mathcal{D}$ one can obtain stream function $\Psi$ and velocity potential $\Phi$ by inverting the Laplace operator $\nabla^2$:
The velocities $u,v$ are then obtained from $(u,v) = \nabla^\bot\Psi + \nabla\Phi$ following the defintion from above and $\nabla^\bot = (-R^{-1}\partial_\theta, (R\cos\theta)^{-1}\partial_\lambda)$
Alternatively, we can use the velocities $U = u\cos\theta, V = v\cos\theta$, which we do as the meridional gradient for spherical harmonics is easier implemented with a $\cos\theta$-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with $U,V$ and not $u,v$. From $u,v$ we can return to $\zeta, \mathcal{D}$ via
\[\begin{aligned}
+\zeta &= \frac{1}{R\cos\theta}\partial_\lambda v - \frac{1}{R\cos\theta}\partial_\theta (u \cos\theta) \\
+\mathcal{D} &= \frac{1}{R\cos\theta}\partial_\lambda u + \frac{1}{R\cos\theta}\partial_\theta (v \cos\theta).
+\end{aligned}\]
which is a more convenient formulation as required $\cos\theta$ scalings are reduced to a minimum. The remaining $(U,V)*\cos^{-2}\theta$ are done in grid-point space and usually in combination with other operations like the computation of the vorticity flux. But also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out too. As described in Meridional derivative, it is more convenient to implement $\cos\theta \partial_\theta$ via a recursion relation for the Legendre polynomials than $\partial_\theta$ directly. How the operators $\nabla, \nabla \times, \nabla \cdot$ can be implemented with spherical harmonics is presented in the following sections.
The zonal derivative of a scalar field $\Psi$ in spectral space is the zonal derivative of all its respective spherical harmonics $\Psi_{lm}(\phi,\theta)$ (now we use $\phi$ for longitudes to avoid confusion with the Legendre polynomials $\lambda_{lm}$)
So for every spectral harmonic, $\cos(\theta)v_{lm}$ is obtained from $\Psi_{lm}$ via a multiplication with $im/R$. Unscaling the $\cos(\theta)$-factor is done after transforming the spectral coefficients $v_{lm}$ into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as $\tilde{\Psi} = R^{-1}\Psi$ such that the division by radius $R$ in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order $im$. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number $m$ times imaginary $i$.
The meridioinal derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]
In the following we use the example of obtaining the zonal velocity $u$ from the stream function $\Psi$, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion
To obtain the coefficient of each spherical harmonic $l,m$ of the meridional gradient of a spectral field, two coefficients at $l-1,m$ and $l+1,m$ have to be combined. This means that the coefficient of a gradient $((\cos\theta) u)_{lm}$ is a linear combination of the coefficients of one higher and one lower degree $\Psi_{l+1,m},\Psi_{l-1,m}$. As the coefficient $\Psi_{lm}$ with $m<l$ are zero, the sectoral harmonics ($l=m$) of the gradients are obtained from the first off-diagonal only. However, the $l=l_{max}$ harmonics of the gradients require the $l_{max}-1$ as well as the $l_{max}+1$ harmonics. In SpeedyWeather.jl vector quantitie like $u,v$ use therefore one more meridional mode than scalar quantities such as vorticity $\zeta$ or stream function $\Psi$. The meridional derivative in SpeedyWeather.jl also omits the $1/R$-scaling as explained for the Zonal derivative and in Radius scaling.
The meridional gradient as described above can be applied to scalars, such as $\Psi$ and $\Phi$ in the conversion to velocities $(u,v) = \nabla^\bot\Psi + \nabla\Phi$, however, the operators curl $\nabla \times$ and divergence $\nabla \cdot$ in spherical coordinates involve a $\cos\theta$ scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].
Given that $R\zeta = \cos^{-1}\partial_\lambda v - \cos^{-1}\partial_\theta (u \cos\theta)$, we therefore have to evaluate a meridional integral of the form
Now we expand $(\tfrac{u}{\cos\theta})$ but only the $l,m$ harmonic will project onto$P_{l,m}$. Let $u^* = u\cos^{-1}\theta, v^* = v\cos^{-1}\theta$ we then have in total
And the divergence $D$ is similar, but $(u,v) \to (-v,u)$. We have moved the scaling with the radius $R$ directly into $\zeta,D$ as further described in Radius scaling.
The spectral Laplacian is easily applied to the coefficients $\Psi_{lm}$ of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator $\nabla^2$ in spherical coordinates with eigenvalues $-l(l+1)$ divided by the radius squared $R^2$, i.e. $\nabla^2 \Psi$ becomes $\tfrac{-l(l+1)}{R^2}\Psi_{lm}$ in spectral space. For example, vorticity $\zeta$ and streamfunction $\Psi$ are related by $\zeta = \nabla^2\Psi$ in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree $l$ and order $m$ to
\[\zeta_{l,m} = \frac{-l(l+1)}{R^2}\Psi_{l,m}\]
This can be easily inverted to obtain the stream function $\Psi$ from vorticity $\zeta$ instead. In order to avoid division by zero, we set $\Psi_{0,0}$ here, given that the stream function is only defined up to a constant anyway.
After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity $\zeta$ and divergence $D$ (which are prognostic variables) to $U=u\cos\theta, V=v\cos\theta$. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree $l$ (the meridional gradient). It is therefore computationally more efficient to compute $U,V$ directly from $\zeta,D$ instead of calculating stream function and velocity potential first. In total we have
SpeedyTransforms is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it) and can also be used without running simulations. It is just not put into its own respective repository.
The prognostic variables in spectral space are called
vor # Vorticity of horizontal wind field
+ div # Divergence of horizontal wind field
+ temp # Absolute temperature [K]
+ pres_surf # Logarithm of surface pressure [log(Pa)]
+ humid # Specific humidity [g/kg]
their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are
We follow Julia's style guide and highlight here some important aspects of it.
Bang (!) convention. A function func does not change its input arguments, however, func! does.
Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.
Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup
but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.
Settings
This document was generated with Documenter.jl version 0.27.24 on Wednesday 7 June 2023. Using Julia version 1.8.5.
A mathematical and implementation-specific description of the dynamical core used in SpeedyWeather.jl. We start by describing the barotropic vorticity equations which is one set of equations that SpeedyWeather.jl can solve (see How to run SpeedyWeather.jl) as many details therein also apply to the Shallow water equations and Primitive equations explained thereafter.
The dynamical core presented here largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2], a shallow water model [3] and a primitive equation model[4].
The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity $\zeta$ with advection, Coriolis force and diffusion in a single global layer on the sphere.
with time $t$, velocity vector $\mathbf{u} = (u, v)$, Coriolis parameter $f$, and hyperdiffusion $(-1)^{n+1} \nu \nabla^{2n} \zeta$ ($n$ is the hyperdiffusion order; see Horizontal diffusion). We also include a forcing vector $\mathbf{F} = (F_u,F_v)$ which acts on the zonal velocity $u$ and the meridional velocity $v$ and hence its curl $\nabla \times \mathbf{F}$ is a tendency for relative vorticity $\zeta$.
Starting with some relative vorticity $\zeta$, the Laplacian is inverted to obtain the stream function $\Psi$
\[\Psi = \nabla^{-2}\zeta\]
The zonal velocity $u$ and meridional velocity $v$ are then the (negative) meridional gradient and zonal gradient of $\Psi$
where $\zeta = \hat{\mathbf{z}} \cdot (\nabla \times \mathbf{u})$ is the relative vorticity, $\mathcal{D} = \nabla \cdot \mathbf{u}$ the divergence, and $\eta$ the deviation from the fluid's rest height.
In SpeedyWeather.jl we use hyerdiffusion through an $n$-th power Laplacian $(-1)^{n+1}\nabla^{2n}$ (hyper when $n>1$) which can be implemented as a multiplication of the spectral coefficients $\Psi_{lm}$ with $(-l(l+1))^nR^{-2n}$ (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the $(-l(l+1))^nR^{-2n}$ can be precomputed. Note the sign change $(-1)^{n+1}$ here is such that the dissipative nature of the diffusion operator is retained for $n$ odd and even.
In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step $\Delta t$ of variable $\zeta$ to obtain from time steps $i-1$ and $i$, the next time step $i+1$
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t d\zeta,\]
with $d\zeta$ being some tendency evaluated from $\zeta_i$. Now we want to add a diffusion term $(-1)^{n+1}\nu \nabla^{2n}\zeta$ with viscosity $\nu$, wich however, is implicitly calculated from $\zeta_{i+1}$, then
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t (d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i+1})\]
As the application of $(-1)^{n+1}\nu\nabla^{2n}$ is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to
\[\zeta_{i+1} = \frac{\zeta_{i-1} + 2\Delta t d\zeta}{1 - 2\Delta (-1)^{n+1}\nu\nabla^{2n}},\]
and expand the numerator to
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t \frac{d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t (-1)^{n+1}\nu \nabla^{2n}},\]
Hence the diffusion can be applied implicitly by updating the tendency $d\zeta$ as
\[d\zeta \to \frac{d\zeta + (-1)^{n+1}\nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t \nu \nabla^{2n}}\]
which only depends on $\zeta_{i-1}$. Now let $D_\text{explicit} = (-1)^{n+1}\nu\nabla^{2n}$ be the explicit part and $D_\text{implicit} = 1 - (-1)^{n+1} 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are $D_\text{implicit} = 1 - 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic $l,m$ we do
Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power $n$.
In physics, the Laplace operator $\nabla^2$ is often used to represent diffusion due to viscosity in a fluid. In that case, the viscosity coefficient is $\nu$ of units $\text{m}^2\text{s}^{-1}$ and the full operator reads as $\nu \nabla^2$ with units $(\text{m}^2\text{s}^{-1})(\text{m}^{-2}) = \text{s}^{-1}$. This motivates us to normalize the Laplace operator by a constant of units $\text{m}^{-2}$ and the viscosity coefficient by its inverse such that the viscosity coefficient becomes a damping timescale of unit $\text{s}^{-1}$. Given the application in spectral space we decide to normalize by the largest eigenvalue $-l_\text{max}(l_\text{max}+1)$ such that all entries in the discrete spectral Laplace operator are in $[0,1]$. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized viscosity coefficient $\nu^* = l_\text{max}(l_\text{max}+1)\nu$ (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have
In the barotropic voriticity equation model the inversion of the Laplcians in order to obtain $\Psi$ from $\zeta$ therefore becomes
\[\tilde{\zeta} = \tilde{\nabla}^2 \tilde{\Psi}\]
where the dimensionless gradients simply omit the scaling with $1/R$, $\tilde{\nabla} = R\nabla$. The Barotropic vorticity equation scaled with $R^2$ is
Similar to the scaled barotropic vorticity equations, the scaled shallow water equations scale the vorticity and the divergence equation with $R^2$, but the continuity equation with $R$
This document was generated with Documenter.jl version 0.27.24 on Wednesday 7 June 2023. Using Julia version 1.8.5.
diff --git a/previews/PR349/functions/index.html b/previews/PR349/functions/index.html
new file mode 100644
index 000000000..a14dab726
--- /dev/null
+++ b/previews/PR349/functions/index.html
@@ -0,0 +1,180 @@
+
+Function and type index · SpeedyWeather.jl
Spectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities u,v.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for primitive equation models. Updates grid vorticity, grid divergence, grid temperature, pressure (pres_grid) and the velocities u,v.
Spectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Returns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.
Truncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.
Truncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.
Laplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.
Keyword arguments
add=true adds the ∇²(alms) to the output
flipsign=true computes -∇²(alms) instead
inverse=true computes ∇⁻²(alms) instead
Default is add=false, flipsign=false, inverse=false. These options can be combined.
Divergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.
Curl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.
Get U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.
Get U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.
Construct Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. Pass on SpectralGrid to calculate the following fields
spectral_grid::SpectralGrid: SpectralGrid that defines spectral and grid resolution
Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid of the dynamical core
nlat_half::Int64: resolution parameter nlat_half of Grid, # of latitudes on one hemisphere (incl Equator)
nlon_max::Int64: maximum number of longitudes (at/around Equator)
nlon::Int64: =nlon_max, same (used for compatibility), TODO: still needed?
nlat::Int64: number of latitude rings
nlev::Int64: number of vertical levels
npoints::Int64: total number of grid points
radius::AbstractFloat: Planet's radius [m]
latd::Vector{Float64}: array of latitudes in degrees (90˚...-90˚)
lond::Vector{Float64}: array of longitudes in degrees (0...360˚), empty for non-full grids
londs::Vector{NF} where NF<:AbstractFloat: longitude (-180˚...180˚) for each grid point in ring order
latds::Vector{NF} where NF<:AbstractFloat: latitude (-90˚...˚90) for each grid point in ring order
sinlat::Vector{NF} where NF<:AbstractFloat: sin of latitudes
coslat::Vector{NF} where NF<:AbstractFloat: cos of latitudes
coslat⁻¹::Vector{NF} where NF<:AbstractFloat: = 1/cos(lat)
coslat²::Vector{NF} where NF<:AbstractFloat: = cos²(lat)
coslat⁻²::Vector{NF} where NF<:AbstractFloat: # = 1/cos²(lat)
σ_levels_half::Vector{NF} where NF<:AbstractFloat: σ at half levels, σ_k+1/2
σ_levels_full::Vector{NF} where NF<:AbstractFloat: σ at full levels, σₖ
σ_levels_thick::Vector{NF} where NF<:AbstractFloat: σ level thicknesses, σₖ₊₁ - σₖ
ln_σ_levels_full::Vector{NF} where NF<:AbstractFloat: log of σ at full levels, include surface (σ=1) as last element
Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF.
Performs the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.
The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and but let's start but how they can be used
The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object which defines the resolution in spectral and in grid-point space. The generator SpectralGrid() can take as a keyword argument Grid which can be any of the grids described below. The resolution of the grid, however, is not directly chosen, but determined from the spectral resolution trunc and the dealiasing factor. More in Matching spectral and grid resolution.
RingGrids is a module too!
While RingGrids is the underlying module that SpeedyWeather.jl uses for data structs on the sphere, the module can also be used independently of SpeedyWeather, for example to interpolate between data on different grids. See RingGrids
SpeedyWeather.jl's spectral transform supports all ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on these rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.
All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).
Currently the following full grids <: AbstractFullGrid are implemented
FullGaussianGrid, a full grid with Gaussian latitudes
FullClenshawGrid, a full grid with equi-angle latitudes
and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are
OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron
OctahedralClenshawGrid, similar but based on equi-angle latitudes
HEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces
OctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.
An overview of these grids is visualised here
Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.
All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half.
HEALPix grids do not use Nside as resolution parameter
The original formulation for HEALPix grids use $N_{side}$, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use $N_{side}$ for the documentation or within functions though.
A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation T with a grid resolution N (=nlat_half) such that T + 1 = N. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at $l_{max}=31$ in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid.
Let J be the total number of rings. Then we have
$T \approx J$ for linear truncation
$\frac{3}{2}T \approx J$ for quadratic truncation
$2T \approx J$ for cubic truncation
and in general $\frac{m+1}{2}T \approx J$ for m-th order truncation. So the higher the truncaction order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. In SpeedyWeather.jl the parameter dealiasing controls this option, = 1 would be linear, = 2 quadratic, = 3 cubic etc.
For now just a quick overview of how the grid resolution changes when dealiasing is passed onto SpectralGrid on the FullGaussianGrid
trunc
dealiasing
FullGaussianGrid size
31
1
64x32
31
2
96x48
31
3
128x64
42
1
96x48
42
2
128x64
42
3
192x96
...
...
...
You will obtain this information every time you create a SpectralGrid(;Grid,trunc,dealiasing).
Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are $N_\varphi$ basepixels in zonal direction and $N_\theta$ basepixels in meridional direction. For $N_\varphi = 4$ and $N_\theta = 3$ we obtain the classical HEALPix grid with $N_\varphi N_\theta = 12$ basepixels shown above in Implemented Grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always $2N$, so 32 at $N=16$) and there are polar caps above and below the equatorial zone with the border at $\cos(\theta) = 2/3$ ($\theta$ in colatitudes).
Following Górski, 2004[1], the $z=cos(\theta)$ colatitude of the $j$-th ring in the north polar cap, $j=1,...,N_{side}$ with $2N_{side} = N$ is
\[z = 1 - \frac{j^2}{3N_{side}^2}\]
and on that ring, the longitude $\phi$ of the $i$-th point ($i$ is the in-ring-index) is at
\[\phi = \frac{\pi}{2j}(i-\tfrac{1}{2})\]
The in-ring index $i$ goes from $i=1,...,4$ for the first (i.e. northern-most) ring, $i=1,...,8$ for the second ring and $i = 1,...,4j$ for the $j$-th ring in the northern polar cap.
In the north equatorial belt $j=N_{side},...,2N_{side}$ this changes to
\[z = \frac{4}{3} - \frac{2j}{3N_{side}}\]
and the longitudes change to ($i$ is always $i = 1,...,4N_{side}$ in the equatorial belt meaning the number of longitude points is constant here)
The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.
The cell boundaries are obtained by setting $i = k + 1/2$ or $i = k + 1/2 + j$ (half indices) into the equations above, such that $z(\phi,k)$, a function for the cosine of colatitude $z$ of index $k$ and the longitude $\phi$ is obtained. These are then (northern polar cap)
While the classic HEALPix grid is based on a dodecahedron, other choices for $N_\varphi$ and $N_\theta$ in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With $N_\varphi = 4$ and $N_\theta = 1$ we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, $2\pi$ around the Equator versus $\pi$ between the poles. $N_\varphi = 6, N_\theta = 2$ or $N_\varphi = 8, N_\theta = 3$ are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visulations of these grids.
We call the $N_\varphi = 4, N_\theta = 1$ HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As $N_\theta = 1$ there is no equatorial belt which simplifies the grid. The latitude of the $j$-th isolatitude ring on the OctaHEALPixGrid is defined by
\[z = 1 - \frac{j^2}{N^2},\]
with $j=1,...,N$, and similarly for the southern hemisphere by symmetry. On this grid $N_{side} = N$ where $N$= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index $i = 1,...,4j$ are
\[\phi = \frac{\pi}{2j}(i - \tfrac{1}{2})\]
and again, the southern hemisphere grid points are obtained by symmetry.
The $3N_{side}^2$ in the denominator of the HEALPix grid came simply $N^2$ for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).
[1] Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976
Settings
This document was generated with Documenter.jl version 0.27.24 on Wednesday 7 June 2023. Using Julia version 1.8.5.
diff --git a/previews/PR349/how_to_run_speedy/index.html b/previews/PR349/how_to_run_speedy/index.html
new file mode 100644
index 000000000..ba162cda9
--- /dev/null
+++ b/previews/PR349/how_to_run_speedy/index.html
@@ -0,0 +1,57 @@
+
+How to run SpeedyWeather.jl · SpeedyWeather.jl
We want to use the barotropic model to simulate some free-decaying 2D turbulence on the sphere without rotation. We start by defining the SpectralGrid object. To have a resolution of about 100km, we choose a spectral resolution of T127 (see Available resolutions) and nlev=1 vertical levels. The SpectralGrid object will provide us with some more information
There are other options to create a planet but they are irreleveant for the barotropic vorticity equations. We also want to specify the initial conditions, randomly distributed vorticity is already defined
By default, the power of vorticity is spectrally distributed with $k^{-3}$, $k$ being the horizontal wavenumber, and the amplitude is $10^{-5}\text{ s}^{-1}$.
Now we want to construct a BarotropicModel with these
julia> model = BarotropicModel(;spectral_grid, initial_conditions, planet=still_earth);
The model contains all the parameters, but isn't initialized yet, which we can do with and then run it.
The run! command will always return the prognostic variables, which, by default, are plotted for surface relative vorticity with a unicode plot. The resolution of the plot is not necessarily representative but it lets us have a quick look at the result
Woohoo! I can see turbulence! You could pick up where this simulation stopped by simply doing run!(simulation,n_days=50) again. We didn't store any output, which you can do by run!(simulation,output=true), which will switch on NetCDF output with default settings. More options on output in NetCDF output.
As a second example, let's investigate the Galewsky et al.[1] test case for the shallow water equations with and without mountains. As the shallow water system has also only one level, we can reuse the SpectralGrid from Example 1.
Although the orography is zero, you have to pass on spectral_grid so that it can still initialize zero-arrays of the right size and element type. Awesome. This time the initial conditions should be set the the Galewsky et al.[1] zonal jet, which is already defined as
The jet sits at 45˚N with a maximum velocity of 80m/s and a perturbation as described in their paper. Now we construct a model, but this time a ShallowWaterModel
julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);
+julia> simulation = initialize!(model);
Oh yeah. That looks like the wobbly jet in their paper. Let's run it again for another 6 days but this time also store NetCDF output.
julia> run!(simulation,n_days=6,output=true)
+Weather is speedy: run 0002 100%|███████████████████████| Time: 0:00:12 (115.37 years/day)
The progress bar tells us that the simulation run got the identification "0002", meaning that data is stored in the folder /run_0002, so let's plot that data properly (and not just using UnicodePlots).
julia> using PyPlot, NCDatasets
+julia> ds = NCDataset("run_0002/output.nc");
+julia> ds["vor"]
+vor (384 × 192 × 1 × 25)
+ Datatype: Float32
+ Dimensions: lon × lat × lev × time
+ Attributes:
+ units = 1/s
+ missing_value = NaN
+ long_name = relative vorticity
+ _FillValue = NaN
Vorticity vor is stored as a 384x192x1x25 array, we may want to look at the first time step, which is the end of the previous simulation (time=6days) which we didn't store output for.
julia> vor = ds["vor"][:,:,1,1];
+julia> lat = ds["lat"][:];
+julia> lon = ds["lon"][:];
+julia> pcolormesh(lon,lat,vor')
Which looks like
You see that the unicode plot heavily coarse-grains the simulation, well it's unicode after all! And now the last time step, that means time=12days is
julia> vor = ds["vor"][:,:,1,25];
+julia> pcolormesh(lon,lat,vor')
The jet broke up into many small eddies, but the turbulence is still confined to the northern hemisphere, cool! How this may change when we add mountains (we had NoOrography above!), say Earth's orography, you may ask? Let's try it out! We create an EarthOrography struct like so
It will read the orography from file as shown, and there are some smoothing options too, but let's not change them. Same as before, create a model, intialize into a simulation, run. This time directly for 12 days so that we can compare with the last plot
julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);
+julia> simulation = initialize!(model);
+julia> run!(simulation,n_days=12,output=true)
+Weather is speedy: run 0003 100%|███████████████████████| Time: 0:00:35 (79.16 years/day)
This time the run got the id "0003", but otherwise we do as before.
Interesting! The initial conditions have zero velocity in the southern hemisphere, but still, one can see some imprint of the orography on vorticity. You can spot the coastline of Antarctica; the Andes and Greenland are somewhat visible too. Mountains also completely changed the flow after 12 days, probably not surprising!
[1] Galewsky, Scott, Polvani, 2004. An initial-value problem for testing numerical models of the global shallow-water equations, Tellus A. DOI: 10.3402/tellusa.v56i5.14436
Settings
This document was generated with Documenter.jl version 0.27.24 on Wednesday 7 June 2023. Using Julia version 1.8.5.
Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.
SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to perform some calculations in spectral space (time integration, gradients, linear terms) and some in grid-point space (advection, non-linear terms, parameterizations). The prognostic variables used are vorticity, divergence, absolute temperature, logarithm of surface pressure and specific humidity. The time stepping uses a leapfrog scheme with additional filters and a semi-implicit formulation for gravity waves. The default resolution is T31 (96x48 grid points on a regular Gaussian grid, about 400km at the Equator) and 8 vertical levels.
Simple parameterizations are used to represent the physical processes convection, large-scale condensation, clouds, short-wave radiation, long-waves radiation, surface fluxes of momentum and energy, and vertical diffusion.
MK received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022. Since 2023 this project is also funded by the National Science Foundation NSF.
Settings
This document was generated with Documenter.jl version 0.27.24 on Wednesday 7 June 2023. Using Julia version 1.8.5.
LowerTriangularMatrices is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.
This module defines LowerTriangularMatrix, a lower triangular matrix, which in contrast to LinearAlgebra.LowerTriangular does not store the entries above the diagonal. SpeedyWeather.jl uses LowerTriangularMatrix which is defined as a subtype of AbstractMatrix to store the spherical harmonic coefficients (see Spectral packing).
LowerTriangularMatrix supports two types of indexing: 1) by denoting two indices, column and row [l,m] or 2) by denoting a single index [lm]. The double index works as expected
But the single index skips the zero entries in the upper triangle, i.e.
julia> L[4]
+Float16(0.478)
which, important, is different from single indices of an AbstractMatrix
julia> Matrix(L)[4]
+Float16(0.0)
In performance-critical code a single index should be used, as this directly maps to the index of the underlying data vector. The double index is somewhat slower as it first has to be converted to the corresponding single index.
Consequently, many loops in SpeedyWeather.jl are build with the following structure
n,m = size(L)
+ij = 0
+for j in 1:m
+ for i in j:n
+ ij += 1
+ L[ij] = i+j
+ end
+end
which loops over all lower triangle entries of L::LowerTriangularMatrix and the single index ij is simply counted up. However, one could also use [i,j] as indices in the loop body or to perform any calculation (i+j here). An iterator over all entries in the lower triangle can be created by
for ij in eachindex(L)
+ # do something
+end
The setindex! functionality of matrixes will throw a BoundsError when trying to write into the upper triangle of a LowerTriangularMatrix, for example
julia> L[2,1] = 0 # valid index
+0
+
+julia> L[1,2] = 0 # invalid index in the upper triangle
+ERROR: BoundsError: attempt to access 3×3 LowerTriangularMatrix{Float32} at index [1, 2]
The LowerTriangularMatrices module's main purpose is not linear algebra, and it's implementation may not be efficient, however, many operations work as expected
Note, however, that the latter includes a conversion to Matrix, which is true for many operations, including inv or \. Hence when trying to do more sophisticated linear algebra with LowerTriangularMatrix we quickly leave lower triangular-land and go back to normal matrix-land.
L = LowerTriangularMatrix{T}(v::Vector{T},m::Int,n::Int)
A lower triangular matrix implementation that only stores the non-zero entries explicitly. L<:AbstractMatrix although in general we have L[i] != Matrix(L)[i], the former skips zero entries, tha latter includes them.
k = ij2k( i::Integer, # row index of matrix
+ j::Integer, # column index of matrix
+ m::Integer) # number of rows in matrix
Converts the index pair i,j of an mxn LowerTriangularMatrix L to a single index k that indexes the same element in the corresponding vector that stores only the lower triangle (the non-zero entries) of L.
creates unit_range::UnitRange to loop over all non-zeros in the LowerTriangularMatrices provided as arguments. Checks bounds first. All LowerTriangularMatrix's need to be of the same size. Like eachindex but skips the upper triangle with zeros in L.
SpeedyWeather.jl uses NetCDF to output the data of a simulation. The following describes the details of this and how to change the way in which the NetCDF output is written. There are many options to this available.
The output writer is a component of every Model, i.e. BarotropicModel, ShallowWaterModel, PrimitiveDryModel and PrimitiveWetModel, hence a non-default output writer can be passed on as a keyword argument to the model constructor
So after we have defined the grid through the SpectralGrid object we can use and change the implemented OutputWriter by passing on the following arguments
the spectral_grid has to be the first argument then the model type (Barotropic, ShallowWater, PrimitiveDry, PrimitiveWet) which helps the output writer to make default choices on which variables to output. However, we can also pass on further keyword arguments. So let's start with an example.
which will now output every hour. It is important to pass on the new output writer my_output_writer to the model constructor, otherwise it will not be part of your model and the default is used instead. Note that output_dt has to be understood as the minimum frequency or maximum output time step. Example, we run the model at a resolution of T85 and the time step is going to be 670s
This means that after 32 time steps 5h 57min and 20s will have passed where output will happen as the next time step would be >6h. The time axis of the NetCDF output will look like
This is so that we don't interpolate in time during output to hit exactly every 6 hours, but at the same time have a constant spacing in time between output time steps.
Say we want to run the model at a given horizontal resolution but want to output on another resolution, the OutputWriter takes as argument output_Grid<:AbstractFullGrid and nlat_half::Int. So for example output_Grid=FullClenshawGrid and nlat_half=48 will always interpolate onto a regular 192x95 longitude-latitude grid of 1.875˚ resolution, regardless the grid and resolution used for the model integration.
Note that by default the output is on the corresponding full of the grid used in the dynamical core so that interpolation only happens at most in the zonal direction as they share the location of the latitude rings. You can check this by
So the corresponding full grid of an OctahedralGaussianGrid is the FullGaussiangrid and the same resolution nlat_half is chosen by default in the output writer (which you can change though as shown above). Overview of the corresponding full grids
Grid
Corresponding full grid
FullGaussianGrid
FullGaussianGrid
FullClenshawGrid
FullClenshawGrid
OctahadralGaussianGrid
FullGaussianGrid
OctahedralClensawhGrid
FullClenshawGrid
HEALPixGrid
FullHEALPixGrid
OctaHEALPixGrid
FullOctaHEALPixGrid
The grids FullHEALPixGrid, FullOctaHEALPixGrid share the same latitude rings as their reduced grids, but have always as many longitude points as they are at most around the equator. These grids are not tested in the dynamical core (but you may use them experimentally) and mostly designed for output purposes.
That's easy by passing on path="/my/favourite/path/" and the folder run_* with * the identification of the run (that's the id keyword, which can be manually set but is also automatically determined as a number counting up depending on which folders already exist) will be created within.
which will be used instead of a 4 digit number like 0001, 0002 which is automatically determined if id is not provided. You will see the id of the run in the progress bar
Weather is speedy: run diffusion_test 100%|███████████████████████| Time: 0:00:12 (19.20 years/day)
and the run folder, here run_diffusion_test, is also named accordingly
Further options are described in the OutputWriter docstring, (also accessible via julia>?OutputWriter for example). Note that some fields are actual options, but others are derived from the options you provided or are arrays/objects the output writer needs, but shouldn't be passed on by the user. The actual options are declared as [OPTION] in the following
NetCDF output writer. Contains all output options and auxiliary fields for output interpolation. To be initialised with OutputWriter(::SpectralGrid,::Type{<:ModelSetup},kwargs...) to pass on the resolution information and the model type which chooses which variables to output. Options include
spectral_grid::SpectralGrid
output::Bool
path::String: [OPTION] path to output folder, run_???? will be created within
id::String: [OPTION] run identification number/string
run_path::String
filename::String: [OPTION] name of the output netcdf file
write_restart::Bool: [OPTION] also write restart file if output==true?
pkg_version::VersionNumber
startdate::Dates.DateTime
output_dt::Float64: [OPTION] output frequency, time step [hrs]
output_dt_sec::Int64: actual output time step [sec]
output_vars::Vector{Symbol}: [OPTION] which variables to output, u, v, vor, div, pres, temp, humid
missing_value::Union{Float32, Float64}: [OPTION] missing value to be used in netcdf output
compression_level::Int64: [OPTION] lossless compression level; 1=low but fast, 9=high but slow
keepbits::SpeedyWeather.Keepbits: [OPTION] mantissa bits to keep for every variable
This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmopshere. Every section is followed by a brief description of implementation details.
RingGrids is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.
RingGrids defines several iso-latitude grids, which are mathematically described in the section on Grids. In brief, they include the regular latitude-longitude grids (here called FullClenshawGrid) as well as grids which latitudes are shifted to the Gaussian latitudes and reduced grids, meaning that they have a decreasing number of longitudinal points towards the poles to be more equal-area than full grids.
RingGrids defines and exports the following grids:
full grids: FullClenshawGrid, FullGaussianGrid, FullHEALPix, and FullOctaHEALPix
reduced grids: OctahedralGaussianGrid, OctahedralClenshawGrid, OctaHEALPixGrid and HEALPixGrid
The following explanation of how to use these can be mostly applied to any of them, however, there are certain functions that are not defined, e.g. the full grids can be trivially converted to a Matrix but not the OctahedralGaussianGrid.
What is a ring?
We use the term ring, short for iso-latitude ring, to refer to a sequence of grid points that all share the same latitude. A latitude-longitude grid is a ring grid, as it organises its grid-points into rings. However, other grids, like the cubed-sphere are not based on iso-latitude rings. SpeedyWeather.jl only works with ring grids because its a requirement for the Spherical Harmonic Transform to be efficient. See Grids.
Every grid in RingGrids has a grid.data field, which is a vector containing the data on the grid. The grid points are unravelled west to east then north to south, meaning that it starts at 90˚N and 0˚E then walks eastward for 360˚ before jumping on the next latitude ring further south, this way circling around the sphere till reaching the south pole. This may also be called ring order.
Data in a Matrix which follows this ring order can be put on a FullGaussianGrid like so
using SpeedyWeather.RingGrids
+map = randn(Float32,8,4)
A full Gaussian grid has always $2N$ x $N$ grid points, but a FullClenshawGrid has $2N$ x $N-1$, if those dimensions don't match, the creation will throw an error. To reobtain the data from a grid, you can access its data field which returns a normal Vector
Which can be reshaped to reobtain map from above. Alternatively you can Matrix(grid) to do this in one step
map == Matrix(FullGaussianGrid(map))
true
You can also use zeros,ones,rand,randn to create a grid, whereby nlat_half, i.e. the number of latitude rings on one hemisphere, Equator included, is used as a resolution parameter and here as a second argument.
As only the full grids can be reshaped into a matrix, the underyling data structure of any AbstractGrid is a vector. As shown in the examples above, one can therefore inspect the data as if it was a vector. But as that data has, through its <:AbstractGrid type, all the geometric information available to plot it on a map, RingGrids also exports plot function, based on UnicodePlots' heatmap.
All RingGrids have a single index ij which follows the ring order. While this is obviously not super exciting here are some examples how to make better use of the information that the data sits on a grid.
We obtain the latitudes of the rings of a grid by calling get_latd (get_lond is only defined for full grids, or use get_latdlonds for latitudes, longitudes per grid point not per ring)
Now we could calculate Coriolis and add it on the grid as follows
rotation = 7.29e-5 # angular frequency of Earth's rotation [rad/s]
+coriolis = 2rotation*sind.(latd) # vector of coriolis parameters per latitude ring
+
+rings = eachring(grid)
+for (j,ring) in enumerate(rings)
+ f = coriolis[j]
+ for ij in ring
+ grid[ij] += f
+ end
+end
eachring creates a vector of UnitRange indices, such that we can loop over the ring index j (j=1 being closest to the North pole) pull the coriolis parameter at that latitude and then loop over all in-ring indices i (changing longitudes) to do something on the grid. Something similar can be done to scale/unscale with the cosine of latitude for example. We can always loop over all grid-points like so
In most cases we will want to use RingGrids so that our data directly comes with the geometric information of where the grid-point is one the sphere. We have seen how to use get_latd, get_lond, ... for that above. This information generally can also be used to interpolate our data from grid to another or to request an interpolated value on some coordinates. Using our data on grid which is an OctahedralGaussianGrid from above we can use the interpolate function to get it onto a FullGaussianGrid (or any other grid for purpose)
By default this will linearly interpolate (it's an Anvil interpolator, see below) onto a grid with the same nlat_half, but we can also coarse-grain or fine-grain by specifying nlat_half directly as 2nd argument
So we got from an 8-ring OctahedralGaussianGrid{Float16} to a 12-ring FullGaussianGrid{Float64}, so it did a conversion from Float16 to Float64 on the fly too, because the default precision is Float64 unless specified. interpolate(FullGaussianGrid{Float16},6,grid) would have interpolated onto a grid with element type Float16.
One can also interpolate onto a give cordinate ˚N, ˚E like so
interpolate(30.0,10.0,grid)
0.0852909f0
we interpolated the data from grid onto 30˚N, 10˚E. To do this simultaneously for many coordinates they can be packed into a vector too
Every time an interpolation like interpolate(30.0,10.0,grid) is called, several things happen, which are important to understand to know how to get the fastest interpolation out of this module in a given situation. Under the hood an interpolation takes three arguments
output vector
input grid
interpolator
The output vector is just an array into which the interpolated data is written, providing this prevents unnecessary allocation of memory in case the destination array of the interpolation already exists. The input grid contains the data which is subject to interpolation, it must come on a ring grid, however, its coordinate information is actually already in the interpolator. The interpolator knows about the geometry of the grid the data is coming on and the coordinates it is supposed to interpolate onto. It has therefore precalculated the indices that are needed to access the right data on the input grid and the weights it needs to apply in the actual interplation operation. The only thing it does not know is the actual data values of that grid. So in the case you want to interpolate from grid A to grid B many times, you can just reuse the same interpolator. If you want to change the coordinates of the output grid but its total number of points remain constants then you can update the locator inside the interpolator and only else you will need to create a new interpolator. Let's look at this in practice. Say we have two grids an want to interpolate between them
Now we have created an interpolator interp which knows about the geometry where to interpolate from and the coordinates there to interpolate to. It is also initialized, meaning it has precomputed the indices to of grid_in that are supposed to be used. It just does not know about the data of grid_in (and neither of grid_out which will be overwritten anyway). We can now do
which is identical to interpolate(grid_out,grid_in) but you can reuse interp for other data. The interpolation can also handle various element types (the interpolator interp does not have to be updated for this either)
and we have converted data from a HEALPixGrid{Float64} (Float64 is always default if not specified) to a FullClenshawGrid{Float16} including the type conversion Float64-Float16 on the fly. Technically there are three data types and their combinations possible: The input data will come with a type, the output array has an element type and the interpolator has precomputed weights with a given type. Say we want to go from Float16 data on an OctahedralGaussianGrid to Float16 on a FullClenshawGrid but using Float32 precision for the interpolation itself, we would do this by
As a last example we want to illustrate a situation where we would always want to interplate onto 10 coordinates, but their locations may change. In order to avoid recreating an interpolator object we would do (AnvilInterpolator is described in Anvil interpolator)
npoints = 10 # number of coordinates to interpolate onto
+interp = AnvilInterpolator(Float32,HEALPixGrid,24,npoints)
with the first argument being the number format used during interpolation, then the input grid type, its resolution in terms of nlat_half and then the number of points to interpolate onto. However, interp is not yet initialized as it does not know about the destination coordinates yet. Let's define them, but note that we already decided there's only 10 of them above.
but allows for a reuse of the interpolator. Note that the two output arrays are not exactly identical because we manually set our interpolator interp to use Float32 for the interplation whereas the default is Float64.
Currently the only interpolator implemented is a 4-point bilinear interpolator, which schematically works as follows. Anvil interpolation is the bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate.
0..............1 # fraction of distance Δab between a,b
+ |< Δab >|
+
+0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x
+.Δy |
+. |
+.v x
+. |
+1 c ------ o ---- d
+
+ |< Δcd >|
+ 0...............1 # fraction of distance Δcd between c,d
+
+^ fraction of distance Δy between a-b and c-d.
This interpolation is chosen as by definiton of the ring grids, a and b share the same latitude, so do c and d, but the longitudes can be different for all four, a,b,c,d.
Returns a vector nlons for the number of longitude points per latitude ring, north to south. Provide grid Grid and its resolution parameter nlat_half. For both_hemisphere==false only the northern hemisphere (incl Equator) is returned.
This document was generated with Documenter.jl version 0.27.24 on Wednesday 7 June 2023. Using Julia version 1.8.5.
diff --git a/previews/PR349/search_index.js b/previews/PR349/search_index.js
new file mode 100644
index 000000000..151b423f4
--- /dev/null
+++ b/previews/PR349/search_index.js
@@ -0,0 +1,3 @@
+var documenterSearchIndex = {"docs":
+[{"location":"development/#Development-notes","page":"Development notes","title":"Development notes","text":"","category":"section"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To run tests, from the path of your local clone of the repository do:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=. -e 'import Pkg; Pkg.test()'","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To install dependencies:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project -e 'import Pkg; Pkg.instantiate()`","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"then opening:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"you are able to using SpeedyWeather.","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To generate the docs:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=docs -e 'import Pkg; Pkg.develop(path=\".\"); Pkg.instantiate()'\njulia --project=docs docs/make.jl","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"If the docs are generated successfully, you view them by opening docs/build/index.html in your favorite browser.","category":"page"},{"location":"installation/#Installation","page":"Installation","title":"Installation","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"SpeedyWeather.jl is registered in the Julia Registry. In most cases just open the Julia REPL and type","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia> using Pkg\njulia> Pkg.add(\"SpeedyWeather\")","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"which will automatically install the latest release and all necessary dependencies. If you run into any troubles please raise an issue","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"However, you may want to make use of the latest features, then install directly from the main branch with","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia> Pkg.add(url=\"https://github.com/SpeedyWeather/SpeedyWeather.jl\",rev=\"main\")","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"other branches than main can be similarly installed.","category":"page"},{"location":"installation/#Compatibility-with-Julia-versions","page":"Installation","title":"Compatibility with Julia versions","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"SpeedyWeather.jl usually lives on the latest minor release and/or its predecessor. At the moment (May 2023) this means ","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"Julia v1.8\nJulia v1.9","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"are supported, but we dropped the support of earlier versions.","category":"page"},{"location":"output/#NetCDF-output","page":"NetCDF output","title":"NetCDF output","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"SpeedyWeather.jl uses NetCDF to output the data of a simulation. The following describes the details of this and how to change the way in which the NetCDF output is written. There are many options to this available.","category":"page"},{"location":"output/#Accessing-the-NetCDF-output-writer","page":"NetCDF output","title":"Accessing the NetCDF output writer","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"The output writer is a component of every Model, i.e. BarotropicModel, ShallowWaterModel, PrimitiveDryModel and PrimitiveWetModel, hence a non-default output writer can be passed on as a keyword argument to the model constructor","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> spectral_grid = SpectralGrid()\njulia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry)\njulia> model = PrimitiveDryModel(;spectral_grid, output=my_output_writer)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"So after we have defined the grid through the SpectralGrid object we can use and change the implemented OutputWriter by passing on the following arguments","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, kwargs...)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"the spectral_grid has to be the first argument then the model type (Barotropic, ShallowWater, PrimitiveDry, PrimitiveWet) which helps the output writer to make default choices on which variables to output. However, we can also pass on further keyword arguments. So let's start with an example.","category":"page"},{"location":"output/#Example-1:-NetCDF-output-every-hour","page":"NetCDF output","title":"Example 1: NetCDF output every hour","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"If we want to increase the frequency of the output we can choose output_dt (default =6 in hours) like so","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, output_dt=1)\njulia> model = PrimitiveDryModel(;spectral_grid, output=my_output_writer)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"which will now output every hour. It is important to pass on the new output writer my_output_writer to the model constructor, otherwise it will not be part of your model and the default is used instead. Note that output_dt has to be understood as the minimum frequency or maximum output time step. Example, we run the model at a resolution of T85 and the time step is going to be 670s","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> spectral_grid = SpectralGrid(trunc=85)\njulia> time_stepper = Leapfrog(spectral_grid)\nLeapfrog{Float32}:\n...\n Δt_sec::Int64 = 670\n...","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This means that after 32 time steps 5h 57min and 20s will have passed where output will happen as the next time step would be >6h. The time axis of the NetCDF output will look like","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> using NCDatasets\njulia> ds = NCDataset(\"run_0001/output.nc\");\njulia> ds[\"time\"][:]\n5-element Vector{Dates.DateTime}:\n 2000-01-01T00:00:00\n 2000-01-01T05:57:20\n 2000-01-01T11:54:40\n 2000-01-01T17:52:00\n 2000-01-01T23:49:20\n\njulia> diff(ds[\"time\"][:])\n4-element Vector{Dates.Millisecond}:\n 21440000 milliseconds\n 21440000 milliseconds\n 21440000 milliseconds\n 21440000 milliseconds","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This is so that we don't interpolate in time during output to hit exactly every 6 hours, but at the same time have a constant spacing in time between output time steps.","category":"page"},{"location":"output/#Example-2:-Output-onto-a-higher/lower-resolution-grid","page":"NetCDF output","title":"Example 2: Output onto a higher/lower resolution grid","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Say we want to run the model at a given horizontal resolution but want to output on another resolution, the OutputWriter takes as argument output_Grid<:AbstractFullGrid and nlat_half::Int. So for example output_Grid=FullClenshawGrid and nlat_half=48 will always interpolate onto a regular 192x95 longitude-latitude grid of 1.875˚ resolution, regardless the grid and resolution used for the model integration.","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, output_Grid=FullClenshawGrid, nlat_half=48)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Note that by default the output is on the corresponding full of the grid used in the dynamical core so that interpolation only happens at most in the zonal direction as they share the location of the latitude rings. You can check this by","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> RingGrids.full_grid(OctahedralGaussianGrid)\nFullGaussianGrid","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"So the corresponding full grid of an OctahedralGaussianGrid is the FullGaussiangrid and the same resolution nlat_half is chosen by default in the output writer (which you can change though as shown above). Overview of the corresponding full grids","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Grid Corresponding full grid\nFullGaussianGrid FullGaussianGrid\nFullClenshawGrid FullClenshawGrid\nOctahadralGaussianGrid FullGaussianGrid\nOctahedralClensawhGrid FullClenshawGrid\nHEALPixGrid FullHEALPixGrid\nOctaHEALPixGrid FullOctaHEALPixGrid","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"The grids FullHEALPixGrid, FullOctaHEALPixGrid share the same latitude rings as their reduced grids, but have always as many longitude points as they are at most around the equator. These grids are not tested in the dynamical core (but you may use them experimentally) and mostly designed for output purposes.","category":"page"},{"location":"output/#Example-3:-Changing-the-output-path-or-identification","page":"NetCDF output","title":"Example 3: Changing the output path or identification","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"That's easy by passing on path=\"/my/favourite/path/\" and the folder run_* with * the identification of the run (that's the id keyword, which can be manually set but is also automatically determined as a number counting up depending on which folders already exist) will be created within.","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> path = pwd()\n\"/Users/milan\"\njulia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, path=path)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This folder must already exist. If you want to give your run a name/identification you can pass on id","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid,PrimitiveDry,id=\"diffusion_test\");","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"which will be used instead of a 4 digit number like 0001, 0002 which is automatically determined if id is not provided. You will see the id of the run in the progress bar","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Weather is speedy: run diffusion_test 100%|███████████████████████| Time: 0:00:12 (19.20 years/day)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"and the run folder, here run_diffusion_test, is also named accordingly","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"shell> ls\n...\nrun_diffusion_test\n...","category":"page"},{"location":"output/#Further-options","page":"NetCDF output","title":"Further options","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Further options are described in the OutputWriter docstring, (also accessible via julia>?OutputWriter for example). Note that some fields are actual options, but others are derived from the options you provided or are arrays/objects the output writer needs, but shouldn't be passed on by the user. The actual options are declared as [OPTION] in the following","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"OutputWriter","category":"page"},{"location":"output/#SpeedyWeather.OutputWriter","page":"NetCDF output","title":"SpeedyWeather.OutputWriter","text":"NetCDF output writer. Contains all output options and auxiliary fields for output interpolation. To be initialised with OutputWriter(::SpectralGrid,::Type{<:ModelSetup},kwargs...) to pass on the resolution information and the model type which chooses which variables to output. Options include\n\nspectral_grid::SpectralGrid\noutput::Bool\npath::String: [OPTION] path to output folder, run_???? will be created within\nid::String: [OPTION] run identification number/string\nrun_path::String\nfilename::String: [OPTION] name of the output netcdf file\nwrite_restart::Bool: [OPTION] also write restart file if output==true?\npkg_version::VersionNumber\nstartdate::Dates.DateTime\noutput_dt::Float64: [OPTION] output frequency, time step [hrs]\noutput_dt_sec::Int64: actual output time step [sec]\noutput_vars::Vector{Symbol}: [OPTION] which variables to output, u, v, vor, div, pres, temp, humid\nmissing_value::Union{Float32, Float64}: [OPTION] missing value to be used in netcdf output\ncompression_level::Int64: [OPTION] lossless compression level; 1=low but fast, 9=high but slow\nkeepbits::SpeedyWeather.Keepbits: [OPTION] mantissa bits to keep for every variable\noutput_every_n_steps::Int64\ntimestep_counter::Int64\noutput_counter::Int64\nnetcdf_file::Union{Nothing, NetCDF.NcFile}\ninput_Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}\nas_matrix::Bool: [OPTION] sort grid points into a matrix (interpolation-free), for OctahedralClenshawGrid, OctaHEALPixGrid only\nquadrant_rotation::NTuple{4, Int64}\nmatrix_quadrant::NTuple{4, Tuple{Int64, Int64}}\noutput_Grid::Type{<:SpeedyWeather.RingGrids.AbstractFullGrid}: [OPTION] the grid used for output, full grids only\nnlat_half::Int64: [OPTION] the resolution of the output grid, default: same nlat_half as in the dynamical core\nnlon::Int64\nnlat::Int64\nnpoints::Int64\nnlev::Int64\ninterpolator::SpeedyWeather.RingGrids.AbstractInterpolator\nu::Matrix{NF} where NF<:Union{Float32, Float64}\nv::Matrix{NF} where NF<:Union{Float32, Float64}\nvor::Matrix{NF} where NF<:Union{Float32, Float64}\ndiv::Matrix{NF} where NF<:Union{Float32, Float64}\ntemp::Matrix{NF} where NF<:Union{Float32, Float64}\npres::Matrix{NF} where NF<:Union{Float32, Float64}\nhumid::Matrix{NF} where NF<:Union{Float32, Float64}\nprecip_cond::Matrix{NF} where NF<:Union{Float32, Float64}\nprecip_conv::Matrix{NF} where NF<:Union{Float32, Float64}\n\n\n\n\n\n","category":"type"},{"location":"functions/#Function-and-type-index","page":"Function and type index","title":"Function and type index","text":"","category":"section"},{"location":"functions/#Parameters-and-constants","page":"Function and type index","title":"Parameters and constants","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Parameters\nSpeedyWeather.Constants","category":"page"},{"location":"functions/#Boundaries-and-boundary-conditions","page":"Function and type index","title":"Boundaries and boundary conditions","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Boundaries","category":"page"},{"location":"functions/#Spherical-harmonic-transform","page":"Function and type index","title":"Spherical harmonic transform","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.GeoSpectral\nSpeedyWeather.SpectralTransform\nSpeedyWeather.spectral\nSpeedyWeather.spectral!\nSpeedyWeather.gridded\nSpeedyWeather.gridded!\nSpeedyWeather.triangular_truncation\nSpeedyWeather.roundup_fft\nSpeedyWeather.spectral_truncation\nSpeedyWeather.spectral_truncation!\nSpeedyWeather.spectral_interpolation!\nSpeedyWeather.get_legendre_polynomials!\nSpeedyWeather.∇²!\nSpeedyWeather.∇²\nSpeedyWeather.∇⁻²!\nSpeedyWeather.∇⁻²\nSpeedyWeather.gradient_latitude!\nSpeedyWeather.gradient_latitude\nSpeedyWeather.gradient_longitude!\nSpeedyWeather.gradient_longitude\nSpeedyWeather.divergence!\nSpeedyWeather.curl!\nSpeedyWeather._divergence!\nSpeedyWeather.curl_div!\nSpeedyWeather.UV_from_vordiv!\nSpeedyWeather.UV_from_vor!\nSpeedyWeather.ϵlm\nSpeedyWeather.get_recursion_factors","category":"page"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.SpectralTransform","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"S = SpectralTransform{NF<:AbstractFloat}(...)\n\nSpectralTransform struct that contains all parameters and preallocated arrays for the spectral transform.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral","text":"alms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\nalms = spectral( map::AbstractGrid;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to Grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\nalms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nSpectral transform (grid to spectral) map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral!","text":"spectral!( alms::LowerTriangularMatrix,\n map::AbstractGrid,\n S::SpectralTransform)\n\nSpectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded","text":"map = gridded( alms::AbstractMatrix;\n recompute_legendre::Bool=true,\n grid::Type{<:AbstractGrid}=DEFAULT_GRID)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).\n\n\n\n\n\nmap = gridded( alms::AbstractMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!(\n diagn::DiagnosticVariables,\n progn::PrognosticVariables,\n lf::Int64,\n model::SpeedyWeather.ModelSetup\n)\n\n\nPropagate the spectral state of progn to diagn using time step/leapfrog index lf. Function barrier that calls gridded! for the respective model.\n\n\n\n\n\ngridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::Barotropic\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.\n\n\n\n\n\ngridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::ShallowWater\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities u,v.\n\n\n\n\n\ngridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::PrimitiveEquation\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for primitive equation models. Updates grid vorticity, grid divergence, grid temperature, pressure (pres_grid) and the velocities u,v.\n\n\n\n\n\ngridded!( map::AbstractGrid,\n alms::LowerTriangularMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral_truncation","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation","text":"alms_trunc = spectral_truncation(alms,trunc)\n\nReturns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral_truncation!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms::AbstractMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.\n\n\n\n\n\nspectral_truncation!(alms::LowerTriangularMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.\n\n\n\n\n\nspectral_truncation!(alms,trunc)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.\n\n\n\n\n\nspectral_truncation!(alms)\n\nTruncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.∇²!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.∇²!","text":"∇²!( ∇²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false,\n inverse::Bool=false)\n\nLaplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.\n\nKeyword arguments\n\nadd=true adds the ∇²(alms) to the output\nflipsign=true computes -∇²(alms) instead\ninverse=true computes ∇⁻²(alms) instead\n\nDefault is add=false, flipsign=false, inverse=false. These options can be combined.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.∇⁻²!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.∇⁻²!","text":"∇⁻²!( ∇⁻²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false)\n\nCalls ∇²!(∇⁻²alms, alms, S; add, flipsign, inverse=true).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.divergence!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.divergence!","text":"divergence!(div::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform{NF};\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nDivergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.curl!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.curl!","text":"curl!( curl::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform;\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nCurl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.UV_from_vordiv!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.UV_from_vordiv!","text":"UV_from_vordiv!(U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n div::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.UV_from_vor!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.UV_from_vor!","text":"UV_from_vor!( U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"function"},{"location":"functions/#Dynamics","page":"Function and type index","title":"Dynamics","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.bernoulli_potential!\nSpeedyWeather.volume_flux_divergence!\nSpeedyWeather.vorticity_fluxes!\nSpeedyWeather.vorticity_flux_curl!\nSpeedyWeather.vorticity_flux_divergence!","category":"page"},{"location":"functions/#SpeedyWeather.bernoulli_potential!","page":"Function and type index","title":"SpeedyWeather.bernoulli_potential!","text":"bernoulli_potential!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},\n S::SpectralTransform\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nComputes the Laplace operator ∇² of the Bernoulli potential B in spectral space.\n\ncomputes the kinetic energy KE = ½(u²+v²) on the grid\ntransforms KE to spectral space\nadds geopotential for the Bernoulli potential in spectral space\ntakes the Laplace operator.\n\nThis version is used for both ShallowWater and PrimitiveEquation, only the geopotential calculation in geopotential! differs.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.volume_flux_divergence!","page":"Function and type index","title":"SpeedyWeather.volume_flux_divergence!","text":"volume_flux_divergence!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surface::SpeedyWeather.SurfaceVariables,\n orog::SpeedyWeather.AbstractOrography,\n constants::DynamicsConstants,\n G::Geometry,\n S::SpectralTransform\n)\n\n\nComputes the (negative) divergence of the volume fluxes uh,vh for the continuity equation, -∇⋅(uh,vh).\n\n\n\n\n\n","category":"function"},{"location":"functions/#Geometry","page":"Function and type index","title":"Geometry","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Geometry\nSpeedyWeather.vertical_coordinates\nSpeedyWeather.GenLogisticCoefs\nSpeedyWeather.generalised_logistic","category":"page"},{"location":"functions/#SpeedyWeather.Geometry","page":"Function and type index","title":"SpeedyWeather.Geometry","text":"Construct Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. Pass on SpectralGrid to calculate the following fields\n\nspectral_grid::SpectralGrid: SpectralGrid that defines spectral and grid resolution\nGrid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid of the dynamical core\nnlat_half::Int64: resolution parameter nlat_half of Grid, # of latitudes on one hemisphere (incl Equator)\nnlon_max::Int64: maximum number of longitudes (at/around Equator)\nnlon::Int64: =nlon_max, same (used for compatibility), TODO: still needed?\nnlat::Int64: number of latitude rings\nnlev::Int64: number of vertical levels\nnpoints::Int64: total number of grid points\nradius::AbstractFloat: Planet's radius [m]\nlatd::Vector{Float64}: array of latitudes in degrees (90˚...-90˚)\nlond::Vector{Float64}: array of longitudes in degrees (0...360˚), empty for non-full grids\nlonds::Vector{NF} where NF<:AbstractFloat: longitude (-180˚...180˚) for each grid point in ring order\nlatds::Vector{NF} where NF<:AbstractFloat: latitude (-90˚...˚90) for each grid point in ring order\nsinlat::Vector{NF} where NF<:AbstractFloat: sin of latitudes\ncoslat::Vector{NF} where NF<:AbstractFloat: cos of latitudes\ncoslat⁻¹::Vector{NF} where NF<:AbstractFloat: = 1/cos(lat)\ncoslat²::Vector{NF} where NF<:AbstractFloat: = cos²(lat)\ncoslat⁻²::Vector{NF} where NF<:AbstractFloat: # = 1/cos²(lat)\nσ_levels_half::Vector{NF} where NF<:AbstractFloat: σ at half levels, σ_k+1/2\nσ_levels_full::Vector{NF} where NF<:AbstractFloat: σ at full levels, σₖ\nσ_levels_thick::Vector{NF} where NF<:AbstractFloat: σ level thicknesses, σₖ₊₁ - σₖ\nln_σ_levels_full::Vector{NF} where NF<:AbstractFloat: log of σ at full levels, include surface (σ=1) as last element\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.GenLogisticCoefs","page":"Function and type index","title":"SpeedyWeather.GenLogisticCoefs","text":"Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF.\n\nFollowing the notation of https://en.wikipedia.org/wiki/Generalisedlogisticfunction (Dec 15 2021).\n\nChange default parameters for more/fewer levels in the stratosphere vs troposphere vs boundary layer.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.generalised_logistic","page":"Function and type index","title":"SpeedyWeather.generalised_logistic","text":"Generalised logistic function based on the coefficients in coefs.\n\n\n\n\n\n","category":"function"},{"location":"functions/#Time-stepping","page":"Function and type index","title":"Time stepping","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.time_stepping!\nSpeedyWeather.timestep!\nSpeedyWeather.first_timesteps!\nSpeedyWeather.leapfrog!","category":"page"},{"location":"functions/#SpeedyWeather.time_stepping!","page":"Function and type index","title":"SpeedyWeather.time_stepping!","text":"time_stepping!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n model::SpeedyWeather.ModelSetup\n) -> PrognosticVariables\n\n\nMain time loop that that initializes output and feedback, loops over all time steps and calls the output and feedback functions.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.timestep!","page":"Function and type index","title":"SpeedyWeather.timestep!","text":"timestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::Barotropic\n)\ntimestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::Barotropic,\n lf1::Int64\n)\ntimestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::Barotropic,\n lf1::Int64,\n lf2::Int64\n)\n\n\nCalculate a single time step for the model <: Barotropic.\n\n\n\n\n\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::ShallowWater\n) -> SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::ShallowWater,\n lf1::Int64\n) -> SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::ShallowWater,\n lf1::Int64,\n lf2::Int64\n) -> SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat\n\n\nCalculate a single time step for the model <: ShallowWater.\n\n\n\n\n\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::PrimitiveEquation\n) -> Any\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::PrimitiveEquation,\n lf1::Int64\n) -> Any\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n time::Dates.DateTime,\n dt::Real,\n i::Integer,\n model::PrimitiveEquation,\n lf1::Int64,\n lf2::Int64\n) -> Any\n\n\nCalculate a single time step for the model<:PrimitiveEquation\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.first_timesteps!","page":"Function and type index","title":"SpeedyWeather.first_timesteps!","text":"first_timesteps!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n clock::SpeedyWeather.Clock,\n model::SpeedyWeather.ModelSetup,\n output::SpeedyWeather.AbstractOutputWriter\n) -> typeof(time)\n\n\nPerforms the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.leapfrog!","page":"Function and type index","title":"SpeedyWeather.leapfrog!","text":"leapfrog!(\n A_old::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n A_new::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n tendency::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n dt::Real,\n lf::Int64,\n L::Leapfrog{NF<:AbstractFloat}\n)\n\n\nPerforms one leapfrog time step with (lf=2) or without (lf=1) Robert+William's filter (see William (2009), Montly Weather Review, Eq. 7-9).\n\n\n\n\n\n","category":"function"},{"location":"functions/#Longwave-radiation","page":"Function and type index","title":"Longwave radiation","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.radset!\nSpeedyWeather.radlw_down!\nSpeedyWeather.compute_bbe!\nSpeedyWeather.radlw_up!","category":"page"},{"location":"functions/#Shortwave-radiation","page":"Function and type index","title":"Shortwave radiation","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.shortwave_radiation!\nSpeedyWeather.solar!\nSpeedyWeather.sol_oz!\nSpeedyWeather.cloud!\nSpeedyWeather.radsw!","category":"page"},{"location":"parametrizations/#Parameterizations","page":"Parameterizations","title":"Parameterizations","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmopshere. Every section is followed by a brief description of implementation details.","category":"page"},{"location":"parametrizations/#Convection","page":"Parameterizations","title":"Convection","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Large-scale-condensation","page":"Parameterizations","title":"Large-scale condensation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Clouds","page":"Parameterizations","title":"Clouds","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Short-wave-radiation","page":"Parameterizations","title":"Short-wave radiation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Long-wave-radiation","page":"Parameterizations","title":"Long-wave radiation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Surface-fluxes-of-momentum-and-energy","page":"Parameterizations","title":"Surface fluxes of momentum and energy","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Vertical-diffusion","page":"Parameterizations","title":"Vertical diffusion","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"how_to_run_speedy/#How-to-run-SpeedyWeather.jl","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Creating a SpeedyWeather.jl simulation and running it consists conceptually of 4 steps","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Create a SpectralGrid which defines the grid and spectral resolution\nCreate a model\nInitialize a model to obtain a Simulation.\nRun the simulation.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"In the following we will describe these steps in more detail, but let's start with some examples first.","category":"page"},{"location":"how_to_run_speedy/#Example-1:-2D-turbulence-on-a-non-rotating-sphere","page":"How to run SpeedyWeather.jl","title":"Example 1: 2D turbulence on a non-rotating sphere","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"We want to use the barotropic model to simulate some free-decaying 2D turbulence on the sphere without rotation. We start by defining the SpectralGrid object. To have a resolution of about 100km, we choose a spectral resolution of T127 (see Available resolutions) and nlev=1 vertical levels. The SpectralGrid object will provide us with some more information","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> spectral_grid = SpectralGrid(trunc=127,nlev=1)\nSpectralGrid:\n Spectral: T127 LowerTriangularMatrix{Complex{Float32}}, radius = 6.371e6 m\n Grid: 40320-element, 192-ring OctahedralGaussianGrid{Float32} (quadratic)\n Resolution: 112km (average)\n Vertical: 1-level SigmaCoordinates","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"We could have specified further options, but let's ignore that for now. Next step we create a planet that's like Earth but not rotating","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> still_earth = Earth(rotation=0)\nMain.SpeedyWeather.Earth\n rotation: Float64 0.0\n gravity: Float64 9.81\n daily_cycle: Bool true\n length_of_day: Float64 24.0\n seasonal_cycle: Bool true\n length_of_year: Float64 365.25\n equinox: Dates.DateTime\n axial_tilt: Float64 23.4","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"There are other options to create a planet but they are irreleveant for the barotropic vorticity equations. We also want to specify the initial conditions, randomly distributed vorticity is already defined","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> initial_conditions = StartWithRandomVorticity()\nStartWithRandomVorticity\n power_law: Float64 -3.0\n amplitude: Float64 1.0e-5","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"By default, the power of vorticity is spectrally distributed with k^-3, k being the horizontal wavenumber, and the amplitude is 10^-5text s^-1.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Now we want to construct a BarotropicModel with these","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> model = BarotropicModel(;spectral_grid, initial_conditions, planet=still_earth);","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The model contains all the parameters, but isn't initialized yet, which we can do with and then run it.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> simulation = initialize!(model);\njulia> run!(simulation,n_days=30)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The run! command will always return the prognostic variables, which, by default, are plotted for surface relative vorticity with a unicode plot. The resolution of the plot is not necessarily representative but it lets us have a quick look at the result","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Barotropic vorticity unicode plot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Woohoo! I can see turbulence! You could pick up where this simulation stopped by simply doing run!(simulation,n_days=50) again. We didn't store any output, which you can do by run!(simulation,output=true), which will switch on NetCDF output with default settings. More options on output in NetCDF output.","category":"page"},{"location":"how_to_run_speedy/#Example-2:-Shallow-water-with-mountains","page":"How to run SpeedyWeather.jl","title":"Example 2: Shallow water with mountains","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"As a second example, let's investigate the Galewsky et al.[1] test case for the shallow water equations with and without mountains. As the shallow water system has also only one level, we can reuse the SpectralGrid from Example 1.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> spectral_grid = SpectralGrid(trunc=127,nlev=1)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Now as a first simulation, we want to disable any orography, so we create a NoOrography","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> orography = NoOrography(spectral_grid)\nNoOrography{Float32, OctahedralGaussianGrid{Float32}}","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Although the orography is zero, you have to pass on spectral_grid so that it can still initialize zero-arrays of the right size and element type. Awesome. This time the initial conditions should be set the the Galewsky et al.[1] zonal jet, which is already defined as","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> initial_conditions = ZonalJet()\nZonalJet\n latitude: Float64 45.0\n width: Float64 19.28571428571429\n umax: Float64 80.0\n perturb_lat: Float64 45.0\n perturb_lon: Float64 270.0\n perturb_xwidth: Float64 19.098593171027442\n perturb_ywidth: Float64 3.819718634205488\n perturb_height: Float64 120.0","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The jet sits at 45˚N with a maximum velocity of 80m/s and a perturbation as described in their paper. Now we construct a model, but this time a ShallowWaterModel","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);\njulia> simulation = initialize!(model);","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet unicode plot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Oh yeah. That looks like the wobbly jet in their paper. Let's run it again for another 6 days but this time also store NetCDF output.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> run!(simulation,n_days=6,output=true)\nWeather is speedy: run 0002 100%|███████████████████████| Time: 0:00:12 (115.37 years/day)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The progress bar tells us that the simulation run got the identification \"0002\", meaning that data is stored in the folder /run_0002, so let's plot that data properly (and not just using UnicodePlots).","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> using PyPlot, NCDatasets\njulia> ds = NCDataset(\"run_0002/output.nc\");\njulia> ds[\"vor\"]\nvor (384 × 192 × 1 × 25)\n Datatype: Float32\n Dimensions: lon × lat × lev × time\n Attributes:\n units = 1/s\n missing_value = NaN\n long_name = relative vorticity\n _FillValue = NaN","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Vorticity vor is stored as a 384x192x1x25 array, we may want to look at the first time step, which is the end of the previous simulation (time=6days) which we didn't store output for.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> vor = ds[\"vor\"][:,:,1,1];\njulia> lat = ds[\"lat\"][:];\njulia> lon = ds[\"lon\"][:];\njulia> pcolormesh(lon,lat,vor')","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Which looks like","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet pyplot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"You see that the unicode plot heavily coarse-grains the simulation, well it's unicode after all! And now the last time step, that means time=12days is","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> vor = ds[\"vor\"][:,:,1,25];\njulia> pcolormesh(lon,lat,vor')","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet pyplot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The jet broke up into many small eddies, but the turbulence is still confined to the northern hemisphere, cool! How this may change when we add mountains (we had NoOrography above!), say Earth's orography, you may ask? Let's try it out! We create an EarthOrography struct like so","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> orography = EarthOrography(spectral_grid)\nEarthOrography{Float32, OctahedralGaussianGrid{Float32}}:\n path::String = SpeedyWeather.jl/input_data\n file::String = orography_F512.nc\n scale::Float64 = 1.0\n smoothing::Bool = true\n smoothing_power::Float64 = 1.0\n smoothing_strength::Float64 = 0.1\n smoothing_truncation::Int64 = 85","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"It will read the orography from file as shown, and there are some smoothing options too, but let's not change them. Same as before, create a model, intialize into a simulation, run. This time directly for 12 days so that we can compare with the last plot","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);\njulia> simulation = initialize!(model);\njulia> run!(simulation,n_days=12,output=true)\nWeather is speedy: run 0003 100%|███████████████████████| Time: 0:00:35 (79.16 years/day)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"This time the run got the id \"0003\", but otherwise we do as before.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet pyplot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Interesting! The initial conditions have zero velocity in the southern hemisphere, but still, one can see some imprint of the orography on vorticity. You can spot the coastline of Antarctica; the Andes and Greenland are somewhat visible too. Mountains also completely changed the flow after 12 days, probably not surprising!","category":"page"},{"location":"how_to_run_speedy/#SpectralGrid","page":"How to run SpeedyWeather.jl","title":"SpectralGrid","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object. We have seen some examples above, now let's look into the details","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"SpectralGrid","category":"page"},{"location":"how_to_run_speedy/#SpeedyWeather.SpectralGrid","page":"How to run SpeedyWeather.jl","title":"SpeedyWeather.SpectralGrid","text":"Defines the horizontal spectral resolution and corresponding grid and the vertical coordinate for SpeedyWeather.jl. Options are\n\nNF::Type{<:AbstractFloat}: number format used throughout the model\ntrunc::Int64: horizontal resolution as the maximum degree of spherical harmonics\nGrid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: horizontal grid used for calculations in grid-point space\ndealiasing::Float64: how to match spectral with grid resolution: dealiasing factor, 1=linear, 2=quadratic, 3=cubic grid\nradius::Float64: radius of the sphere [m]\nnlat_half::Int64: number of latitude rings on one hemisphere (Equator incl)\nnpoints::Int64: total number of grid points in the horizontal\nnlev::Int64: number of vertical levels\nvertical_coordinates::SpeedyWeather.VerticalCoordinates: coordinates used to discretize the vertical\n\nnlat_half and npoints should not be chosen but are derived from trunc, Grid and dealiasing.\n\n\n\n\n\n","category":"type"},{"location":"how_to_run_speedy/#References","page":"How to run SpeedyWeather.jl","title":"References","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"[1] Galewsky, Scott, Polvani, 2004. An initial-value problem for testing numerical models of the global shallow-water equations, Tellus A. DOI: 10.3402/tellusa.v56i5.14436","category":"page"},{"location":"speedytransforms/#SpeedyTransforms","page":"Submodule: SpeedyTransforms","title":"SpeedyTransforms","text":"","category":"section"},{"location":"speedytransforms/","page":"Submodule: SpeedyTransforms","title":"Submodule: SpeedyTransforms","text":"SpeedyTransforms is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"speedytransforms/#Functions","page":"Submodule: SpeedyTransforms","title":"Functions","text":"","category":"section"},{"location":"grids/#Grids","page":"Grids","title":"Grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and but let's start but how they can be used","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"julia> spectral_grid = SpectralGrid(Grid = FullGaussianGrid)\nSpectralGrid:\n Spectral: T31 LowerTriangularMatrix{Complex{Float32}}, radius = 6.371e6 m\n Grid: 4608-element, 48-ring FullGaussianGrid{Float32} (quadratic)\n Resolution: 333km (average)\n Vertical: 8-level SigmaCoordinates","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object which defines the resolution in spectral and in grid-point space. The generator SpectralGrid() can take as a keyword argument Grid which can be any of the grids described below. The resolution of the grid, however, is not directly chosen, but determined from the spectral resolution trunc and the dealiasing factor. More in Matching spectral and grid resolution.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: RingGrids is a module too!\nWhile RingGrids is the underlying module that SpeedyWeather.jl uses for data structs on the sphere, the module can also be used independently of SpeedyWeather, for example to interpolate between data on different grids. See RingGrids","category":"page"},{"location":"grids/#Ring-based-equi-longitude-grids","page":"Grids","title":"Ring-based equi-longitude grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"SpeedyWeather.jl's spectral transform supports all ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on these rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.","category":"page"},{"location":"grids/#Implemented-grids","page":"Grids","title":"Implemented grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Currently the following full grids <: AbstractFullGrid are implemented","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"FullGaussianGrid, a full grid with Gaussian latitudes\nFullClenshawGrid, a full grid with equi-angle latitudes","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron\nOctahedralClenshawGrid, similar but based on equi-angle latitudes\nHEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces\nOctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"An overview of these grids is visualised here","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"(Image: Overview of implemented grids in SpeedyWeather.jl)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.","category":"page"},{"location":"grids/#Grid-resolution","page":"Grids","title":"Grid resolution","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half. ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: HEALPix grids do not use Nside as resolution parameter\nThe original formulation for HEALPix grids use N_side, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use N_side for the documentation or within functions though.","category":"page"},{"location":"grids/#Matching-spectral-and-grid-resolution","page":"Grids","title":"Matching spectral and grid resolution","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation T with a grid resolution N (=nlat_half) such that T + 1 = N. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at l_max=31 in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Let J be the total number of rings. Then we have","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"T approx J for linear truncation\nfrac32T approx J for quadratic truncation\n2T approx J for cubic truncation","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and in general fracm+12T approx J for m-th order truncation. So the higher the truncaction order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. In SpeedyWeather.jl the parameter dealiasing controls this option, = 1 would be linear, = 2 quadratic, = 3 cubic etc.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"For now just a quick overview of how the grid resolution changes when dealiasing is passed onto SpectralGrid on the FullGaussianGrid","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"trunc dealiasing FullGaussianGrid size\n31 1 64x32\n31 2 96x48\n31 3 128x64\n42 1 96x48\n42 2 128x64\n42 3 192x96\n... ... ...","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"You will obtain this information every time you create a SpectralGrid(;Grid,trunc,dealiasing).","category":"page"},{"location":"grids/#Full-Gaussian-grid","page":"Grids","title":"Full Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#Full-Clenshaw-Curtis-grid","page":"Grids","title":"Full Clenshaw-Curtis grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#Octahedral-Gaussian-grid","page":"Grids","title":"Octahedral Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#The-HEALPix-grid","page":"Grids","title":"The HEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are N_varphi basepixels in zonal direction and N_theta basepixels in meridional direction. For N_varphi = 4 and N_theta = 3 we obtain the classical HEALPix grid with N_varphi N_theta = 12 basepixels shown above in Implemented Grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always 2N, so 32 at N=16) and there are polar caps above and below the equatorial zone with the border at cos(theta) = 23 (theta in colatitudes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Following Górski, 2004[1], the z=cos(theta) colatitude of the j-th ring in the north polar cap, j=1N_side with 2N_side = N is ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^23N_side^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and on that ring, the longitude phi of the i-th point (i is the in-ring-index) is at","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i-tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The in-ring index i goes from i=14 for the first (i.e. northern-most) ring, i=18 for the second ring and i = 14j for the j-th ring in the northern polar cap.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"In the north equatorial belt j=N_side2N_side this changes to","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac43 - frac2j3N_side","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and the longitudes change to (i is always i = 14N_side in the equatorial belt meaning the number of longitude points is constant here)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2N_side(i - fracs2) quad s = (j - N_side + 1) mod 2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The cell boundaries are obtained by setting i = k + 12 or i = k + 12 + j (half indices) into the equations above, such that z(phik), a function for the cosine of colatitude z of index k and the longitude phi is obtained. These are then (northern polar cap)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^23N_side^2left(fracpi2phi_tright)^2 quad z = 1 - frack^23N_side^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with phi_t = phi mod tfracpi2 and in the equatorial belt","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac23-frac4k3N_side pm frac8phi3pi","category":"page"},{"location":"grids/#OctaHEALPix-grid","page":"Grids","title":"OctaHEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"While the classic HEALPix grid is based on a dodecahedron, other choices for N_varphi and N_theta in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With N_varphi = 4 and N_theta = 1 we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, 2pi around the Equator versus pi between the poles. N_varphi = 6 N_theta = 2 or N_varphi = 8 N_theta = 3 are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visulations of these grids.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"We call the N_varphi = 4 N_theta = 1 HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As N_theta = 1 there is no equatorial belt which simplifies the grid. The latitude of the j-th isolatitude ring on the OctaHEALPixGrid is defined by","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^2N^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with j=1N, and similarly for the southern hemisphere by symmetry. On this grid N_side = N where N= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index i = 14j are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i - tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and again, the southern hemisphere grid points are obtained by symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries-2","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Similar to the grid cell boundaries for the HEALPix grid, the OctaHEALPix grid's boundaries are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^2N^2left(fracpi2phi_tright)^2 quad z = 1 - frack^2N^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The 3N_side^2 in the denominator of the HEALPix grid came simply N^2 for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).","category":"page"},{"location":"grids/#References","page":"Grids","title":"References","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"[1] Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976","category":"page"},{"location":"boundary_conditions/#Boundary-conditions","page":"Boundary conditions","title":"Boundary conditions","text":"","category":"section"},{"location":"boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"This page describes the formulation of boundary conditions and their implementation.","category":"page"},{"location":"lowertriangularmatrices/#LowerTriangularMatrices","page":"Submodule: LowerTriangularMatrices","title":"LowerTriangularMatrices","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"LowerTriangularMatrices is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"This module defines LowerTriangularMatrix, a lower triangular matrix, which in contrast to LinearAlgebra.LowerTriangular does not store the entries above the diagonal. SpeedyWeather.jl uses LowerTriangularMatrix which is defined as a subtype of AbstractMatrix to store the spherical harmonic coefficients (see Spectral packing). ","category":"page"},{"location":"lowertriangularmatrices/#Creation-of-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Creation of LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"A LowerTriangularMatrix can be created using zeros,ones,rand, or randn","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> using SpeedyWeather.LowerTriangularMatrices\n\njulia> L = rand(LowerTriangularMatrix{Float32},5,5)\n5×5 LowerTriangularMatrix{Float32}:\n 0.912744 0.0 0.0 0.0 0.0\n 0.0737592 0.230592 0.0 0.0 0.0\n 0.799679 0.0765255 0.888098 0.0 0.0\n 0.670835 0.997938 0.505276 0.492966 0.0\n 0.949321 0.193692 0.793623 0.152817 0.357968","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"or the undef initializor LowerTriangularMatrix{Float32}(undef,3,3). The element type is arbitrary though, you can use any type T too.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Alternatively, it can be created through conversion from Matrix, which drops the upper triangle entries and sets them to zero","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> M = rand(Float16,3,3)\n3×3 Matrix{Float16}:\n 0.2222 0.694 0.3452\n 0.2158 0.04443 0.274\n 0.9746 0.793 0.6294\n\njulia> LowerTriangularMatrix(M)\n3×3 LowerTriangularMatrix{Float16}:\n 0.2222 0.0 0.0\n 0.2158 0.04443 0.0\n 0.9746 0.793 0.6294","category":"page"},{"location":"lowertriangularmatrices/#Indexing-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Indexing LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"LowerTriangularMatrix supports two types of indexing: 1) by denoting two indices, column and row [l,m] or 2) by denoting a single index [lm]. The double index works as expected","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L\n3×3 LowerTriangularMatrix{Float16}:\n 0.1499 0.0 0.0\n 0.1177 0.478 0.0\n 0.1709 0.756 0.3223\n\njulia> L[2,2]\nFloat16(0.478)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"But the single index skips the zero entries in the upper triangle, i.e.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L[4]\nFloat16(0.478)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"which, important, is different from single indices of an AbstractMatrix","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> Matrix(L)[4]\nFloat16(0.0)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"In performance-critical code a single index should be used, as this directly maps to the index of the underlying data vector. The double index is somewhat slower as it first has to be converted to the corresponding single index.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Consequently, many loops in SpeedyWeather.jl are build with the following structure","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"n,m = size(L)\nij = 0\nfor j in 1:m\n for i in j:n\n ij += 1\n L[ij] = i+j\n end\nend","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"which loops over all lower triangle entries of L::LowerTriangularMatrix and the single index ij is simply counted up. However, one could also use [i,j] as indices in the loop body or to perform any calculation (i+j here). An iterator over all entries in the lower triangle can be created by","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"for ij in eachindex(L)\n # do something\nend","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"The setindex! functionality of matrixes will throw a BoundsError when trying to write into the upper triangle of a LowerTriangularMatrix, for example","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L[2,1] = 0 # valid index\n0\n\njulia> L[1,2] = 0 # invalid index in the upper triangle\nERROR: BoundsError: attempt to access 3×3 LowerTriangularMatrix{Float32} at index [1, 2]","category":"page"},{"location":"lowertriangularmatrices/#Linear-algebra-with-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Linear algebra with LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"The LowerTriangularMatrices module's main purpose is not linear algebra, and it's implementation may not be efficient, however, many operations work as expected","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L = rand(LowerTriangularMatrix{Float32},3,3)\n3×3 LowerTriangularMatrix{Float32}:\n 0.57649 0.0 0.0\n 0.348685 0.875371 0.0\n 0.881923 0.850552 0.998306\n\njulia> L + L\n3×3 LowerTriangularMatrix{Float32}:\n 1.15298 0.0 0.0\n 0.697371 1.75074 0.0\n 1.76385 1.7011 1.99661\n\njulia> L * L\n3×3 Matrix{Float32}:\n 0.332341 0.0 0.0\n 0.506243 0.766275 0.0\n 1.68542 1.59366 0.996616","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Note, however, that the latter includes a conversion to Matrix, which is true for many operations, including inv or \\. Hence when trying to do more sophisticated linear algebra with LowerTriangularMatrix we quickly leave lower triangular-land and go back to normal matrix-land.","category":"page"},{"location":"lowertriangularmatrices/#Function-index","page":"Submodule: LowerTriangularMatrices","title":"Function index","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"LowerTriangularMatrix\nLowerTriangularMatrices.ij2k\nBase.fill!(L::LowerTriangularMatrix{T}, x) where T\nLowerTriangularMatrices.eachharmonic","category":"page"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix","text":"L = LowerTriangularMatrix{T}(v::Vector{T},m::Int,n::Int)\n\nA lower triangular matrix implementation that only stores the non-zero entries explicitly. L<:AbstractMatrix although in general we have L[i] != Matrix(L)[i], the former skips zero entries, tha latter includes them.\n\n\n\n\n\n","category":"type"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.ij2k","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.ij2k","text":"k = ij2k( i::Integer, # row index of matrix\n j::Integer, # column index of matrix\n m::Integer) # number of rows in matrix\n\nConverts the index pair i,j of an mxn LowerTriangularMatrix L to a single index k that indexes the same element in the corresponding vector that stores only the lower triangle (the non-zero entries) of L.\n\n\n\n\n\n","category":"function"},{"location":"lowertriangularmatrices/#Base.fill!-Union{Tuple{T}, Tuple{LowerTriangularMatrix{T}, Any}} where T","page":"Submodule: LowerTriangularMatrices","title":"Base.fill!","text":"fill!(L::LowerTriangularMatrix,x)\n\nFills the elements of L with x. Faster than fill!(::AbstractArray,x) as only the non-zero elements in L are assigned with x.\n\n\n\n\n\n","category":"method"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.eachharmonic","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.eachharmonic","text":"unit_range = eachharmonic(L::LowerTriangular)\n\ncreates unit_range::UnitRange to loop over all non-zeros in a LowerTriangularMatrix L. Like eachindex but skips the upper triangle with zeros in L.\n\n\n\n\n\nunit_range = eachharmonic(Ls::LowerTriangularMatrix...)\n\ncreates unit_range::UnitRange to loop over all non-zeros in the LowerTriangularMatrices provided as arguments. Checks bounds first. All LowerTriangularMatrix's need to be of the same size. Like eachindex but skips the upper triangle with zeros in L.\n\n\n\n\n\n","category":"function"},{"location":"conventions/#Style-and-convention-guide","page":"Style and convention guide","title":"Style and convention guide","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"In SpeedyWeather.jl we've been following the several conventions that are documented here.","category":"page"},{"location":"conventions/#Variable-naming","page":"Style and convention guide","title":"Variable naming","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The prognostic variables in spectral space are called","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" vor # Vorticity of horizontal wind field\n div # Divergence of horizontal wind field\n temp # Absolute temperature [K]\n pres_surf # Logarithm of surface pressure [log(Pa)]\n humid # Specific humidity [g/kg]","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" u\n v\n geopot\n ...","category":"page"},{"location":"conventions/#Preallocation","page":"Style and convention guide","title":"Preallocation","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"All arrays representing variables are preallocated and grouped into two structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" Prog::PrognosticVariables\n Diag::DiagnosticVariables","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The Diag struct contains further structs which represent the grid-point transformations of the prognostic variables and their tendencies.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" gridvars::GridVariables\n tendencies::Tendencies\n ...","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Constant arrays are grouped into several structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Boundaries","category":"page"},{"location":"conventions/#Julian-conventions","page":"Style and convention guide","title":"Julian conventions","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"We follow Julia's style guide and highlight here some important aspects of it.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Bang (!) convention. A function func does not change its input arguments, however, func! does.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.","category":"page"},{"location":"extending/#New-model-setups","page":"Extending SpeedyWeather","title":"New model setups","text":"","category":"section"},{"location":"extending/","page":"Extending SpeedyWeather","title":"Extending SpeedyWeather","text":"more to come...","category":"page"},{"location":"dynamical_core/#Dynamical-core","page":"Dynamical core","title":"Dynamical core","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"A mathematical and implementation-specific description of the dynamical core used in SpeedyWeather.jl. We start by describing the barotropic vorticity equations which is one set of equations that SpeedyWeather.jl can solve (see How to run SpeedyWeather.jl) as many details therein also apply to the Shallow water equations and Primitive equations explained thereafter.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The dynamical core presented here largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2], a shallow water model [3] and a primitive equation model[4]. ","category":"page"},{"location":"dynamical_core/#Barotropic-vorticity-equation","page":"Dynamical core","title":"Barotropic vorticity equation","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity zeta with advection, Coriolis force and diffusion in a single global layer on the sphere.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"fracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) =\nnabla times mathbfF + (-1)^n+1nunabla^2nzeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with time t, velocity vector mathbfu = (u v), Coriolis parameter f, and hyperdiffusion (-1)^n+1 nu nabla^2n zeta (n is the hyperdiffusion order; see Horizontal diffusion). We also include a forcing vector mathbfF = (F_uF_v) which acts on the zonal velocity u and the meridional velocity v and hence its curl nabla times mathbfF is a tendency for relative vorticity zeta.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Starting with some relative vorticity zeta, the Laplacian is inverted to obtain the stream function Psi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Psi = nabla^-2zeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The zonal velocity u and meridional velocity v are then the (negative) meridional gradient and zonal gradient of Psi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nu = -frac1R fracpartial Psipartial theta \nv = frac1Rcos(theta) fracpartial Psipartial phi \nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"which is described in Derivatives in spherical coordinates.","category":"page"},{"location":"dynamical_core/#Algorithm","page":"Dynamical core","title":"Algorithm","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"We briefly outline the algorithm that SpeedyWeather.jl uses in order to integrate the barotropic vorticity equation. As an intial step","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"0. Start with initial conditions of zeta_lm in spectral space and transform this model state to grid-point space:","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Invert the Laplacian to obtain the stream function Psi_lm in spectral space\nobtain zonal velocity (cos(theta)u)_lm through a Meridional derivative\nobtain meridional velocity (cos(theta)v)_lm through a Zonal derivative\nTransform zonal and meridional velocity (cos(theta)u)_lm, (cos(theta)v)_lm to grid-point space\nUnscale the cos(theta) factor to obtain uv\nTransform zeta_lm to zeta in grid-point space","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Now loop over","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Compute the forcing vector mathbfF = (F_uF_v) for u and v\nMultiply uv with zeta+f in grid-point space\nAdd A = F_u + v(zeta + f) and B = F_v - u(zeta + f)\nTransform these vector components to spectral space A_lm, B_lm\nCompute the curl of (AB)_lm in spectral space which is the tendency of zeta_lm\nCompute the Horizontal diffusion based on that tendency\nCompute a leapfrog time step as described in Time integration\nTransform the spectral state of zeta_lm to grid-point uvzeta as described in 0.\nPossibly do some output\nRepeat from 1.","category":"page"},{"location":"dynamical_core/#Shallow-water-equations","page":"Dynamical core","title":"Shallow water equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nfracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) = (-1)^n+1nunabla^2nzeta \nfracpartial mathcalDpartial t - nabla times (mathbfu(zeta + f)) = -nabla^2(tfrac12(u^2 + v^2) + geta) + (-1)^n+1nunabla^2nmathcalD \nfracpartial etapartial t + nabla cdot (mathbfuh) = 0\nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"where zeta = hatmathbfz cdot (nabla times mathbfu) is the relative vorticity, mathcalD = nabla cdot mathbfu the divergence, and eta the deviation from the fluid's rest height.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Note: more to come...","category":"page"},{"location":"dynamical_core/#Primitive-equations","page":"Dynamical core","title":"Primitive equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The primitive equations solved by SpeedyWeather.jl are","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\npartial_t u = \npartial_t v = \npartial_t T = \npartial_t Q = \nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Note: more to come...","category":"page"},{"location":"dynamical_core/#Horizontal-diffusion","page":"Dynamical core","title":"Horizontal diffusion","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In SpeedyWeather.jl we use hyerdiffusion through an n-th power Laplacian (-1)^n+1nabla^2n (hyper when n1) which can be implemented as a multiplication of the spectral coefficients Psi_lm with (-l(l+1))^nR^-2n (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the (-l(l+1))^nR^-2n can be precomputed. Note the sign change (-1)^n+1 here is such that the dissipative nature of the diffusion operator is retained for n odd and even.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step Delta t of variable zeta to obtain from time steps i-1 and i, the next time step i+1","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t dzeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with dzeta being some tendency evaluated from zeta_i. Now we want to add a diffusion term (-1)^n+1nu nabla^2nzeta with viscosity nu, wich however, is implicitly calculated from zeta_i+1, then","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t (dzeta + (-1)^n+1 nunabla^2nzeta_i+1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"As the application of (-1)^n+1nunabla^2n is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = fraczeta_i-1 + 2Delta t dzeta1 - 2Delta (-1)^n+1nunabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and expand the numerator to","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t fracdzeta + (-1)^n+1 nunabla^2nzeta_i-11 - 2Delta t (-1)^n+1nu nabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Hence the diffusion can be applied implicitly by updating the tendency dzeta as","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"dzeta to fracdzeta + (-1)^n+1nunabla^2nzeta_i-11 - 2Delta t nu nabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"which only depends on zeta_i-1. Now let D_textexplicit = (-1)^n+1nunabla^2n be the explicit part and D_textimplicit = 1 - (-1)^n+1 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are D_textimplicit = 1 - 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic lm we do","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"dzeta to D_textimplicit^-1(dzeta + D_textexplicitzeta_i-1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power n.","category":"page"},{"location":"dynamical_core/#Normalization-of-diffusion","page":"Dynamical core","title":"Normalization of diffusion","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In physics, the Laplace operator nabla^2 is often used to represent diffusion due to viscosity in a fluid. In that case, the viscosity coefficient is nu of units textm^2texts^-1 and the full operator reads as nu nabla^2 with units (textm^2texts^-1)(textm^-2) = texts^-1. This motivates us to normalize the Laplace operator by a constant of units textm^-2 and the viscosity coefficient by its inverse such that the viscosity coefficient becomes a damping timescale of unit texts^-1. Given the application in spectral space we decide to normalize by the largest eigenvalue -l_textmax(l_textmax+1) such that all entries in the discrete spectral Laplace operator are in 01. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized viscosity coefficient nu^* = l_textmax(l_textmax+1)nu (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have ","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"D^textexplicit_lm = -nu^* fracl(l+1)l_textmax(l_textmax+1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and the hyper-Laplacian of power n follows as","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"D^textexplicitn_lm = -nu^* left(fracl(l+1)l_textmax(l_textmax+1)right)^n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and the implicit part is accordingly D^textimplicitn_lm = 1 - 2Delta t D^textexplicitn_lm.","category":"page"},{"location":"dynamical_core/#Radius-scaling","page":"Dynamical core","title":"Radius scaling","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"SpeedyWeather.jl uses a scaling for vorticity zeta and stream function Psi that is","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildezeta = zeta R tildePsi = Psi R^-1","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In the barotropic voriticity equation model the inversion of the Laplcians in order to obtain Psi from zeta therefore becomes","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildezeta = tildenabla^2 tildePsi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"where the dimensionless gradients simply omit the scaling with 1R, tildenabla = Rnabla. The Barotropic vorticity equation scaled with R^2 is","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"partial_tildettildezeta + tildenabla cdot (mathbfu(tildezeta + tildef)) = tildenutildenabla^2ntildezeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildet = tR^-1, the scaled time t\nmathbfu = (uv), the velocity vector (no scaling applied)\ntildef = fR, the scaled Coriolis parameter f\ntildenu = nu^* R, the scaled viscosity nu^*, which itself is normalized to a damping time scale, see Normalization of diffusion.","category":"page"},{"location":"dynamical_core/#Scaled-shallow-water-equations","page":"Dynamical core","title":"Scaled shallow water equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Similar to the scaled barotropic vorticity equations, the scaled shallow water equations scale the vorticity and the divergence equation with R^2, but the continuity equation with R","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nfracpartial tildezetapartial tildet + tildenabla cdot (mathbfu(tildezeta + tildef)) =\ntildenutildenabla^2ntildezeta \nfracpartial tildemathcalDpartial tildet - tildenabla times (mathbfu(tildezeta + tildef)) =\n-tildenabla^2left(tfrac12(u^2 + v^2) + geta right) + tildenutildenabla^2ntildemathcalD \nfracpartial etapartial tildet + tildenabla cdot (mathbfuh) = 0\nendaligned","category":"page"},{"location":"dynamical_core/#Time-integration","page":"Dynamical core","title":"Time integration","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"SpeedyWeather.jl uses a leapfrog time scheme with a Robert's and William's filter to dampen the computational mode and achieve 3rd order accuracy.","category":"page"},{"location":"dynamical_core/#Oscillation-equation","page":"Dynamical core","title":"Oscillation equation","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"fracdFdt = iomega F","category":"page"},{"location":"dynamical_core/#References","page":"Dynamical core","title":"References","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[1]: Geophysical Fluid Dynamics Laboratory, Idealized models with spectral dynamics","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[2]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[3]: Geophysical Fluid Dynamics Laboratory, The Shallow Water Equations.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[4]: Geophysical Fluid Dynamics Laboratory, The Spectral Dynamical Core","category":"page"},{"location":"spectral_transform/#Spherical-Harmonic-Transform","page":"Spherical harmonic transform","title":"Spherical Harmonic Transform","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space on a longitude-latitude regular Gaussian grid.","category":"page"},{"location":"spectral_transform/#Inspiration","page":"Spherical harmonic transform","title":"Inspiration","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform implemented by SpeedyWeather.jl follows largely Justin Willmert's CMB.jl package and makes use of AssociatedLegendrePolynomials.jl and FFTW.jl (for Float32/64) or GenericFFT.jl (for generic) for the Fourier transform. Justin described his work in a Blog series [1][2][3][4][5][6][7][8].","category":"page"},{"location":"spectral_transform/#Spherical-harmonics","page":"Spherical harmonic transform","title":"Spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spherical harmonics Y_lm of degree l and order m over the longitude phi = (02pi) and colatitudes theta = (-pi2pi2), are","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Y_lm(phi theta) = lambda_l^m(sintheta) e^imphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with lambda_l^m being the pre-normalized associated Legendre polynomials, and e^imphi are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Latitudes versus colatitudes\nThe implementations of the spherical transforms in SpeedyWeather.jl use colatitudes theta = (0pi) (0 at the north pole) but the dynamical core uses latitudes theta = (-pi2pi2) (pi2 at the north pole). However, all arrays are always sorted north to south such that [i,1] will access the northern-most grid points. Note: We may also use latitudes in the spherical harmonic transform in the future for consistency. ","category":"page"},{"location":"spectral_transform/#Synthesis-(spectral-to-grid)","page":"Spherical harmonic transform","title":"Synthesis (spectral to grid)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The synthesis (or inverse transform) takes the spectral coefficients a_lm and transforms them to grid-point values f(phitheta) (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics Y_lm with non-zero coefficients.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"f(phitheta) = sum_l=0^infty sum_m=-l^l a_lm Y_lm(phitheta)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We obtain an approximation with a finite set of a_lm by truncating the series after l = l_max.","category":"page"},{"location":"spectral_transform/#Analysis-(grid-to-spectral)","page":"Spherical harmonic transform","title":"Analysis (grid to spectral)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting in grid-point space we can transform a field f(lambdatheta) into the spectral space of the spherical harmonics by","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"a_lm = int_0^2pi int_-tfracpi2^tfracpi2 f(lambdatheta) Y_lm(lambdatheta) cos theta dtheta dlambda","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This integral has to be discretized to when grid-point values f(lambda_itheta_i) are used. For more details, see [7],[8].","category":"page"},{"location":"spectral_transform/#Spectral-packing","page":"Spherical harmonic transform","title":"Spectral packing","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Spectral packing is the way how the coefficients a_lm of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree l and order m as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Every row represents an order l geq 0, starting from l=0 at the top. Every column represents an order m geq 0, starting from m=0 on the left. The coefficients of these spherical harmonics are directly mapped into a matrix a_lm as ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 \n a_10 a_11 \n a_20 a_12 a_22 \n a_30 a_13 a_23 a_33","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal explicitly stores zeros. Note that internally vector fields include an additional degree, such that l_max = m_max + 1 (see Gradients in spherical coordinates for more information). The harmonics with a_l0 (the first column) are also called zonal harmonics as they are constant with longitude phi. The harmonics with a_ll (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into 2l sectors in longitude phi without a zero-crossing in latitude.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Array indices\nFor a spectral field alms note that due to Julia's 1-based indexing the coefficient a_lm is obtained via alms[l+1,m+1].","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Fortran speedy does not use the same spectral packing as SpeedyWeather.jl. The alternative packing lm therein uses l=m and m=l-m as summarized in the following table.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"degree l order m l=m m=l-m\n0 0 0 0\n1 0 0 1\n1 1 1 0\n2 0 0 2\n2 1 1 1\n2 2 2 0\n3 0 0 3\n... ... ... ...","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 a_10 a_20 a_30\n a_11 a_21 a_31 \n a_22 a_32 \n a_33 ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran-speedy.","category":"page"},{"location":"spectral_transform/#Example-transforms","page":"Spherical harmonic transform","title":"Example transforms","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"julia> using SpeedyWeather\njulia> alms = zeros(ComplexF64,3,3) # spectral coefficients\njulia> alms[2,2] = 1 # only l=1,m=1 harmonic\njulia> map = gridded(alms) # convert to grid space\n8×4 Matrix{Float64}:\n -0.324541 -0.600363 -0.600363 -0.324541\n -0.134429 -0.248678 -0.248678 -0.134429\n 0.134429 0.248678 0.248678 0.134429\n 0.324541 0.600363 0.600363 0.324541\n 0.324541 0.600363 0.600363 0.324541\n 0.134429 0.248678 0.248678 0.134429\n -0.134429 -0.248678 -0.248678 -0.134429\n -0.324541 -0.600363 -0.600363 -0.324541\n \njulia> spectral(map) # back to spectral space\n3×3 Matrix{ComplexF64}:\n 0.0+0.0im 0.0+0.0im 0.0+0.0im\n 0.0+0.0im 1.0+3.60727e-17im 0.0+0.0im\n 0.0+0.0im 0.0+0.0im 0.0+0.0im","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"and we have successfully reobtained the l=m=1 spherical harmonic.","category":"page"},{"location":"spectral_transform/#Available-horizontal-resolutions","page":"Spherical harmonic transform","title":"Available horizontal resolutions","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with l leq l_max and m leq m_max are explicitly represented. This is usually described as Tm_max, with l_max = m_max (although in vector quantities require one more degree l in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with l_max = m_max = 31. Note that the degree l and order m are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Using triangular truncation[9], there are constraints on the corresponding grid resolution. Let nlon, nlat be the number of longitudes, latitudes on a regular Gaussian grid. Then spectral and grid resolution have to be chosen such that","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nlon geq 3l_max+1\nnlat geq (3l_max+1)2","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In general, we choose nlon = 2nlat, and ideally nlon is easily Fourier-transformable, e.g. nlon = 2^i3^j5^k with some integers ijk geq 0. SpeedyWeather.jl is tested at the following horizontal resolutions, with Delta x = tfrac2pi Rnlon as the approximate grid spacing at the Equator","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"l_max nlon nlat Delta x\n31 (default) 96 48 400 km\n42 128 64 300 km\n85 256 128 160 km\n170 512 256 80 km\n341 1024 512 40 km\n682 2048 1024 20 km","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Choosing trunc as argument in run_speedy will automatically choose nlon,nlat as presented in the table. Other common choices are T63 (192x96), T127 (384x192), T255 (768x384), T511 (1536x768), among others.","category":"page"},{"location":"spectral_transform/#Derivatives-in-spherical-coordinates","page":"Spherical harmonic transform","title":"Derivatives in spherical coordinates","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Horizontal gradients in spherical coordinates are defined for a scalar field A and the latitudes theta and longitudes lambda as","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla A = left(frac1Rcosthetafracpartial Apartial lambda frac1Rfracpartial Apartial theta right)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"However, the divergence of a vector field mathbfu = (uv) includes additional cos(theta) scalings","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla cdot mathbfu = frac1Rcosthetafracpartial upartial lambda +\nfrac1Rcosthetafracpartial (v costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"and similar for the curl","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla times mathbfu = frac1Rcosthetafracpartial vpartial lambda -\nfrac1Rcosthetafracpartial (u costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The radius of the sphere (i.e. Earth) is R. The zonal gradient scales with 1cos(theta) as the longitudes converge towards the poles (note that theta describes latitudes here, defintions using colatitudes replace the cos with a sin.)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting with a spectral field of vorticity zeta and divergence mathcalD one can obtain stream function Psi and velocity potential Phi by inverting the Laplace operator nabla^2:","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi = nabla^-2zeta quad Phi = nabla^-2mathcalD","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The velocities uv are then obtained from (uv) = nabla^botPsi + nablaPhi following the defintion from above and nabla^bot = (-R^-1partial_theta (Rcostheta)^-1partial_lambda)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nu = -frac1Rpartial_thetaPsi + frac1Rcosthetapartial_lambdaPhi \nv = +frac1Rpartial_thetaPhi + frac1Rcosthetapartial_lambdaPsi\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Alternatively, we can use the velocities U = ucostheta V = vcostheta, which we do as the meridional gradient for spherical harmonics is easier implemented with a costheta-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with UV and not uv. From uv we can return to zeta mathcalD via","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nzeta = frac1Rcosthetapartial_lambda v - frac1Rcosthetapartial_theta (u costheta) \nmathcalD = frac1Rcosthetapartial_lambda u + frac1Rcosthetapartial_theta (v costheta)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Equivalently, we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU = -fraccosthetaRpartial_thetaPsi + frac1Rpartial_lambdaPhi \nV = +fraccosthetaRpartial_thetaPhi + frac1Rpartial_lambdaPsi \nzeta = frac1Rpartial_lambda left( fracVcos^2theta right) -\nfraccosthetaRpartial_theta left( fracUcos^2theta right) \nmathcalD = frac1Rpartial_lambda left( fracUcos^2theta right) +\nfraccosthetaRpartial_theta left( fracVcos^2theta right)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is a more convenient formulation as required costheta scalings are reduced to a minimum. The remaining (UV)*cos^-2theta are done in grid-point space and usually in combination with other operations like the computation of the vorticity flux. But also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out too. As described in Meridional derivative, it is more convenient to implement costheta partial_theta via a recursion relation for the Legendre polynomials than partial_theta directly. How the operators nabla nabla times nabla cdot can be implemented with spherical harmonics is presented in the following sections.","category":"page"},{"location":"spectral_transform/#Zonal-derivative","page":"Spherical harmonic transform","title":"Zonal derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The zonal derivative of a scalar field Psi in spectral space is the zonal derivative of all its respective spherical harmonics Psi_lm(phitheta) (now we use phi for longitudes to avoid confusion with the Legendre polynomials lambda_lm)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"v_lm = frac1R cos(theta) fracpartialpartial phi left( lambda_l^m(costheta) e^imphi right) =\nfracimR cos(theta) lambda_l^m(costheta) e^imphi = fracimR cos(theta) Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"So for every spectral harmonic, cos(theta)v_lm is obtained from Psi_lm via a multiplication with imR. Unscaling the cos(theta)-factor is done after transforming the spectral coefficients v_lm into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as tildePsi = R^-1Psi such that the division by radius R in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order im. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number m times imaginary i.","category":"page"},{"location":"spectral_transform/#Meridional-derivative","page":"Spherical harmonic transform","title":"Meridional derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridioinal derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costheta fracdP_lmdtheta = -lepsilon_l+1mP_l+1m + (l+1)epsilon_lmP_l-1m","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with recursion factors","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"epsilon_lm = sqrtfracl^2-m^24l^2-1","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In the following we use the example of obtaining the zonal velocity u from the stream function Psi, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi(lambdatheta) = sum_lmPsi_lmP_lm(sintheta)e^imlambda","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"we multiply with -R^-1costhetapartial_theta to obtain","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costhetaleft(-frac1Rpartial_thetaPsi right) = -frac1Rsum_lmPsi_lme^imlambdacosthetapartial_theta P_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"at which point the recursion from above can be applied. Collecting terms proportional to P_lm then yields","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"(cos(theta)u)_lm = -frac1R(-(l-1)epsilon_lmPsi_l-1m + (l+2)epsilon_l+1mPsi_l+1m)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"To obtain the coefficient of each spherical harmonic lm of the meridional gradient of a spectral field, two coefficients at l-1m and l+1m have to be combined. This means that the coefficient of a gradient ((costheta) u)_lm is a linear combination of the coefficients of one higher and one lower degree Psi_l+1mPsi_l-1m. As the coefficient Psi_lm with ml are zero, the sectoral harmonics (l=m) of the gradients are obtained from the first off-diagonal only. However, the l=l_max harmonics of the gradients require the l_max-1 as well as the l_max+1 harmonics. In SpeedyWeather.jl vector quantitie like uv use therefore one more meridional mode than scalar quantities such as vorticity zeta or stream function Psi. The meridional derivative in SpeedyWeather.jl also omits the 1R-scaling as explained for the Zonal derivative and in Radius scaling.","category":"page"},{"location":"spectral_transform/#Divergence-and-curl-in-spherical-harmonics","page":"Spherical harmonic transform","title":"Divergence and curl in spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridional gradient as described above can be applied to scalars, such as Psi and Phi in the conversion to velocities (uv) = nabla^botPsi + nablaPhi, however, the operators curl nabla times and divergence nabla cdot in spherical coordinates involve a costheta scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform of vorticity zeta is","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac12piint_-tfracpi2^tfracpi2int_0^2pi zeta(lambdatheta) P_lm(sintheta) e^imlambda dlambda costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Given that Rzeta = cos^-1partial_lambda v - cos^-1partial_theta (u costheta), we therefore have to evaluate a meridional integral of the form","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"int P_lm frac1cos theta partial_theta(u costheta)) cos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which can be solved through integration by parts. As ucostheta = 0 at theta = pm tfracpi2 only the integral","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int partial_theta P_lm (u costheta) dtheta = -int costheta partial_theta P_lm (fracucostheta) costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"remains. Inserting the recurrence relation from the Meridional derivative turns this into","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int left(-l epsilon_l+1mP_l+1m + (l+1)epsilon_lm P_l-1m right) (fracucostheta) cos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Now we expand (tfracucostheta) but only the lm harmonic will project ontoP_lm. Let u^* = ucos^-1theta v^* = vcos^-1theta we then have in total","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nRzeta_lm = imv^*_lm + (l+1)epsilon_lmu^*_l-1m - lepsilon_l+1mu^*_l+1m \nRD_lm = imu^*_lm - (l+1)epsilon_lmv^*_l-1m + lepsilon_l+1mv^*_l+1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"And the divergence D is similar, but (uv) to (-vu). We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#Laplacian","page":"Spherical harmonic transform","title":"Laplacian","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral Laplacian is easily applied to the coefficients Psi_lm of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator nabla^2 in spherical coordinates with eigenvalues -l(l+1) divided by the radius squared R^2, i.e. nabla^2 Psi becomes tfrac-l(l+1)R^2Psi_lm in spectral space. For example, vorticity zeta and streamfunction Psi are related by zeta = nabla^2Psi in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree l and order m to","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac-l(l+1)R^2Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This can be easily inverted to obtain the stream function Psi from vorticity zeta instead. In order to avoid division by zero, we set Psi_00 here, given that the stream function is only defined up to a constant anyway.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nPsi_lm = fracR^2-l(l+1)zeta_lm quad foralllm 0\nPsi_00 = 0\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"See also Horizontal diffusion and Normalization of diffusion.","category":"page"},{"location":"spectral_transform/#U,V-from-vorticity-and-divergence","page":"Spherical harmonic transform","title":"U,V from vorticity and divergence","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity zeta and divergence D (which are prognostic variables) to U=ucostheta V=vcostheta. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree l (the meridional gradient). It is therefore computationally more efficient to compute UV directly from zetaD instead of calculating stream function and velocity potential first. In total we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU_lm = -fraciml(l+1)(RD)_lm + fracepsilon_l+1ml+1(Rzeta)_l+1m - fracepsilon_lml(Rzeta)_l-1m \nV_lm = -fraciml(l+1)(Rzeta)_lm - fracepsilon_l+1ml+1(RD)_l+1m + fracepsilon_lml(RD)_l-1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#References","page":"Spherical harmonic transform","title":"References","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[1]: Justin Willmert, 2020. Introduction to Associated Legendre Polynomials (Legendre.jl Series, Part I)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[2]: Justin Willmert, 2020. Calculating Legendre Polynomials (Legendre.jl Series, Part II)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[3]: Justin Willmert, 2020. Pre-normalizing Legendre Polynomials (Legendre.jl Series, Part III)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[4]: Justin Willmert, 2020. Maintaining numerical accuracy in the Legendre recurrences (Legendre.jl Series, Part IV)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[5]: Justin Willmert, 2020. Introducing Legendre.jl (Legendre.jl Series, Part V)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[6]: Justin Willmert, 2020. Numerical Accuracy of the Spherical Harmonic Recurrence Coefficient (Legendre.jl Series Addendum)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[7]: Justin Willmert, 2020. Notes on Calculating the Spherical Harmonics","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[8]: Justin Willmert, 2022. More Notes on Calculating the Spherical Harmonics: Analysis of maps to harmonic coefficients","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[9]: David Randall, 2021. An Introduction to Numerical Modeling of the Atmosphere, Chapter 22.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[10]: Dale Durran, 2010. Numerical Methods for Fluid Dynamics, Springer. In particular section 6.2, 6.4.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[11]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"ringgrids/#RingGrids","page":"Submodule: RingGrids","title":"RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids defines several iso-latitude grids, which are mathematically described in the section on Grids. In brief, they include the regular latitude-longitude grids (here called FullClenshawGrid) as well as grids which latitudes are shifted to the Gaussian latitudes and reduced grids, meaning that they have a decreasing number of longitudinal points towards the poles to be more equal-area than full grids.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids defines and exports the following grids:","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"full grids: FullClenshawGrid, FullGaussianGrid, FullHEALPix, and FullOctaHEALPix\nreduced grids: OctahedralGaussianGrid, OctahedralClenshawGrid, OctaHEALPixGrid and HEALPixGrid","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"The following explanation of how to use these can be mostly applied to any of them, however, there are certain functions that are not defined, e.g. the full grids can be trivially converted to a Matrix but not the OctahedralGaussianGrid.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"note: What is a ring?\nWe use the term ring, short for iso-latitude ring, to refer to a sequence of grid points that all share the same latitude. A latitude-longitude grid is a ring grid, as it organises its grid-points into rings. However, other grids, like the cubed-sphere are not based on iso-latitude rings. SpeedyWeather.jl only works with ring grids because its a requirement for the Spherical Harmonic Transform to be efficient. See Grids.","category":"page"},{"location":"ringgrids/#Creating-data-on-a-RingGrid","page":"Submodule: RingGrids","title":"Creating data on a RingGrid","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Every grid in RingGrids has a grid.data field, which is a vector containing the data on the grid. The grid points are unravelled west to east then north to south, meaning that it starts at 90˚N and 0˚E then walks eastward for 360˚ before jumping on the next latitude ring further south, this way circling around the sphere till reaching the south pole. This may also be called ring order.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Data in a Matrix which follows this ring order can be put on a FullGaussianGrid like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"using SpeedyWeather.RingGrids\nmap = randn(Float32,8,4)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid = FullGaussianGrid(map)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"A full Gaussian grid has always 2N x N grid points, but a FullClenshawGrid has 2N x N-1, if those dimensions don't match, the creation will throw an error. To reobtain the data from a grid, you can access its data field which returns a normal Vector","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid.data","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Which can be reshaped to reobtain map from above. Alternatively you can Matrix(grid) to do this in one step","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"map == Matrix(FullGaussianGrid(map))","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"You can also use zeros,ones,rand,randn to create a grid, whereby nlat_half, i.e. the number of latitude rings on one hemisphere, Equator included, is used as a resolution parameter and here as a second argument.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"nlat_half = 4\ngrid = randn(OctahedralGaussianGrid{Float16},nlat_half)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"and any element type T can be used for OctahedralGaussianGrid{T} and similar for other grid types.","category":"page"},{"location":"ringgrids/#Visualising-RingGrid-data","page":"Submodule: RingGrids","title":"Visualising RingGrid data","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"As only the full grids can be reshaped into a matrix, the underyling data structure of any AbstractGrid is a vector. As shown in the examples above, one can therefore inspect the data as if it was a vector. But as that data has, through its <:AbstractGrid type, all the geometric information available to plot it on a map, RingGrids also exports plot function, based on UnicodePlots' heatmap.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"nlat_half = 24\ngrid = randn(OctahedralGaussianGrid,nlat_half)\nplot(grid)","category":"page"},{"location":"ringgrids/#Indexing-RingGrids","page":"Submodule: RingGrids","title":"Indexing RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"All RingGrids have a single index ij which follows the ring order. While this is obviously not super exciting here are some examples how to make better use of the information that the data sits on a grid.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"We obtain the latitudes of the rings of a grid by calling get_latd (get_lond is only defined for full grids, or use get_latdlonds for latitudes, longitudes per grid point not per ring)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid = randn(OctahedralClenshawGrid,5)\nlatd = get_latd(grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Now we could calculate Coriolis and add it on the grid as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"rotation = 7.29e-5 # angular frequency of Earth's rotation [rad/s]\ncoriolis = 2rotation*sind.(latd) # vector of coriolis parameters per latitude ring\n\nrings = eachring(grid)\nfor (j,ring) in enumerate(rings)\n f = coriolis[j]\n for ij in ring\n grid[ij] += f\n end\nend","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"eachring creates a vector of UnitRange indices, such that we can loop over the ring index j (j=1 being closest to the North pole) pull the coriolis parameter at that latitude and then loop over all in-ring indices i (changing longitudes) to do something on the grid. Something similar can be done to scale/unscale with the cosine of latitude for example. We can always loop over all grid-points like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"for ij in eachgridpoint(grid)\n grid[ij]\nend","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"or use eachindex instead.","category":"page"},{"location":"ringgrids/#Interpolation-on-RingGrids","page":"Submodule: RingGrids","title":"Interpolation on RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"In most cases we will want to use RingGrids so that our data directly comes with the geometric information of where the grid-point is one the sphere. We have seen how to use get_latd, get_lond, ... for that above. This information generally can also be used to interpolate our data from grid to another or to request an interpolated value on some coordinates. Using our data on grid which is an OctahedralGaussianGrid from above we can use the interpolate function to get it onto a FullGaussianGrid (or any other grid for purpose)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid = randn(OctahedralGaussianGrid{Float32},4)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(FullGaussianGrid,grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"By default this will linearly interpolate (it's an Anvil interpolator, see below) onto a grid with the same nlat_half, but we can also coarse-grain or fine-grain by specifying nlat_half directly as 2nd argument","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(FullGaussianGrid,6,grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"So we got from an 8-ring OctahedralGaussianGrid{Float16} to a 12-ring FullGaussianGrid{Float64}, so it did a conversion from Float16 to Float64 on the fly too, because the default precision is Float64 unless specified. interpolate(FullGaussianGrid{Float16},6,grid) would have interpolated onto a grid with element type Float16.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"One can also interpolate onto a give cordinate ˚N, ˚E like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(30.0,10.0,grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"we interpolated the data from grid onto 30˚N, 10˚E. To do this simultaneously for many coordinates they can be packed into a vector too","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate([30.0,40.0,50.0],[10.0,10.0,10.0],grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which returns the data on grid at 30˚N, 40˚N, 50˚N, and 10˚E respectively. Note how the interpolation here retains the element type of grid.","category":"page"},{"location":"ringgrids/#Performance-for-RingGrid-interpolation","page":"Submodule: RingGrids","title":"Performance for RingGrid interpolation","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Every time an interpolation like interpolate(30.0,10.0,grid) is called, several things happen, which are important to understand to know how to get the fastest interpolation out of this module in a given situation. Under the hood an interpolation takes three arguments","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"output vector\ninput grid\ninterpolator","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"The output vector is just an array into which the interpolated data is written, providing this prevents unnecessary allocation of memory in case the destination array of the interpolation already exists. The input grid contains the data which is subject to interpolation, it must come on a ring grid, however, its coordinate information is actually already in the interpolator. The interpolator knows about the geometry of the grid the data is coming on and the coordinates it is supposed to interpolate onto. It has therefore precalculated the indices that are needed to access the right data on the input grid and the weights it needs to apply in the actual interplation operation. The only thing it does not know is the actual data values of that grid. So in the case you want to interpolate from grid A to grid B many times, you can just reuse the same interpolator. If you want to change the coordinates of the output grid but its total number of points remain constants then you can update the locator inside the interpolator and only else you will need to create a new interpolator. Let's look at this in practice. Say we have two grids an want to interpolate between them","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid_in = rand(HEALPixGrid,4)\ngrid_out = zeros(FullClenshawGrid,6)\ninterp = RingGrids.interpolator(grid_out,grid_in)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Now we have created an interpolator interp which knows about the geometry where to interpolate from and the coordinates there to interpolate to. It is also initialized, meaning it has precomputed the indices to of grid_in that are supposed to be used. It just does not know about the data of grid_in (and neither of grid_out which will be overwritten anyway). We can now do","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate!(grid_out,grid_in,interp)\ngrid_out","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which is identical to interpolate(grid_out,grid_in) but you can reuse interp for other data. The interpolation can also handle various element types (the interpolator interp does not have to be updated for this either)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid_out = zeros(FullClenshawGrid{Float16},6);\ninterpolate!(grid_out,grid_in,interp)\ngrid_out","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"and we have converted data from a HEALPixGrid{Float64} (Float64 is always default if not specified) to a FullClenshawGrid{Float16} including the type conversion Float64-Float16 on the fly. Technically there are three data types and their combinations possible: The input data will come with a type, the output array has an element type and the interpolator has precomputed weights with a given type. Say we want to go from Float16 data on an OctahedralGaussianGrid to Float16 on a FullClenshawGrid but using Float32 precision for the interpolation itself, we would do this by","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid_in = randn(OctahedralGaussianGrid{Float16},24)\ngrid_out = zeros(FullClenshawGrid{Float16},24)\ninterp = RingGrids.interpolator(Float32,grid_out,grid_in)\ninterpolate!(grid_out,grid_in,interp)\ngrid_out","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"As a last example we want to illustrate a situation where we would always want to interplate onto 10 coordinates, but their locations may change. In order to avoid recreating an interpolator object we would do (AnvilInterpolator is described in Anvil interpolator)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"npoints = 10 # number of coordinates to interpolate onto\ninterp = AnvilInterpolator(Float32,HEALPixGrid,24,npoints)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"with the first argument being the number format used during interpolation, then the input grid type, its resolution in terms of nlat_half and then the number of points to interpolate onto. However, interp is not yet initialized as it does not know about the destination coordinates yet. Let's define them, but note that we already decided there's only 10 of them above.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"latds = collect(0.0:5.0:45.0)\nlonds = collect(-10.0:2.0:8.0)\nnothing # hide","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"now we can update the locator inside our interpolator as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids.update_locator!(interp,latds,londs)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"With data matching the input from above, a nlat_half=24 HEALPixGrid, and allocate 10-element output vector","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"output_vec = zeros(10)\ngrid_input = rand(HEALPixGrid,24)\nnothing # hide","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"we can use the interpolator as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate!(output_vec,grid_input,interp)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which is the approximately the same as doing it directly without creating an interpolator first and updating its locator","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(latds,londs,grid_input)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"but allows for a reuse of the interpolator. Note that the two output arrays are not exactly identical because we manually set our interpolator interp to use Float32 for the interplation whereas the default is Float64.","category":"page"},{"location":"ringgrids/#Anvil-interpolator","page":"Submodule: RingGrids","title":"Anvil interpolator","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Currently the only interpolator implemented is a 4-point bilinear interpolator, which schematically works as follows. Anvil interpolation is the bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":" 0..............1 # fraction of distance Δab between a,b\n |< Δab >|\n\n0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x\n.Δy |\n. |\n.v x \n. |\n1 c ------ o ---- d\n\n |< Δcd >|\n 0...............1 # fraction of distance Δcd between c,d\n\n^ fraction of distance Δy between a-b and c-d.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"This interpolation is chosen as by definiton of the ring grids, a and b share the same latitude, so do c and d, but the longitudes can be different for all four, a,b,c,d.","category":"page"},{"location":"ringgrids/#Function-index","page":"Submodule: RingGrids","title":"Function index","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids.each_index_in_ring\nRingGrids.eachgridpoint\nRingGrids.eachring\nRingGrids.whichring\nRingGrids.get_nlons","category":"page"},{"location":"ringgrids/#SpeedyWeather.RingGrids.each_index_in_ring","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.each_index_in_ring","text":"i = each_index_in_ring(grid,j)\n\nUnitRange i to access data on grid grid on ring j.\n\n\n\n\n\n","category":"function"},{"location":"ringgrids/#SpeedyWeather.RingGrids.eachgridpoint","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.eachgridpoint","text":"ijs = eachgridpoint(grid)\n\nUnitRange ijs to access each grid point on grid grid.\n\n\n\n\n\n","category":"function"},{"location":"ringgrids/#SpeedyWeather.RingGrids.eachring","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.eachring","text":"eachring(grid::SpeedyWeather.RingGrids.AbstractGrid) -> Any\n\n\nVector{UnitRange} rings to loop over every ring of grid grid and then each grid point per ring. To be used like\n\nrings = eachring(grid)\nfor ring in rings\n for ij in ring\n grid[ij]\n\n\n\n\n\neachring(\n grid1::SpeedyWeather.RingGrids.AbstractGrid,\n grids::Grid<:SpeedyWeather.RingGrids.AbstractGrid...\n) -> Any\n\n\nSame as eachring(grid) but performs a bounds check to assess that all grids in grids are of same size.\n\n\n\n\n\n","category":"function"},{"location":"ringgrids/#SpeedyWeather.RingGrids.whichring","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.whichring","text":"whichring(\n ij::Integer,\n rings::Vector{UnitRange{Int64}}\n) -> Int64\n\n\nObtain ring index j from gridpoint ij and Vector{UnitRange} describing rind indices as obtained from eachring(::Grid)\n\n\n\n\n\n","category":"function"},{"location":"ringgrids/#SpeedyWeather.RingGrids.get_nlons","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.get_nlons","text":"get_nlons(\n Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid},\n nlat_half::Integer;\n both_hemispheres\n) -> Any\n\n\nReturns a vector nlons for the number of longitude points per latitude ring, north to south. Provide grid Grid and its resolution parameter nlat_half. For both_hemisphere==false only the northern hemisphere (incl Equator) is returned.\n\n\n\n\n\n","category":"function"},{"location":"#SpeedyWeather.jl-documentation","page":"Home","title":"SpeedyWeather.jl documentation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.","category":"page"},{"location":"#Overview","page":"Home","title":"Overview","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to perform some calculations in spectral space (time integration, gradients, linear terms) and some in grid-point space (advection, non-linear terms, parameterizations). The prognostic variables used are vorticity, divergence, absolute temperature, logarithm of surface pressure and specific humidity. The time stepping uses a leapfrog scheme with additional filters and a semi-implicit formulation for gravity waves. The default resolution is T31 (96x48 grid points on a regular Gaussian grid, about 400km at the Equator) and 8 vertical levels.","category":"page"},{"location":"","page":"Home","title":"Home","text":"Simple parameterizations are used to represent the physical processes convection, large-scale condensation, clouds, short-wave radiation, long-waves radiation, surface fluxes of momentum and energy, and vertical diffusion.","category":"page"},{"location":"#Manual-outline","page":"Home","title":"Manual outline","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"See the following pages of the documentation for more details","category":"page"},{"location":"","page":"Home","title":"Home","text":"Installation\nHow to run SpeedyWeather.jl\nSpherical harmonic transform\nGrids\nDynamical core\nParametrizations\nExtending SpeedyWeather","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the submodules","category":"page"},{"location":"","page":"Home","title":"Home","text":"RingGrids and their interpolation \nLowerTriangularMatrices \nSpeedyTransforms","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the original documentation by Molteni and Kucharski.","category":"page"},{"location":"#Developers","page":"Home","title":"Developers","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The development of SpeedyWeather.jl is lead by Milan Klöwer and current and past contributors include","category":"page"},{"location":"","page":"Home","title":"Home","text":"Tom Kimpson\nAlistair White\nMaximilian Gelbrecht\nDavid Meyer\nDaisuke Hotta\nNavid Constantinou","category":"page"},{"location":"","page":"Home","title":"Home","text":"Any contributions are always welcome!","category":"page"},{"location":"#Funding","page":"Home","title":"Funding","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"MK received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022. Since 2023 this project is also funded by the National Science Foundation NSF.","category":"page"},{"location":"time_integration/#Time-integration","page":"Time integration","title":"Time integration","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"SpeedyWeather.jl uses a leapfrog time scheme with a Robert's and William's filter to dampen the computational mode and achieve 3rd order accuracy.","category":"page"},{"location":"time_integration/#Oscillation-equation","page":"Time integration","title":"Oscillation equation","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"fracdFdt = iomega F","category":"page"},{"location":"time_integration/#Implementation-details","page":"Time integration","title":"Implementation details","text":"","category":"section"}]
+}
diff --git a/previews/PR349/siteinfo.js b/previews/PR349/siteinfo.js
new file mode 100644
index 000000000..b82ce3d08
--- /dev/null
+++ b/previews/PR349/siteinfo.js
@@ -0,0 +1 @@
+var DOCUMENTER_CURRENT_VERSION = "previews/PR349";
diff --git a/previews/PR349/spectral_transform/index.html b/previews/PR349/spectral_transform/index.html
new file mode 100644
index 000000000..893a97e08
--- /dev/null
+++ b/previews/PR349/spectral_transform/index.html
@@ -0,0 +1,45 @@
+
+Spherical harmonic transform · SpeedyWeather.jl
The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space on a longitude-latitude regular Gaussian grid.
with $\lambda_l^m$ being the pre-normalized associated Legendre polynomials, and $e^{im\phi}$ are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.
Latitudes versus colatitudes
The implementations of the spherical transforms in SpeedyWeather.jl use colatitudes $\theta = (0,\pi)$ (0 at the north pole) but the dynamical core uses latitudes $\theta = (-\pi/2,\pi/2)$ ($\pi/2$ at the north pole). However, all arrays are always sorted north to south such that [i,1] will access the northern-most grid points. Note: We may also use latitudes in the spherical harmonic transform in the future for consistency.
The synthesis (or inverse transform) takes the spectral coefficients $a_{lm}$ and transforms them to grid-point values $f(\phi,\theta)$ (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics $Y_{lm}$ with non-zero coefficients.
Spectral packing is the way how the coefficients $a_{lm}$ of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree $l$ and order $m$ as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)
Every row represents an order $l \geq 0$, starting from $l=0$ at the top. Every column represents an order $m \geq 0$, starting from $m=0$ on the left. The coefficients of these spherical harmonics are directly mapped into a matrix $a_{lm}$ as
$m$
$l$
$a_{00}$
$a_{10}$
$a_{11}$
$a_{20}$
$a_{12}$
$a_{22}$
$a_{30}$
$a_{13}$
$a_{23}$
$a_{33}$
which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal explicitly stores zeros. Note that internally vector fields include an additional degree, such that $l_{max} = m_{max} + 1$ (see Gradients in spherical coordinates for more information). The harmonics with $a_{l0}$ (the first column) are also called zonal harmonics as they are constant with longitude $\phi$. The harmonics with $a_{ll}$ (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into $2l$ sectors in longitude $\phi$ without a zero-crossing in latitude.
Array indices
For a spectral field alms note that due to Julia's 1-based indexing the coefficient $a_{lm}$ is obtained via alms[l+1,m+1].
Fortran speedy does not use the same spectral packing as SpeedyWeather.jl. The alternative packing $l',m'$ therein uses $l'=m$ and $m'=l-m$ as summarized in the following table.
degree $l$
order $m$
$l'=m$
$m'=l-m$
0
0
0
0
1
0
0
1
1
1
1
0
2
0
0
2
2
1
1
1
2
2
2
0
3
0
0
3
...
...
...
...
This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices
$m'$
$l'$
$a_{00}$
$a_{10}$
$a_{20}$
$a_{30}$
$a_{11}$
$a_{21}$
$a_{31}$
$a_{22}$
$a_{32}$
$a_{33}$
This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran-speedy.
SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with $l \leq l_{max}$ and $|m| \leq m_{max}$ are explicitly represented. This is usually described as $Tm_{max}$, with $l_{max} = m_{max}$ (although in vector quantities require one more degree $l$ in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with $l_{max} = m_{max} = 31$. Note that the degree $l$ and order $m$ are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.
Using triangular truncation[9], there are constraints on the corresponding grid resolution. Let nlon, nlat be the number of longitudes, latitudes on a regular Gaussian grid. Then spectral and grid resolution have to be chosen such that
$nlon \geq 3l_{max}+1$
$nlat \geq (3l_{max}+1)/2$
In general, we choose $nlon = 2nlat$, and ideally $nlon$ is easily Fourier-transformable, e.g. $nlon = 2^i3^j5^k$ with some integers $i,j,k \geq 0$. SpeedyWeather.jl is tested at the following horizontal resolutions, with $\Delta x = \tfrac{2\pi R}{nlon}$ as the approximate grid spacing at the Equator
$l_{max}$
nlon
nlat
$\Delta x$
31 (default)
96
48
400 km
42
128
64
300 km
85
256
128
160 km
170
512
256
80 km
341
1024
512
40 km
682
2048
1024
20 km
Choosing trunc as argument in run_speedy will automatically choose nlon,nlat as presented in the table. Other common choices are T63 (192x96), T127 (384x192), T255 (768x384), T511 (1536x768), among others.
The radius of the sphere (i.e. Earth) is $R$. The zonal gradient scales with $1/\cos(\theta)$ as the longitudes converge towards the poles (note that $\theta$ describes latitudes here, defintions using colatitudes replace the $\cos$ with a $\sin$.)
Starting with a spectral field of vorticity $\zeta$ and divergence $\mathcal{D}$ one can obtain stream function $\Psi$ and velocity potential $\Phi$ by inverting the Laplace operator $\nabla^2$:
The velocities $u,v$ are then obtained from $(u,v) = \nabla^\bot\Psi + \nabla\Phi$ following the defintion from above and $\nabla^\bot = (-R^{-1}\partial_\theta, (R\cos\theta)^{-1}\partial_\lambda)$
Alternatively, we can use the velocities $U = u\cos\theta, V = v\cos\theta$, which we do as the meridional gradient for spherical harmonics is easier implemented with a $\cos\theta$-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with $U,V$ and not $u,v$. From $u,v$ we can return to $\zeta, \mathcal{D}$ via
\[\begin{aligned}
+\zeta &= \frac{1}{R\cos\theta}\partial_\lambda v - \frac{1}{R\cos\theta}\partial_\theta (u \cos\theta) \\
+\mathcal{D} &= \frac{1}{R\cos\theta}\partial_\lambda u + \frac{1}{R\cos\theta}\partial_\theta (v \cos\theta).
+\end{aligned}\]
which is a more convenient formulation as required $\cos\theta$ scalings are reduced to a minimum. The remaining $(U,V)*\cos^{-2}\theta$ are done in grid-point space and usually in combination with other operations like the computation of the vorticity flux. But also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out too. As described in Meridional derivative, it is more convenient to implement $\cos\theta \partial_\theta$ via a recursion relation for the Legendre polynomials than $\partial_\theta$ directly. How the operators $\nabla, \nabla \times, \nabla \cdot$ can be implemented with spherical harmonics is presented in the following sections.
The zonal derivative of a scalar field $\Psi$ in spectral space is the zonal derivative of all its respective spherical harmonics $\Psi_{lm}(\phi,\theta)$ (now we use $\phi$ for longitudes to avoid confusion with the Legendre polynomials $\lambda_{lm}$)
So for every spectral harmonic, $\cos(\theta)v_{lm}$ is obtained from $\Psi_{lm}$ via a multiplication with $im/R$. Unscaling the $\cos(\theta)$-factor is done after transforming the spectral coefficients $v_{lm}$ into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as $\tilde{\Psi} = R^{-1}\Psi$ such that the division by radius $R$ in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order $im$. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number $m$ times imaginary $i$.
The meridioinal derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]
In the following we use the example of obtaining the zonal velocity $u$ from the stream function $\Psi$, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion
To obtain the coefficient of each spherical harmonic $l,m$ of the meridional gradient of a spectral field, two coefficients at $l-1,m$ and $l+1,m$ have to be combined. This means that the coefficient of a gradient $((\cos\theta) u)_{lm}$ is a linear combination of the coefficients of one higher and one lower degree $\Psi_{l+1,m},\Psi_{l-1,m}$. As the coefficient $\Psi_{lm}$ with $m<l$ are zero, the sectoral harmonics ($l=m$) of the gradients are obtained from the first off-diagonal only. However, the $l=l_{max}$ harmonics of the gradients require the $l_{max}-1$ as well as the $l_{max}+1$ harmonics. In SpeedyWeather.jl vector quantitie like $u,v$ use therefore one more meridional mode than scalar quantities such as vorticity $\zeta$ or stream function $\Psi$. The meridional derivative in SpeedyWeather.jl also omits the $1/R$-scaling as explained for the Zonal derivative and in Radius scaling.
The meridional gradient as described above can be applied to scalars, such as $\Psi$ and $\Phi$ in the conversion to velocities $(u,v) = \nabla^\bot\Psi + \nabla\Phi$, however, the operators curl $\nabla \times$ and divergence $\nabla \cdot$ in spherical coordinates involve a $\cos\theta$ scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].
Given that $R\zeta = \cos^{-1}\partial_\lambda v - \cos^{-1}\partial_\theta (u \cos\theta)$, we therefore have to evaluate a meridional integral of the form
Now we expand $(\tfrac{u}{\cos\theta})$ but only the $l,m$ harmonic will project onto$P_{l,m}$. Let $u^* = u\cos^{-1}\theta, v^* = v\cos^{-1}\theta$ we then have in total
And the divergence $D$ is similar, but $(u,v) \to (-v,u)$. We have moved the scaling with the radius $R$ directly into $\zeta,D$ as further described in Radius scaling.
The spectral Laplacian is easily applied to the coefficients $\Psi_{lm}$ of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator $\nabla^2$ in spherical coordinates with eigenvalues $-l(l+1)$ divided by the radius squared $R^2$, i.e. $\nabla^2 \Psi$ becomes $\tfrac{-l(l+1)}{R^2}\Psi_{lm}$ in spectral space. For example, vorticity $\zeta$ and streamfunction $\Psi$ are related by $\zeta = \nabla^2\Psi$ in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree $l$ and order $m$ to
\[\zeta_{l,m} = \frac{-l(l+1)}{R^2}\Psi_{l,m}\]
This can be easily inverted to obtain the stream function $\Psi$ from vorticity $\zeta$ instead. In order to avoid division by zero, we set $\Psi_{0,0}$ here, given that the stream function is only defined up to a constant anyway.
After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity $\zeta$ and divergence $D$ (which are prognostic variables) to $U=u\cos\theta, V=v\cos\theta$. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree $l$ (the meridional gradient). It is therefore computationally more efficient to compute $U,V$ directly from $\zeta,D$ instead of calculating stream function and velocity potential first. In total we have
SpeedyTransforms is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it) and can also be used without running simulations. It is just not put into its own respective repository.
The barotropic vorticity model describes the evolution of a 2D non-divergent flow with velocity components $\mathbf{u} = (u,v)$ through self-advection, forces and dissipation. Due to the non-divergent nature of the flow, it can be described by (the vertical component) of the relative vorticity $\zeta = \nabla \times \mathbf{u}$.
The dynamical core presented here to solve the barotropic vorticity equations largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2].
The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity $\zeta$ with advection, Coriolis force, forcing and diffusion in a single global layer on the sphere.
We denote time$t$, velocity vector $\mathbf{u} = (u, v)$, Coriolis parameter $f$, and hyperdiffusion $(-1)^{n+1} \nu \nabla^{2n} \zeta$ ($n$ is the hyperdiffusion order, see Horizontal diffusion). We also include a forcing vector $\mathbf{F} = (F_u,F_v)$ which acts on the zonal velocity $u$ and the meridional velocity $v$ and hence its curl $\nabla \times \mathbf{F}$ is a tendency for relative vorticity $\zeta$.
Starting with some relative vorticity $\zeta$, the Laplacian is inverted to obtain the stream function $\Psi$
\[\Psi = \nabla^{-2}\zeta\]
The zonal velocity $u$ and meridional velocity $v$ are then the (negative) meridional gradient and zonal gradient of $\Psi$
which is described in Derivatives in spherical coordinates. Using $u$ and $v$ we can then advect the absolute vorticity $\zeta + f$. In order to avoid to calculate both the curl and the divergence of a flux we rewrite the barotropic vorticity equation as
with $\mathbf{u}_\perp = (v,-u)$ the rotated velocity vector, because $-\nabla\cdot\mathbf{u} = \nabla \times \mathbf{u}_\perp$. This is the form that is solved in the BarotropicModel, as outlined in the following section.
In SpeedyWeather.jl we use hyerdiffusion through an $n$-th power Laplacian $(-1)^{n+1}\nabla^{2n}$ (hyper when $n>1$) which can be implemented as a multiplication of the spectral coefficients $\Psi_{lm}$ with $(-l(l+1))^nR^{-2n}$ (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the $(-l(l+1))^nR^{-2n}$ can be precomputed. Note the sign change $(-1)^{n+1}$ here is such that the dissipative nature of the diffusion operator is retained for $n$ odd and even.
In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step $\Delta t$ of variable $\zeta$ to obtain from time steps $i-1$ and $i$, the next time step $i+1$
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t d\zeta,\]
with $d\zeta$ being some tendency evaluated from $\zeta_i$. Now we want to add a diffusion term $(-1)^{n+1}\nu \nabla^{2n}\zeta$ with coefficient $\nu$, which however, is implicitly calculated from $\zeta_{i+1}$, then
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t (d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i+1})\]
As the application of $(-1)^{n+1}\nu\nabla^{2n}$ is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to
\[\zeta_{i+1} = \frac{\zeta_{i-1} + 2\Delta t d\zeta}{1 - 2\Delta (-1)^{n+1}\nu\nabla^{2n}},\]
and expand the numerator to
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t \frac{d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t (-1)^{n+1}\nu \nabla^{2n}},\]
Hence the diffusion can be applied implicitly by updating the tendency $d\zeta$ as
\[d\zeta \to \frac{d\zeta + (-1)^{n+1}\nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t \nu \nabla^{2n}}\]
which only depends on $\zeta_{i-1}$. Now let $D_\text{explicit} = (-1)^{n+1}\nu\nabla^{2n}$ be the explicit part and $D_\text{implicit} = 1 - (-1)^{n+1} 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are $D_\text{implicit} = 1 - 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic $l,m$ we do
Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power $n$.
In physics, the Laplace operator $\nabla^2$ is often used to represent diffusion due to viscosity in a fluid or diffusion that needs to be added to retain numerical stability. In both cases, the coefficient is $\nu$ of units $\text{m}^2\text{s}^{-1}$ and the full operator reads as $\nu \nabla^2$ with units $(\text{m}^2\text{s}^{-1})(\text{m}^{-2}) = \text{s}^{-1}$. This motivates us to normalize the Laplace operator by a constant of units $\text{m}^{-2}$ and the coefficient by its inverse such that it becomes a damping timescale of unit $\text{s}^{-1}$. Given the application in spectral space we decide to normalize by the largest eigenvalue $-l_\text{max}(l_\text{max}+1)$ such that all entries in the discrete spectral Laplace operator are in $[0,1]$. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized coefficient $\nu^* = l_\text{max}(l_\text{max}+1)\nu$ (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have
and the implicit part is accordingly $D^\text{implicit,n}_{l,m} = 1 - 2\Delta t D^\text{explicit,n}_{l,m}$. Note that the diffusion time scale $\nu^*$ is then also scaled by the radius, see next section.
Similar to a non-dimensionalization of the equations, SpeedyWeather.jl scales the barotropic vorticity equation with $R^2$ to obtain normalized gradient operators as follows. A scaling for vorticity $\zeta$ and stream function $\Psi$ is used that is
This is also convenient as vorticity is often $10^{-5}\text{ s}^{-1}$ in the atmosphere, but the streamfunction more like $10^5\text{ m}^2\text{ s}^{-1}$ and so this scaling brings both closer to 1 with a typical radius of the Earth of 6371km. The inversion of the Laplacians in order to obtain $\Psi$ from $\zeta$ therefore becomes
\[\tilde{\zeta} = \tilde{\nabla}^2 \tilde{\Psi}\]
where the dimensionless gradients simply omit the scaling with $1/R$, $\tilde{\nabla} = R\nabla$. The Barotropic vorticity equation scaled with $R^2$ is
$\mathbf{u} = (u,v)$, the velocity vector (no scaling applied)
$\tilde{f} = fR$, the scaled Coriolis parameter $f$
$\tilde{\mathbf{F}} = R\mathbf{F}$, the scaled forcing vector $\mathbf{F}$
$\tilde{\nu} = \nu^* R$, the scaled diffusion coefficient $\nu^*$, which itself is normalized to a damping time scale, see Normalization of diffusion.
So scaling with the radius squared means we can use dimensionless operators, however, this comes at the cost of needing to deal with both a time step in seconds as well as a scaled time step in seconds per meter, which can be confusing. Furthermore, some constants like Coriolis or the diffusion coefficient need to be scaled too during initialisation, which may be confusing too because values are not what users expect them to be. SpeedyWeather.jl follows the logic that the scaling to the prognostic variables is only applied just before the time integration and variables are unscaled for output and after the time integration finished. That way, the scaling is hidden as much as possible from the user. In hopefully many other cases it is clearly denoted that a variable or constant is scaled.
meaning we step from the previous time step $i-1$, leapfrogging over the current time step$i$ to the next time step $i+1$ by evaluating the tendencies on the right-hand side $RHS$ at the current time step $i$. The time stepping is done in spectral space. Once the right-hand side $RHS$ is evaluated, leapfrogging is a linear operation, meaning that its simply applied to every spectral coefficient $\zeta_{lm}$ as one would evaluate it on every grid point in grid-point models.
For the Leapfrog time integration two time steps of the prognostic variables have to be stored, $i-1$ and $i$. Time step $i$ is used to evaluate the tendencies which are then added to $i-1$ in a step that also swaps the indices for the next time step $i \to i-1$ and $i+1 \to i$, so that no additional memory than two time steps have to be stored at the same time.
The Leapfrog time integration has to be initialised with an Euler forward step in order to have a second time step $i+1$ available when starting from $i$ to actually leapfrog over. SpeedyWeather.jl therefore does two initial time steps that are different from the leapfrog time steps that follow and that have been described above.
an Euler forward step with $\Delta t/2$, then
one leapfrog time step with $\Delta t$, then
leapfrog with $2 \Delta t$ till the end
This is particularly done in a way that after 2. we have $t=0$ at $i-1$ and $t=\Delta t$ at $i$ available so that 3. can start the leapfrogging without any offset from the intuitive spacing $0,\Delta t, 2\Delta t, 3\Delta t,...$. The following schematic can be useful
time at step $i-1$
time at step $i$
time step at $i+1$
Initial conditions
$t = 0$
1: Euler
(T) $\quad t = 0$
$t=\Delta t/2$
2: Leapfrog with $\Delta t$
$t = 0$
(T) $\quad t = \Delta t/2$
$t = \Delta t$
3 to $n$: Leapfrog with $2\Delta t$
$t-\Delta t$
(T) $\qquad \quad \quad t$
$t+\Delta t$
The time step that is used to evaluate the tendencies is denoted with (T). It is always the time step furthest in time that is available.
The standard leapfrog time integration is often combined with a Robert-Asselin filter[Robert66][Asselin72] to dampen a computational mode. The idea is to start with a standard leapfrog step to obtain the next time step $i+1$ but then to correct the current time step $i$ by applying a filter which dampens the computational mode. The filter looks like a discrete Laplacian in time with a $(1, -2, 1)$ stencil, and so, maybe unsurprisingly, is efficient to filter out a "grid-scale oscillation" in time, aka the computational mode. Let $v$ be the unfiltered variable and $u$ be the filtered variable, $F$ the right-hand side tendency, then the standard leapfrog step is
\[v_{i+1} = u_{i-1} + 2\Delta tF(v_i)\]
Meaning we start with a filtered variable $u$ at the previous time step $i-1$, evaluate the tendency $F(v_i)$ based on the current time step $i$ to obtain an unfiltered next time step $v_{i+1}$. We then filter the current time step $i$ (which will become $i-1$ on the next iteration)
by adding a discrete Laplacian with coefficient $\tfrac{\nu}{2}$ to it, evaluated from the available filtered and unfiltered time steps centred around $i$: $v_{i-1}$ is not available anymore because it was overwritten by the filtering at the previous iteration, $u_i, u_{i+1}$ are not filtered yet when applying the Laplacian. The filter parameter $\nu$ is typically chosen between 0.01-0.2, with stronger filtering for higher values.
Williams[Williams2009] then proposed an additional filter step to regain accuracy that is otherwise lost with a strong Robert-Asselin filter[Amezcua2011][Williams2011]. Now let $w$ be unfiltered, $v$ be once filtered, and $u$ twice filtered, then
with the Williams filter parameter $\alpha \in [0.5,1]$. For $\alpha=1$ we're back with the Robert-Asselin filter (the first two lines).
The Laplacian in the parentheses is often called a displacement, meaning that the filtered value is displaced (or corrected) in the direction of the two surrounding time steps. The Williams filter now also applies the same displacement, but in the opposite direction to the next time step $i+1$ as a correction step (line 3 above) for a once-filtered value $v_{i+1}$ which will then be twice-filtered by the Robert-Asselin filter on the next iteration. For more details see the referenced publications.
The initial Euler step (see Time integration, Table) is not filtered. Both the the Robert-Asselin and Williams filter are then switched on for all following leapfrog time steps.
Robert66Robert, André. “The Integration of a Low Order Spectral Form of the Primitive Meteorological Equations.” Journal of the Meteorological Society of Japan 44 (1966): 237-245.
Williams2009Williams, P. D., 2009: A Proposed Modification to the Robert–Asselin Time Filter. Mon. Wea. Rev., 137, 2538–2546, 10.1175/2009MWR2724.1.
Amezcua2011Amezcua, J., E. Kalnay, and P. D. Williams, 2011: The Effects of the RAW Filter on the Climatology and Forecast Skill of the SPEEDY Model. Mon. Wea. Rev., 139, 608–619, doi:10.1175/2010MWR3530.1.
Williams2011Williams, P. D., 2011: The RAW Filter: An Improvement to the Robert–Asselin Filter in Semi-Implicit Integrations. Mon. Wea. Rev., 139, 1996–2007, doi:10.1175/2010MWR3601.1.
Settings
This document was generated with Documenter.jl version 0.27.24 on Tuesday 20 June 2023. Using Julia version 1.8.5.
diff --git a/previews/PR360/conventions/index.html b/previews/PR360/conventions/index.html
new file mode 100644
index 000000000..4532e8893
--- /dev/null
+++ b/previews/PR360/conventions/index.html
@@ -0,0 +1,12 @@
+
+Style and convention guide · SpeedyWeather.jl
The prognostic variables in spectral space are called
vor # Vorticity of horizontal wind field
+ div # Divergence of horizontal wind field
+ temp # Absolute temperature [K]
+ pres_surf # Logarithm of surface pressure [log(Pa)]
+ humid # Specific humidity [g/kg]
their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are
We follow Julia's style guide and highlight here some important aspects of it.
Bang (!) convention. A function func does not change its input arguments, however, func! does.
Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.
Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup
but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.
Settings
This document was generated with Documenter.jl version 0.27.24 on Tuesday 20 June 2023. Using Julia version 1.8.5.
This document was generated with Documenter.jl version 0.27.24 on Tuesday 20 June 2023. Using Julia version 1.8.5.
diff --git a/previews/PR360/functions/index.html b/previews/PR360/functions/index.html
new file mode 100644
index 000000000..3156cad3b
--- /dev/null
+++ b/previews/PR360/functions/index.html
@@ -0,0 +1,634 @@
+
+Function and type index · SpeedyWeather.jl
The BarotropicModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.
spectral_grid::SpectralGrid: dictates resolution for many other components
planet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics
atmosphere::SpeedyWeather.AbstractAtmosphere
forcing::SpeedyWeather.AbstractForcing{NF} where NF<:AbstractFloat
Mutable struct that contains all prognostic (copies thereof) and diagnostic variables in a single column needed to evaluate the physical parametrizations. For now the struct is mutable as we will reuse the struct to iterate over horizontal grid points. Every column vector has nlev entries, from [1] at the top to [end] at the lowermost model level at the planetary boundary layer.
nlev::Int64
nband::Int64
n_stratosphere_levels::Int64
jring::Int64
lond::AbstractFloat
latd::AbstractFloat
u::Vector{NF} where NF<:AbstractFloat
v::Vector{NF} where NF<:AbstractFloat
temp::Vector{NF} where NF<:AbstractFloat
humid::Vector{NF} where NF<:AbstractFloat
ln_pres::Vector{NF} where NF<:AbstractFloat
pres::Vector{NF} where NF<:AbstractFloat
u_tend::Vector{NF} where NF<:AbstractFloat
v_tend::Vector{NF} where NF<:AbstractFloat
temp_tend::Vector{NF} where NF<:AbstractFloat
humid_tend::Vector{NF} where NF<:AbstractFloat
geopot::Vector{NF} where NF<:AbstractFloat
flux_u_upward::Vector{NF} where NF<:AbstractFloat
flux_u_downward::Vector{NF} where NF<:AbstractFloat
flux_v_upward::Vector{NF} where NF<:AbstractFloat
flux_v_downward::Vector{NF} where NF<:AbstractFloat
flux_temp_upward::Vector{NF} where NF<:AbstractFloat
flux_temp_downward::Vector{NF} where NF<:AbstractFloat
flux_humid_upward::Vector{NF} where NF<:AbstractFloat
flux_humid_downward::Vector{NF} where NF<:AbstractFloat
sat_humid::Vector{NF} where NF<:AbstractFloat
sat_vap_pres::Vector{NF} where NF<:AbstractFloat
dry_static_energy::Vector{NF} where NF<:AbstractFloat
moist_static_energy::Vector{NF} where NF<:AbstractFloat
humid_half::Vector{NF} where NF<:AbstractFloat
sat_humid_half::Vector{NF} where NF<:AbstractFloat
sat_moist_static_energy::Vector{NF} where NF<:AbstractFloat
dry_static_energy_half::Vector{NF} where NF<:AbstractFloat
sat_moist_static_energy_half::Vector{NF} where NF<:AbstractFloat
conditional_instability::Bool
activate_convection::Bool
cloud_top::Int64
excess_humidity::AbstractFloat
cloud_base_mass_flux::AbstractFloat
precip_convection::AbstractFloat
net_flux_humid::Vector{NF} where NF<:AbstractFloat
net_flux_dry_static_energy::Vector{NF} where NF<:AbstractFloat
entrainment_profile::Vector{NF} where NF<:AbstractFloat
Create a struct Earth<:AbstractPlanet, with the following physical/orbital characteristics. Note that radius is not part of it as this should be chosen in SpectralGrid. Keyword arguments are
rotation::Float64: angular frequency of Earth's rotation [rad/s]
Create a struct EarthAtmosphere<:AbstractPlanet, with the following physical/chemical characteristics. Note that radius is not part of it as this should be chosen in SpectralGrid. Keyword arguments are
mol_mass_dry_air::Float64: molar mass of dry air [g/mol]
mol_mass_vapour::Float64: molar mass of water vapour [g/mol]
cₚ::Float64: specific heat at constant pressure [J/K/kg]
R_gas::Float64: universal gas constant [J/K/mol]
R_dry::Float64: specific gas constant for dry air [J/kg/K]
R_vapour::Float64: specific gas constant for water vapour [J/kg/K]
water_density::Float64: water density [kg/m³]
latent_heat_condensation::Float64: latent heat of condensation [J/g] for consistency with specific humidity [g/Kg], also called alhc
latent_heat_sublimation::Float64: latent heat of sublimation [J/g], also called alhs
Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF.
Construct Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. Pass on SpectralGrid to calculate the following fields
spectral_grid::SpectralGrid: SpectralGrid that defines spectral and grid resolution
Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid of the dynamical core
nlat_half::Int64: resolution parameter nlat_half of Grid, # of latitudes on one hemisphere (incl Equator)
nlon_max::Int64: maximum number of longitudes (at/around Equator)
nlon::Int64: =nlon_max, same (used for compatibility), TODO: still needed?
nlat::Int64: number of latitude rings
nlev::Int64: number of vertical levels
npoints::Int64: total number of grid points
radius::AbstractFloat: Planet's radius [m]
latd::Vector{Float64}: array of latitudes in degrees (90˚...-90˚)
lond::Vector{Float64}: array of longitudes in degrees (0...360˚), empty for non-full grids
londs::Vector{NF} where NF<:AbstractFloat: longitude (-180˚...180˚) for each grid point in ring order
latds::Vector{NF} where NF<:AbstractFloat: latitude (-90˚...˚90) for each grid point in ring order
sinlat::Vector{NF} where NF<:AbstractFloat: sin of latitudes
coslat::Vector{NF} where NF<:AbstractFloat: cos of latitudes
coslat⁻¹::Vector{NF} where NF<:AbstractFloat: = 1/cos(lat)
coslat²::Vector{NF} where NF<:AbstractFloat: = cos²(lat)
coslat⁻²::Vector{NF} where NF<:AbstractFloat: # = 1/cos²(lat)
σ_levels_half::Vector{NF} where NF<:AbstractFloat: σ at half levels, σ_k+1/2
σ_levels_full::Vector{NF} where NF<:AbstractFloat: σ at full levels, σₖ
σ_levels_thick::Vector{NF} where NF<:AbstractFloat: σ level thicknesses, σₖ₊₁ - σₖ
ln_σ_levels_full::Vector{NF} where NF<:AbstractFloat: log of σ at full levels, include surface (σ=1) as last element
Struct for horizontal hyper diffusion of vor, div, temp; implicitly in spectral space with a power of the Laplacian (default=4) and the strength controlled by time_scale. Options exist to scale the diffusion by resolution, and adaptive depending on the current vorticity maximum to increase diffusion in active layers. Furthermore the power can be decreased above the tapering_σ to power_stratosphere (default 2). For Barotropic, ShallowWater, the default non-adaptive constant-time scale hyper diffusion is used. Options are
trunc::Int64: spectral resolution
nlev::Int64: number of vertical levels
power::Float64: power of Laplacian
time_scale::Float64: diffusion time scales [hrs]
resolution_scaling::Float64: stronger diffusion with resolution? 0: constant with trunc, 1: (inverse) linear with trunc, etc
power_stratosphere::Float64: different power for tropopause/stratosphere
tapering_σ::Float64: linearly scale towards power_stratosphere above this σ
adaptive::Bool: adaptive = higher diffusion for layers with higher vorticity levels.
vor_max::Float64: above this (absolute) vorticity level [1/s], diffusion is increased
adaptive_strength::Float64: increase strength above vor_max by this factor times max(abs(vor))/vor_max
Struct that holds various precomputed arrays for the semi-implicit correction to prevent gravity waves from amplifying in the primitive equation model.
NetCDF output writer. Contains all output options and auxiliary fields for output interpolation. To be initialised with OutputWriter(::SpectralGrid,::Type{<:ModelSetup},kwargs...) to pass on the resolution information and the model type which chooses which variables to output. Options include
spectral_grid::SpectralGrid
output::Bool
path::String: [OPTION] path to output folder, run_???? will be created within
id::String: [OPTION] run identification number/string
run_path::String
filename::String: [OPTION] name of the output netcdf file
write_restart::Bool: [OPTION] also write restart file if output==true?
pkg_version::VersionNumber
startdate::Dates.DateTime
output_dt::Float64: [OPTION] output frequency, time step [hrs]
output_dt_sec::Int64: actual output time step [sec]
output_vars::Vector{Symbol}: [OPTION] which variables to output, u, v, vor, div, pres, temp, humid
missing_value::Union{Float32, Float64}: [OPTION] missing value to be used in netcdf output
compression_level::Int64: [OPTION] lossless compression level; 1=low but fast, 9=high but slow
keepbits::SpeedyWeather.Keepbits: [OPTION] mantissa bits to keep for every variable
The PrimitiveDryModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.
spectral_grid::SpectralGrid: dictates resolution for many other components
planet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics
The PrimitiveDryModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.
spectral_grid::SpectralGrid: dictates resolution for many other components
planet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics
The ShallowWaterModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.
spectral_grid::SpectralGrid: dictates resolution for many other components
planet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics
atmosphere::SpeedyWeather.AbstractAtmosphere
forcing::SpeedyWeather.AbstractForcing{NF} where NF<:AbstractFloat
Restart from a previous SpeedyWeather.jl simulation via the restart file restart.jld2 Applies interpolation in the horizontal but not in the vertical. restart.jld2 is identified by
path::String: path for restart file
id::Union{Int64, String}: run_id of restart file in run_????/restart.jld2
Create a struct that contains all parameters for the Galewsky et al, 2004 zonal jet intitial conditions for the shallow water model. Default values as in Galewsky.
latitude::Float64: jet latitude [˚N]
width::Float64: jet width [˚], default ≈ 19.29˚
umax::Float64: jet maximum velocity [m/s]
perturb_lat::Float64: perturbation latitude [˚N], position in jet by default
Create a struct that contains all parameters for the Jablonowski and Williamson, 2006 intitial conditions for the primitive equation model. Default values as in Jablonowski.
η₀::Float64: conversion from σ to Jablonowski's ηᵥ-coordinates
u₀::Float64: max amplitude of zonal wind [m/s]
perturb_lat::Float64: perturbation centred at [˚N]
perturb_lon::Float64: perturbation centred at [˚E]
perturb_uₚ::Float64: perturbation strength [m/s]
perturb_radius::Float64: radius of Gaussian perturbation in units of Earth's radius [1]
ΔT::Float64: temperature difference used for stratospheric lapse rate [K], Jablonowski uses ΔT = 4.8e5 [K]
Tmin::Float64: minimum temperature [K] of profile
pressure_on_orography::Bool: initialize pressure given the atmosphere.lapse_rate on orography?
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for primitive equation models. Updates grid vorticity, grid divergence, grid temperature, pressure (pres_grid) and the velocities u,v.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities u,v.
Vertical sigma coordinates defined by their nlev+1 half levels σ_levels_half. Sigma coordinates are fraction of surface pressure (p/p0) and are sorted from top (stratosphere) to bottom (surface). The first half level is at 0 the last at 1. Evaluate a generalised logistic function with coefficients in P for the distribution of values in between. Default coefficients follow the L31 configuration historically used at ECMWF.
Performs the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.
Calculate the geopotential based on temp in a single column. This exclues the surface geopotential that would need to be added to the returned vector. Function not used in the dynamical core but for post-processing and analysis.
Update C::ColumnVariables by copying the prognostic variables from D::DiagnosticVariables at gridpoint index ij. Provide G::Geometry for coordinate information.
Checks existing run_???? folders in path to determine a 4-digit id number by counting up. E.g. if folder run_0001 exists it will return the string "0002". Does not create a folder for the returned run id.
Calculate thermodynamic quantities like saturation vapour pressure, saturation specific humidity, dry static energy, moist static energy and saturation moist static energy from the prognostic column variables.
Apply horizontal diffusion applied to vorticity, diffusion and temperature in the PrimitiveEquation models. Uses the constant diffusion for temperature but possibly adaptive diffusion for vorticity and divergence.
Apply horizontal diffusion to a 2D field A in spectral space by updating its tendency tendency with an implicitly calculated diffusion term. The implicit diffusion of the next time step is split into an explicit part ∇²ⁿ_expl and an implicit part ∇²ⁿ_impl, such that both can be calculated in a single forward step by using A as well as its tendency tendency.
implicit_correction!(
+ diagn::SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},
+ progn::SpeedyWeather.PrognosticLayerTimesteps{NF},
+ diagn_surface::SpeedyWeather.SurfaceVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},
+ progn_surface::SpeedyWeather.PrognosticSurfaceTimesteps{NF},
+ implicit::SpeedyWeather.ImplicitShallowWater
+)
+
Apply correction to the tendencies in diagn to prevent the gravity waves from amplifying. The correction is implicitly evaluated using the parameter implicit.α to switch between forward, centered implicit or backward evaluation of the gravity wave terms.
Precomputes the hyper diffusion terms in scheme for layer k based on the model time step in L, the vertical level sigma level in G, and the current (absolute) vorticity maximum level vor_max
initialize the JablonowskiRelaxation temperature relaxation by precomputing terms for the equilibrium temperature Teq and the frequency (strength of relaxation).
Calls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.
Calls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.
Calls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.
Creates a netcdf file on disk and the corresponding netcdf_file object preallocated with output variables and dimensions. write_output! then writes consecuitive time steps into this file.
Large-scale condensation for a column by relaxation back to a reference relative humidity if larger than that. Calculates the tendencies for specific humidity and temperature and integrates the large-scale precipitation vertically for output.
So that the second term inside the Laplace operator can be added to the geopotential. Rd is the gas constant, Tᵥ the virtual temperature and Tᵥ' its anomaly wrt to the average or reference temperature Tₖ, lnpₛ is the logarithm of surface pressure.
Linear virtual temperature for model::PrimitiveDry: Just copy over arrays from temp to temp_virt at timestep lf in spectral space as humidity is zero in this model.
with the static energy SE, the latent heat of condensation Lc, the geopotential Φ. As well as the saturation moist static energy which replaces Q with Q_sat
Compute tendencies for u,v,temp,humid from physical parametrizations. Extract for each vertical atmospheric column the prognostic variables (stored in diagn as they are grid-point transformed), loop over all grid-points, compute all parametrizations on a single-column basis, then write the tendencies back into a horizontal field of tendencies.
Returns Dates.CompoundPeriod rounding to either (days, hours), (hours, minutes), (minutes, seconds), or seconds with 1 decimal place accuracy for >10s and two for less. E.g.
Compute (1) the saturation vapour pressure as a function of temperature using the August-Roche-Magnus formula,
eᵢ(T) = e₀ * exp(Cᵢ * (T - T₀) / (T - Tᵢ)),
where T is in Kelvin and i = 1,2 for saturation with respect to water and ice, respectively. And (2) the saturation specific humidity according to the formula,
0.622 * e / (p - (1 - 0.622) * e),
where e is the saturation vapour pressure, p is the pressure, and 0.622 is the ratio of the molecular weight of water to dry air.
Sets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.
set_var!(progn::PrognosticVariables{NF},
+ varname::Symbol,
+ var::Vector{<:LowerTriangularMatrix};
+ lf::Integer=1) where NF
Sets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in spectral space.
Sets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.
set_var!(progn::PrognosticVariables{NF},
+ varname::Symbol,
+ var::Vector{<:AbstractGrid};
+ lf::Integer=1) where NF
Sets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.
Computes the tendency of the logarithm of surface pressure as
-(ū*px + v̄*py) - D̄
with ū,v̄ being the vertically averaged velocities; px, py the gradients of the logarithm of surface pressure ln(p_s) and D̄ the vertically averaged divergence.
Calculate ∇ln(p_s) in spectral space, convert to grid.
Multiply ū,v̄ with ∇ln(p_s) in grid-point space, convert to spectral.
D̄ is subtracted in spectral space.
Set tendency of the l=m=0 mode to 0 for better mass conservation.
+= because the tendencies already contain parameterizations and vertical advection. T' is the anomaly with respect to the reference/average temperature. Tᵥ is the virtual temperature used in the adiabatic term κTᵥ*Dlnp/Dt.
Virtual temperature in grid-point space: For the PrimitiveDry temperature and virtual temperature are the same (humidity=0). Just copy over the arrays.
Tendencies for vorticity and divergence. Excluding Bernoulli potential with geopotential and linear pressure gradient inside the Laplace operator, which are added later in spectral space.
+= because the tendencies already contain the parameterizations and vertical advection. f is coriolis, ζ relative vorticity, R the gas constant Tᵥ' the virtual temperature anomaly, ∇lnp the gradient of surface pressure and _x and _y its zonal/meridional components. The tendencies are then curled/dived to get the tendencies for vorticity/divergence in spectral space
with Fᵤ,Fᵥ from u_tend_grid/v_tend_grid that are assumed to be alread set in forcing!. Set div=false for the BarotropicModel which doesn't require the divergence tendency.
Write the parametrization tendencies from C::ColumnVariables into the horizontal fields of tendencies stored in D::DiagnosticVariables at gridpoint index ij.
Writes the variables from diagn of time step i at time time into outputter.netcdf_file. Simply escapes for no netcdf output of if output shouldn't be written on this time step. Interpolates onto output grid and resolution as specified in outputter, converts to output number format, truncates the mantissa for higher compression and applies lossless compression.
A restart file restart.jld2 with the prognostic variables is written to the output folder (or current path) that can be used to restart the model. restart.jld2 will then be used as initial conditions. The prognostic variables are bitrounded for compression and the 2nd leapfrog time step is discarded. Variables in restart file are unscaled.
The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and but let's start but how they can be used
The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object which defines the resolution in spectral and in grid-point space. The generator SpectralGrid() can take as a keyword argument Grid which can be any of the grids described below. The resolution of the grid, however, is not directly chosen, but determined from the spectral resolution trunc and the dealiasing factor. More in Matching spectral and grid resolution.
RingGrids is a module too!
While RingGrids is the underlying module that SpeedyWeather.jl uses for data structs on the sphere, the module can also be used independently of SpeedyWeather, for example to interpolate between data on different grids. See RingGrids
SpeedyWeather.jl's spectral transform supports all ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on these rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.
Is the FullClenshawGrid a longitude-latitude grid?
Short answer: Yes. The FullClenshawGrid is a specific longitude-latitude grid with equi-angle spacing. The most common grids for geoscientific data use regular spacings for 0-360˚E in longitude and 90˚N-90˚S. The FullClenshawGrid does that too, but it does not have a point on the North or South pole, and the central latitude ring sits exactly on the Equator. We name it Clenshaw following the Clenshaw-Curtis quadrature that is used in the Legendre transfrom in the same way as Gaussian refers to the Gaussian quadrature.
All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).
Currently the following full grids <: AbstractFullGrid are implemented
FullGaussianGrid, a full grid with Gaussian latitudes
FullClenshawGrid, a full grid with equi-angle latitudes
and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are
OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron
OctahedralClenshawGrid, similar but based on equi-angle latitudes
HEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces
OctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.
An overview of these grids is visualised here
Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.
All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half.
HEALPix grids do not use Nside as resolution parameter
The original formulation for HEALPix grids use $N_{side}$, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use $N_{side}$ for the documentation or within functions though.
A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation $T$ with a grid resolution $N$ (=nlat_half) such that $T + 1 = N$. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at $l_{max}=31$ in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid. In SpeedyWeather.jl the choice of the order of truncation is controlled with the dealiasing parameter in the SpectralGrid construction.
Let J be the total number of rings. Then we have
$T \approx J$ for linear truncation, i.e. dealiasing = 1
$\frac{3}{2}T \approx J$ for quadratic truncation, i.e. dealiasing = 2
$2T \approx J$ for cubic truncation, , i.e. dealiasing = 3
and in general $\frac{m+1}{2}T \approx J$ for m-th order truncation. So the higher the truncation order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. A quick overview of how the grid resolution changes when dealiasing is passed onto SpectralGrid on the FullGaussianGrid
trunc
dealiasing
FullGaussianGrid size
31
1
64x32
31
2
96x48
31
3
128x64
42
1
96x48
42
2
128x64
42
3
192x96
...
...
...
You will obtain this information every time you create a SpectralGrid(;Grid,trunc,dealiasing).
Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are $N_\varphi$ basepixels in zonal direction and $N_\theta$ basepixels in meridional direction. For $N_\varphi = 4$ and $N_\theta = 3$ we obtain the classical HEALPix grid with $N_\varphi N_\theta = 12$ basepixels shown above in Implemented grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always $2N$, so 32 at $N=16$) and there are polar caps above and below the equatorial zone with the border at $\cos(\theta) = 2/3$ ($\theta$ in colatitudes).
Following Górski, 2004[1], the $z=cos(\theta)$ colatitude of the $j$-th ring in the north polar cap, $j=1,...,N_{side}$ with $2N_{side} = N$ is
\[z = 1 - \frac{j^2}{3N_{side}^2}\]
and on that ring, the longitude $\phi$ of the $i$-th point ($i$ is the in-ring-index) is at
\[\phi = \frac{\pi}{2j}(i-\tfrac{1}{2})\]
The in-ring index $i$ goes from $i=1,...,4$ for the first (i.e. northern-most) ring, $i=1,...,8$ for the second ring and $i = 1,...,4j$ for the $j$-th ring in the northern polar cap.
In the north equatorial belt $j=N_{side},...,2N_{side}$ this changes to
\[z = \frac{4}{3} - \frac{2j}{3N_{side}}\]
and the longitudes change to ($i$ is always $i = 1,...,4N_{side}$ in the equatorial belt meaning the number of longitude points is constant here)
The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.
The cell boundaries are obtained by setting $i = k + 1/2$ or $i = k + 1/2 + j$ (half indices) into the equations above, such that $z(\phi,k)$, a function for the cosine of colatitude $z$ of index $k$ and the longitude $\phi$ is obtained. These are then (northern polar cap)
While the classic HEALPix grid is based on a dodecahedron, other choices for $N_\varphi$ and $N_\theta$ in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With $N_\varphi = 4$ and $N_\theta = 1$ we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, $2\pi$ around the Equator versus $\pi$ between the poles. $N_\varphi = 6, N_\theta = 2$ or $N_\varphi = 8, N_\theta = 3$ are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visualizations of these grids.
We call the $N_\varphi = 4, N_\theta = 1$ HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As $N_\theta = 1$ there is no equatorial belt which simplifies the grid. The latitude of the $j$-th isolatitude ring on the OctaHEALPixGrid is defined by
\[z = 1 - \frac{j^2}{N^2},\]
with $j=1,...,N$, and similarly for the southern hemisphere by symmetry. On this grid $N_{side} = N$ where $N$= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index $i = 1,...,4j$ are
\[\phi = \frac{\pi}{2j}(i - \tfrac{1}{2})\]
and again, the southern hemisphere grid points are obtained by symmetry.
The $3N_{side}^2$ in the denominator of the HEALPix grid came simply $N^2$ for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).
1Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976
Settings
This document was generated with Documenter.jl version 0.27.24 on Tuesday 20 June 2023. Using Julia version 1.8.5.
diff --git a/previews/PR360/how_to_run_speedy/index.html b/previews/PR360/how_to_run_speedy/index.html
new file mode 100644
index 000000000..dcecc5b18
--- /dev/null
+++ b/previews/PR360/how_to_run_speedy/index.html
@@ -0,0 +1,57 @@
+
+How to run SpeedyWeather.jl · SpeedyWeather.jl
We want to use the barotropic model to simulate some free-decaying 2D turbulence on the sphere without rotation. We start by defining the SpectralGrid object. To have a resolution of about 100km, we choose a spectral resolution of T127 (see Available horizontal resolutions) and nlev=1 vertical levels. The SpectralGrid object will provide us with some more information
There are other options to create a planet but they are irrelevant for the barotropic vorticity equations. We also want to specify the initial conditions, randomly distributed vorticity is already defined
By default, the power of vorticity is spectrally distributed with $k^{-3}$, $k$ being the horizontal wavenumber, and the amplitude is $10^{-5}\text{ s}^{-1}$.
Now we want to construct a BarotropicModel with these
julia> model = BarotropicModel(;spectral_grid, initial_conditions, planet=still_earth);
The model contains all the parameters, but isn't initialized yet, which we can do with and then run it.
The run! command will always return the prognostic variables, which, by default, are plotted for surface relative vorticity with a unicode plot. The resolution of the plot is not necessarily representative but it lets us have a quick look at the result
Woohoo! I can see turbulence! You could pick up where this simulation stopped by simply doing run!(simulation,n_days=50) again. We didn't store any output, which you can do by run!(simulation,output=true), which will switch on NetCDF output with default settings. More options on output in NetCDF output.
As a second example, let's investigate the Galewsky et al.[1] test case for the shallow water equations with and without mountains. As the shallow water system has also only one level, we can reuse the SpectralGrid from Example 1.
Although the orography is zero, you have to pass on spectral_grid so that it can still initialize zero-arrays of the right size and element type. Awesome. This time the initial conditions should be set the the Galewsky et al.[1] zonal jet, which is already defined as
The jet sits at 45˚N with a maximum velocity of 80m/s and a perturbation as described in their paper. Now we construct a model, but this time a ShallowWaterModel
julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);
+julia> simulation = initialize!(model);
Oh yeah. That looks like the wobbly jet in their paper. Let's run it again for another 6 days but this time also store NetCDF output.
julia> run!(simulation,n_days=6,output=true)
+Weather is speedy: run 0002 100%|███████████████████████| Time: 0:00:12 (115.37 years/day)
The progress bar tells us that the simulation run got the identification "0002", meaning that data is stored in the folder /run_0002, so let's plot that data properly (and not just using UnicodePlots).
julia> using PyPlot, NCDatasets
+julia> ds = NCDataset("run_0002/output.nc");
+julia> ds["vor"]
+vor (384 × 192 × 1 × 25)
+ Datatype: Float32
+ Dimensions: lon × lat × lev × time
+ Attributes:
+ units = 1/s
+ missing_value = NaN
+ long_name = relative vorticity
+ _FillValue = NaN
Vorticity vor is stored as a 384x192x1x25 array, we may want to look at the first time step, which is the end of the previous simulation (time=6days) which we didn't store output for.
julia> vor = ds["vor"][:,:,1,1];
+julia> lat = ds["lat"][:];
+julia> lon = ds["lon"][:];
+julia> pcolormesh(lon,lat,vor')
Which looks like
You see that the unicode plot heavily coarse-grains the simulation, well it's unicode after all! And now the last time step, that means time=12days is
julia> vor = ds["vor"][:,:,1,25];
+julia> pcolormesh(lon,lat,vor')
The jet broke up into many small eddies, but the turbulence is still confined to the northern hemisphere, cool! How this may change when we add mountains (we had NoOrography above!), say Earth's orography, you may ask? Let's try it out! We create an EarthOrography struct like so
It will read the orography from file as shown, and there are some smoothing options too, but let's not change them. Same as before, create a model, initialize into a simulation, run. This time directly for 12 days so that we can compare with the last plot
julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);
+julia> simulation = initialize!(model);
+julia> run!(simulation,n_days=12,output=true)
+Weather is speedy: run 0003 100%|███████████████████████| Time: 0:00:35 (79.16 years/day)
This time the run got the id "0003", but otherwise we do as before.
Interesting! The initial conditions have zero velocity in the southern hemisphere, but still, one can see some imprint of the orography on vorticity. You can spot the coastline of Antarctica; the Andes and Greenland are somewhat visible too. Mountains also completely changed the flow after 12 days, probably not surprising!
[1] Galewsky, Scott, Polvani, 2004. An initial-value problem for testing numerical models of the global shallow-water equations, Tellus A. DOI: 10.3402/tellusa.v56i5.14436
Settings
This document was generated with Documenter.jl version 0.27.24 on Tuesday 20 June 2023. Using Julia version 1.8.5.
Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.
SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to simulate the general circulation of the atmosphere. The prognostic variables used are vorticity, divergence, temperature, surface pressure and specific humidity. Simple parameterizations represent various climate processes: Radiation, clouds, precipitation, surface fluxes, among others.
SpeedyWeather.jl defines
BarotropicModel for the 2D barotropic vorticity equation
ShallowWaterModel for the 2D shallow water equations
PrimitiveDryModel for the 3D primitive equations without humidity
PrimitiveWetModel for the 3D primitive equations with humidity
and solves these equations in spherical coordinates as described in this documentation.
MK received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022. Since 2023 this project is also funded by the National Science Foundation NSF.
Settings
This document was generated with Documenter.jl version 0.27.24 on Tuesday 20 June 2023. Using Julia version 1.8.5.
LowerTriangularMatrices is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.
This module defines LowerTriangularMatrix, a lower triangular matrix, which in contrast to LinearAlgebra.LowerTriangular does not store the entries above the diagonal. SpeedyWeather.jl uses LowerTriangularMatrix which is defined as a subtype of AbstractMatrix to store the spherical harmonic coefficients (see Spectral packing).
LowerTriangularMatrix supports two types of indexing: 1) by denoting two indices, column and row [l,m] or 2) by denoting a single index [lm]. The double index works as expected
But the single index skips the zero entries in the upper triangle, i.e.
julia> L[4]
+Float16(0.478)
which, important, is different from single indices of an AbstractMatrix
julia> Matrix(L)[4]
+Float16(0.0)
In performance-critical code a single index should be used, as this directly maps to the index of the underlying data vector. The double index is somewhat slower as it first has to be converted to the corresponding single index.
Consequently, many loops in SpeedyWeather.jl are build with the following structure
n,m = size(L)
+ij = 0
+for j in 1:m
+ for i in j:n
+ ij += 1
+ L[ij] = i+j
+ end
+end
which loops over all lower triangle entries of L::LowerTriangularMatrix and the single index ij is simply counted up. However, one could also use [i,j] as indices in the loop body or to perform any calculation (i+j here). An iterator over all entries in the lower triangle can be created by
for ij in eachindex(L)
+ # do something
+end
The setindex! functionality of matrixes will throw a BoundsError when trying to write into the upper triangle of a LowerTriangularMatrix, for example
julia> L[2,1] = 0 # valid index
+0
+
+julia> L[1,2] = 0 # invalid index in the upper triangle
+ERROR: BoundsError: attempt to access 3×3 LowerTriangularMatrix{Float32} at index [1, 2]
The LowerTriangularMatrices module's main purpose is not linear algebra, and it's implementation may not be efficient, however, many operations work as expected
Note, however, that the latter includes a conversion to Matrix, which is true for many operations, including inv or \. Hence when trying to do more sophisticated linear algebra with LowerTriangularMatrix we quickly leave lower triangular-land and go back to normal matrix-land.
L = LowerTriangularMatrix{T}(v::Vector{T},m::Int,n::Int)
A lower triangular matrix implementation that only stores the non-zero entries explicitly. L<:AbstractMatrix although in general we have L[i] != Matrix(L)[i], the former skips zero entries, tha latter includes them.
creates unit_range::UnitRange to loop over all non-zeros in the LowerTriangularMatrices provided as arguments. Checks bounds first. All LowerTriangularMatrix's need to be of the same size. Like eachindex but skips the upper triangle with zeros in L.
k = ij2k( i::Integer, # row index of matrix
+ j::Integer, # column index of matrix
+ m::Integer) # number of rows in matrix
Converts the index pair i,j of an mxn LowerTriangularMatrix L to a single index k that indexes the same element in the corresponding vector that stores only the lower triangle (the non-zero entries) of L.
SpeedyWeather.jl uses NetCDF to output the data of a simulation. The following describes the details of this and how to change the way in which the NetCDF output is written. There are many options to this available.
The output writer is a component of every Model, i.e. BarotropicModel, ShallowWaterModel, PrimitiveDryModel and PrimitiveWetModel, hence a non-default output writer can be passed on as a keyword argument to the model constructor
julia> using SpeedyWeather
+julia> spectral_grid = SpectralGrid()
+julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry)
+julia> model = PrimitiveDryModel(;spectral_grid, output=my_output_writer)
So after we have defined the grid through the SpectralGrid object we can use and change the implemented OutputWriter by passing on the following arguments
the spectral_grid has to be the first argument then the model type (Barotropic, ShallowWater, PrimitiveDry, PrimitiveWet) which helps the output writer to make default choices on which variables to output. However, we can also pass on further keyword arguments. So let's start with an example.
which will now output every hour. It is important to pass on the new output writer my_output_writer to the model constructor, otherwise it will not be part of your model and the default is used instead. Note that output_dt has to be understood as the minimum frequency or maximum output time step. Example, we run the model at a resolution of T85 and the time step is going to be 670s
This means that after 32 time steps 5h 57min and 20s will have passed where output will happen as the next time step would be >6h. The time axis of the NetCDF output will look like
This is so that we don't interpolate in time during output to hit exactly every 6 hours, but at the same time have a constant spacing in time between output time steps.
Say we want to run the model at a given horizontal resolution but want to output on another resolution, the OutputWriter takes as argument output_Grid<:AbstractFullGrid and nlat_half::Int. So for example output_Grid=FullClenshawGrid and nlat_half=48 will always interpolate onto a regular 192x95 longitude-latitude grid of 1.875˚ resolution, regardless the grid and resolution used for the model integration.
Note that by default the output is on the corresponding full of the grid used in the dynamical core so that interpolation only happens at most in the zonal direction as they share the location of the latitude rings. You can check this by
So the corresponding full grid of an OctahedralGaussianGrid is the FullGaussiangrid and the same resolution nlat_half is chosen by default in the output writer (which you can change though as shown above). Overview of the corresponding full grids
Grid
Corresponding full grid
FullGaussianGrid
FullGaussianGrid
FullClenshawGrid
FullClenshawGrid
OctahadralGaussianGrid
FullGaussianGrid
OctahedralClensawhGrid
FullClenshawGrid
HEALPixGrid
FullHEALPixGrid
OctaHEALPixGrid
FullOctaHEALPixGrid
The grids FullHEALPixGrid, FullOctaHEALPixGrid share the same latitude rings as their reduced grids, but have always as many longitude points as they are at most around the equator. These grids are not tested in the dynamical core (but you may use them experimentally) and mostly designed for output purposes.
That's easy by passing on path="/my/favourite/path/" and the folder run_* with * the identification of the run (that's the id keyword, which can be manually set but is also automatically determined as a number counting up depending on which folders already exist) will be created within.
which will be used instead of a 4 digit number like 0001, 0002 which is automatically determined if id is not provided. You will see the id of the run in the progress bar
Weather is speedy: run diffusion_test 100%|███████████████████████| Time: 0:00:12 (19.20 years/day)
and the run folder, here run_diffusion_test, is also named accordingly
Further options are described in the OutputWriter docstring, (also accessible via julia>?OutputWriter for example). Note that some fields are actual options, but others are derived from the options you provided or are arrays/objects the output writer needs, but shouldn't be passed on by the user. The actual options are declared as [OPTION] in the following
Missing docstring.
Missing docstring for OutputWriter. Check Documenter's build log for details.
Settings
This document was generated with Documenter.jl version 0.27.24 on Tuesday 20 June 2023. Using Julia version 1.8.5.
This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmosphere. Every section is followed by a brief description of implementation details.
The primitive equations are a hydrostatic approximation of the compressible Navier-Stokes equations for an ideal gas on a rotating sphere. We largely follow the idealised spectral dynamical core developed by GFDL[1] and documented therein[2].
The primitive equations solved by SpeedyWeather.jl for relative vorticity $\zeta$, divergence $\mathcal{D}$, logarithm of surface pressure $\ln p_s$, temperature $T$ and specific humidity $q$ are
with velocity $\mathbf{u} = (u,v)$, rotated velocity $\mathbf{u}_\perp = (v,-u)$, Coriolis parameter $f$, $W$ the vertical advection operator, dry air gas constant $R_d$, virtual temperature $T_v$, geopotential $\Phi$, pressure $p$, thermodynamic $\kappa = R\_d/c_p$ with $c_p$ the heat capacity at constant pressure. Horizontal hyper diffusion of the form $(-1)^{n+1}\nu\nabla^{2n}$ with coefficient $\nu$ and power $n$ is added for every variable that is advected, meaning $\zeta, \mathcal{D}, T, q$, but left out here for clarity, see Horizontal diffusion.
The parameterizations for the tendencies of $u,v,T,q$ from physical processes are denoted as $\mathcal{P}_\mathbf{u} = (\mathcal{P}_u, \mathcal{P}_v), \mathcal{P}_T, \mathcal{P}_q$ and are further described in the corresponding sections, see Parameterizations.
SpeedyWeather.jl implements a PrimitiveWet and a PrimitiveDry dynamical core. For a dry atmosphere, we have $q = 0$ and the virtual temperature $T_v = T$ equals the temperature (often called absolute to distinguish from the virtual temperature). The terms in the primitive equations and their discretizations are discussed in the following sections.
Virtual temperature is the temperature dry air would need to have to be as light as moist air. It is used in the dynamical core to include the effect of humidity on the density while replacing density through the ideal gas law with temperature.
We assume the atmosphere to be composed of two ideal gases: Dry air and water vapour. Given a specific humidity $q$ both gases mix, their pressures $p_d$, $p_w$ ($d$ for dry, $w$ for water vapour), and densities $\rho_d, \rho_w$ add in a given air parcel that has temperature $T$. The ideal gas law then holds for both gases
\[\begin{aligned}
+p_d &= \rho_d R_d T \\
+p_w &= \rho_w R_w T \\
+\end{aligned}\]
with the respective specific gas constants $R_d = R/m_d$ and $R_w = R/m_w$ obtained from the univeral gas constant $R$ divided by the molecular masses of the gas. The total pressure $p$ in the air parcel is
\[p = p_d + p_w = (\rho_d R_d + \rho_w R_w)T\]
We ultimately want to replace the density $\rho = \rho_w + \rho_d$ in the dynamical core, using the ideal gas law, with the temperature $T$, so that we never have to calculate the density explicitly. However, in order to not deal with two densities (dry air and water vapour) we would like to replace temperature with a virtual temperature that includes the effect of humidity on the density. So, whereever we use the ideal gas law to replace density with temperature, we would use the virtual temperature, which is a function of the absolute temperature and specific humidity, instead. A higher specific humidity in an air parcel lowers the density as water vapour is lighter than dry air. Consequently, the virtual temperature of moist air is higher than its absolute temperature because warmer air is lighter too at constant pressure. We therefore think of the virtual temperature as the temperature dry air would need to have to be as light as moist air.
Starting with the last equation, with some manipulation we can write the ideal gas law as total density $rho$ times a gas constant times the virtual temperature that is supposed to be a function of absolute temperature, humidity and some constants
as some constant that is positive for water vapour being lighter than dry air ($\tfrac{R_d}{R_w} = \tfrac{m_w}{m_d} < 1$) and
\[q = \frac{\rho_w}{\rho_w + \rho_d}\]
as the specific humidity. Given temperature $T$ and specific humidity $q$, we can therefore calculate the virtual temperature $T_v$ as
\[T_v = (1 + \mu q)T\]
For completeness we want to mention here that the above product, because it is a product of two variables $q,T$ has to be computed in grid-point space, see [Spectral Transform]. To obtain an approximation to the virtual temperature in spectral space without expensive transforms one can linearize
\[T_v = T + \mu q\bar{T}\]
With a global constant temperature $\bar{T}$, for example obtained from the $l=m=0$ mode, $\bar{T} = T_{0,0}\frac{1}{\sqrt{4\pi}}$ but depending on the normalization of the spherical harmonics that factor needs adjustment.
In the hydrostatic approximation the vertical momentum equation becomes
\[\frac{\partial p}{\partial z} = -\rho g,\]
meaning that the (negative) vertical pressure gradient is given by the density in that layer times the gravitational acceleration. The heavier the fluid the more the pressure will increase below. Inserting the ideal gas law
Note that we use the Virtual temperature here as we replaced the density through the ideal gas law with temperature. Given a vertical temperature profile $T_v$ and the (constant) surface geopotential $\Phi_s = gz_s$ where $z_s$ is the orography, we can integrate this equation from the surface to the top to obtain $\Phi_k$ on every layer $k$. The surface is at $k = N+\tfrac{1}{2}$ (see Vertical coordinates) with $N$ vertical levels. We can integrate the geopotential onto half levels as
RingGrids is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.
RingGrids defines several iso-latitude grids, which are mathematically described in the section on Grids. In brief, they include the regular latitude-longitude grids (here called FullClenshawGrid) as well as grids which latitudes are shifted to the Gaussian latitudes and reduced grids, meaning that they have a decreasing number of longitudinal points towards the poles to be more equal-area than full grids.
RingGrids defines and exports the following grids:
full grids: FullClenshawGrid, FullGaussianGrid, FullHEALPix, and FullOctaHEALPix
reduced grids: OctahedralGaussianGrid, OctahedralClenshawGrid, OctaHEALPixGrid and HEALPixGrid
The following explanation of how to use these can be mostly applied to any of them, however, there are certain functions that are not defined, e.g. the full grids can be trivially converted to a Matrix (i.e. they are rectangular grids) but not the OctahedralGaussianGrid.
What is a ring?
We use the term ring, short for iso-latitude ring, to refer to a sequence of grid points that all share the same latitude. A latitude-longitude grid is a ring grid, as it organises its grid-points into rings. However, other grids, like the cubed-sphere are not based on iso-latitude rings. SpeedyWeather.jl only works with ring grids because its a requirement for the Spherical Harmonic Transform to be efficient. See Grids.
Every grid in RingGrids has a grid.data field, which is a vector containing the data on the grid. The grid points are unravelled west to east then north to south, meaning that it starts at 90˚N and 0˚E then walks eastward for 360˚ before jumping on the next latitude ring further south, this way circling around the sphere till reaching the south pole. This may also be called ring order.
Data in a Matrix which follows this ring order can be put on a FullGaussianGrid like so
using SpeedyWeather.RingGrids
+map = randn(Float32,8,4)
A full Gaussian grid has always $2N$ x $N$ grid points, but a FullClenshawGrid has $2N$ x $N-1$, if those dimensions don't match, the creation will throw an error. To reobtain the data from a grid, you can access its data field which returns a normal Vector
Which can be reshaped to reobtain map from above. Alternatively you can Matrix(grid) to do this in one step
map == Matrix(FullGaussianGrid(map))
true
You can also use zeros,ones,rand,randn to create a grid, whereby nlat_half, i.e. the number of latitude rings on one hemisphere, Equator included, is used as a resolution parameter and here as a second argument.
As only the full grids can be reshaped into a matrix, the underlying data structure of any AbstractGrid is a vector. As shown in the examples above, one can therefore inspect the data as if it was a vector. But as that data has, through its <:AbstractGrid type, all the geometric information available to plot it on a map, RingGrids also exports plot function, based on UnicodePlots' heatmap.
All RingGrids have a single index ij which follows the ring order. While this is obviously not super exciting here are some examples how to make better use of the information that the data sits on a grid.
We obtain the latitudes of the rings of a grid by calling get_latd (get_lond is only defined for full grids, or use get_latdlonds for latitudes, longitudes per grid point not per ring)
Now we could calculate Coriolis and add it on the grid as follows
rotation = 7.29e-5 # angular frequency of Earth's rotation [rad/s]
+coriolis = 2rotation*sind.(latd) # vector of coriolis parameters per latitude ring
+
+rings = eachring(grid)
+for (j,ring) in enumerate(rings)
+ f = coriolis[j]
+ for ij in ring
+ grid[ij] += f
+ end
+end
eachring creates a vector of UnitRange indices, such that we can loop over the ring index j (j=1 being closest to the North pole) pull the coriolis parameter at that latitude and then loop over all in-ring indices i (changing longitudes) to do something on the grid. Something similar can be done to scale/unscale with the cosine of latitude for example. We can always loop over all grid-points like so
In most cases we will want to use RingGrids so that our data directly comes with the geometric information of where the grid-point is one the sphere. We have seen how to use get_latd, get_lond, ... for that above. This information generally can also be used to interpolate our data from grid to another or to request an interpolated value on some coordinates. Using our data on grid which is an OctahedralGaussianGrid from above we can use the interpolate function to get it onto a FullGaussianGrid (or any other grid for purpose)
By default this will linearly interpolate (it's an Anvil interpolator, see below) onto a grid with the same nlat_half, but we can also coarse-grain or fine-grain by specifying nlat_half directly as 2nd argument
So we got from an 8-ring OctahedralGaussianGrid{Float16} to a 12-ring FullGaussianGrid{Float64}, so it did a conversion from Float16 to Float64 on the fly too, because the default precision is Float64 unless specified. interpolate(FullGaussianGrid{Float16},6,grid) would have interpolated onto a grid with element type Float16.
One can also interpolate onto a given coordinate ˚N, ˚E like so
interpolate(30.0,10.0,grid)
0.07829903f0
we interpolated the data from grid onto 30˚N, 10˚E. To do this simultaneously for many coordinates they can be packed into a vector too
Every time an interpolation like interpolate(30.0,10.0,grid) is called, several things happen, which are important to understand to know how to get the fastest interpolation out of this module in a given situation. Under the hood an interpolation takes three arguments
output vector
input grid
interpolator
The output vector is just an array into which the interpolated data is written, providing this prevents unnecessary allocation of memory in case the destination array of the interpolation already exists. The input grid contains the data which is subject to interpolation, it must come on a ring grid, however, its coordinate information is actually already in the interpolator. The interpolator knows about the geometry of the grid the data is coming on and the coordinates it is supposed to interpolate onto. It has therefore precalculated the indices that are needed to access the right data on the input grid and the weights it needs to apply in the actual interpolation operation. The only thing it does not know is the actual data values of that grid. So in the case you want to interpolate from grid A to grid B many times, you can just reuse the same interpolator. If you want to change the coordinates of the output grid but its total number of points remain constants then you can update the locator inside the interpolator and only else you will need to create a new interpolator. Let's look at this in practice. Say we have two grids an want to interpolate between them
Now we have created an interpolator interp which knows about the geometry where to interpolate from and the coordinates there to interpolate to. It is also initialized, meaning it has precomputed the indices to of grid_in that are supposed to be used. It just does not know about the data of grid_in (and neither of grid_out which will be overwritten anyway). We can now do
which is identical to interpolate(grid_out,grid_in) but you can reuse interp for other data. The interpolation can also handle various element types (the interpolator interp does not have to be updated for this either)
and we have converted data from a HEALPixGrid{Float64} (Float64 is always default if not specified) to a FullClenshawGrid{Float16} including the type conversion Float64-Float16 on the fly. Technically there are three data types and their combinations possible: The input data will come with a type, the output array has an element type and the interpolator has precomputed weights with a given type. Say we want to go from Float16 data on an OctahedralGaussianGrid to Float16 on a FullClenshawGrid but using Float32 precision for the interpolation itself, we would do this by
As a last example we want to illustrate a situation where we would always want to interpolate onto 10 coordinates, but their locations may change. In order to avoid recreating an interpolator object we would do (AnvilInterpolator is described in Anvil interpolator)
npoints = 10 # number of coordinates to interpolate onto
+interp = AnvilInterpolator(Float32,HEALPixGrid,24,npoints)
with the first argument being the number format used during interpolation, then the input grid type, its resolution in terms of nlat_half and then the number of points to interpolate onto. However, interp is not yet initialized as it does not know about the destination coordinates yet. Let's define them, but note that we already decided there's only 10 of them above.
but allows for a reuse of the interpolator. Note that the two output arrays are not exactly identical because we manually set our interpolator interp to use Float32 for the interpolation whereas the default is Float64.
Currently the only interpolator implemented is a 4-point bilinear interpolator, which schematically works as follows. Anvil interpolation is the bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate.
0..............1 # fraction of distance Δab between a,b
+ |< Δab >|
+
+0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x
+.Δy |
+. |
+.v x
+. |
+1 c ------ o ---- d
+
+ |< Δcd >|
+ 0...............1 # fraction of distance Δcd between c,d
+
+^ fraction of distance Δy between a-b and c-d.
This interpolation is chosen as by definition of the ring grids, a and b share the same latitude, so do c and d, but the longitudes can be different for all four, a,b,c,d.
abstract type AbstractFullGrid{T} <: AbstractGrid{T} end
An AbstractFullGrid is a horizontal grid with a constant number of longitude points across latitude rings. Different latitudes can be used, Gaussian latitudes, equi-angle latitdes, or others.
abstract type AbstractGrid{T} <: AbstractVector{T} end
The abstract supertype for all spatial grids on the sphere supported by SpeedyWeather.jl. Every new grid has to be of the form
abstract type AbstractGridClass{T} <: AbstractGrid{T} end
+struct MyNewGrid{T} <: AbstractGridClass{T}
+ data::Vector{T} # all grid points unravelled into a vector
+ nlat_half::Int # resolution: latitude rings on one hemisphere (Equator incl)
+end
MyNewGrid should belong to a grid class like AbstractFullGrid, AbstractOctahedralGrid or AbstractHEALPixGrid (that already exist but you may introduce a new class of grids) that share certain features such as the number of longitude points per latitude ring and indexing, but may have different latitudes or offset rotations. Each new grid Grid (or grid class) then has to implement the following methods (as an example, see octahedral.jl)
Fundamental grid properties getnpoints # total number of grid points nlatodd # does the grid have an odd number of latitude rings? getnlat # total number of latitude rings getnlat_half # number of latitude rings on one hemisphere incl Equator
Indexing getnlonmax # maximum number of longitudes points (at the Equator) getnlonperring # number of longitudes on ring j eachindexinring # a unit range that indexes all longitude points on a ring
Coordinates getcolat # vector of colatitudes (radians) getcolatlon # vectors of colatitudes, longitudes (both radians)
Spectral truncation truncationorder # linear, quadratic, cubic = 1,2,3 for grid gettruncation # spectral truncation given a grid resolution get_resolution # grid resolution given a spectral truncation
Quadrature weights and solid angles getquadratureweights # = sinθ Δθ for grid points on ring j for meridional integration getsolidangle # = sinθ Δθ Δϕ, solid angle of grid points on ring j
abstract type AbstractHEALPixGrid{T} <: AbstractGrid{T} end
An AbstractHEALPixGrid is a horizontal grid similar to the standard HEALPixGrid, but different latitudes can be used, the default HEALPix latitudes or others.
Supertype of every Locator, which locates the indices on a grid to be used to perform an interpolation. E.g. AnvilLocator uses a 4-point stencil for every new coordinate to interpolate onto. Higher order stencils can be implemented by defining OtherLocator <: AbstractLocactor.
abstract type AbstractOctaHEALPixGrid{T} <: AbstractGrid{T} end
An AbstractOctaHEALPixGrid is a horizontal grid similar to the standard OctahedralGrid, but the number of points in the ring closest to the Poles starts from 4 instead of 20, and the longitude of the first point in each ring is shifted as in HEALPixGrid. Also, different latitudes can be used.
abstract type AbstractOctahedralGrid{T} <: AbstractGrid{T} end
An AbstractOctahedralGrid is a horizontal grid with 16+4i longitude points on the latitude ring i starting with i=1 around the pole. Different latitudes can be used, Gaussian latitudes, equi-angle latitdes, or others.
Contains arrays that locates grid points of a given field to be uses in an interpolation and their weights. This Locator is a 4-point average in an anvil-shaped grid-point arrangement between two latitude rings.
L = AnvilLocator( ::Type{NF}, # number format used for the interpolation
+ npoints::Integer # number of points to interpolate onto
+ ) where {NF<:AbstractFloat}
Zero generator function for the 4-point average AnvilLocator. Use update_locator! to update the grid indices used for interpolation and their weights. The number format NF is the format used for the calculations within the interpolation, the input data and/or output data formats may differ.
A FullClenshawGrid is a regular latitude-longitude grid with an odd number of nlat equi-spaced latitudes, the central latitude ring is on the Equator. The same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field data that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
A full Gaussian grid is a regular latitude-longitude grid that uses nlat Gaussian latitudes, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
A full HEALPix grid is a regular latitude-longitude grid that uses nlat latitudes from the HEALPix grid, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
A full OctaHEALPix grid is a regular latitude-longitude grid that uses nlat OctaHEALPix latitudes, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
G = GridGeometry( Grid::Type{<:AbstractGrid},
+ nlat_half::Integer)
Precomputed arrays describing the geometry of the Grid with resolution nlat_half. Contains latitudes and longitudes of grid points, their ring index j and their unravelled indices ij.
A HEALPix grid with 12 faces, each nsidexnside grid points, each covering the same area. The number of latitude rings on one hemisphere (incl Equator) nlat_half is used as resolution parameter. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
A OctaHEALPix grid with 4 base faces, each nlat_halfxnlat_half grid points, each covering the same area. The values of all grid points are stored in a vector field data that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
An Octahedral Clenshaw grid that uses nlat equi-spaced latitudes. Like FullClenshawGrid, the central latitude ring is on the Equator. Like OctahedralGaussianGrid, the number of longitude points per latitude ring decreases towards the poles. Starting with 20 equi-spaced longitude points (starting at 0˚E) on the rings around the poles, each latitude ring towards the equator has consecuitively 4 more points, one for each face of the octahedron. E.g. 20,24,28,32,...nlon-4,nlon,nlon,nlon-4,...,32,28,24,20. The maximum number of longitue points is nlon. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
An Octahedral Gaussian grid that uses nlat Gaussian latitudes, but a decreasing number of longitude points per latitude ring towards the poles. Starting with 20 equi-spaced longitude points (starting at 0˚E) on the rings around the poles, each latitude ring towards the equator has consecuitively 4 more points, one for each face of the octahedron. E.g. 20,24,28,32,...nlon-4,nlon,nlon,nlon-4,...,32,28,24,20. The maximum number of longitue points is nlon. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
Sorts the gridpoints in G into the matrix M without interpolation. Every quadrant of the grid G is rotated as specified in quadrant_rotation, 0 is no rotation, 1 is 90˚ clockwise, 2 is 180˚ etc. Grid quadrants are counted eastward starting from 0˚E. The grid quadrants are moved into the matrix quadrant (i,j) as specified. Defaults are equivalent to centered at 0˚E and a rotation such that the North Pole is at M's midpoint.
Sorts the gridpoints in G into the matrix M without interpolation. Every quadrant of the grid G is rotated as specified in quadrant_rotation, 0 is no rotation, 1 is 90˚ clockwise, 2 is 180˚ etc. Grid quadrants are counted eastward starting from 0˚E. The grid quadrants are moved into the matrix quadrant (i,j) as specified. Defaults are equivalent to centered at 0˚E and a rotation such that the North Pole is at M's midpoint.
Like Matrix!(::AbstractMatrix,::OctaHEALPixGrid) but for simultaneous processing of tuples ((M1,G1),(M2,G2),...) with matrices Mi and grids Gi. All matrices and grids have to be of the same size respectively.
Like Matrix!(::AbstractMatrix,::OctahedralClenshawGrid) but for simultaneous processing of tuples ((M1,G1),(M2,G2),...) with matrices Mi and grids Gi. All matrices and grids have to be of the same size respectively.
The bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate. See schematic:
0..............1 # fraction of distance Δab between a,b
+ |< Δab >|
+
+ 0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x
+ .Δy |
+ . |
+ .v x
+ . |
+ 1 c ------ o ---- d
+
+ |< Δcd >|
+ 0...............1 # fraction of distance Δcd between c,d
Computes the average at the North and South pole from a given grid A and it's precomputed ring indices rings. The North pole average is an equally weighted average of all grid points on the northern-most ring. Similar for the South pole.
Returns a vector nlons for the number of longitude points per latitude ring, north to south. Provide grid Grid and its resolution parameter nlat_half. For both_hemisphere==false only the northern hemisphere (incl Equator) is returned.
This document was generated with Documenter.jl version 0.27.24 on Tuesday 20 June 2023. Using Julia version 1.8.5.
diff --git a/previews/PR360/search_index.js b/previews/PR360/search_index.js
new file mode 100644
index 000000000..c97668e0c
--- /dev/null
+++ b/previews/PR360/search_index.js
@@ -0,0 +1,3 @@
+var documenterSearchIndex = {"docs":
+[{"location":"parameterizations/#parameterizations","page":"Parameterizations","title":"Parameterizations","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmosphere. Every section is followed by a brief description of implementation details.","category":"page"},{"location":"parameterizations/#Convection","page":"Parameterizations","title":"Convection","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Large-scale-condensation","page":"Parameterizations","title":"Large-scale condensation","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Clouds","page":"Parameterizations","title":"Clouds","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Short-wave-radiation","page":"Parameterizations","title":"Short-wave radiation","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Long-wave-radiation","page":"Parameterizations","title":"Long-wave radiation","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Surface-fluxes-of-momentum-and-energy","page":"Parameterizations","title":"Surface fluxes of momentum and energy","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Vertical-diffusion","page":"Parameterizations","title":"Vertical diffusion","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"development/#Development-notes","page":"Development notes","title":"Development notes","text":"","category":"section"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To run tests, from the path of your local clone of the repository do:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=. -e 'import Pkg; Pkg.test()'","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To install dependencies:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project -e 'import Pkg; Pkg.instantiate()`","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"then opening:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"you are able to using SpeedyWeather.","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To generate the docs:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=docs -e 'import Pkg; Pkg.develop(path=\".\"); Pkg.instantiate()'\njulia --project=docs docs/make.jl","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"If the docs are generated successfully, you view them by opening docs/build/index.html in your favorite browser.","category":"page"},{"location":"barotropic/#Barotropic-vorticity-model","page":"Barotropic model","title":"Barotropic vorticity model","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The barotropic vorticity model describes the evolution of a 2D non-divergent flow with velocity components mathbfu = (uv) through self-advection, forces and dissipation. Due to the non-divergent nature of the flow, it can be described by (the vertical component) of the relative vorticity zeta = nabla times mathbfu.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The dynamical core presented here to solve the barotropic vorticity equations largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2].","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Many concepts of the Shallow water model and the Primitive equation model are similar, such that for example horizontal diffusion and the Time integration are only explained here.","category":"page"},{"location":"barotropic/#Barotropic-vorticity-equation","page":"Barotropic model","title":"Barotropic vorticity equation","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity zeta with advection, Coriolis force, forcing and diffusion in a single global layer on the sphere.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"fracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) =\nnabla times mathbfF + (-1)^n+1nunabla^2nzeta","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"We denote timet, velocity vector mathbfu = (u v), Coriolis parameter f, and hyperdiffusion (-1)^n+1 nu nabla^2n zeta (n is the hyperdiffusion order, see Horizontal diffusion). We also include a forcing vector mathbfF = (F_uF_v) which acts on the zonal velocity u and the meridional velocity v and hence its curl nabla times mathbfF is a tendency for relative vorticity zeta.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Starting with some relative vorticity zeta, the Laplacian is inverted to obtain the stream function Psi","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Psi = nabla^-2zeta","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The zonal velocity u and meridional velocity v are then the (negative) meridional gradient and zonal gradient of Psi","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"beginaligned\nu = -frac1R fracpartial Psipartial theta \nv = frac1Rcos(theta) fracpartial Psipartial phi \nendaligned","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"which is described in Derivatives in spherical coordinates. Using u and v we can then advect the absolute vorticity zeta + f. In order to avoid to calculate both the curl and the divergence of a flux we rewrite the barotropic vorticity equation as","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"fracpartial zetapartial t =\nnabla times (mathbfF + mathbfu_perp(zeta + f)) + (-1)^n+1nunabla^2nzeta","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"with mathbfu_perp = (v-u) the rotated velocity vector, because -nablacdotmathbfu = nabla times mathbfu_perp. This is the form that is solved in the BarotropicModel, as outlined in the following section.","category":"page"},{"location":"barotropic/#Algorithm","page":"Barotropic model","title":"Algorithm","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"We briefly outline the algorithm that SpeedyWeather.jl uses in order to integrate the barotropic vorticity equation. As an initial step","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"0. Start with initial conditions of zeta_lm in spectral space and transform this model state to grid-point space:","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Invert the Laplacian of vorticity zeta_lm to obtain the stream function Psi_lm in spectral space\nobtain zonal velocity (cos(theta)u)_lm through a Meridional derivative\nobtain meridional velocity (cos(theta)v)_lm through a Zonal derivative\nTransform zonal and meridional velocity (cos(theta)u)_lm, (cos(theta)v)_lm to grid-point space\nUnscale the cos(theta) factor to obtain uv\nTransform zeta_lm to zeta in grid-point space","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Now loop over","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Compute the forcing vector mathbfF = (F_uF_v) for u and v\nMultiply uv with zeta+f in grid-point space\nAdd A = F_u + v(zeta + f) and B = F_v - u(zeta + f)\nTransform these vector components to spectral space A_lm, B_lm\nCompute the curl of (AB)_lm in spectral space which is the tendency of zeta_lm\nCompute the horizontal diffusion based on that tendency\nCompute a leapfrog time step as described in Time integration with a Robert-Asselin and Williams filter\nTransform the new spectral state of zeta_lm to grid-point uvzeta as described in 0.\nPossibly do some output\nRepeat from 1.","category":"page"},{"location":"barotropic/#diffusion","page":"Barotropic model","title":"Horizontal diffusion","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"In SpeedyWeather.jl we use hyerdiffusion through an n-th power Laplacian (-1)^n+1nabla^2n (hyper when n1) which can be implemented as a multiplication of the spectral coefficients Psi_lm with (-l(l+1))^nR^-2n (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the (-l(l+1))^nR^-2n can be precomputed. Note the sign change (-1)^n+1 here is such that the dissipative nature of the diffusion operator is retained for n odd and even.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step Delta t of variable zeta to obtain from time steps i-1 and i, the next time step i+1","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"zeta_i+1 = zeta_i-1 + 2Delta t dzeta","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"with dzeta being some tendency evaluated from zeta_i. Now we want to add a diffusion term (-1)^n+1nu nabla^2nzeta with coefficient nu, which however, is implicitly calculated from zeta_i+1, then","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"zeta_i+1 = zeta_i-1 + 2Delta t (dzeta + (-1)^n+1 nunabla^2nzeta_i+1)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"As the application of (-1)^n+1nunabla^2n is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"zeta_i+1 = fraczeta_i-1 + 2Delta t dzeta1 - 2Delta (-1)^n+1nunabla^2n","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"and expand the numerator to","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"zeta_i+1 = zeta_i-1 + 2Delta t fracdzeta + (-1)^n+1 nunabla^2nzeta_i-11 - 2Delta t (-1)^n+1nu nabla^2n","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Hence the diffusion can be applied implicitly by updating the tendency dzeta as","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"dzeta to fracdzeta + (-1)^n+1nunabla^2nzeta_i-11 - 2Delta t nu nabla^2n","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"which only depends on zeta_i-1. Now let D_textexplicit = (-1)^n+1nunabla^2n be the explicit part and D_textimplicit = 1 - (-1)^n+1 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are D_textimplicit = 1 - 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic lm we do","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"dzeta to D_textimplicit^-1(dzeta + D_textexplicitzeta_i-1)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power n.","category":"page"},{"location":"barotropic/#Normalization-of-diffusion","page":"Barotropic model","title":"Normalization of diffusion","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"In physics, the Laplace operator nabla^2 is often used to represent diffusion due to viscosity in a fluid or diffusion that needs to be added to retain numerical stability. In both cases, the coefficient is nu of units textm^2texts^-1 and the full operator reads as nu nabla^2 with units (textm^2texts^-1)(textm^-2) = texts^-1. This motivates us to normalize the Laplace operator by a constant of units textm^-2 and the coefficient by its inverse such that it becomes a damping timescale of unit texts^-1. Given the application in spectral space we decide to normalize by the largest eigenvalue -l_textmax(l_textmax+1) such that all entries in the discrete spectral Laplace operator are in 01. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized coefficient nu^* = l_textmax(l_textmax+1)nu (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have ","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"D^textexplicit_lm = -nu^* fracl(l+1)l_textmax(l_textmax+1)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"and the hyper-Laplacian of power n follows as","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"D^textexplicitn_lm = -nu^* left(fracl(l+1)l_textmax(l_textmax+1)right)^n","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"and the implicit part is accordingly D^textimplicitn_lm = 1 - 2Delta t D^textexplicitn_lm. Note that the diffusion time scale nu^* is then also scaled by the radius, see next section.","category":"page"},{"location":"barotropic/#scaling","page":"Barotropic model","title":"Radius scaling","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Similar to a non-dimensionalization of the equations, SpeedyWeather.jl scales the barotropic vorticity equation with R^2 to obtain normalized gradient operators as follows. A scaling for vorticity zeta and stream function Psi is used that is","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"tildezeta = zeta R tildePsi = Psi R^-1","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"This is also convenient as vorticity is often 10^-5text s^-1 in the atmosphere, but the streamfunction more like 10^5text m^2text s^-1 and so this scaling brings both closer to 1 with a typical radius of the Earth of 6371km. The inversion of the Laplacians in order to obtain Psi from zeta therefore becomes","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"tildezeta = tildenabla^2 tildePsi","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"where the dimensionless gradients simply omit the scaling with 1R, tildenabla = Rnabla. The Barotropic vorticity equation scaled with R^2 is","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"partial_tildettildezeta + tildenabla cdot (mathbfu(tildezeta + tildef)) =\nnabla times tildemathbfF + (-1)^n+1tildenutildenabla^2ntildezeta","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"with","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"tildet = tR^-1, the scaled time t\nmathbfu = (uv), the velocity vector (no scaling applied)\ntildef = fR, the scaled Coriolis parameter f\ntildemathbfF = RmathbfF, the scaled forcing vector mathbfF\ntildenu = nu^* R, the scaled diffusion coefficient nu^*, which itself is normalized to a damping time scale, see Normalization of diffusion.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"So scaling with the radius squared means we can use dimensionless operators, however, this comes at the cost of needing to deal with both a time step in seconds as well as a scaled time step in seconds per meter, which can be confusing. Furthermore, some constants like Coriolis or the diffusion coefficient need to be scaled too during initialisation, which may be confusing too because values are not what users expect them to be. SpeedyWeather.jl follows the logic that the scaling to the prognostic variables is only applied just before the time integration and variables are unscaled for output and after the time integration finished. That way, the scaling is hidden as much as possible from the user. In hopefully many other cases it is clearly denoted that a variable or constant is scaled.","category":"page"},{"location":"barotropic/#leapfrog","page":"Barotropic model","title":"Time integration","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"SpeedyWeather.jl is based on the Leapfrog time integration, which, for relative vorticity zeta, is in its simplest form","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"fraczeta_i+1 - zeta_i-12Delta t = RHS(zeta_i)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"meaning we step from the previous time step i-1, leapfrogging over the current time stepi to the next time step i+1 by evaluating the tendencies on the right-hand side RHS at the current time step i. The time stepping is done in spectral space. Once the right-hand side RHS is evaluated, leapfrogging is a linear operation, meaning that its simply applied to every spectral coefficient zeta_lm as one would evaluate it on every grid point in grid-point models.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"For the Leapfrog time integration two time steps of the prognostic variables have to be stored, i-1 and i. Time step i is used to evaluate the tendencies which are then added to i-1 in a step that also swaps the indices for the next time step i to i-1 and i+1 to i, so that no additional memory than two time steps have to be stored at the same time.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The Leapfrog time integration has to be initialised with an Euler forward step in order to have a second time step i+1 available when starting from i to actually leapfrog over. SpeedyWeather.jl therefore does two initial time steps that are different from the leapfrog time steps that follow and that have been described above.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"an Euler forward step with Delta t2, then\none leapfrog time step with Delta t, then\nleapfrog with 2 Delta t till the end","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"This is particularly done in a way that after 2. we have t=0 at i-1 and t=Delta t at i available so that 3. can start the leapfrogging without any offset from the intuitive spacing 0Delta t 2Delta t 3Delta t. The following schematic can be useful","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":" time at step i-1 time at step i time step at i+1\nInitial conditions t = 0 \n1: Euler (T) quad t = 0 t=Delta t2 \n2: Leapfrog with Delta t t = 0 (T) quad t = Delta t2 t = Delta t\n3 to n: Leapfrog with 2Delta t t-Delta t (T) qquad quad quad t t+Delta t","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The time step that is used to evaluate the tendencies is denoted with (T). It is always the time step furthest in time that is available.","category":"page"},{"location":"barotropic/#Robert-Asselin-and-Williams-filter","page":"Barotropic model","title":"Robert-Asselin and Williams filter","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The standard leapfrog time integration is often combined with a Robert-Asselin filter[Robert66][Asselin72] to dampen a computational mode. The idea is to start with a standard leapfrog step to obtain the next time step i+1 but then to correct the current time step i by applying a filter which dampens the computational mode. The filter looks like a discrete Laplacian in time with a (1 -2 1) stencil, and so, maybe unsurprisingly, is efficient to filter out a \"grid-scale oscillation\" in time, aka the computational mode. Let v be the unfiltered variable and u be the filtered variable, F the right-hand side tendency, then the standard leapfrog step is","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"v_i+1 = u_i-1 + 2Delta tF(v_i)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Meaning we start with a filtered variable u at the previous time step i-1, evaluate the tendency F(v_i) based on the current time step i to obtain an unfiltered next time step v_i+1. We then filter the current time step i (which will become i-1 on the next iteration)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"u_i = v_i + fracnu2(v_i+1 - 2v_i + u_i-1)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"by adding a discrete Laplacian with coefficient tfracnu2 to it, evaluated from the available filtered and unfiltered time steps centred around i: v_i-1 is not available anymore because it was overwritten by the filtering at the previous iteration, u_i u_i+1 are not filtered yet when applying the Laplacian. The filter parameter nu is typically chosen between 0.01-0.2, with stronger filtering for higher values.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Williams[Williams2009] then proposed an additional filter step to regain accuracy that is otherwise lost with a strong Robert-Asselin filter[Amezcua2011][Williams2011]. Now let w be unfiltered, v be once filtered, and u twice filtered, then","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"beginaligned\nw_i+1 = u_i-1 + 2Delta tF(v_i) \nu_i = v_i + fracnualpha2(w_i+1 - 2v_i + u_i-1) \nv_i+1 = w_i+1 - fracnu(1-alpha)2(w_i+1 - 2v_i + u_i-1)\nendaligned","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"with the Williams filter parameter alpha in 051. For alpha=1 we're back with the Robert-Asselin filter (the first two lines).","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The Laplacian in the parentheses is often called a displacement, meaning that the filtered value is displaced (or corrected) in the direction of the two surrounding time steps. The Williams filter now also applies the same displacement, but in the opposite direction to the next time step i+1 as a correction step (line 3 above) for a once-filtered value v_i+1 which will then be twice-filtered by the Robert-Asselin filter on the next iteration. For more details see the referenced publications.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The initial Euler step (see Time integration, Table) is not filtered. Both the the Robert-Asselin and Williams filter are then switched on for all following leapfrog time steps.","category":"page"},{"location":"barotropic/#References","page":"Barotropic model","title":"References","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[1]: Geophysical Fluid Dynamics Laboratory, Idealized models with spectral dynamics","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[2]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[Robert66]: Robert, André. “The Integration of a Low Order Spectral Form of the Primitive Meteorological Equations.” Journal of the Meteorological Society of Japan 44 (1966): 237-245.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[Asselin72]: ASSELIN, R., 1972: Frequency Filter for Time Integrations. Mon. Wea. Rev., 100, 487–490, doi:10.1175/1520-0493(1972)100<0487:FFFTI>2.3.CO;2","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[Williams2009]: Williams, P. D., 2009: A Proposed Modification to the Robert–Asselin Time Filter. Mon. Wea. Rev., 137, 2538–2546, 10.1175/2009MWR2724.1.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[Amezcua2011]: Amezcua, J., E. Kalnay, and P. D. Williams, 2011: The Effects of the RAW Filter on the Climatology and Forecast Skill of the SPEEDY Model. Mon. Wea. Rev., 139, 608–619, doi:10.1175/2010MWR3530.1. ","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[Williams2011]: Williams, P. D., 2011: The RAW Filter: An Improvement to the Robert–Asselin Filter in Semi-Implicit Integrations. Mon. Wea. Rev., 139, 1996–2007, doi:10.1175/2010MWR3601.1. ","category":"page"},{"location":"installation/#Installation","page":"Installation","title":"Installation","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"SpeedyWeather.jl is registered in the Julia Registry. In most cases just open the Julia REPL and type","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia> using Pkg\njulia> Pkg.add(\"SpeedyWeather\")","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"or, equivalently, (] opens the package manager)","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia>] add SpeedyWeather","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"which will automatically install the latest release and all necessary dependencies. If you run into any troubles please raise an issue.","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"However, you may want to make use of the latest features, then install directly from the main branch with","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia> Pkg.add(url=\"https://github.com/SpeedyWeather/SpeedyWeather.jl\",rev=\"main\")","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"other branches than main can be similarly installed. You can also type, equivalently,","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia>] add https://github.com/SpeedyWeather/SpeedyWeather.jl#main","category":"page"},{"location":"installation/#Compatibility-with-Julia-versions","page":"Installation","title":"Compatibility with Julia versions","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"SpeedyWeather.jl usually lives on the latest minor release and/or its predecessor. At the moment (June 2023) this means ","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"Julia v1.8\nJulia v1.9","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"are supported, but we dropped the support of earlier versions.","category":"page"},{"location":"output/#NetCDF-output","page":"NetCDF output","title":"NetCDF output","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"SpeedyWeather.jl uses NetCDF to output the data of a simulation. The following describes the details of this and how to change the way in which the NetCDF output is written. There are many options to this available.","category":"page"},{"location":"output/#Accessing-the-NetCDF-output-writer","page":"NetCDF output","title":"Accessing the NetCDF output writer","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"The output writer is a component of every Model, i.e. BarotropicModel, ShallowWaterModel, PrimitiveDryModel and PrimitiveWetModel, hence a non-default output writer can be passed on as a keyword argument to the model constructor","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> using SpeedyWeather\njulia> spectral_grid = SpectralGrid()\njulia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry)\njulia> model = PrimitiveDryModel(;spectral_grid, output=my_output_writer)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"So after we have defined the grid through the SpectralGrid object we can use and change the implemented OutputWriter by passing on the following arguments","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, kwargs...)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"the spectral_grid has to be the first argument then the model type (Barotropic, ShallowWater, PrimitiveDry, PrimitiveWet) which helps the output writer to make default choices on which variables to output. However, we can also pass on further keyword arguments. So let's start with an example.","category":"page"},{"location":"output/#Example-1:-NetCDF-output-every-hour","page":"NetCDF output","title":"Example 1: NetCDF output every hour","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"If we want to increase the frequency of the output we can choose output_dt (default =6 in hours) like so","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, output_dt=1)\njulia> model = PrimitiveDryModel(;spectral_grid, output=my_output_writer)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"which will now output every hour. It is important to pass on the new output writer my_output_writer to the model constructor, otherwise it will not be part of your model and the default is used instead. Note that output_dt has to be understood as the minimum frequency or maximum output time step. Example, we run the model at a resolution of T85 and the time step is going to be 670s","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> spectral_grid = SpectralGrid(trunc=85)\njulia> time_stepper = Leapfrog(spectral_grid)\nLeapfrog{Float32}:\n...\n Δt_sec::Int64 = 670\n...","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This means that after 32 time steps 5h 57min and 20s will have passed where output will happen as the next time step would be >6h. The time axis of the NetCDF output will look like","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> using NCDatasets\njulia> ds = NCDataset(\"run_0001/output.nc\");\njulia> ds[\"time\"][:]\n5-element Vector{Dates.DateTime}:\n 2000-01-01T00:00:00\n 2000-01-01T05:57:20\n 2000-01-01T11:54:40\n 2000-01-01T17:52:00\n 2000-01-01T23:49:20\n\njulia> diff(ds[\"time\"][:])\n4-element Vector{Dates.Millisecond}:\n 21440000 milliseconds\n 21440000 milliseconds\n 21440000 milliseconds\n 21440000 milliseconds","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This is so that we don't interpolate in time during output to hit exactly every 6 hours, but at the same time have a constant spacing in time between output time steps.","category":"page"},{"location":"output/#Example-2:-Output-onto-a-higher/lower-resolution-grid","page":"NetCDF output","title":"Example 2: Output onto a higher/lower resolution grid","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Say we want to run the model at a given horizontal resolution but want to output on another resolution, the OutputWriter takes as argument output_Grid<:AbstractFullGrid and nlat_half::Int. So for example output_Grid=FullClenshawGrid and nlat_half=48 will always interpolate onto a regular 192x95 longitude-latitude grid of 1.875˚ resolution, regardless the grid and resolution used for the model integration.","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, output_Grid=FullClenshawGrid, nlat_half=48)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Note that by default the output is on the corresponding full of the grid used in the dynamical core so that interpolation only happens at most in the zonal direction as they share the location of the latitude rings. You can check this by","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> RingGrids.full_grid(OctahedralGaussianGrid)\nFullGaussianGrid","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"So the corresponding full grid of an OctahedralGaussianGrid is the FullGaussiangrid and the same resolution nlat_half is chosen by default in the output writer (which you can change though as shown above). Overview of the corresponding full grids","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Grid Corresponding full grid\nFullGaussianGrid FullGaussianGrid\nFullClenshawGrid FullClenshawGrid\nOctahadralGaussianGrid FullGaussianGrid\nOctahedralClensawhGrid FullClenshawGrid\nHEALPixGrid FullHEALPixGrid\nOctaHEALPixGrid FullOctaHEALPixGrid","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"The grids FullHEALPixGrid, FullOctaHEALPixGrid share the same latitude rings as their reduced grids, but have always as many longitude points as they are at most around the equator. These grids are not tested in the dynamical core (but you may use them experimentally) and mostly designed for output purposes.","category":"page"},{"location":"output/#Example-3:-Changing-the-output-path-or-identification","page":"NetCDF output","title":"Example 3: Changing the output path or identification","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"That's easy by passing on path=\"/my/favourite/path/\" and the folder run_* with * the identification of the run (that's the id keyword, which can be manually set but is also automatically determined as a number counting up depending on which folders already exist) will be created within.","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> path = pwd()\n\"/Users/milan\"\njulia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, path=path)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This folder must already exist. If you want to give your run a name/identification you can pass on id","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid,PrimitiveDry,id=\"diffusion_test\");","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"which will be used instead of a 4 digit number like 0001, 0002 which is automatically determined if id is not provided. You will see the id of the run in the progress bar","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Weather is speedy: run diffusion_test 100%|███████████████████████| Time: 0:00:12 (19.20 years/day)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"and the run folder, here run_diffusion_test, is also named accordingly","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"shell> ls\n...\nrun_diffusion_test\n...","category":"page"},{"location":"output/#Further-options","page":"NetCDF output","title":"Further options","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Further options are described in the OutputWriter docstring, (also accessible via julia>?OutputWriter for example). Note that some fields are actual options, but others are derived from the options you provided or are arrays/objects the output writer needs, but shouldn't be passed on by the user. The actual options are declared as [OPTION] in the following","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"OutputWriter","category":"page"},{"location":"functions/#Function-and-type-index","page":"Function and type index","title":"Function and type index","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"Modules = [SpeedyWeather]","category":"page"},{"location":"functions/#SpeedyWeather.AbstractDevice","page":"Function and type index","title":"SpeedyWeather.AbstractDevice","text":"abstract type AbstractDevice\n\nSupertype of all devices SpeedyWeather.jl can ran on\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.BarotropicModel","page":"Function and type index","title":"SpeedyWeather.BarotropicModel","text":"The BarotropicModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.\n\nspectral_grid::SpectralGrid: dictates resolution for many other components\nplanet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics\natmosphere::SpeedyWeather.AbstractAtmosphere\nforcing::SpeedyWeather.AbstractForcing{NF} where NF<:AbstractFloat\ninitial_conditions::SpeedyWeather.InitialConditions\ntime_stepping::SpeedyWeather.TimeStepper{NF} where NF<:AbstractFloat\nspectral_transform::SpectralTransform\nhorizontal_diffusion::SpeedyWeather.HorizontalDiffusion{NF} where NF<:AbstractFloat\nimplicit::SpeedyWeather.AbstractImplicit{NF} where NF<:AbstractFloat\ngeometry::Geometry\nconstants::DynamicsConstants\ndevice_setup::SpeedyWeather.DeviceSetup\noutput::SpeedyWeather.AbstractOutputWriter\nfeedback::SpeedyWeather.AbstractFeedback\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.CPUDevice","page":"Function and type index","title":"SpeedyWeather.CPUDevice","text":"CPUDevice <: AbstractDevice\n\nIndicates that SpeedyWeather.jl runs on a single CPU \n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Clock","page":"Function and type index","title":"SpeedyWeather.Clock","text":"Clock struct keeps track of the model time, how many days to integrate for and how many time steps this takes\n\ntime::Dates.DateTime: current model time\nn_days::Float64: number of days to integrate for, set in run!(::Simulation)\nn_timesteps::Int64: number of time steps to integrate for, set in initialize!(::Clock,::TimeStepper)\n\n.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ColumnVariables","page":"Function and type index","title":"SpeedyWeather.ColumnVariables","text":"Mutable struct that contains all prognostic (copies thereof) and diagnostic variables in a single column needed to evaluate the physical parametrizations. For now the struct is mutable as we will reuse the struct to iterate over horizontal grid points. Every column vector has nlev entries, from [1] at the top to [end] at the lowermost model level at the planetary boundary layer.\n\nnlev::Int64\nnband::Int64\nn_stratosphere_levels::Int64\njring::Int64\nlond::AbstractFloat\nlatd::AbstractFloat\nu::Vector{NF} where NF<:AbstractFloat\nv::Vector{NF} where NF<:AbstractFloat\ntemp::Vector{NF} where NF<:AbstractFloat\nhumid::Vector{NF} where NF<:AbstractFloat\nln_pres::Vector{NF} where NF<:AbstractFloat\npres::Vector{NF} where NF<:AbstractFloat\nu_tend::Vector{NF} where NF<:AbstractFloat\nv_tend::Vector{NF} where NF<:AbstractFloat\ntemp_tend::Vector{NF} where NF<:AbstractFloat\nhumid_tend::Vector{NF} where NF<:AbstractFloat\ngeopot::Vector{NF} where NF<:AbstractFloat\nflux_u_upward::Vector{NF} where NF<:AbstractFloat\nflux_u_downward::Vector{NF} where NF<:AbstractFloat\nflux_v_upward::Vector{NF} where NF<:AbstractFloat\nflux_v_downward::Vector{NF} where NF<:AbstractFloat\nflux_temp_upward::Vector{NF} where NF<:AbstractFloat\nflux_temp_downward::Vector{NF} where NF<:AbstractFloat\nflux_humid_upward::Vector{NF} where NF<:AbstractFloat\nflux_humid_downward::Vector{NF} where NF<:AbstractFloat\nsat_humid::Vector{NF} where NF<:AbstractFloat\nsat_vap_pres::Vector{NF} where NF<:AbstractFloat\ndry_static_energy::Vector{NF} where NF<:AbstractFloat\nmoist_static_energy::Vector{NF} where NF<:AbstractFloat\nhumid_half::Vector{NF} where NF<:AbstractFloat\nsat_humid_half::Vector{NF} where NF<:AbstractFloat\nsat_moist_static_energy::Vector{NF} where NF<:AbstractFloat\ndry_static_energy_half::Vector{NF} where NF<:AbstractFloat\nsat_moist_static_energy_half::Vector{NF} where NF<:AbstractFloat\nconditional_instability::Bool\nactivate_convection::Bool\ncloud_top::Int64\nexcess_humidity::AbstractFloat\ncloud_base_mass_flux::AbstractFloat\nprecip_convection::AbstractFloat\nnet_flux_humid::Vector{NF} where NF<:AbstractFloat\nnet_flux_dry_static_energy::Vector{NF} where NF<:AbstractFloat\nentrainment_profile::Vector{NF} where NF<:AbstractFloat\nprecip_large_scale::AbstractFloat\nwvi::Matrix{NF} where NF<:AbstractFloat\ntau2::Matrix{NF} where NF<:AbstractFloat\ndfabs::Vector{NF} where NF<:AbstractFloat\nfsfcd::AbstractFloat\nst4a::Matrix{NF} where NF<:AbstractFloat\nflux::Vector{NF} where NF<:AbstractFloat\nfsfcu::AbstractFloat\nts::AbstractFloat\nfsfc::AbstractFloat\nftop::AbstractFloat\nstratc::Vector{NF} where NF<:AbstractFloat\ntyear::AbstractFloat\ncsol::AbstractFloat\ntopsr::AbstractFloat\nfsol::AbstractFloat\nozupp::AbstractFloat\nozone::AbstractFloat\nzenit::AbstractFloat\nstratz::AbstractFloat\nalbsfc::AbstractFloat\nssrd::AbstractFloat\nssr::AbstractFloat\ntsr::AbstractFloat\ntend_t_rsw::Vector{NF} where NF<:AbstractFloat\nnorm_pres::AbstractFloat\nicltop::Int64\ncloudc::AbstractFloat\nclstr::AbstractFloat\nqcloud::AbstractFloat\nfmask::AbstractFloat\nrel_hum::Vector{NF} where NF<:AbstractFloat\ngrad_dry_static_energy::AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.DeviceSetup","page":"Function and type index","title":"SpeedyWeather.DeviceSetup","text":"DeviceSetup{S<:AbstractDevice}\n\nHolds information about the device the model is running on and workgroup size. \n\ndevice::AbstractDevice: Device the model is running on \ndevice_KA::KernelAbstractions.Device: Device for use with KernelAbstractions\nn: workgroup size \n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.DiagnosticVariables","page":"Function and type index","title":"SpeedyWeather.DiagnosticVariables","text":"DiagnosticVariables{Grid<:AbstractGrid,NF<:AbstractFloat}\n\nStruct holding the diagnostic variables.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.DynamicsConstants","page":"Function and type index","title":"SpeedyWeather.DynamicsConstants","text":"Struct holding constants needed at runtime for the dynamical core in number format NF.\n\nradius::AbstractFloat: Radius of Planet [m]\nrotation::AbstractFloat: Angular frequency of Planet's rotation [1/s]\ngravity::AbstractFloat: Gravitational acceleration [m/s^2]\nlayer_thickness::AbstractFloat: shallow water layer thickness [m]\nR_dry::AbstractFloat: specific gas constant for dry air [J/kg/K]\nR_vapour::AbstractFloat: specific gas constant for water vapour [J/kg/K]\nμ_virt_temp::AbstractFloat: used in Tv = T(1+μq) for virt temp Tv(T,q) calculation\ncₚ::AbstractFloat: specific heat at constant pressure [J/K/kg]\nκ::AbstractFloat: = R_dry/cₚ, gas const for air over heat capacity\nwater_density::AbstractFloat: water density [kg/m³]\nf_coriolis::Vector{NF} where NF<:AbstractFloat: coriolis frequency 1/s = 2Ωsin(lat)radius\nσ_lnp_A::Vector{NF} where NF<:AbstractFloat: σ-related factor A needed for adiabatic terms\nσ_lnp_B::Vector{NF} where NF<:AbstractFloat: σ-related factor B needed for adiabatic terms\nΔp_geopot_half::Vector{NF} where NF<:AbstractFloat: = R*(ln(pk+1) - ln(pk+1/2)), for half level geopotential\nΔp_geopot_full::Vector{NF} where NF<:AbstractFloat: = R*(ln(pk+1/2) - ln(pk)), for full level geopotential\ntemp_ref_profile::Vector{NF} where NF<:AbstractFloat: reference temperature profile\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.DynamicsConstants-Tuple{SpectralGrid, SpeedyWeather.AbstractPlanet, SpeedyWeather.AbstractAtmosphere, Geometry}","page":"Function and type index","title":"SpeedyWeather.DynamicsConstants","text":"DynamicsConstants(\n spectral_grid::SpectralGrid,\n planet::SpeedyWeather.AbstractPlanet,\n atmosphere::SpeedyWeather.AbstractAtmosphere,\n geometry::Geometry\n) -> Any\n\n\nGenerator function for a DynamicsConstants struct.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.DynamicsVariables","page":"Function and type index","title":"SpeedyWeather.DynamicsVariables","text":"DynamicsVariables{Grid<:AbstractGrid,NF<:AbstractFloat}\n\nStruct holding intermediate quantities for the dynamics of a given layer.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Earth","page":"Function and type index","title":"SpeedyWeather.Earth","text":"Create a struct Earth<:AbstractPlanet, with the following physical/orbital characteristics. Note that radius is not part of it as this should be chosen in SpectralGrid. Keyword arguments are\n\nrotation::Float64: angular frequency of Earth's rotation [rad/s]\ngravity::Float64: gravitational acceleration [m/s^2]\ndaily_cycle::Bool: switch on/off daily cycle\nlength_of_day::Float64: [hrs] in a day\nseasonal_cycle::Bool: switch on/off seasonal cycle\nlength_of_year::Float64: [days] in a year\nequinox::Dates.DateTime: time of spring equinox (year irrelevant)\naxial_tilt::Float64: angle [˚] rotation axis tilt wrt to orbit\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.EarthAtmosphere","page":"Function and type index","title":"SpeedyWeather.EarthAtmosphere","text":"Create a struct EarthAtmosphere<:AbstractPlanet, with the following physical/chemical characteristics. Note that radius is not part of it as this should be chosen in SpectralGrid. Keyword arguments are\n\nmol_mass_dry_air::Float64: molar mass of dry air [g/mol]\nmol_mass_vapour::Float64: molar mass of water vapour [g/mol]\ncₚ::Float64: specific heat at constant pressure [J/K/kg]\nR_gas::Float64: universal gas constant [J/K/mol]\nR_dry::Float64: specific gas constant for dry air [J/kg/K]\nR_vapour::Float64: specific gas constant for water vapour [J/kg/K]\nwater_density::Float64: water density [kg/m³]\nlatent_heat_condensation::Float64: latent heat of condensation [J/g] for consistency with specific humidity [g/Kg], also called alhc\nlatent_heat_sublimation::Float64: latent heat of sublimation [J/g], also called alhs\nstefan_boltzmann::Float64: stefan-Boltzmann constant [W/m²/K⁴]\nlapse_rate::Float64: moist adiabatic temperature lapse rate -dTdz [K/km]\ntemp_ref::Float64: absolute temperature at surface z=0 [K]\ntemp_top::Float64: absolute temperature in stratosphere [K]\nΔT_stratosphere::Float64: for stratospheric lapse rate [K] after Jablonowski\nσ_tropopause::Float64: start of the stratosphere in sigma coordinates\nσ_boundary_layer::Float64: top of the planetary boundary layer in sigma coordinates\nscale_height::Float64: scale height for pressure [km]\npres_ref::Float64: surface pressure [hPa]\nscale_height_humid::Float64: scale height for specific humidity [km]\nrelhumid_ref::Float64: relative humidity of near-surface air [1]\nwater_pres_ref::Float64: saturation water vapour pressure [Pa]\nlayer_thickness::Float64: layer thickness for the shallow water model [km]\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.EarthOrography","page":"Function and type index","title":"SpeedyWeather.EarthOrography","text":"Earth's orography read from file, with smoothing.\n\npath::String: path to the folder containing the orography file, pkg path default\nfile::String: filename of orography\nfile_Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: Grid the orography file comes on\nscale::Float64: scale orography by a factor\nsmoothing::Bool: smooth the orography field?\nsmoothing_power::Float64: power of Laplacian for smoothing\nsmoothing_strength::Float64: highest degree l is multiplied by\nsmoothing_truncation::Int64: resolution of orography in spectral trunc\norography::SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat: height [m] on grid-point space.\ngeopot_surf::LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat: surface geopotential, height*gravity [m²/s²]\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.EarthOrography-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.EarthOrography","text":"EarthOrography(\n spectral_grid::SpectralGrid;\n kwargs...\n) -> Any\n\n\nGenerator function pulling the resolution information from spectral_grid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.Feedback","page":"Function and type index","title":"SpeedyWeather.Feedback","text":"Feedback() -> Feedback\nFeedback(verbose::Bool) -> Feedback\nFeedback(verbose::Bool, debug::Bool) -> Feedback\n\n\nGenerator function for a Feedback struct.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Feedback-2","page":"Function and type index","title":"SpeedyWeather.Feedback","text":"Feedback struct that contains options and object for command-line feedback like the progress meter.\n\nverbose::Bool: print feedback to REPL?\ndebug::Bool: check for NaRs in the prognostic variables\noutput::Bool: write a progress.txt file? State synced with OutputWriter.output\nid::Union{Int64, String}: identification of run, taken from ::OutputWriter\nrun_path::String: path to run folder, taken from ::OutputWriter\nprogress_meter::ProgressMeter.Progress: struct containing everything progress related\nprogress_txt::Union{Nothing, IOStream}: txt is a Nothing in case of no output\nnars_detected::Bool: did Infs/NaNs occur in the simulation?\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.GPUDevice","page":"Function and type index","title":"SpeedyWeather.GPUDevice","text":"GPUDevice <: AbstractDevice\n\nIndicates that SpeedyWeather.jl runs on a single GPU\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.GenLogisticCoefs","page":"Function and type index","title":"SpeedyWeather.GenLogisticCoefs","text":"Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF.\n\nFollowing the notation of https://en.wikipedia.org/wiki/Generalisedlogisticfunction (Dec 15 2021).\n\nChange default parameters for more/fewer levels in the stratosphere vs troposphere vs boundary layer.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Geometry","page":"Function and type index","title":"SpeedyWeather.Geometry","text":"Construct Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. Pass on SpectralGrid to calculate the following fields\n\nspectral_grid::SpectralGrid: SpectralGrid that defines spectral and grid resolution\nGrid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid of the dynamical core\nnlat_half::Int64: resolution parameter nlat_half of Grid, # of latitudes on one hemisphere (incl Equator)\nnlon_max::Int64: maximum number of longitudes (at/around Equator)\nnlon::Int64: =nlon_max, same (used for compatibility), TODO: still needed?\nnlat::Int64: number of latitude rings\nnlev::Int64: number of vertical levels\nnpoints::Int64: total number of grid points\nradius::AbstractFloat: Planet's radius [m]\nlatd::Vector{Float64}: array of latitudes in degrees (90˚...-90˚)\nlond::Vector{Float64}: array of longitudes in degrees (0...360˚), empty for non-full grids\nlonds::Vector{NF} where NF<:AbstractFloat: longitude (-180˚...180˚) for each grid point in ring order\nlatds::Vector{NF} where NF<:AbstractFloat: latitude (-90˚...˚90) for each grid point in ring order\nsinlat::Vector{NF} where NF<:AbstractFloat: sin of latitudes\ncoslat::Vector{NF} where NF<:AbstractFloat: cos of latitudes\ncoslat⁻¹::Vector{NF} where NF<:AbstractFloat: = 1/cos(lat)\ncoslat²::Vector{NF} where NF<:AbstractFloat: = cos²(lat)\ncoslat⁻²::Vector{NF} where NF<:AbstractFloat: # = 1/cos²(lat)\nσ_levels_half::Vector{NF} where NF<:AbstractFloat: σ at half levels, σ_k+1/2\nσ_levels_full::Vector{NF} where NF<:AbstractFloat: σ at full levels, σₖ\nσ_levels_thick::Vector{NF} where NF<:AbstractFloat: σ level thicknesses, σₖ₊₁ - σₖ\nln_σ_levels_full::Vector{NF} where NF<:AbstractFloat: log of σ at full levels, include surface (σ=1) as last element\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Geometry-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.Geometry","text":"Geometry(spectral_grid::SpectralGrid) -> Any\n\n\nGenerator function for Geometry struct based on spectral_grid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.GridVariables","page":"Function and type index","title":"SpeedyWeather.GridVariables","text":"GridVariables{NF<:AbstractFloat}\n\nStruct holding the prognostic spectral variables of a given layer in grid point space.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.HeldSuarez","page":"Function and type index","title":"SpeedyWeather.HeldSuarez","text":"Struct that defines the temperature relaxation from Held and Suarez, 1996 BAMS\n\nnlat::Int64: number of latitude rings\nnlev::Int64: number of vertical levels\nσb::Float64: sigma coordinate below which faster surface relaxation is applied\nrelax_time_slow::Float64: time scale [hrs] for slow global relaxation\nrelax_time_fast::Float64: time scale [hrs] for faster tropical surface relaxation\nTmin::Float64: minimum equilibrium temperature [K]\nTmax::Float64: maximum equilibrium temperature [K]\nΔTy::Float64: meridional temperature gradient [K]\nΔθz::Float64: vertical temperature gradient [K]\nκ::Base.RefValue{NF} where NF<:AbstractFloat\np₀::Base.RefValue{NF} where NF<:AbstractFloat\ntemp_relax_freq::Matrix{NF} where NF<:AbstractFloat\ntemp_equil_a::Vector{NF} where NF<:AbstractFloat\ntemp_equil_b::Vector{NF} where NF<:AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.HeldSuarez-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.HeldSuarez","text":"HeldSuarez(SG::SpectralGrid; kwargs...) -> Any\n\n\ncreate a HeldSuarez temperature relaxation with arrays allocated given spectral_grid\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.HyperDiffusion","page":"Function and type index","title":"SpeedyWeather.HyperDiffusion","text":"Struct for horizontal hyper diffusion of vor, div, temp; implicitly in spectral space with a power of the Laplacian (default=4) and the strength controlled by time_scale. Options exist to scale the diffusion by resolution, and adaptive depending on the current vorticity maximum to increase diffusion in active layers. Furthermore the power can be decreased above the tapering_σ to power_stratosphere (default 2). For Barotropic, ShallowWater, the default non-adaptive constant-time scale hyper diffusion is used. Options are\n\ntrunc::Int64: spectral resolution\nnlev::Int64: number of vertical levels\npower::Float64: power of Laplacian\ntime_scale::Float64: diffusion time scales [hrs]\nresolution_scaling::Float64: stronger diffusion with resolution? 0: constant with trunc, 1: (inverse) linear with trunc, etc\npower_stratosphere::Float64: different power for tropopause/stratosphere\ntapering_σ::Float64: linearly scale towards power_stratosphere above this σ\nadaptive::Bool: adaptive = higher diffusion for layers with higher vorticity levels.\nvor_max::Float64: above this (absolute) vorticity level [1/s], diffusion is increased\nadaptive_strength::Float64: increase strength above vor_max by this factor times max(abs(vor))/vor_max\n∇²ⁿ_2D::Vector\n∇²ⁿ_2D_implicit::Vector\n∇²ⁿ::Array{Vector{NF}, 1} where NF\n∇²ⁿ_implicit::Array{Vector{NF}, 1} where NF\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.HyperDiffusion-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.HyperDiffusion","text":"HyperDiffusion(\n spectral_grid::SpectralGrid;\n kwargs...\n) -> Any\n\n\nGenerator function based on the resolutin in spectral_grid. Passes on keyword arguments.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.ImplicitPrimitiveEq","page":"Function and type index","title":"SpeedyWeather.ImplicitPrimitiveEq","text":"Struct that holds various precomputed arrays for the semi-implicit correction to prevent gravity waves from amplifying in the primitive equation model.\n\ntrunc::Int64: spectral resolution\nnlev::Int64: number of vertical levels\nα::Float64: time-step coefficient: 0=explicit, 0.5=centred implicit, 1=backward implicit\ntemp_profile::Vector{NF} where NF<:AbstractFloat: vertical temperature profile, obtained from diagn\nξ::Base.RefValue{NF} where NF<:AbstractFloat: time step 2α*Δt packed in RefValue for mutability\nR::Matrix{NF} where NF<:AbstractFloat: divergence: operator for the geopotential calculation\nU::Vector{NF} where NF<:AbstractFloat: divergence: the -RdTₖ∇² term excl the eigenvalues from ∇² for divergence\nL::Matrix{NF} where NF<:AbstractFloat: temperature: operator for the TₖD + κTₖDlnps/Dt term\nW::Vector{NF} where NF<:AbstractFloat: pressure: vertical averaging of the -D̄ term in the log surface pres equation\nL0::Vector{NF} where NF<:AbstractFloat: components to construct L, 1/ 2Δσ\nL1::Matrix{NF} where NF<:AbstractFloat: vert advection term in the temperature equation (below+above)\nL2::Vector{NF} where NF<:AbstractFloat: factor in front of the divsumabove term\nL3::Matrix{NF} where NF<:AbstractFloat: sumabove operator itself\nL4::Vector{NF} where NF<:AbstractFloat: factor in front of div term in Dlnps/Dt\nS::Matrix{NF} where NF<:AbstractFloat: for every l the matrix to be inverted\nS⁻¹::Array{NF, 3} where NF<:AbstractFloat: combined inverted operator: S = 1 - ξ²(RL + UW)\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ImplicitPrimitiveEq-Tuple{SpectralGrid, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.ImplicitPrimitiveEq","text":"ImplicitPrimitiveEq(\n spectral_grid::SpectralGrid,\n kwargs...\n) -> Any\n\n\nGenerator using the resolution from SpectralGrid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.ImplicitShallowWater","page":"Function and type index","title":"SpeedyWeather.ImplicitShallowWater","text":"Struct that holds various precomputed arrays for the semi-implicit correction to prevent gravity waves from amplifying in the shallow water model.\n\ntrunc::Int64\nα::Float64: coefficient for semi-implicit computations to filter gravity waves\nH::Base.RefValue{NF} where NF<:AbstractFloat\nξH::Base.RefValue{NF} where NF<:AbstractFloat\ng∇²::Vector{NF} where NF<:AbstractFloat\nξg∇²::Vector{NF} where NF<:AbstractFloat\nS⁻¹::Vector{NF} where NF<:AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ImplicitShallowWater-Tuple{SpectralGrid, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.ImplicitShallowWater","text":"ImplicitShallowWater(\n spectral_grid::SpectralGrid,\n kwargs...\n) -> Any\n\n\nGenerator using the resolution from spectral_grid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.JablonowskiRelaxation","page":"Function and type index","title":"SpeedyWeather.JablonowskiRelaxation","text":"HeldSuarez-like temperature relaxation, but towards the Jablonowski temperature profile with increasing temperatures in the stratosphere.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.JablonowskiRelaxation-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.JablonowskiRelaxation","text":"JablonowskiRelaxation(SG::SpectralGrid; kwargs...) -> Any\n\n\ncreate a JablonowskiRelaxation temperature relaxation with arrays allocated given spectral_grid\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.Keepbits","page":"Function and type index","title":"SpeedyWeather.Keepbits","text":"Number of mantissa bits to keep for each prognostic variable when compressed for netCDF and .jld2 data output.\n\nu::Int64\nv::Int64\nvor::Int64\ndiv::Int64\ntemp::Int64\npres::Int64\nhumid::Int64\nprecip_cond::Int64\nprecip_conv::Int64\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Leapfrog","page":"Function and type index","title":"SpeedyWeather.Leapfrog","text":"Leapfrog time stepping defined by the following fields\n\ntrunc::Int64: spectral resolution (max degree of spherical harmonics)\nΔt_at_T31::Float64: time step in minutes for T31, scale linearly to trunc\nradius::Any: radius of sphere [m], used for scaling\nrobert_filter::Any: Robert (1966) time filter coefficeint to suppress comput. mode\nwilliams_filter::Any: Williams time filter (Amezcua 2011) coefficient for 3rd order acc\nΔt_sec::Int64: time step Δt [s] at specified resolution\nΔt::Any: time step Δt [s/m] at specified resolution, scaled by 1/radius\nΔt_hrs::Float64: convert time step Δt from minutes to hours\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Leapfrog-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.Leapfrog","text":"Leapfrog(spectral_grid::SpectralGrid; kwargs...) -> Any\n\n\nGenerator function for a Leapfrog struct using spectral_grid for the resolution information.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.LinearDrag","page":"Function and type index","title":"SpeedyWeather.LinearDrag","text":"Linear boundary layer drag Following Held and Suarez, 1996 BAMS\n\nσb::Float64\ntime_scale::Float64\nnlev::Int64\ndrag_coefs::Vector{NF} where NF<:AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.LinearDrag-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.LinearDrag","text":"LinearDrag(SG::SpectralGrid; kwargs...) -> Any\n\n\nGenerator function using nlev from SG::SpectralGrid\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.MagnusCoefs","page":"Function and type index","title":"SpeedyWeather.MagnusCoefs","text":"Parameters for computing saturation vapour pressure using the August-Roche-Magnus formula,\n\neᵢ(T) = e₀ * exp(Cᵢ * (T - T₀) / (T - Tᵢ)),\n\nwhere T is in Kelvin and i = 1,2 for saturation with respect to water and ice, respectively.\n\ne₀::AbstractFloat: Saturation vapour pressure at 0°C [Pa]\nT₀::AbstractFloat: 0°C in Kelvin\nT₁::AbstractFloat\nT₂::AbstractFloat\nC₁::AbstractFloat\nC₂::AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.NoBoundaryLayerDrag","page":"Function and type index","title":"SpeedyWeather.NoBoundaryLayerDrag","text":"Concrete type that disables the boundary layer drag scheme.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.NoOrography","page":"Function and type index","title":"SpeedyWeather.NoOrography","text":"Orography with zero height in orography and zero surface geopotential geopot_surf.\n\norography::SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat: height [m] on grid-point space.\ngeopot_surf::LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat: surface geopotential, height*gravity [m²/s²]\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.NoOrography-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.NoOrography","text":"NoOrography(spectral_grid::SpectralGrid) -> NoOrography\n\n\nGenerator function pulling the resolution information from spectral_grid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.OutputWriter","page":"Function and type index","title":"SpeedyWeather.OutputWriter","text":"NetCDF output writer. Contains all output options and auxiliary fields for output interpolation. To be initialised with OutputWriter(::SpectralGrid,::Type{<:ModelSetup},kwargs...) to pass on the resolution information and the model type which chooses which variables to output. Options include\n\nspectral_grid::SpectralGrid\noutput::Bool\npath::String: [OPTION] path to output folder, run_???? will be created within\nid::String: [OPTION] run identification number/string\nrun_path::String\nfilename::String: [OPTION] name of the output netcdf file\nwrite_restart::Bool: [OPTION] also write restart file if output==true?\npkg_version::VersionNumber\nstartdate::Dates.DateTime\noutput_dt::Float64: [OPTION] output frequency, time step [hrs]\noutput_dt_sec::Int64: actual output time step [sec]\noutput_vars::Vector{Symbol}: [OPTION] which variables to output, u, v, vor, div, pres, temp, humid\nmissing_value::Union{Float32, Float64}: [OPTION] missing value to be used in netcdf output\ncompression_level::Int64: [OPTION] lossless compression level; 1=low but fast, 9=high but slow\nkeepbits::SpeedyWeather.Keepbits: [OPTION] mantissa bits to keep for every variable\noutput_every_n_steps::Int64\ntimestep_counter::Int64\noutput_counter::Int64\nnetcdf_file::Union{Nothing, NetCDF.NcFile}\ninput_Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}\nas_matrix::Bool: [OPTION] sort grid points into a matrix (interpolation-free), for OctahedralClenshawGrid, OctaHEALPixGrid only\nquadrant_rotation::NTuple{4, Int64}\nmatrix_quadrant::NTuple{4, Tuple{Int64, Int64}}\noutput_Grid::Type{<:SpeedyWeather.RingGrids.AbstractFullGrid}: [OPTION] the grid used for output, full grids only\nnlat_half::Int64: [OPTION] the resolution of the output grid, default: same nlat_half as in the dynamical core\nnlon::Int64\nnlat::Int64\nnpoints::Int64\nnlev::Int64\ninterpolator::SpeedyWeather.RingGrids.AbstractInterpolator\nu::Matrix{NF} where NF<:Union{Float32, Float64}\nv::Matrix{NF} where NF<:Union{Float32, Float64}\nvor::Matrix{NF} where NF<:Union{Float32, Float64}\ndiv::Matrix{NF} where NF<:Union{Float32, Float64}\ntemp::Matrix{NF} where NF<:Union{Float32, Float64}\npres::Matrix{NF} where NF<:Union{Float32, Float64}\nhumid::Matrix{NF} where NF<:Union{Float32, Float64}\nprecip_cond::Matrix{NF} where NF<:Union{Float32, Float64}\nprecip_conv::Matrix{NF} where NF<:Union{Float32, Float64}\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.PrimitiveDryModel","page":"Function and type index","title":"SpeedyWeather.PrimitiveDryModel","text":"The PrimitiveDryModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.\n\nspectral_grid::SpectralGrid: dictates resolution for many other components\nplanet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics\natmosphere::SpeedyWeather.AbstractAtmosphere\ninitial_conditions::SpeedyWeather.InitialConditions\norography::SpeedyWeather.AbstractOrography{NF} where NF<:AbstractFloat\nphysics::Bool\nboundary_layer_drag::SpeedyWeather.BoundaryLayerDrag{NF} where NF<:AbstractFloat\ntemperature_relaxation::SpeedyWeather.TemperatureRelaxation{NF} where NF<:AbstractFloat\nstatic_energy_diffusion::SpeedyWeather.VerticalDiffusion{NF} where NF<:AbstractFloat\ntime_stepping::SpeedyWeather.TimeStepper{NF} where NF<:AbstractFloat\nspectral_transform::SpectralTransform\nhorizontal_diffusion::SpeedyWeather.HorizontalDiffusion{NF} where NF<:AbstractFloat\nimplicit::SpeedyWeather.AbstractImplicit{NF} where NF<:AbstractFloat\ngeometry::Geometry\nconstants::DynamicsConstants\ndevice_setup::SpeedyWeather.DeviceSetup\noutput::SpeedyWeather.AbstractOutputWriter\nfeedback::SpeedyWeather.AbstractFeedback\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.PrimitiveWetModel","page":"Function and type index","title":"SpeedyWeather.PrimitiveWetModel","text":"The PrimitiveDryModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.\n\nspectral_grid::SpectralGrid: dictates resolution for many other components\nplanet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics\natmosphere::SpeedyWeather.AbstractAtmosphere\ninitial_conditions::SpeedyWeather.InitialConditions\norography::SpeedyWeather.AbstractOrography{NF} where NF<:AbstractFloat\nphysics::Bool\nthermodynamics::SpeedyWeather.Thermodynamics{NF} where NF<:AbstractFloat\nboundary_layer_drag::SpeedyWeather.BoundaryLayerDrag{NF} where NF<:AbstractFloat\ntemperature_relaxation::SpeedyWeather.TemperatureRelaxation{NF} where NF<:AbstractFloat\nstatic_energy_diffusion::SpeedyWeather.VerticalDiffusion{NF} where NF<:AbstractFloat\nlarge_scale_condensation::SpeedyWeather.AbstractCondensation{NF} where NF<:AbstractFloat\ntime_stepping::SpeedyWeather.TimeStepper{NF} where NF<:AbstractFloat\nspectral_transform::SpectralTransform\nhorizontal_diffusion::SpeedyWeather.HorizontalDiffusion{NF} where NF<:AbstractFloat\nimplicit::SpeedyWeather.AbstractImplicit{NF} where NF<:AbstractFloat\ngeometry::Geometry\nconstants::DynamicsConstants\ndevice_setup::SpeedyWeather.DeviceSetup\noutput::SpeedyWeather.AbstractOutputWriter\nfeedback::SpeedyWeather.AbstractFeedback\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ShallowWaterModel","page":"Function and type index","title":"SpeedyWeather.ShallowWaterModel","text":"The ShallowWaterModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.\n\nspectral_grid::SpectralGrid: dictates resolution for many other components\nplanet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics\natmosphere::SpeedyWeather.AbstractAtmosphere\nforcing::SpeedyWeather.AbstractForcing{NF} where NF<:AbstractFloat\ninitial_conditions::SpeedyWeather.InitialConditions\norography::SpeedyWeather.AbstractOrography{NF} where NF<:AbstractFloat\ntime_stepping::SpeedyWeather.TimeStepper{NF} where NF<:AbstractFloat\nspectral_transform::SpectralTransform\nhorizontal_diffusion::SpeedyWeather.HorizontalDiffusion{NF} where NF<:AbstractFloat\nimplicit::SpeedyWeather.AbstractImplicit{NF} where NF<:AbstractFloat\ngeometry::Geometry\nconstants::DynamicsConstants\ndevice_setup::SpeedyWeather.DeviceSetup\noutput::SpeedyWeather.AbstractOutputWriter\nfeedback::SpeedyWeather.AbstractFeedback\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Simulation","page":"Function and type index","title":"SpeedyWeather.Simulation","text":"Simulation is a container struct to be used with run!(::Simulation). It contains\n\nprognostic_variables::PrognosticVariables: define the current state of the model\ndiagnostic_variables::DiagnosticVariables: contain the tendencies and auxiliary arrays to compute them\nmodel::SpeedyWeather.ModelSetup: all parameters, constant at runtime\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.SpectralGrid","page":"Function and type index","title":"SpeedyWeather.SpectralGrid","text":"Defines the horizontal spectral resolution and corresponding grid and the vertical coordinate for SpeedyWeather.jl. Options are\n\nNF::Type{<:AbstractFloat}: number format used throughout the model\ntrunc::Int64: horizontal resolution as the maximum degree of spherical harmonics\nGrid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: horizontal grid used for calculations in grid-point space\ndealiasing::Float64: how to match spectral with grid resolution: dealiasing factor, 1=linear, 2=quadratic, 3=cubic grid\nradius::Float64: radius of the sphere [m]\nnlat_half::Int64: number of latitude rings on one hemisphere (Equator incl)\nnpoints::Int64: total number of grid points in the horizontal\nnlev::Int64: number of vertical levels\nvertical_coordinates::SpeedyWeather.VerticalCoordinates: coordinates used to discretize the vertical\n\nnlat_half and npoints should not be chosen but are derived from trunc, Grid and dealiasing.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.SpeedyCondensation","page":"Function and type index","title":"SpeedyWeather.SpeedyCondensation","text":"Large scale condensation as in Fortran SPEEDY with default values from therein.\n\nnlev::Int64: number of vertical levels\nthreshold_boundary_layer::Float64: Relative humidity threshold for boundary layer\nthreshold_range::Float64: Vertical range of relative humidity threshold\nthreshold_max::Float64: Maximum relative humidity threshold [1]\ntime_scale::Float64: Relaxation time for humidity [hrs]\nn_stratosphere_levels::Base.RefValue{Int64}\nhumid_tend_max::Vector{NF} where NF<:AbstractFloat\nrelative_threshold::Vector{NF} where NF<:AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.SpectralTransform-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"SpectralTransform(\n spectral_grid::SpectralGrid;\n recompute_legendre,\n kwargs...\n) -> SpectralTransform\n\n\nGenerator function for a SpectralTransform struct pulling in parameters from a SpectralGrid struct.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.StartFromFile","page":"Function and type index","title":"SpeedyWeather.StartFromFile","text":"Restart from a previous SpeedyWeather.jl simulation via the restart file restart.jld2 Applies interpolation in the horizontal but not in the vertical. restart.jld2 is identified by\n\npath::String: path for restart file\nid::Union{Int64, String}: run_id of restart file in run_????/restart.jld2\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.StartWithRandomVorticity","page":"Function and type index","title":"SpeedyWeather.StartWithRandomVorticity","text":"Start with random vorticity as initial conditions\n\npower::Float64: Power of the spectral distribution k^power\namplitude::Float64: (approximate) amplitude in [1/s], used as standard deviation of spherical harmonic coefficients\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.StaticEnergyDiffusion","page":"Function and type index","title":"SpeedyWeather.StaticEnergyDiffusion","text":"Diffusion of dry static energy: A relaxation towards a reference gradient of static energy wrt to geopotential, see Fortran SPEEDY documentation.\n\ntime_scale::Float64: time scale [hrs] for strength\nstatic_energy_lapse_rate::Float64: [1] ∂SE/∂Φ, vertical gradient of static energy SE with geopotential Φ\nFstar::Base.RefValue{NF} where NF<:AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Tendencies","page":"Function and type index","title":"SpeedyWeather.Tendencies","text":"Tendencies{Grid<:AbstractGrid,NF<:AbstractFloat}\n\nStruct holding the tendencies of the prognostic spectral variables for a given layer.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ZonalJet","page":"Function and type index","title":"SpeedyWeather.ZonalJet","text":"Create a struct that contains all parameters for the Galewsky et al, 2004 zonal jet intitial conditions for the shallow water model. Default values as in Galewsky.\n\nlatitude::Float64: jet latitude [˚N]\nwidth::Float64: jet width [˚], default ≈ 19.29˚\numax::Float64: jet maximum velocity [m/s]\nperturb_lat::Float64: perturbation latitude [˚N], position in jet by default\nperturb_lon::Float64: perturbation longitude [˚E]\nperturb_xwidth::Float64: perturbation zonal extent [˚], default ≈ 19.1˚\nperturb_ywidth::Float64: perturbation meridinoal extent [˚], default ≈ 3.8˚\nperturb_height::Float64: perturbation amplitude [m]\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ZonalRidge","page":"Function and type index","title":"SpeedyWeather.ZonalRidge","text":"Zonal ridge orography after Jablonowski and Williamson, 2006.\n\nη₀::Float64: conversion from σ to Jablonowski's ηᵥ-coordinates\nu₀::Float64: max amplitude of zonal wind [m/s] that scales orography height\norography::SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat: height [m] on grid-point space.\ngeopot_surf::LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat: surface geopotential, height*gravity [m²/s²]\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ZonalRidge-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.ZonalRidge","text":"ZonalRidge(spectral_grid::SpectralGrid; kwargs...) -> Any\n\n\nGenerator function pulling the resolution information from spectral_grid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.ZonalWind","page":"Function and type index","title":"SpeedyWeather.ZonalWind","text":"Create a struct that contains all parameters for the Jablonowski and Williamson, 2006 intitial conditions for the primitive equation model. Default values as in Jablonowski.\n\nη₀::Float64: conversion from σ to Jablonowski's ηᵥ-coordinates\nu₀::Float64: max amplitude of zonal wind [m/s]\nperturb_lat::Float64: perturbation centred at [˚N]\nperturb_lon::Float64: perturbation centred at [˚E]\nperturb_uₚ::Float64: perturbation strength [m/s]\nperturb_radius::Float64: radius of Gaussian perturbation in units of Earth's radius [1]\nΔT::Float64: temperature difference used for stratospheric lapse rate [K], Jablonowski uses ΔT = 4.8e5 [K]\nTmin::Float64: minimum temperature [K] of profile\npressure_on_orography::Bool: initialize pressure given the atmosphere.lapse_rate on orography?\n\n\n\n\n\n","category":"type"},{"location":"functions/#Base.copy!-Tuple{PrognosticVariables, PrognosticVariables}","page":"Function and type index","title":"Base.copy!","text":"copy!(progn_new::PrognosticVariables, progn_old::PrognosticVariables)\n\nCopies entries of progn_old into progn_new. Only copies those variables that are present in the model of both progn_new and progn_old.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.Device-Tuple{}","page":"Function and type index","title":"SpeedyWeather.Device","text":"Device()\n\nReturn default used device for internal purposes, either CPUDevice or GPUDevice if a GPU is available.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.DeviceArray-Tuple{SpeedyWeather.GPUDevice, Any}","page":"Function and type index","title":"SpeedyWeather.DeviceArray","text":"DeviceArray(device::AbstractDevice, x)\n\nAdapts x to a CuArray when device<:GPUDevice is used, otherwise a regular Array. Uses adapt, thus also can return SubArrays etc.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.DeviceArrayNotAdapt-Tuple{SpeedyWeather.GPUDevice, Any}","page":"Function and type index","title":"SpeedyWeather.DeviceArrayNotAdapt","text":"DeviceArrayNotAdapt(device::AbstractDevice, x)\n\nReturns a CuArray when device<:GPUDevice is used, otherwise a regular Array. Doesn't uses adapt, therefore always returns CuArray/Array\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.Device_KernelAbstractions-Tuple{SpeedyWeather.CPUDevice}","page":"Function and type index","title":"SpeedyWeather.Device_KernelAbstractions","text":"Device_KernelAbstractions(::AbstractDevice)\n\nReturn used device for use with KernelAbstractions\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.Device_KernelAbstractions-Tuple{}","page":"Function and type index","title":"SpeedyWeather.Device_KernelAbstractions","text":"Device_KernelAbstractions()\n\nReturn default used device for KernelAbstractions, either CPU or CUDADevice if a GPU is available\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!-Tuple{DiagnosticVariables, PrognosticVariables, Int64, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!(\n diagn::DiagnosticVariables,\n progn::PrognosticVariables,\n lf::Int64,\n model::SpeedyWeather.ModelSetup\n)\n\n\nPropagate the spectral state of progn to diagn using time step/leapfrog index lf. Function barrier that calls gridded! for the respective model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticLayerTimesteps, Int64, Barotropic}","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::Barotropic\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticLayerTimesteps, Int64, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::PrimitiveEquation\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for primitive equation models. Updates grid vorticity, grid divergence, grid temperature, pressure (pres_grid) and the velocities u,v.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticLayerTimesteps, Int64, ShallowWater}","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::ShallowWater\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities u,v.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather._scale_lat!-Union{Tuple{NF}, Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}, AbstractVector}} where NF<:AbstractFloat","page":"Function and type index","title":"SpeedyWeather._scale_lat!","text":"_scale_lat!(\n A::SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n v::AbstractVector\n)\n\n\nGeneric latitude scaling applied to A in-place with latitude-like vector v.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.allocate-Union{Tuple{Model}, Tuple{Type{PrognosticVariables}, SpectralGrid, Type{Model}}} where Model<:SpeedyWeather.ModelSetup","page":"Function and type index","title":"SpeedyWeather.allocate","text":"allocate(\n _::Type{PrognosticVariables},\n spectral_grid::SpectralGrid,\n _::Type{Model<:SpeedyWeather.ModelSetup}\n) -> PrognosticVariables\n\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.bernoulli_potential!-Union{Tuple{NF}, Tuple{SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, SpectralTransform}} where NF","page":"Function and type index","title":"SpeedyWeather.bernoulli_potential!","text":"bernoulli_potential!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},\n S::SpectralTransform\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nComputes the Laplace operator ∇² of the Bernoulli potential B in spectral space.\n\ncomputes the kinetic energy KE = ½(u²+v²) on the grid\ntransforms KE to spectral space\nadds geopotential for the Bernoulli potential in spectral space\ntakes the Laplace operator.\n\nThis version is used for both ShallowWater and PrimitiveEquation, only the geopotential calculation in geopotential! differs.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.boundary_layer_drag!-Tuple{ColumnVariables, LinearDrag}","page":"Function and type index","title":"SpeedyWeather.boundary_layer_drag!","text":"boundary_layer_drag!(\n column::ColumnVariables,\n scheme::LinearDrag\n)\n\n\nCompute tendency for boundary layer drag of a column and add to its tendencies fields\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.boundary_layer_drag!-Tuple{ColumnVariables, SpeedyWeather.NoBoundaryLayerDrag, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.boundary_layer_drag!","text":"NoBoundaryLayer scheme just passes.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.clip_negatives!-Union{Tuple{AbstractArray{T}}, Tuple{T}} where T","page":"Function and type index","title":"SpeedyWeather.clip_negatives!","text":"clip_negatives!(A::AbstractArray)\n\nSet all negative entries a in A to zero.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.create_output_folder-Tuple{String, Union{Int64, String}}","page":"Function and type index","title":"SpeedyWeather.create_output_folder","text":"create_output_folder(\n path::String,\n id::Union{Int64, String}\n) -> String\n\n\nCreates a new folder run_* with the identification id. Also returns the full path run_path of that folder.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.default_sigma_coordinates-Tuple{Integer}","page":"Function and type index","title":"SpeedyWeather.default_sigma_coordinates","text":"default_sigma_coordinates(nlev::Integer) -> Any\n\n\nVertical sigma coordinates defined by their nlev+1 half levels σ_levels_half. Sigma coordinates are fraction of surface pressure (p/p0) and are sorted from top (stratosphere) to bottom (surface). The first half level is at 0 the last at 1. Evaluate a generalised logistic function with coefficients in P for the distribution of values in between. Default coefficients follow the L31 configuration historically used at ECMWF.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.dry_static_energy!-Tuple{ColumnVariables, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.dry_static_energy!","text":"dry_static_energy!(\n column::ColumnVariables,\n constants::DynamicsConstants\n)\n\n\nCompute the dry static energy SE = cₚT + Φ (latent heat times temperature plus geopotential) for the column.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.dynamics_tendencies!","page":"Function and type index","title":"SpeedyWeather.dynamics_tendencies!","text":"dynamics_tendencies!(\n diagn::DiagnosticVariables,\n progn::PrognosticVariables,\n model::PrimitiveEquation\n) -> Any\ndynamics_tendencies!(\n diagn::DiagnosticVariables,\n progn::PrognosticVariables,\n model::PrimitiveEquation,\n lf::Int64\n) -> Any\n\n\nCalculate all tendencies for the PrimitiveEquation model (wet or dry).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.dynamics_tendencies!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, Dates.DateTime, Barotropic}","page":"Function and type index","title":"SpeedyWeather.dynamics_tendencies!","text":"dynamics_tendencies!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n time::Dates.DateTime,\n model::Barotropic\n)\n\n\nCalculate all tendencies for the BarotropicModel.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.dynamics_tendencies!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.SurfaceVariables, LowerTriangularMatrix, Dates.DateTime, ShallowWater}","page":"Function and type index","title":"SpeedyWeather.dynamics_tendencies!","text":"dynamics_tendencies!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surface::SpeedyWeather.SurfaceVariables,\n pres::LowerTriangularMatrix,\n time::Dates.DateTime,\n model::ShallowWater\n)\n\n\nCalculate all tendencies for the ShallowWaterModel.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.first_timesteps!-Tuple{PrognosticVariables, DiagnosticVariables, SpeedyWeather.ModelSetup, SpeedyWeather.AbstractOutputWriter}","page":"Function and type index","title":"SpeedyWeather.first_timesteps!","text":"first_timesteps!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n model::SpeedyWeather.ModelSetup,\n output::SpeedyWeather.AbstractOutputWriter\n) -> typeof(time)\n\n\nPerforms the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.flipsign!-Tuple{AbstractArray}","page":"Function and type index","title":"SpeedyWeather.flipsign!","text":"flipgsign!(A::AbstractArray)\n\nLike -A but in-place.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.flux_divergence!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, SpeedyWeather.RingGrids.AbstractGrid{NF}, SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Geometry{NF}, SpectralTransform{NF}}} where NF","page":"Function and type index","title":"SpeedyWeather.flux_divergence!","text":"flux_divergence!(\n A_tend::LowerTriangularMatrix{Complex{NF}},\n A_grid::SpeedyWeather.RingGrids.AbstractGrid{NF},\n diagn::SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},\n G::Geometry{NF},\n S::SpectralTransform{NF};\n add,\n flipsign\n)\n\n\nComputes ∇⋅((u,v)*A) with the option to add/overwrite A_tend and to flip_sign of the flux divergence by doing so.\n\nA_tend = ∇⋅((u,v)*A) for add=false, flip_sign=false\nA_tend = -∇⋅((u,v)*A) for add=false, flip_sign=true\nA_tend += ∇⋅((u,v)*A) for add=true, flip_sign=false\nA_tend -= ∇⋅((u,v)*A) for add=true, flip_sign=true\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.fluxes_to_tendencies!-Tuple{ColumnVariables, Geometry, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.fluxes_to_tendencies!","text":"fluxes_to_tendencies!(\n column::ColumnVariables,\n geometry::Geometry,\n constants::DynamicsConstants\n)\n\n\nConvert the fluxes on half levels to tendencies on full levels.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.generalised_logistic-Tuple{Any, SpeedyWeather.GenLogisticCoefs}","page":"Function and type index","title":"SpeedyWeather.generalised_logistic","text":"Generalised logistic function based on the coefficients in coefs.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.geopotential!-Tuple{DiagnosticVariables, SpeedyWeather.AbstractOrography, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.geopotential!","text":"geopotential!(\n diagn::DiagnosticVariables,\n O::SpeedyWeather.AbstractOrography,\n C::DynamicsConstants\n)\n\n\nCompute spectral geopotential geopot from spectral temperature temp and spectral surface geopotential geopot_surf (orography*gravity).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.geopotential!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, LowerTriangularMatrix, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.geopotential!","text":"geopotential!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n pres::LowerTriangularMatrix,\n C::DynamicsConstants\n) -> Any\n\n\ncalculates the geopotential in the ShallowWaterModel as g*η, i.e. gravity times the interface displacement (field pres)\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.geopotential!-Tuple{Vector, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.geopotential!","text":"geopotential!(temp::Vector, C::DynamicsConstants) -> Vector\n\n\nCalculate the geopotential based on temp in a single column. This exclues the surface geopotential that would need to be added to the returned vector. Function not used in the dynamical core but for post-processing and analysis.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_column!-Tuple{ColumnVariables, DiagnosticVariables, Int64, Geometry}","page":"Function and type index","title":"SpeedyWeather.get_column!","text":"Recalculate ring index if not provided.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_column!-Tuple{ColumnVariables, DiagnosticVariables, Integer, Integer, Geometry}","page":"Function and type index","title":"SpeedyWeather.get_column!","text":"get_column!(\n C::ColumnVariables,\n D::DiagnosticVariables,\n ij::Integer,\n jring::Integer,\n G::Geometry\n)\n\n\nUpdate C::ColumnVariables by copying the prognostic variables from D::DiagnosticVariables at gridpoint index ij. Provide G::Geometry for coordinate information.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_full_output_file_path-Tuple{OutputWriter}","page":"Function and type index","title":"SpeedyWeather.get_full_output_file_path","text":"get_full_output_file_path(output::OutputWriter) -> String\n\n\nReturns the full path of the output file after it was created.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_run_id-Tuple{String, String}","page":"Function and type index","title":"SpeedyWeather.get_run_id","text":"get_run_id(path::String, id::String) -> String\n\n\nChecks existing run_???? folders in path to determine a 4-digit id number by counting up. E.g. if folder run_0001 exists it will return the string \"0002\". Does not create a folder for the returned run id.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_thermodynamics!-Tuple{ColumnVariables, PrimitiveDry}","page":"Function and type index","title":"SpeedyWeather.get_thermodynamics!","text":"get_thermodynamics!(\n column::ColumnVariables,\n model::PrimitiveDry\n)\n\n\nCalculate the dry static energy for the primitive dry model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_thermodynamics!-Tuple{ColumnVariables, PrimitiveWet}","page":"Function and type index","title":"SpeedyWeather.get_thermodynamics!","text":"get_thermodynamics!(\n column::ColumnVariables,\n model::PrimitiveWet\n)\n\n\nCalculate thermodynamic quantities like saturation vapour pressure, saturation specific humidity, dry static energy, moist static energy and saturation moist static energy from the prognostic column variables.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_var-Tuple{PrognosticVariables, Symbol}","page":"Function and type index","title":"SpeedyWeather.get_var","text":"get_var(progn::PrognosticVariables, var_name::Symbol; lf::Integer=1)\n\nReturns the prognostic variable var_name at leapfrog index lf as a Vector{LowerTriangularMatrices}.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.has-Tuple{Type{<:SpeedyWeather.ModelSetup}, Symbol}","page":"Function and type index","title":"SpeedyWeather.has","text":"has(\n M::Type{<:SpeedyWeather.ModelSetup},\n var_name::Symbol\n) -> Bool\n\n\nReturns true if the model M has a prognostic variable var_name, false otherwise. The default fallback is that all variables are included. \n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.horizontal_diffusion!","page":"Function and type index","title":"SpeedyWeather.horizontal_diffusion!","text":"horizontal_diffusion!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n model::Barotropic\n)\nhorizontal_diffusion!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n model::Barotropic,\n lf::Int64\n)\n\n\nApply horizontal diffusion to vorticity in the Barotropic models.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.horizontal_diffusion!-2","page":"Function and type index","title":"SpeedyWeather.horizontal_diffusion!","text":"horizontal_diffusion!(\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::ShallowWater\n)\nhorizontal_diffusion!(\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::ShallowWater,\n lf::Int64\n)\n\n\nApply horizontal diffusion to vorticity and diffusion in the ShallowWater models.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.horizontal_diffusion!-3","page":"Function and type index","title":"SpeedyWeather.horizontal_diffusion!","text":"horizontal_diffusion!(\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::PrimitiveEquation\n) -> Union{Nothing, Bool}\nhorizontal_diffusion!(\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::PrimitiveEquation,\n lf::Int64\n) -> Union{Nothing, Bool}\n\n\nApply horizontal diffusion applied to vorticity, diffusion and temperature in the PrimitiveEquation models. Uses the constant diffusion for temperature but possibly adaptive diffusion for vorticity and divergence.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.horizontal_diffusion!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, AbstractVector{NF}, AbstractVector{NF}}} where NF<:AbstractFloat","page":"Function and type index","title":"SpeedyWeather.horizontal_diffusion!","text":"horizontal_diffusion!(\n tendency::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n A::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n ∇²ⁿ_expl::AbstractArray{NF<:AbstractFloat, 1},\n ∇²ⁿ_impl::AbstractArray{NF<:AbstractFloat, 1}\n)\n\n\nApply horizontal diffusion to a 2D field A in spectral space by updating its tendency tendency with an implicitly calculated diffusion term. The implicit diffusion of the next time step is split into an explicit part ∇²ⁿ_expl and an implicit part ∇²ⁿ_impl, such that both can be calculated in a single forward step by using A as well as its tendency tendency.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.implicit_correction!-Tuple{DiagnosticVariables, SpeedyWeather.ImplicitPrimitiveEq, PrognosticVariables}","page":"Function and type index","title":"SpeedyWeather.implicit_correction!","text":"implicit_correction!(\n diagn::DiagnosticVariables,\n implicit::SpeedyWeather.ImplicitPrimitiveEq,\n progn::PrognosticVariables\n) -> Any\n\n\nApply the implicit corrections to dampen gravity waves in the primitive equation models.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.implicit_correction!-Union{Tuple{NF}, Tuple{SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, SpeedyWeather.PrognosticLayerTimesteps{NF}, SpeedyWeather.SurfaceVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, SpeedyWeather.PrognosticSurfaceTimesteps{NF}, SpeedyWeather.ImplicitShallowWater}} where NF","page":"Function and type index","title":"SpeedyWeather.implicit_correction!","text":"implicit_correction!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},\n progn::SpeedyWeather.PrognosticLayerTimesteps{NF},\n diagn_surface::SpeedyWeather.SurfaceVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},\n progn_surface::SpeedyWeather.PrognosticSurfaceTimesteps{NF},\n implicit::SpeedyWeather.ImplicitShallowWater\n)\n\n\nApply correction to the tendencies in diagn to prevent the gravity waves from amplifying. The correction is implicitly evaluated using the parameter implicit.α to switch between forward, centered implicit or backward evaluation of the gravity wave terms.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initial_conditions!-Tuple{PrognosticVariables, StartFromFile, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.initial_conditions!","text":"initial_conditions!(\n progn_new::PrognosticVariables,\n initial_conditions::StartFromFile,\n model::SpeedyWeather.ModelSetup\n) -> PrognosticVariables\n\n\nRestart from a previous SpeedyWeather.jl simulation via the restart file restart.jld2 Applies interpolation in the horizontal but not in the vertical.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initial_conditions!-Tuple{PrognosticVariables, ZonalJet, ShallowWater}","page":"Function and type index","title":"SpeedyWeather.initial_conditions!","text":"initial_conditions!(\n progn::PrognosticVariables,\n initial_conditions::ZonalJet,\n model::ShallowWater\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nInitial conditions from Galewsky, 2004, Tellus\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initial_conditions!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, StartWithRandomVorticity, SpeedyWeather.ModelSetup}} where NF","page":"Function and type index","title":"SpeedyWeather.initial_conditions!","text":"initial_conditions!(\n progn::PrognosticVariables{NF},\n initial_conditions::StartWithRandomVorticity,\n model::SpeedyWeather.ModelSetup\n)\n\n\nStart with random vorticity as initial conditions\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initial_conditions!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, ZonalWind, PrimitiveEquation}} where NF","page":"Function and type index","title":"SpeedyWeather.initial_conditions!","text":"initial_conditions!(\n progn::PrognosticVariables{NF},\n initial_conditions::ZonalWind,\n model::PrimitiveEquation\n)\n\n\nInitial conditions from Jablonowski and Williamson, 2006, QJR Meteorol. Soc\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initial_conditions-Tuple{Model} where Model","page":"Function and type index","title":"SpeedyWeather.initial_conditions","text":"initial_conditions(model) -> PrognosticVariables\n\n\nAllocate the prognostic variables and then set to initial conditions.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::HyperDiffusion,\n k::Int64,\n G::Geometry,\n L::SpeedyWeather.TimeStepper\n)\ninitialize!(\n scheme::HyperDiffusion,\n k::Int64,\n G::Geometry,\n L::SpeedyWeather.TimeStepper,\n vor_max::Real\n)\n\n\nPrecomputes the hyper diffusion terms in scheme for layer k based on the model time step in L, the vertical level sigma level in G, and the current (absolute) vorticity maximum level vor_max\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{Barotropic}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(model::Barotropic) -> SpeedyWeather.Simulation\n\n\nCalls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping!.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{EarthOrography, SpeedyWeather.AbstractPlanet, SpectralTransform, Geometry}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n orog::EarthOrography,\n P::SpeedyWeather.AbstractPlanet,\n S::SpectralTransform,\n G::Geometry\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nInitialize the arrays orography,geopot_surf in orog by reading the orography field from file.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{Feedback, SpeedyWeather.Clock, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n feedback::Feedback,\n clock::SpeedyWeather.Clock,\n model::SpeedyWeather.ModelSetup\n) -> Union{Nothing, IOStream}\n\n\nInitializes the a Feedback struct.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{HeldSuarez, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(scheme::HeldSuarez, model::PrimitiveEquation)\n\n\ninitialize the HeldSuarez temperature relaxation by precomputing terms for the equilibrium temperature Teq.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{HyperDiffusion, SpeedyWeather.DiagnosticVariablesLayer, Geometry, SpeedyWeather.TimeStepper}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::HyperDiffusion,\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n G::Geometry,\n L::SpeedyWeather.TimeStepper\n)\n\n\nPre-function to other initialize!(::HyperDiffusion) initialisors that calculates the (absolute) vorticity maximum for the layer of diagn.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{HyperDiffusion, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::HyperDiffusion,\n model::SpeedyWeather.ModelSetup\n)\n\n\nPrecomputes the hyper diffusion terms in scheme based on the model time step, and possibly with a changing strength/power in the vertical.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{HyperDiffusion, SpeedyWeather.TimeStepper}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::HyperDiffusion,\n L::SpeedyWeather.TimeStepper\n)\n\n\nPrecomputes the 2D hyper diffusion terms in scheme based on the model time step.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{JablonowskiRelaxation, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::JablonowskiRelaxation,\n model::PrimitiveEquation\n)\n\n\ninitialize the JablonowskiRelaxation temperature relaxation by precomputing terms for the equilibrium temperature Teq and the frequency (strength of relaxation).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{LinearDrag, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(scheme::LinearDrag, model::PrimitiveEquation)\n\n\nPrecomputes the drag coefficients for this BoundaryLayerDrag scheme.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{NoTemperatureRelaxation, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::NoTemperatureRelaxation,\n model::PrimitiveEquation\n)\n\n\njust passes, does not need any initialization.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{PrimitiveDry}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(model::PrimitiveDry) -> SpeedyWeather.Simulation\n\n\nCalls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{PrimitiveWet}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(model::PrimitiveWet) -> SpeedyWeather.Simulation\n\n\nCalls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{ShallowWater}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(model::ShallowWater) -> SpeedyWeather.Simulation\n\n\nCalls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyCondensation, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::SpeedyCondensation,\n model::PrimitiveEquation\n)\n\n\nInitialize the SpeedyCondensation scheme.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyWeather.Clock, SpeedyWeather.TimeStepper}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n clock::SpeedyWeather.Clock,\n time_stepping::SpeedyWeather.TimeStepper\n) -> SpeedyWeather.Clock\n\n\nInitialize the clock with the time step Δt in the time_stepping.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyWeather.ImplicitPrimitiveEq, Integer, Real, DiagnosticVariables, Geometry, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n implicit::SpeedyWeather.ImplicitPrimitiveEq,\n i::Integer,\n dt::Real,\n diagn::DiagnosticVariables,\n geometry::Geometry,\n constants::DynamicsConstants\n)\n\n\nReinitialize implicit occasionally based on time step i and implicit.recalculate.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyWeather.ImplicitPrimitiveEq, Real, DiagnosticVariables, Geometry, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n implicit::SpeedyWeather.ImplicitPrimitiveEq,\n dt::Real,\n diagn::DiagnosticVariables,\n geometry::Geometry,\n constants::DynamicsConstants\n)\n\n\nInitialize the implicit terms for the PrimitiveEquation models.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyWeather.ImplicitShallowWater, Real, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n implicit::SpeedyWeather.ImplicitShallowWater,\n dt::Real,\n constants::DynamicsConstants\n)\n\n\nUpdate the implicit terms in implicit for the shallow water model as they depend on the time step dt.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyWeather.NoBoundaryLayerDrag, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"NoBoundaryLayer scheme does not need any initialisation.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{ZonalRidge, SpeedyWeather.AbstractPlanet, SpectralTransform, Geometry}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n orog::ZonalRidge,\n P::SpeedyWeather.AbstractPlanet,\n S::SpectralTransform,\n G::Geometry\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nInitialize the arrays orography,geopot_surf in orog following Jablonowski and Williamson, 2006.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Union{Tuple{Model}, Tuple{output_NF}, Tuple{OutputWriter{output_NF, Model}, SpeedyWeather.AbstractFeedback, SpeedyWeather.TimeStepper, DiagnosticVariables, Model}} where {output_NF, Model}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n output::OutputWriter{output_NF, Model},\n feedback::SpeedyWeather.AbstractFeedback,\n time_stepping::SpeedyWeather.TimeStepper,\n diagn::DiagnosticVariables,\n model\n)\n\n\nCreates a netcdf file on disk and the corresponding netcdf_file object preallocated with output variables and dimensions. write_output! then writes consecuitive time steps into this file.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Union{Tuple{NF}, Tuple{SpeedyWeather.StaticEnergyDiffusion{NF}, PrimitiveEquation}} where NF","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::SpeedyWeather.StaticEnergyDiffusion{NF},\n model::PrimitiveEquation\n) -> Any\n\n\nInitialize dry static energy diffusion.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize_geopotential-Tuple{Vector, Vector, Real}","page":"Function and type index","title":"SpeedyWeather.initialize_geopotential","text":"initialize_geopotential(\n σ_levels_full::Vector,\n σ_levels_half::Vector,\n R_dry::Real\n) -> Tuple{Vector{Float64}, Vector{Float64}}\n\n\nPrecomputes constants for the vertical integration of the geopotential, defined as\n\nΦ_{k+1/2} = Φ_{k+1} + R*T_{k+1}*(ln(p_{k+1}) - ln(p_{k+1/2})) (half levels) Φ_k = Φ_{k+1/2} + R*T_k*(ln(p_{k+1/2}) - ln(p_k)) (full levels)\n\nSame formula but k → k-1/2.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.isdecreasing-Tuple{Vector}","page":"Function and type index","title":"SpeedyWeather.isdecreasing","text":"true/false = isdecreasing(v::Vector)\n\nCheck whether elements of a vector v are strictly decreasing.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.isincreasing-Tuple{Vector}","page":"Function and type index","title":"SpeedyWeather.isincreasing","text":"true/false = isincreasing(v::Vector)\n\nCheck whether elements of a vector v are strictly increasing.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.large_scale_condensation!-Tuple{ColumnVariables, PrimitiveDry}","page":"Function and type index","title":"SpeedyWeather.large_scale_condensation!","text":"large_scale_condensation!(\n column::ColumnVariables,\n model::PrimitiveDry\n)\n\n\nNo condensation in a PrimitiveDry model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.large_scale_condensation!-Tuple{ColumnVariables, PrimitiveWet}","page":"Function and type index","title":"SpeedyWeather.large_scale_condensation!","text":"Function barrier only.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.large_scale_condensation!-Union{Tuple{NF}, Tuple{ColumnVariables{NF}, SpeedyCondensation, Geometry, DynamicsConstants, SpeedyWeather.AbstractAtmosphere, SpeedyWeather.TimeStepper}} where NF","page":"Function and type index","title":"SpeedyWeather.large_scale_condensation!","text":"large_scale_condensation!(\n column::ColumnVariables{NF},\n scheme::SpeedyCondensation,\n geometry::Geometry,\n constants::DynamicsConstants,\n atmosphere::SpeedyWeather.AbstractAtmosphere,\n time_stepping::SpeedyWeather.TimeStepper\n)\n\n\nLarge-scale condensation for a column by relaxation back to a reference relative humidity if larger than that. Calculates the tendencies for specific humidity and temperature and integrates the large-scale precipitation vertically for output.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.launch_kernel!-Tuple{SpeedyWeather.DeviceSetup, Any, Any, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.launch_kernel!","text":"launch_kernel!(device_setup::DeviceSetup, kernel!, ndrange, kernel_args...)\n\nLaunches the kernel! on the device_setup with ndrange computations over the kernel and arguments kernel_args\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.leapfrog!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, Real, Int64, Leapfrog{NF}}} where NF<:AbstractFloat","page":"Function and type index","title":"SpeedyWeather.leapfrog!","text":"leapfrog!(\n A_old::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n A_new::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n tendency::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n dt::Real,\n lf::Int64,\n L::Leapfrog{NF<:AbstractFloat}\n)\n\n\nPerforms one leapfrog time step with (lf=2) or without (lf=1) Robert+Williams filter (see Williams (2009), Montly Weather Review, Eq. 7-9).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.linear_pressure_gradient!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticSurfaceTimesteps, Int64, DynamicsConstants, SpeedyWeather.ImplicitPrimitiveEq}","page":"Function and type index","title":"SpeedyWeather.linear_pressure_gradient!","text":"linear_pressure_gradient!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surface::SpeedyWeather.PrognosticSurfaceTimesteps,\n lf::Int64,\n C::DynamicsConstants,\n I::SpeedyWeather.ImplicitPrimitiveEq\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nAdd the linear contribution of the pressure gradient to the geopotential. The pressure gradient in the divergence equation takes the form\n\n-∇⋅(Rd*Tᵥ*∇lnpₛ) = -∇⋅(Rd*Tᵥ'*∇lnpₛ) - ∇²(Rd*Tₖ*lnpₛ)\n\nSo that the second term inside the Laplace operator can be added to the geopotential. Rd is the gas constant, Tᵥ the virtual temperature and Tᵥ' its anomaly wrt to the average or reference temperature Tₖ, lnpₛ is the logarithm of surface pressure.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.linear_virtual_temperature!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticLayerTimesteps, DynamicsConstants, Int64}","page":"Function and type index","title":"SpeedyWeather.linear_virtual_temperature!","text":"linear_virtual_temperature!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n constants::DynamicsConstants,\n lf::Int64\n) -> Any\n\n\nCalculates a linearised virtual temperature Tᵥ as\n\nTᵥ = T + Tₖμq\n\nWith absolute temperature T, layer-average temperarture Tₖ (computed in temperature_average!), specific humidity q and\n\nμ = (1-ξ)/ξ, ξ = R_dry/R_vapour.\n\nin spectral space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.linear_virtual_temperature!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticLayerTimesteps, PrimitiveDry, Integer}","page":"Function and type index","title":"SpeedyWeather.linear_virtual_temperature!","text":"linear_virtual_temperature!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n model::PrimitiveDry,\n lf::Integer\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nLinear virtual temperature for model::PrimitiveDry: Just copy over arrays from temp to temp_virt at timestep lf in spectral space as humidity is zero in this model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.load_trajectory-Tuple{Union{String, Symbol}, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.load_trajectory","text":"load_trajectory(\n var_name::Union{String, Symbol},\n model::SpeedyWeather.ModelSetup\n) -> Any\n\n\nLoads a var_name trajectory of the model M that has been saved in a netCDF file during the time stepping.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.moist_static_energy!-Tuple{ColumnVariables, SpeedyWeather.Thermodynamics}","page":"Function and type index","title":"SpeedyWeather.moist_static_energy!","text":"moist_static_energy!(\n column::ColumnVariables,\n thermodynamics::SpeedyWeather.Thermodynamics\n)\n\n\nCompute the moist static energy\n\nMSE = SE + Lc*Q = cₚT + Φ + Lc*Q\n\nwith the static energy SE, the latent heat of condensation Lc, the geopotential Φ. As well as the saturation moist static energy which replaces Q with Q_sat\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.nans-Tuple","page":"Function and type index","title":"SpeedyWeather.nans","text":"A = nans(dims...)\n\nAllocate A::Array{Float64} with NaNs.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.nans-Union{Tuple{T}, Tuple{Type{T}, Vararg{Any}}} where T","page":"Function and type index","title":"SpeedyWeather.nans","text":"A = nans(T,dims...)\n\nAllocate array A with NaNs of type T. Similar to zeros(T,dims...).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.nar_detection!-Tuple{Feedback, PrognosticVariables}","page":"Function and type index","title":"SpeedyWeather.nar_detection!","text":"nar_detection!(\n feedback::Feedback,\n progn::PrognosticVariables\n) -> Union{Nothing, Bool}\n\n\nDetect NaR (Not-a-Real) in the prognostic variables.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.parameterization_tendencies!-Tuple{DiagnosticVariables, Dates.DateTime, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.parameterization_tendencies!","text":"parameterization_tendencies!(\n diagn::DiagnosticVariables,\n time::Dates.DateTime,\n model::PrimitiveEquation\n) -> Any\n\n\nCompute tendencies for u,v,temp,humid from physical parametrizations. Extract for each vertical atmospheric column the prognostic variables (stored in diagn as they are grid-point transformed), loop over all grid-points, compute all parametrizations on a single-column basis, then write the tendencies back into a horizontal field of tendencies.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.pressure_on_orography!-Tuple{PrognosticVariables, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.pressure_on_orography!","text":"pressure_on_orography!(\n progn::PrognosticVariables,\n model::PrimitiveEquation\n)\n\n\nInitialize surface pressure on orography by integrating the hydrostatic equation with the reference temperature lapse rate.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.progress!-Tuple{Feedback}","page":"Function and type index","title":"SpeedyWeather.progress!","text":"progress!(feedback::Feedback)\n\n\nCalls the progress meter and writes every 5% progress increase to txt.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.progress_finish!-Tuple{Feedback}","page":"Function and type index","title":"SpeedyWeather.progress_finish!","text":"progress_finish!(F::Feedback)\n\n\nFinalises the progress meter and the progress txt file.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.readable_secs-Tuple{Real}","page":"Function and type index","title":"SpeedyWeather.readable_secs","text":"readable_secs(secs::Real) -> Dates.CompoundPeriod\n\n\nReturns Dates.CompoundPeriod rounding to either (days, hours), (hours, minutes), (minutes, seconds), or seconds with 1 decimal place accuracy for >10s and two for less. E.g.\n\njulia> readable_secs(12345)\n3 hours, 26 minutes\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.remaining_time-Tuple{ProgressMeter.Progress}","page":"Function and type index","title":"SpeedyWeather.remaining_time","text":"remaining_time(p::ProgressMeter.Progress) -> String\n\n\nEstimates the remaining time from a ProgresssMeter.Progress. Adapted from ProgressMeter.jl\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.reset_column!-Union{Tuple{ColumnVariables{NF}}, Tuple{NF}} where NF","page":"Function and type index","title":"SpeedyWeather.reset_column!","text":"reset_column!(column::ColumnVariables{NF})\n\n\nSet the accumulators (tendencies but also vertical sums and similar) back to zero for column to be reused at other grid points.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.run!-Tuple{SpeedyWeather.Simulation}","page":"Function and type index","title":"SpeedyWeather.run!","text":"run!(\n simulation::SpeedyWeather.Simulation;\n initialize,\n n_days,\n startdate,\n output\n) -> PrognosticVariables\n\n\nRun a SpeedyWeather.jl simulation. The simulation.model is assumed to be initialized, otherwise use initialize=true as keyword argument.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.saturation_humidity!-Tuple{ColumnVariables, SpeedyWeather.Thermodynamics}","page":"Function and type index","title":"SpeedyWeather.saturation_humidity!","text":"saturation_humidity!(\n column::ColumnVariables,\n thermodynamics::SpeedyWeather.Thermodynamics\n)\n\n\nCompute (1) the saturation vapour pressure as a function of temperature using the August-Roche-Magnus formula,\n\neᵢ(T) = e₀ * exp(Cᵢ * (T - T₀) / (T - Tᵢ)),\n\nwhere T is in Kelvin and i = 1,2 for saturation with respect to water and ice, respectively. And (2) the saturation specific humidity according to the formula,\n\n0.622 * e / (p - (1 - 0.622) * e),\n\nwhere e is the saturation vapour pressure, p is the pressure, and 0.622 is the ratio of the molecular weight of water to dry air.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.scale!-Tuple{PrognosticVariables, Real}","page":"Function and type index","title":"SpeedyWeather.scale!","text":"scale!(progn::PrognosticVariables, scale::Real) -> Real\n\n\nScales the prognostic variables vorticity and divergence with the Earth's radius which is used in the dynamical core.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.scale!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Real}} where NF","page":"Function and type index","title":"SpeedyWeather.scale!","text":"scale!(\n progn::PrognosticVariables{NF},\n var::Symbol,\n scale::Real\n)\n\n\nScale the variable var inside progn with scalar scale.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_divergence!-Tuple{PrognosticVariables, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.set_divergence!","text":"set_divergence!(progn::PrognosticVariables, varargs...; kwargs...)\n\nSee set_var!\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_humidity!-Tuple{PrognosticVariables, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.set_humidity!","text":"set_humidity!(progn::PrognosticVariables, varargs...; kwargs...)\n\nSee set_var!\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_pressure!","page":"Function and type index","title":"SpeedyWeather.set_pressure!","text":"set_pressure!(progn::PrognosticVariables{NF}, \n pressure::AbstractGrid, \n lf::Integer=1) where NF\n\nSets the prognostic variable with the surface pressure in grid space at leapfrog index lf.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.set_pressure!-2","page":"Function and type index","title":"SpeedyWeather.set_pressure!","text":"set_pressure!(progn::PrognosticVariables{NF}, \n pressure::AbstractMatrix, \n Grid::Type{<:AbstractGrid}, \n lf::Integer=1) where NF\n\nSets the prognostic variable with the surface pressure in grid space at leapfrog index lf.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.set_pressure!-Tuple{PrognosticVariables, SpeedyWeather.RingGrids.AbstractGrid, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.set_pressure!","text":"set_pressure!(progn::PrognosticVariables{NF}, \n pressure::AbstractGrid, \n M::ModelSetup;\n lf::Integer=1) where NF\n\nSets the prognostic variable with the surface pressure in grid space at leapfrog index lf.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_pressure!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, LowerTriangularMatrix}} where NF","page":"Function and type index","title":"SpeedyWeather.set_pressure!","text":"set_pressure!(progn::PrognosticVariables{NF}, \n pressure::LowerTriangularMatrix;\n lf::Integer=1) where NF\n\nSets the prognostic variable with the surface pressure in spectral space at leapfrog index lf.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_temperature!-Tuple{PrognosticVariables, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.set_temperature!","text":"set_temperature!(progn::PrognosticVariables, varargs...; kwargs...)\n\nSee set_var!\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_var!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Number}} where NF","page":"Function and type index","title":"SpeedyWeather.set_var!","text":"function set_var!(progn::PrognosticVariables{NF}, \n varname::Symbol, \n s::Number;\n lf::Integer=1) where NF\n\nSets all values of prognostic variable varname at leapfrog index lf to the scalar s.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_var!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Vector{<:AbstractMatrix}}, Tuple{PrognosticVariables{NF}, Symbol, Vector{<:AbstractMatrix}, Type{<:SpeedyWeather.RingGrids.AbstractGrid}}} where NF","page":"Function and type index","title":"SpeedyWeather.set_var!","text":"set_var!(progn::PrognosticVariables{NF}, \n varname::Symbol, \n var::Vector{<:AbstractMatrix}, \n Grid::Type{<:AbstractGrid}=FullGaussianGrid;\n lf::Integer=1) where NF\n\nSets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_var!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Vector{<:LowerTriangularMatrix}}} where NF","page":"Function and type index","title":"SpeedyWeather.set_var!","text":"set_var!(progn::PrognosticVariables{NF}, \n varname::Symbol, \n var::Vector{<:LowerTriangularMatrix};\n lf::Integer=1) where NF\n\nSets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in spectral space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_var!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Vector{<:SpeedyWeather.RingGrids.AbstractGrid}, SpeedyWeather.ModelSetup}} where NF","page":"Function and type index","title":"SpeedyWeather.set_var!","text":"set_var!(progn::PrognosticVariables{NF}, \n varname::Symbol, \n var::Vector{<:AbstractGrid}, \n M::ModelSetup;\n lf::Integer=1) where NF\n\nSets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_var!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Vector{<:SpeedyWeather.RingGrids.AbstractGrid}}} where NF","page":"Function and type index","title":"SpeedyWeather.set_var!","text":"set_var!(progn::PrognosticVariables{NF}, \n varname::Symbol, \n var::Vector{<:AbstractGrid};\n lf::Integer=1) where NF\n\nSets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_vorticity!-Tuple{PrognosticVariables, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.set_vorticity!","text":"set_vorticity!(progn::PrognosticVariables, varargs...; kwargs...)\n\nSee set_var!\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.sigma_okay-Tuple{Integer, AbstractVector}","page":"Function and type index","title":"SpeedyWeather.sigma_okay","text":"sigma_okay(nlev::Integer, σ_half::AbstractVector) -> Bool\n\n\nCheck that nlev and σ_half match.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.speedstring-Tuple{Any, Any}","page":"Function and type index","title":"SpeedyWeather.speedstring","text":"speedstring(sec_per_iter, dt_in_sec) -> String\n\n\ndefine a ProgressMeter.speedstring method that also takes a time step dt_in_sec to translate sec/iteration to days/days-like speeds.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.static_energy_diffusion!-Union{Tuple{NF}, Tuple{ColumnVariables{NF}, SpeedyWeather.StaticEnergyDiffusion}} where NF","page":"Function and type index","title":"SpeedyWeather.static_energy_diffusion!","text":"static_energy_diffusion!(\n column::ColumnVariables{NF},\n scheme::SpeedyWeather.StaticEnergyDiffusion\n)\n\n\nApply dry static energy diffusion.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.surface_pressure_tendency!-Tuple{SpeedyWeather.SurfaceVariables, SpectralTransform}","page":"Function and type index","title":"SpeedyWeather.surface_pressure_tendency!","text":"surface_pressure_tendency!( Prog::PrognosticVariables,\n Diag::DiagnosticVariables,\n lf::Int,\n M::PrimitiveEquation)\n\nComputes the tendency of the logarithm of surface pressure as\n\n-(ū*px + v̄*py) - D̄\n\nwith ū,v̄ being the vertically averaged velocities; px, py the gradients of the logarithm of surface pressure ln(p_s) and D̄ the vertically averaged divergence.\n\nCalculate ∇ln(p_s) in spectral space, convert to grid.\nMultiply ū,v̄ with ∇ln(p_s) in grid-point space, convert to spectral.\nD̄ is subtracted in spectral space.\nSet tendency of the l=m=0 mode to 0 for better mass conservation.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_anomaly!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.ImplicitPrimitiveEq}","page":"Function and type index","title":"SpeedyWeather.temperature_anomaly!","text":"Convert absolute and virtual temperature to anomalies wrt to the reference profile\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_average!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, LowerTriangularMatrix, SpectralTransform}","page":"Function and type index","title":"SpeedyWeather.temperature_average!","text":"temperature_average!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n temp::LowerTriangularMatrix,\n S::SpectralTransform\n) -> Any\n\n\nCalculates the average temperature of a layer from the l=m=0 harmonic and stores the result in diagn.temp_average\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_relaxation!-Tuple{ColumnVariables, JablonowskiRelaxation}","page":"Function and type index","title":"SpeedyWeather.temperature_relaxation!","text":"temperature_relaxation!(\n column::ColumnVariables,\n scheme::JablonowskiRelaxation\n)\n\n\nApply HeldSuarez-like temperature relaxation to the Jablonowski and Williamson vertical profile.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_relaxation!-Tuple{ColumnVariables, NoTemperatureRelaxation}","page":"Function and type index","title":"SpeedyWeather.temperature_relaxation!","text":"temperature_relaxation!(\n column::ColumnVariables,\n scheme::NoTemperatureRelaxation\n)\n\n\njust passes.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_relaxation!-Union{Tuple{NF}, Tuple{ColumnVariables{NF}, HeldSuarez}} where NF","page":"Function and type index","title":"SpeedyWeather.temperature_relaxation!","text":"temperature_relaxation!(\n column::ColumnVariables{NF},\n scheme::HeldSuarez\n)\n\n\nApply temperature relaxation following Held and Suarez 1996, BAMS.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_tendency!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, DynamicsConstants, Geometry, SpectralTransform, SpeedyWeather.ImplicitPrimitiveEq}","page":"Function and type index","title":"SpeedyWeather.temperature_tendency!","text":"temperature_tendency!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n C::DynamicsConstants,\n G::Geometry,\n S::SpectralTransform,\n I::SpeedyWeather.ImplicitPrimitiveEq\n)\n\n\nCompute the temperature tendency\n\n∂T/∂t += -∇⋅((u,v)*T') + T'D + κTᵥ*Dlnp/Dt\n\n+= because the tendencies already contain parameterizations and vertical advection. T' is the anomaly with respect to the reference/average temperature. Tᵥ is the virtual temperature used in the adiabatic term κTᵥ*Dlnp/Dt.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_tendency!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.temperature_tendency!","text":"temperature_tendency!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::PrimitiveEquation\n)\n\n\nFunction barrier to unpack model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.time_stepping!-Tuple{PrognosticVariables, DiagnosticVariables, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.time_stepping!","text":"time_stepping!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n model::SpeedyWeather.ModelSetup\n) -> PrognosticVariables\n\n\nMain time loop that that initializes output and feedback, loops over all time steps and calls the output and feedback functions.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.timestep!","page":"Function and type index","title":"SpeedyWeather.timestep!","text":"timestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n dt::Real,\n i::Integer,\n model::Barotropic\n)\ntimestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n dt::Real,\n i::Integer,\n model::Barotropic,\n lf1::Int64\n)\ntimestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n dt::Real,\n i::Integer,\n model::Barotropic,\n lf1::Int64,\n lf2::Int64\n)\n\n\nCalculate a single time step for the model <: Barotropic.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.timestep!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, PrimitiveEquation}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, PrimitiveEquation, Int64}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, PrimitiveEquation, Int64, Int64}} where NF<:AbstractFloat","page":"Function and type index","title":"SpeedyWeather.timestep!","text":"timestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::PrimitiveEquation\n) -> Any\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::PrimitiveEquation,\n lf1::Int64\n) -> Any\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::PrimitiveEquation,\n lf1::Int64,\n lf2::Int64\n) -> Any\n\n\nCalculate a single time step for the model<:PrimitiveEquation\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.timestep!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, ShallowWater}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, ShallowWater, Int64}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, ShallowWater, Int64, Int64}} where NF<:AbstractFloat","page":"Function and type index","title":"SpeedyWeather.timestep!","text":"timestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::ShallowWater\n) -> Union{Nothing, SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat}\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::ShallowWater,\n lf1::Int64\n) -> Union{Nothing, SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat}\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::ShallowWater,\n lf1::Int64,\n lf2::Int64\n) -> Union{Nothing, SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat}\n\n\nCalculate a single time step for the model <: ShallowWater.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.underflow!-Union{Tuple{T}, Tuple{AbstractArray{T}, Real}} where T","page":"Function and type index","title":"SpeedyWeather.underflow!","text":"underflow!(A::AbstractArray,ϵ::Real)\n\nUnderflows element a in A to zero if abs(a) < ϵ.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.unscale!-Tuple{AbstractArray, Real}","page":"Function and type index","title":"SpeedyWeather.unscale!","text":"unscale!(variable::AbstractArray, scale::Real) -> Any\n\n\nUndo the radius-scaling for any variable. Method used for netcdf output.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.unscale!-Tuple{PrognosticVariables}","page":"Function and type index","title":"SpeedyWeather.unscale!","text":"unscale!(progn::PrognosticVariables) -> Int64\n\n\nUndo the radius-scaling of vorticity and divergence from scale!(progn,scale::Real).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vertical_integration!-Union{Tuple{NF}, Tuple{DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, PrognosticVariables{NF}, Int64, Geometry{NF}}} where NF","page":"Function and type index","title":"SpeedyWeather.vertical_integration!","text":"vertical_integration!(Diag::DiagnosticVariables,G::Geometry)\n\nCalculates the vertically averaged (weighted by the thickness of the σ level) velocities (*coslat) and divergence. E.g.\n\nu_mean = ∑_k=1^nlev Δσ_k * u_k\n\nu,v are averaged in grid-point space, divergence in spectral space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.virtual_temperature!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, LowerTriangularMatrix, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.virtual_temperature!","text":"virtual_temperature!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n temp::LowerTriangularMatrix,\n constants::DynamicsConstants\n)\n\n\nCalculates the virtual temperature Tᵥ as\n\nTᵥ = T(1+μq)\n\nWith absolute temperature T, specific humidity q and\n\nμ = (1-ξ)/ξ, ξ = R_dry/R_vapour.\n\nin grid-point space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.virtual_temperature!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, LowerTriangularMatrix, PrimitiveDry}","page":"Function and type index","title":"SpeedyWeather.virtual_temperature!","text":"virtual_temperature!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n temp::LowerTriangularMatrix,\n model::PrimitiveDry\n) -> SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat\n\n\nVirtual temperature in grid-point space: For the PrimitiveDry temperature and virtual temperature are the same (humidity=0). Just copy over the arrays.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.volume_flux_divergence!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.SurfaceVariables, SpeedyWeather.AbstractOrography, DynamicsConstants, Geometry, SpectralTransform}","page":"Function and type index","title":"SpeedyWeather.volume_flux_divergence!","text":"volume_flux_divergence!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surface::SpeedyWeather.SurfaceVariables,\n orog::SpeedyWeather.AbstractOrography,\n constants::DynamicsConstants,\n G::Geometry,\n S::SpectralTransform\n)\n\n\nComputes the (negative) divergence of the volume fluxes uh,vh for the continuity equation, -∇⋅(uh,vh).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vordiv_tendencies!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.SurfaceVariables, DynamicsConstants, Geometry, SpectralTransform}","page":"Function and type index","title":"SpeedyWeather.vordiv_tendencies!","text":"vordiv_tendencies!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surf::SpeedyWeather.SurfaceVariables,\n C::DynamicsConstants,\n G::Geometry,\n S::SpectralTransform\n)\n\n\nTendencies for vorticity and divergence. Excluding Bernoulli potential with geopotential and linear pressure gradient inside the Laplace operator, which are added later in spectral space.\n\nu_tend += v*(f+ζ) - RTᵥ'*∇lnp_x\nv_tend += -u*(f+ζ) - RTᵥ'*∇lnp_y\n\n+= because the tendencies already contain the parameterizations and vertical advection. f is coriolis, ζ relative vorticity, R the gas constant Tᵥ' the virtual temperature anomaly, ∇lnp the gradient of surface pressure and _x and _y its zonal/meridional components. The tendencies are then curled/dived to get the tendencies for vorticity/divergence in spectral space\n\n∂ζ/∂t = ∇×(u_tend,v_tend)\n∂D/∂t = ∇⋅(u_tend,v_tend) + ...\n\n+ ... because there's more terms added later for divergence.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vordiv_tendencies!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.SurfaceVariables, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.vordiv_tendencies!","text":"vordiv_tendencies!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surf::SpeedyWeather.SurfaceVariables,\n model::PrimitiveEquation\n)\n\n\nFunction barrier to unpack model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vorticity_flux!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, Barotropic}","page":"Function and type index","title":"SpeedyWeather.vorticity_flux!","text":"vorticity_flux!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::Barotropic\n)\n\n\nVorticity flux tendency in the barotropic vorticity equation\n\n∂ζ/∂t = ∇×(u_tend,v_tend)\n\nwith\n\nu_tend = Fᵤ + v*(ζ+f) v_tend = Fᵥ - u*(ζ+f)\n\nwith Fᵤ,Fᵥ the forcing from forcing! already in u_tend_grid/v_tend_grid and vorticity ζ, coriolis f.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vorticity_flux!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, ShallowWater}","page":"Function and type index","title":"SpeedyWeather.vorticity_flux!","text":"vorticity_flux!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::ShallowWater\n)\n\n\nVorticity flux tendency in the shallow water equations\n\n∂ζ/∂t = ∇×(u_tend,v_tend) ∂D/∂t = ∇⋅(u_tend,v_tend)\n\nwith\n\nu_tend = Fᵤ + v*(ζ+f) v_tend = Fᵥ - u*(ζ+f)\n\nwith Fᵤ,Fᵥ the forcing from forcing! already in u_tend_grid/v_tend_grid and vorticity ζ, coriolis f.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vorticity_flux_curldiv!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, DynamicsConstants, Geometry, SpectralTransform}","page":"Function and type index","title":"SpeedyWeather.vorticity_flux_curldiv!","text":"vorticity_flux_curldiv!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n C::DynamicsConstants,\n G::Geometry,\n S::SpectralTransform;\n div\n)\n\n\nCompute the vorticity advection as the curl/div of the vorticity fluxes\n\n∂ζ/∂t = ∇×(u_tend,v_tend) ∂D/∂t = ∇⋅(u_tend,v_tend)\n\nwith\n\nu_tend = Fᵤ + v*(ζ+f) v_tend = Fᵥ - u*(ζ+f)\n\nwith Fᵤ,Fᵥ from u_tend_grid/v_tend_grid that are assumed to be alread set in forcing!. Set div=false for the BarotropicModel which doesn't require the divergence tendency.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.workgroup_size-Tuple{SpeedyWeather.AbstractDevice}","page":"Function and type index","title":"SpeedyWeather.workgroup_size","text":"workgroup_size(dev::AbstractDevice)\n\nReturns a workgroup size depending on dev. WIP: Will be expanded in the future to also include grid information. \n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.write_column_tendencies!-Tuple{DiagnosticVariables, ColumnVariables, Int64}","page":"Function and type index","title":"SpeedyWeather.write_column_tendencies!","text":"write_column_tendencies!(\n D::DiagnosticVariables,\n C::ColumnVariables,\n ij::Int64\n)\n\n\nWrite the parametrization tendencies from C::ColumnVariables into the horizontal fields of tendencies stored in D::DiagnosticVariables at gridpoint index ij.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.write_netcdf_time!-Tuple{OutputWriter, Dates.DateTime}","page":"Function and type index","title":"SpeedyWeather.write_netcdf_time!","text":"write_netcdf_time!(\n output::OutputWriter,\n time::Dates.DateTime\n)\n\n\nWrite the current time time::DateTime to the netCDF file in output::OutputWriter.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.write_netcdf_variables!-Union{Tuple{Model}, Tuple{Grid}, Tuple{NF}, Tuple{OutputWriter, DiagnosticVariables{NF, Grid, Model}}} where {NF, Grid, Model}","page":"Function and type index","title":"SpeedyWeather.write_netcdf_variables!","text":"write_netcdf_variables!(\n output::OutputWriter,\n diagn::DiagnosticVariables{NF, Grid, Model}\n)\n\n\nWrite diagnostic variables from diagn to the netCDF file in output::OutputWriter.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.write_output!-Tuple{OutputWriter, Dates.DateTime, DiagnosticVariables}","page":"Function and type index","title":"SpeedyWeather.write_output!","text":"write_output!(\n outputter::OutputWriter,\n time::Dates.DateTime,\n diagn::DiagnosticVariables\n)\n\n\nWrites the variables from diagn of time step i at time time into outputter.netcdf_file. Simply escapes for no netcdf output of if output shouldn't be written on this time step. Interpolates onto output grid and resolution as specified in outputter, converts to output number format, truncates the mantissa for higher compression and applies lossless compression.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.write_restart_file-Tuple{PrognosticVariables, OutputWriter}","page":"Function and type index","title":"SpeedyWeather.write_restart_file","text":"write_restart_file(\n progn::PrognosticVariables,\n output::OutputWriter\n) -> Union{Nothing, String}\n\n\nA restart file restart.jld2 with the prognostic variables is written to the output folder (or current path) that can be used to restart the model. restart.jld2 will then be used as initial conditions. The prognostic variables are bitrounded for compression and the 2nd leapfrog time step is discarded. Variables in restart file are unscaled.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.zero_tendencies!-Tuple{DiagnosticVariables}","page":"Function and type index","title":"SpeedyWeather.zero_tendencies!","text":"zero_tendencies!(diagn::DiagnosticVariables)\n\n\nSet the tendencies in diagn to zero.\n\n\n\n\n\n","category":"method"},{"location":"how_to_run_speedy/#How-to-run-SpeedyWeather.jl","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Creating a SpeedyWeather.jl simulation and running it consists conceptually of 4 steps","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Create a SpectralGrid which defines the grid and spectral resolution\nCreate a model\nInitialize a model to obtain a Simulation.\nRun the simulation.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"In the following we will describe these steps in more detail, but let's start with some examples first.","category":"page"},{"location":"how_to_run_speedy/#Example-1:-2D-turbulence-on-a-non-rotating-sphere","page":"How to run SpeedyWeather.jl","title":"Example 1: 2D turbulence on a non-rotating sphere","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"We want to use the barotropic model to simulate some free-decaying 2D turbulence on the sphere without rotation. We start by defining the SpectralGrid object. To have a resolution of about 100km, we choose a spectral resolution of T127 (see Available horizontal resolutions) and nlev=1 vertical levels. The SpectralGrid object will provide us with some more information","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> spectral_grid = SpectralGrid(trunc=127,nlev=1)\nSpectralGrid:\n Spectral: T127 LowerTriangularMatrix{Complex{Float32}}, radius = 6.371e6 m\n Grid: 40320-element, 192-ring OctahedralGaussianGrid{Float32} (quadratic)\n Resolution: 112km (average)\n Vertical: 1-level SigmaCoordinates","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"We could have specified further options, but let's ignore that for now. Next step we create a planet that's like Earth but not rotating","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> still_earth = Earth(rotation=0)\nMain.SpeedyWeather.Earth\n rotation: Float64 0.0\n gravity: Float64 9.81\n daily_cycle: Bool true\n length_of_day: Float64 24.0\n seasonal_cycle: Bool true\n length_of_year: Float64 365.25\n equinox: Dates.DateTime\n axial_tilt: Float64 23.4","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"There are other options to create a planet but they are irrelevant for the barotropic vorticity equations. We also want to specify the initial conditions, randomly distributed vorticity is already defined","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> initial_conditions = StartWithRandomVorticity()\nStartWithRandomVorticity\n power_law: Float64 -3.0\n amplitude: Float64 1.0e-5","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"By default, the power of vorticity is spectrally distributed with k^-3, k being the horizontal wavenumber, and the amplitude is 10^-5text s^-1.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Now we want to construct a BarotropicModel with these","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> model = BarotropicModel(;spectral_grid, initial_conditions, planet=still_earth);","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The model contains all the parameters, but isn't initialized yet, which we can do with and then run it.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> simulation = initialize!(model);\njulia> run!(simulation,n_days=30)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The run! command will always return the prognostic variables, which, by default, are plotted for surface relative vorticity with a unicode plot. The resolution of the plot is not necessarily representative but it lets us have a quick look at the result","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Barotropic vorticity unicode plot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Woohoo! I can see turbulence! You could pick up where this simulation stopped by simply doing run!(simulation,n_days=50) again. We didn't store any output, which you can do by run!(simulation,output=true), which will switch on NetCDF output with default settings. More options on output in NetCDF output.","category":"page"},{"location":"how_to_run_speedy/#Example-2:-Shallow-water-with-mountains","page":"How to run SpeedyWeather.jl","title":"Example 2: Shallow water with mountains","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"As a second example, let's investigate the Galewsky et al.[1] test case for the shallow water equations with and without mountains. As the shallow water system has also only one level, we can reuse the SpectralGrid from Example 1.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> spectral_grid = SpectralGrid(trunc=127,nlev=1)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Now as a first simulation, we want to disable any orography, so we create a NoOrography","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> orography = NoOrography(spectral_grid)\nNoOrography{Float32, OctahedralGaussianGrid{Float32}}","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Although the orography is zero, you have to pass on spectral_grid so that it can still initialize zero-arrays of the right size and element type. Awesome. This time the initial conditions should be set the the Galewsky et al.[1] zonal jet, which is already defined as","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> initial_conditions = ZonalJet()\nZonalJet\n latitude: Float64 45.0\n width: Float64 19.28571428571429\n umax: Float64 80.0\n perturb_lat: Float64 45.0\n perturb_lon: Float64 270.0\n perturb_xwidth: Float64 19.098593171027442\n perturb_ywidth: Float64 3.819718634205488\n perturb_height: Float64 120.0","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The jet sits at 45˚N with a maximum velocity of 80m/s and a perturbation as described in their paper. Now we construct a model, but this time a ShallowWaterModel","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);\njulia> simulation = initialize!(model);","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet unicode plot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Oh yeah. That looks like the wobbly jet in their paper. Let's run it again for another 6 days but this time also store NetCDF output.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> run!(simulation,n_days=6,output=true)\nWeather is speedy: run 0002 100%|███████████████████████| Time: 0:00:12 (115.37 years/day)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The progress bar tells us that the simulation run got the identification \"0002\", meaning that data is stored in the folder /run_0002, so let's plot that data properly (and not just using UnicodePlots).","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> using PyPlot, NCDatasets\njulia> ds = NCDataset(\"run_0002/output.nc\");\njulia> ds[\"vor\"]\nvor (384 × 192 × 1 × 25)\n Datatype: Float32\n Dimensions: lon × lat × lev × time\n Attributes:\n units = 1/s\n missing_value = NaN\n long_name = relative vorticity\n _FillValue = NaN","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Vorticity vor is stored as a 384x192x1x25 array, we may want to look at the first time step, which is the end of the previous simulation (time=6days) which we didn't store output for.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> vor = ds[\"vor\"][:,:,1,1];\njulia> lat = ds[\"lat\"][:];\njulia> lon = ds[\"lon\"][:];\njulia> pcolormesh(lon,lat,vor')","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Which looks like","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet pyplot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"You see that the unicode plot heavily coarse-grains the simulation, well it's unicode after all! And now the last time step, that means time=12days is","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> vor = ds[\"vor\"][:,:,1,25];\njulia> pcolormesh(lon,lat,vor')","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet pyplot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The jet broke up into many small eddies, but the turbulence is still confined to the northern hemisphere, cool! How this may change when we add mountains (we had NoOrography above!), say Earth's orography, you may ask? Let's try it out! We create an EarthOrography struct like so","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> orography = EarthOrography(spectral_grid)\nEarthOrography{Float32, OctahedralGaussianGrid{Float32}}:\n path::String = SpeedyWeather.jl/input_data\n file::String = orography_F512.nc\n scale::Float64 = 1.0\n smoothing::Bool = true\n smoothing_power::Float64 = 1.0\n smoothing_strength::Float64 = 0.1\n smoothing_truncation::Int64 = 85","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"It will read the orography from file as shown, and there are some smoothing options too, but let's not change them. Same as before, create a model, initialize into a simulation, run. This time directly for 12 days so that we can compare with the last plot","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);\njulia> simulation = initialize!(model);\njulia> run!(simulation,n_days=12,output=true)\nWeather is speedy: run 0003 100%|███████████████████████| Time: 0:00:35 (79.16 years/day)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"This time the run got the id \"0003\", but otherwise we do as before.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet pyplot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Interesting! The initial conditions have zero velocity in the southern hemisphere, but still, one can see some imprint of the orography on vorticity. You can spot the coastline of Antarctica; the Andes and Greenland are somewhat visible too. Mountains also completely changed the flow after 12 days, probably not surprising!","category":"page"},{"location":"how_to_run_speedy/#SpectralGrid","page":"How to run SpeedyWeather.jl","title":"SpectralGrid","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object. We have seen some examples above, now let's look into the details","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"SpectralGrid","category":"page"},{"location":"how_to_run_speedy/#References","page":"How to run SpeedyWeather.jl","title":"References","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"[1] Galewsky, Scott, Polvani, 2004. An initial-value problem for testing numerical models of the global shallow-water equations, Tellus A. DOI: 10.3402/tellusa.v56i5.14436","category":"page"},{"location":"speedytransforms/#SpeedyTransforms","page":"Submodule: SpeedyTransforms","title":"SpeedyTransforms","text":"","category":"section"},{"location":"speedytransforms/","page":"Submodule: SpeedyTransforms","title":"Submodule: SpeedyTransforms","text":"SpeedyTransforms is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"speedytransforms/#Example-transforms","page":"Submodule: SpeedyTransforms","title":"Example transforms","text":"","category":"section"},{"location":"speedytransforms/","page":"Submodule: SpeedyTransforms","title":"Submodule: SpeedyTransforms","text":"julia> using SpeedyWeather\njulia> alms = zeros(ComplexF64,3,3) # spectral coefficients\njulia> alms[2,2] = 1 # only l=1,m=1 harmonic\njulia> map = gridded(alms) # convert to grid space\n8×4 Matrix{Float64}:\n -0.324541 -0.600363 -0.600363 -0.324541\n -0.134429 -0.248678 -0.248678 -0.134429\n 0.134429 0.248678 0.248678 0.134429\n 0.324541 0.600363 0.600363 0.324541\n 0.324541 0.600363 0.600363 0.324541\n 0.134429 0.248678 0.248678 0.134429\n -0.134429 -0.248678 -0.248678 -0.134429\n -0.324541 -0.600363 -0.600363 -0.324541\n \njulia> spectral(map) # back to spectral space\n3×3 Matrix{ComplexF64}:\n 0.0+0.0im 0.0+0.0im 0.0+0.0im\n 0.0+0.0im 1.0+3.60727e-17im 0.0+0.0im\n 0.0+0.0im 0.0+0.0im 0.0+0.0im","category":"page"},{"location":"speedytransforms/","page":"Submodule: SpeedyTransforms","title":"Submodule: SpeedyTransforms","text":"and we have successfully reobtained the l=m=1 spherical harmonic.","category":"page"},{"location":"speedytransforms/#Functions-and-type-index","page":"Submodule: SpeedyTransforms","title":"Functions and type index","text":"","category":"section"},{"location":"speedytransforms/","page":"Submodule: SpeedyTransforms","title":"Submodule: SpeedyTransforms","text":"Modules = [SpeedyWeather.SpeedyTransforms]","category":"page"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.SpectralTransform","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"S = SpectralTransform{NF<:AbstractFloat}(...)\n\nSpectralTransform struct that contains all parameters and preallocated arrays for the spectral transform.\n\n\n\n\n\n","category":"type"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.SpectralTransform-Union{Tuple{AbstractArray{Complex{NF}, 2}}, Tuple{NF}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"S = SpectralTransform( alms::AbstractMatrix{Complex{NF}};\n recompute_legendre::Bool=true,\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID)\n\nGenerator function for a SpectralTransform struct based on the size of the spectral coefficients alms and the grid Grid. Recomputes the Legendre polynomials by default.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.SpectralTransform-Union{Tuple{NF}, Tuple{Type{NF}, Type{<:SpeedyWeather.RingGrids.AbstractGrid}, Int64}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"S = SpectralTransform(NF,Grid,trunc)\n\nGenerator function for a SpectralTransform struct. With NF the number format, Grid the grid type <:AbstractGrid and spectral truncation trunc this function sets up necessary constants for the spetral transform. Also plans the Fourier transforms, retrieves the colatitudes, and preallocates the Legendre polynomials (if recompute_legendre == false) and quadrature weights.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.SpectralTransform-Union{Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}}, Tuple{NF}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"S = SpectralTransform( map::AbstractGrid;\n recompute_legendre::Bool=true)\n\nGenerator function for a SpectralTransform struct based on the size and grid type of gridded field map. Recomputes the Legendre polynomials by default.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.UV_from_vor!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.UV_from_vor!","text":"UV_from_vor!( U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.UV_from_vordiv!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.UV_from_vordiv!","text":"UV_from_vordiv!(U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n div::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms._divergence!-Union{Tuple{NF}, Tuple{Any, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms._divergence!","text":"_divergence!( kernel,\n div::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGeneric divergence function of vector u,v that writes into the output into div. Generic as it uses the kernel kernel such that curl, div, add or flipsign options are provided through kernel, but otherwise a single function is used.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.curl!-Tuple{LowerTriangularMatrix, LowerTriangularMatrix, LowerTriangularMatrix, SpectralTransform}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.curl!","text":"curl!( curl::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform;\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nCurl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.divergence!-Tuple{LowerTriangularMatrix, LowerTriangularMatrix, LowerTriangularMatrix, SpectralTransform}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.divergence!","text":"divergence!(div::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform{NF};\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nDivergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.get_recursion_factors-Union{Tuple{NF}, Tuple{Type{NF}, Int64, Int64}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.get_recursion_factors","text":"get_recursion_factors( ::Type{NF}, # number format NF\n lmax::Int, # max degree l of spherical harmonics (0-based here)\n mmax::Int # max order m of spherical harmonics\n ) where {NF<:AbstractFloat}\n\nReturns a matrix of recursion factors ϵ up to degree lmax and order mmax of number format NF.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.gridded!-Union{Tuple{NF}, Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!( map::AbstractGrid,\n alms::LowerTriangularMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.gridded-Union{Tuple{AbstractMatrix{T}}, Tuple{T}, Tuple{NF}} where {NF, T<:Complex{NF}}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.gridded","text":"map = gridded( alms::AbstractMatrix;\n recompute_legendre::Bool=true,\n grid::Type{<:AbstractGrid}=DEFAULT_GRID)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.gridded-Union{Tuple{NF}, Tuple{AbstractMatrix, SpectralTransform{NF}}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.gridded","text":"map = gridded( alms::AbstractMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.is_power_2-Tuple{Integer}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.is_power_2","text":"true/false = is_power_2(i::Integer)\n\nChecks whether an integer i is a power of 2, i.e. i = 2^k, with k = 0,1,2,3,....\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.roundup_fft-Union{Tuple{Integer}, Tuple{T}} where T<:Integer","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.roundup_fft","text":"m = roundup_fft(n::Int;\n small_primes::Vector{Int}=[2,3,5])\n\nReturns an integer m >= n with only small prime factors 2, 3 (default, others can be specified with the keyword argument small_primes) to obtain an efficiently fourier-transformable number of longitudes, m = 2^i * 3^j * 5^k >= n, with i,j,k >=0.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, SpeedyWeather.RingGrids.AbstractGrid{NF}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral!","text":"spectral!( alms::LowerTriangularMatrix,\n map::AbstractGrid,\n S::SpectralTransform)\n\nSpectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral-Tuple{AbstractMatrix}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral","text":"alms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral-Union{Tuple{NF}, Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}, SpectralTransform{NF}}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral","text":"alms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nSpectral transform (grid to spectral) map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral-Union{Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}}, Tuple{NF}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral","text":"alms = spectral( map::AbstractGrid;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to Grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_interpolation-Union{Tuple{NF}, Tuple{Type{NF}, LowerTriangularMatrix, Integer, Integer}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_interpolation","text":"alms_interp = spectral_interpolation( ::Type{NF},\n alms::LowerTriangularMatrix,\n ltrunc::Integer,\n mtrunc::Integer\n ) where NF\n\nReturns a spectral coefficient matrix alms_interp that is alms padded with zeros to interpolate in spectral space. If trunc is smaller or equal to the implicit truncation in alms obtained from its size than spectral_truncation is automatically called instead, returning alms_trunc, a coefficient matrix that is smaller than alms, implicitly setting higher degrees and orders to zero.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_smoothing!-Tuple{LowerTriangularMatrix, Real}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_smoothing!","text":"spectral_smoothing!(A::LowerTriangularMatrix,c;power=1)\n\nSmooth the spectral field A following A = (1-(1-c)∇²ⁿ) with power n of a normalised Laplacian so that the highest degree lmax is dampened by multiplication with c. Anti-diffusion for c>1.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_smoothing-Tuple{LowerTriangularMatrix, Real}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_smoothing","text":"A_smooth = spectral_smoothing(A::LowerTriangularMatrix,c;power=1)\n\nSmooth the spectral field A following A_smooth = (1-c*∇²ⁿ)A with power n of a normalised Laplacian so that the highest degree lmax is dampened by multiplication with c. Anti-diffusion for c<0.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_transform_for_full_grid-Union{Tuple{SpectralTransform{NF}}, Tuple{NF}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_transform_for_full_grid","text":"S2 = spectral_transform_for_full_grid(S::SpectralTransform)\n\nCreate a spectral transform struct S2 similar to the input S, but for the corresponding full grid of the grid in S. The FFT is replanned and lon_offsets are set to 1 (i.e. no rotation). Solid angles for the Legendre transform are recomputed, but all other arrays fields for S, S2 point to the same place in memory, e.g. the Legendre polynomials aren't recomputed or stored twice.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_truncation!-Tuple{AbstractMatrix, Int64}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms,trunc)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_truncation!-Tuple{AbstractMatrix}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms)\n\nTruncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_truncation!-Union{Tuple{NF}, Tuple{AbstractMatrix{NF}, Integer, Integer}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms::AbstractMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_truncation!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{NF}, Integer, Integer}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms::LowerTriangularMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_truncation-Union{Tuple{NF}, Tuple{Type{NF}, LowerTriangularMatrix, Integer, Integer}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation","text":"alms_trunc = spectral_truncation(alms,trunc)\n\nReturns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.ϵlm-Tuple{Int64, Int64}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.ϵlm","text":"ϵ = ϵ(l,m)\n\nRecursion factors ϵ as a function of degree l and order m (0-based) of the spherical harmonics. ϵ(l,m) = sqrt((l^2-m^2)/(4*l^2-1)) with default number format Float64.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.ϵlm-Union{Tuple{NF}, Tuple{Type{NF}, Int64, Int64}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.ϵlm","text":"ϵ = ϵ(NF,l,m)\n\nRecursion factors ϵ as a function of degree l and order m (0-based) of the spherical harmonics. ϵ(l,m) = sqrt((l^2-m^2)/(4*l^2-1)) and then converted to number format NF.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.∇²!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.∇²!","text":"∇²!( ∇²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false,\n inverse::Bool=false)\n\nLaplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.\n\nKeyword arguments\n\nadd=true adds the ∇²(alms) to the output\nflipsign=true computes -∇²(alms) instead\ninverse=true computes ∇⁻²(alms) instead\n\nDefault is add=false, flipsign=false, inverse=false. These options can be combined.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.∇⁻²!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.∇⁻²!","text":"∇⁻²!( ∇⁻²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false)\n\nCalls ∇²!(∇⁻²alms, alms, S; add, flipsign, inverse=true).\n\n\n\n\n\n","category":"method"},{"location":"grids/#Grids","page":"Grids","title":"Grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and but let's start but how they can be used","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"julia> spectral_grid = SpectralGrid(Grid = FullGaussianGrid)\nSpectralGrid:\n Spectral: T31 LowerTriangularMatrix{Complex{Float32}}, radius = 6.371e6 m\n Grid: 4608-element, 48-ring FullGaussianGrid{Float32} (quadratic)\n Resolution: 333km (average)\n Vertical: 8-level SigmaCoordinates","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object which defines the resolution in spectral and in grid-point space. The generator SpectralGrid() can take as a keyword argument Grid which can be any of the grids described below. The resolution of the grid, however, is not directly chosen, but determined from the spectral resolution trunc and the dealiasing factor. More in Matching spectral and grid resolution.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: RingGrids is a module too!\nWhile RingGrids is the underlying module that SpeedyWeather.jl uses for data structs on the sphere, the module can also be used independently of SpeedyWeather, for example to interpolate between data on different grids. See RingGrids","category":"page"},{"location":"grids/#Ring-based-equi-longitude-grids","page":"Grids","title":"Ring-based equi-longitude grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"SpeedyWeather.jl's spectral transform supports all ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on these rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: Is the FullClenshawGrid a longitude-latitude grid?\nShort answer: Yes. The FullClenshawGrid is a specific longitude-latitude grid with equi-angle spacing. The most common grids for geoscientific data use regular spacings for 0-360˚E in longitude and 90˚N-90˚S. The FullClenshawGrid does that too, but it does not have a point on the North or South pole, and the central latitude ring sits exactly on the Equator. We name it Clenshaw following the Clenshaw-Curtis quadrature that is used in the Legendre transfrom in the same way as Gaussian refers to the Gaussian quadrature.","category":"page"},{"location":"grids/#Implemented-grids","page":"Grids","title":"Implemented grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Currently the following full grids <: AbstractFullGrid are implemented","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"FullGaussianGrid, a full grid with Gaussian latitudes\nFullClenshawGrid, a full grid with equi-angle latitudes","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron\nOctahedralClenshawGrid, similar but based on equi-angle latitudes\nHEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces\nOctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"An overview of these grids is visualised here","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"(Image: Overview of implemented grids in SpeedyWeather.jl)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.","category":"page"},{"location":"grids/#Grid-resolution","page":"Grids","title":"Grid resolution","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half. ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: HEALPix grids do not use Nside as resolution parameter\nThe original formulation for HEALPix grids use N_side, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use N_side for the documentation or within functions though.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Related: Effective grid resolution and Available horizontal resolutions.","category":"page"},{"location":"grids/#Matching-spectral-and-grid-resolution","page":"Grids","title":"Matching spectral and grid resolution","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation T with a grid resolution N (=nlat_half) such that T + 1 = N. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at l_max=31 in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid. In SpeedyWeather.jl the choice of the order of truncation is controlled with the dealiasing parameter in the SpectralGrid construction.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Let J be the total number of rings. Then we have","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"T approx J for linear truncation, i.e. dealiasing = 1\nfrac32T approx J for quadratic truncation, i.e. dealiasing = 2\n2T approx J for cubic truncation, , i.e. dealiasing = 3","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and in general fracm+12T approx J for m-th order truncation. So the higher the truncation order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. A quick overview of how the grid resolution changes when dealiasing is passed onto SpectralGrid on the FullGaussianGrid","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"trunc dealiasing FullGaussianGrid size\n31 1 64x32\n31 2 96x48\n31 3 128x64\n42 1 96x48\n42 2 128x64\n42 3 192x96\n... ... ...","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"You will obtain this information every time you create a SpectralGrid(;Grid,trunc,dealiasing).","category":"page"},{"location":"grids/#FullGaussianGrid","page":"Grids","title":"Full Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#FullClenshawGrid","page":"Grids","title":"Full Clenshaw-Curtis grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#Octahedral-Gaussian-grid","page":"Grids","title":"Octahedral Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#HEALPix-grid","page":"Grids","title":"HEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are N_varphi basepixels in zonal direction and N_theta basepixels in meridional direction. For N_varphi = 4 and N_theta = 3 we obtain the classical HEALPix grid with N_varphi N_theta = 12 basepixels shown above in Implemented grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always 2N, so 32 at N=16) and there are polar caps above and below the equatorial zone with the border at cos(theta) = 23 (theta in colatitudes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Following Górski, 2004[1], the z=cos(theta) colatitude of the j-th ring in the north polar cap, j=1N_side with 2N_side = N is ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^23N_side^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and on that ring, the longitude phi of the i-th point (i is the in-ring-index) is at","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i-tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The in-ring index i goes from i=14 for the first (i.e. northern-most) ring, i=18 for the second ring and i = 14j for the j-th ring in the northern polar cap.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"In the north equatorial belt j=N_side2N_side this changes to","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac43 - frac2j3N_side","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and the longitudes change to (i is always i = 14N_side in the equatorial belt meaning the number of longitude points is constant here)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2N_side(i - fracs2) quad s = (j - N_side + 1) mod 2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The cell boundaries are obtained by setting i = k + 12 or i = k + 12 + j (half indices) into the equations above, such that z(phik), a function for the cosine of colatitude z of index k and the longitude phi is obtained. These are then (northern polar cap)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^23N_side^2left(fracpi2phi_tright)^2 quad z = 1 - frack^23N_side^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with phi_t = phi mod tfracpi2 and in the equatorial belt","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac23-frac4k3N_side pm frac8phi3pi","category":"page"},{"location":"grids/#OctaHEALPix-grid","page":"Grids","title":"OctaHEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"While the classic HEALPix grid is based on a dodecahedron, other choices for N_varphi and N_theta in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With N_varphi = 4 and N_theta = 1 we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, 2pi around the Equator versus pi between the poles. N_varphi = 6 N_theta = 2 or N_varphi = 8 N_theta = 3 are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visualizations of these grids.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"We call the N_varphi = 4 N_theta = 1 HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As N_theta = 1 there is no equatorial belt which simplifies the grid. The latitude of the j-th isolatitude ring on the OctaHEALPixGrid is defined by","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^2N^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with j=1N, and similarly for the southern hemisphere by symmetry. On this grid N_side = N where N= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index i = 14j are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i - tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and again, the southern hemisphere grid points are obtained by symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries-2","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Similar to the grid cell boundaries for the HEALPix grid, the OctaHEALPix grid's boundaries are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^2N^2left(fracpi2phi_tright)^2 quad z = 1 - frack^2N^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The 3N_side^2 in the denominator of the HEALPix grid came simply N^2 for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).","category":"page"},{"location":"grids/#References","page":"Grids","title":"References","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"[1]: Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976","category":"page"},{"location":"primitiveequation/#Primitive-equation-model","page":"Primitive equation model","title":"Primitive equation model","text":"","category":"section"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"The primitive equations are a hydrostatic approximation of the compressible Navier-Stokes equations for an ideal gas on a rotating sphere. We largely follow the idealised spectral dynamical core developed by GFDL[1] and documented therein[2].","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"The primitive equations solved by SpeedyWeather.jl for relative vorticity zeta, divergence mathcalD, logarithm of surface pressure ln p_s, temperature T and specific humidity q are","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"beginaligned\nfracpartial zetapartial t = nabla times (mathbfmathcalP_mathbfu\n+ (f+zeta)mathbfu_perp - W(mathbfu) - R_dT_vnabla ln p_s) \nfracpartial mathcalDpartial t = nabla cdot (mathcalP_mathbfu\n+ (f+zeta)mathbfu_perp - W(mathbfu) - R_dT_vnabla ln p_s) - nabla^2(frac12(u^2 + v^2) + Phi) \nfracpartial ln p_spartial t = -frac1p_s nabla cdot int_0^p_s mathbfudp \nfracpartial Tpartial t = mathcalP_T -nablacdot(mathbfuT) + TmathcalD - W(T) + kappa T_v fracD ln pDt \nfracpartial qpartial t = mathcalP_q -nablacdot(mathbfuq) + qmathcalD - W(q)\nendaligned","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"with velocity mathbfu = (uv), rotated velocity mathbfu_perp = (v-u), Coriolis parameter f, W the vertical advection operator, dry air gas constant R_d, virtual temperature T_v, geopotential Phi, pressure p, thermodynamic kappa = R_dc_p with c_p the heat capacity at constant pressure. Horizontal hyper diffusion of the form (-1)^n+1nunabla^2n with coefficient nu and power n is added for every variable that is advected, meaning zeta mathcalD T q, but left out here for clarity, see Horizontal diffusion.","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"The parameterizations for the tendencies of uvTq from physical processes are denoted as mathcalP_mathbfu = (mathcalP_u mathcalP_v) mathcalP_T mathcalP_q and are further described in the corresponding sections, see Parameterizations.","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"SpeedyWeather.jl implements a PrimitiveWet and a PrimitiveDry dynamical core. For a dry atmosphere, we have q = 0 and the virtual temperature T_v = T equals the temperature (often called absolute to distinguish from the virtual temperature). The terms in the primitive equations and their discretizations are discussed in the following sections. ","category":"page"},{"location":"primitiveequation/#Virtual-temperature","page":"Primitive equation model","title":"Virtual temperature","text":"","category":"section"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"info: In short: Virtual temperature\nVirtual temperature is the temperature dry air would need to have to be as light as moist air. It is used in the dynamical core to include the effect of humidity on the density while replacing density through the ideal gas law with temperature.","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"We assume the atmosphere to be composed of two ideal gases: Dry air and water vapour. Given a specific humidity q both gases mix, their pressures p_d, p_w (d for dry, w for water vapour), and densities rho_d rho_w add in a given air parcel that has temperature T. The ideal gas law then holds for both gases","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"beginaligned\np_d = rho_d R_d T \np_w = rho_w R_w T \nendaligned","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"with the respective specific gas constants R_d = Rm_d and R_w = Rm_w obtained from the univeral gas constant R divided by the molecular masses of the gas. The total pressure p in the air parcel is","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"p = p_d + p_w = (rho_d R_d + rho_w R_w)T","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"We ultimately want to replace the density rho = rho_w + rho_d in the dynamical core, using the ideal gas law, with the temperature T, so that we never have to calculate the density explicitly. However, in order to not deal with two densities (dry air and water vapour) we would like to replace temperature with a virtual temperature that includes the effect of humidity on the density. So, whereever we use the ideal gas law to replace density with temperature, we would use the virtual temperature, which is a function of the absolute temperature and specific humidity, instead. A higher specific humidity in an air parcel lowers the density as water vapour is lighter than dry air. Consequently, the virtual temperature of moist air is higher than its absolute temperature because warmer air is lighter too at constant pressure. We therefore think of the virtual temperature as the temperature dry air would need to have to be as light as moist air.","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Starting with the last equation, with some manipulation we can write the ideal gas law as total density rho times a gas constant times the virtual temperature that is supposed to be a function of absolute temperature, humidity and some constants","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"p = (rho R_d + rho_w (R_w - R_d)) T = rho R_d (1 +\nfrac1 - tfracR_dR_wtfracR_dR_w fracrho_wrho_w + rho_d)T","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Now we identify","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"mu = frac1 - tfracR_dR_wtfracR_dR_w","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"as some constant that is positive for water vapour being lighter than dry air (tfracR_dR_w = tfracm_wm_d 1) and","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"q = fracrho_wrho_w + rho_d","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"as the specific humidity. Given temperature T and specific humidity q, we can therefore calculate the virtual temperature T_v as","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"T_v = (1 + mu q)T","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"For completeness we want to mention here that the above product, because it is a product of two variables qT has to be computed in grid-point space, see [Spectral Transform]. To obtain an approximation to the virtual temperature in spectral space without expensive transforms one can linearize","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"T_v = T + mu qbarT","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"With a global constant temperature barT, for example obtained from the l=m=0 mode, barT = T_00frac1sqrt4pi but depending on the normalization of the spherical harmonics that factor needs adjustment.","category":"page"},{"location":"primitiveequation/#Vertical-coordinates","page":"Primitive equation model","title":"Vertical coordinates","text":"","category":"section"},{"location":"primitiveequation/#General","page":"Primitive equation model","title":"General","text":"","category":"section"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Let Psi(xyzt) ","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"SpeedyWeather.jl currently uses sigma coordinates for the vertical. ","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"sigma = fracpp_s","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"p_k = sigma_kp_s","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Delta p_k = p_k+1 - p_k = Delta sigma_k p_s","category":"page"},{"location":"primitiveequation/#Geopotential","page":"Primitive equation model","title":"Geopotential","text":"","category":"section"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"In the hydrostatic approximation the vertical momentum equation becomes","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"fracpartial ppartial z = -rho g","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"meaning that the (negative) vertical pressure gradient is given by the density in that layer times the gravitational acceleration. The heavier the fluid the more the pressure will increase below. Inserting the ideal gas law","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"fracpartial gzpartial p = -fracR_dT_vp","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"with the geopotential Phi = gz we can write this in terms of the logarithm of pressure","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"fracpartial Phipartial ln p = -R_dT_v","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Note that we use the Virtual temperature here as we replaced the density through the ideal gas law with temperature. Given a vertical temperature profile T_v and the (constant) surface geopotential Phi_s = gz_s where z_s is the orography, we can integrate this equation from the surface to the top to obtain Phi_k on every layer k. The surface is at k = N+tfrac12 (see Vertical coordinates) with N vertical levels. We can integrate the geopotential onto half levels as","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Phi_k-tfrac12 = Phi_k+tfrac12 + R_dT^v_k(ln p_k+12 - ln p_k-12)","category":"page"},{"location":"primitiveequation/#Surface-pressure-tendency","page":"Primitive equation model","title":"Surface pressure tendency","text":"","category":"section"},{"location":"primitiveequation/#Vertical-advection","page":"Primitive equation model","title":"Vertical advection","text":"","category":"section"},{"location":"primitiveequation/#Pressure-gradient-force","page":"Primitive equation model","title":"Pressure gradient force","text":"","category":"section"},{"location":"primitiveequation/#Temperature-equation","page":"Primitive equation model","title":"Temperature equation","text":"","category":"section"},{"location":"primitiveequation/#implicit_primitive","page":"Primitive equation model","title":"Semi-implicit time stepping","text":"","category":"section"},{"location":"primitiveequation/#Horizontal-diffusion","page":"Primitive equation model","title":"Horizontal diffusion","text":"","category":"section"},{"location":"primitiveequation/#Algorithm","page":"Primitive equation model","title":"Algorithm","text":"","category":"section"},{"location":"primitiveequation/#Scaled-primitive-equations","page":"Primitive equation model","title":"Scaled primitive equations","text":"","category":"section"},{"location":"primitiveequation/#References","page":"Primitive equation model","title":"References","text":"","category":"section"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"[1]: Geophysical Fluid Dynamics Laboratory, Idealized models with spectral dynamics","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"[2]: Geophysical Fluid Dynamics Laboratory, The Spectral Dynamical Core","category":"page"},{"location":"lowertriangularmatrices/#lowertriangularmatrices","page":"Submodule: LowerTriangularMatrices","title":"LowerTriangularMatrices","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"LowerTriangularMatrices is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"This module defines LowerTriangularMatrix, a lower triangular matrix, which in contrast to LinearAlgebra.LowerTriangular does not store the entries above the diagonal. SpeedyWeather.jl uses LowerTriangularMatrix which is defined as a subtype of AbstractMatrix to store the spherical harmonic coefficients (see Spectral packing). ","category":"page"},{"location":"lowertriangularmatrices/#Creation-of-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Creation of LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"A LowerTriangularMatrix can be created using zeros,ones,rand, or randn","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> using SpeedyWeather.LowerTriangularMatrices\n\njulia> L = rand(LowerTriangularMatrix{Float32},5,5)\n5×5 LowerTriangularMatrix{Float32}:\n 0.912744 0.0 0.0 0.0 0.0\n 0.0737592 0.230592 0.0 0.0 0.0\n 0.799679 0.0765255 0.888098 0.0 0.0\n 0.670835 0.997938 0.505276 0.492966 0.0\n 0.949321 0.193692 0.793623 0.152817 0.357968","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"or the undef initializor LowerTriangularMatrix{Float32}(undef,3,3). The element type is arbitrary though, you can use any type T too.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Alternatively, it can be created through conversion from Matrix, which drops the upper triangle entries and sets them to zero","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> M = rand(Float16,3,3)\n3×3 Matrix{Float16}:\n 0.2222 0.694 0.3452\n 0.2158 0.04443 0.274\n 0.9746 0.793 0.6294\n\njulia> LowerTriangularMatrix(M)\n3×3 LowerTriangularMatrix{Float16}:\n 0.2222 0.0 0.0\n 0.2158 0.04443 0.0\n 0.9746 0.793 0.6294","category":"page"},{"location":"lowertriangularmatrices/#Indexing-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Indexing LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"LowerTriangularMatrix supports two types of indexing: 1) by denoting two indices, column and row [l,m] or 2) by denoting a single index [lm]. The double index works as expected","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L\n3×3 LowerTriangularMatrix{Float16}:\n 0.1499 0.0 0.0\n 0.1177 0.478 0.0\n 0.1709 0.756 0.3223\n\njulia> L[2,2]\nFloat16(0.478)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"But the single index skips the zero entries in the upper triangle, i.e.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L[4]\nFloat16(0.478)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"which, important, is different from single indices of an AbstractMatrix","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> Matrix(L)[4]\nFloat16(0.0)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"In performance-critical code a single index should be used, as this directly maps to the index of the underlying data vector. The double index is somewhat slower as it first has to be converted to the corresponding single index.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Consequently, many loops in SpeedyWeather.jl are build with the following structure","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"n,m = size(L)\nij = 0\nfor j in 1:m\n for i in j:n\n ij += 1\n L[ij] = i+j\n end\nend","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"which loops over all lower triangle entries of L::LowerTriangularMatrix and the single index ij is simply counted up. However, one could also use [i,j] as indices in the loop body or to perform any calculation (i+j here). An iterator over all entries in the lower triangle can be created by","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"for ij in eachindex(L)\n # do something\nend","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"The setindex! functionality of matrixes will throw a BoundsError when trying to write into the upper triangle of a LowerTriangularMatrix, for example","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L[2,1] = 0 # valid index\n0\n\njulia> L[1,2] = 0 # invalid index in the upper triangle\nERROR: BoundsError: attempt to access 3×3 LowerTriangularMatrix{Float32} at index [1, 2]","category":"page"},{"location":"lowertriangularmatrices/#Linear-algebra-with-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Linear algebra with LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"The LowerTriangularMatrices module's main purpose is not linear algebra, and it's implementation may not be efficient, however, many operations work as expected","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L = rand(LowerTriangularMatrix{Float32},3,3)\n3×3 LowerTriangularMatrix{Float32}:\n 0.57649 0.0 0.0\n 0.348685 0.875371 0.0\n 0.881923 0.850552 0.998306\n\njulia> L + L\n3×3 LowerTriangularMatrix{Float32}:\n 1.15298 0.0 0.0\n 0.697371 1.75074 0.0\n 1.76385 1.7011 1.99661\n\njulia> L * L\n3×3 Matrix{Float32}:\n 0.332341 0.0 0.0\n 0.506243 0.766275 0.0\n 1.68542 1.59366 0.996616","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Note, however, that the latter includes a conversion to Matrix, which is true for many operations, including inv or \\. Hence when trying to do more sophisticated linear algebra with LowerTriangularMatrix we quickly leave lower triangular-land and go back to normal matrix-land.","category":"page"},{"location":"lowertriangularmatrices/#Function-and-type-index","page":"Submodule: LowerTriangularMatrices","title":"Function and type index","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Modules = [SpeedyWeather.LowerTriangularMatrices]","category":"page"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix","text":"L = LowerTriangularMatrix{T}(v::Vector{T},m::Int,n::Int)\n\nA lower triangular matrix implementation that only stores the non-zero entries explicitly. L<:AbstractMatrix although in general we have L[i] != Matrix(L)[i], the former skips zero entries, tha latter includes them.\n\n\n\n\n\n","category":"type"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix-Union{Tuple{AbstractMatrix{T}}, Tuple{T}} where T","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix","text":"L = LowerTriangularMatrix(M)\n\nCreate a LowerTriangularMatrix L from Matrix M by copying over the non-zero elements in M.\n\n\n\n\n\n","category":"method"},{"location":"lowertriangularmatrices/#Base.fill!-Union{Tuple{T}, Tuple{LowerTriangularMatrix{T}, Any}} where T","page":"Submodule: LowerTriangularMatrices","title":"Base.fill!","text":"fill!(L::LowerTriangularMatrix,x)\n\nFills the elements of L with x. Faster than fill!(::AbstractArray,x) as only the non-zero elements in L are assigned with x.\n\n\n\n\n\n","category":"method"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.eachharmonic-Tuple{LowerTriangularMatrix}","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.eachharmonic","text":"unit_range = eachharmonic(L::LowerTriangular)\n\ncreates unit_range::UnitRange to loop over all non-zeros in a LowerTriangularMatrix L. Like eachindex but skips the upper triangle with zeros in L.\n\n\n\n\n\n","category":"method"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.eachharmonic-Tuple{Vararg{LowerTriangularMatrix}}","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.eachharmonic","text":"unit_range = eachharmonic(Ls::LowerTriangularMatrix...)\n\ncreates unit_range::UnitRange to loop over all non-zeros in the LowerTriangularMatrices provided as arguments. Checks bounds first. All LowerTriangularMatrix's need to be of the same size. Like eachindex but skips the upper triangle with zeros in L.\n\n\n\n\n\n","category":"method"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.ij2k-Tuple{Integer, Integer, Integer}","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.ij2k","text":"k = ij2k( i::Integer, # row index of matrix\n j::Integer, # column index of matrix\n m::Integer) # number of rows in matrix\n\nConverts the index pair i,j of an mxn LowerTriangularMatrix L to a single index k that indexes the same element in the corresponding vector that stores only the lower triangle (the non-zero entries) of L.\n\n\n\n\n\n","category":"method"},{"location":"conventions/#Style-and-convention-guide","page":"Style and convention guide","title":"Style and convention guide","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"In SpeedyWeather.jl we've been following the several conventions that are documented here.","category":"page"},{"location":"conventions/#Variable-naming","page":"Style and convention guide","title":"Variable naming","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The prognostic variables in spectral space are called","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" vor # Vorticity of horizontal wind field\n div # Divergence of horizontal wind field\n temp # Absolute temperature [K]\n pres_surf # Logarithm of surface pressure [log(Pa)]\n humid # Specific humidity [g/kg]","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" u\n v\n geopot\n ...","category":"page"},{"location":"conventions/#Preallocation","page":"Style and convention guide","title":"Preallocation","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"All arrays representing variables are preallocated and grouped into two structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" Prog::PrognosticVariables\n Diag::DiagnosticVariables","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The Diag struct contains further structs which represent the grid-point transformations of the prognostic variables and their tendencies.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" gridvars::GridVariables\n tendencies::Tendencies\n ...","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Constant arrays are grouped into several structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Boundaries","category":"page"},{"location":"conventions/#Julian-conventions","page":"Style and convention guide","title":"Julian conventions","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"We follow Julia's style guide and highlight here some important aspects of it.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Bang (!) convention. A function func does not change its input arguments, however, func! does.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.","category":"page"},{"location":"shallowwater/#Shallow-water-model","page":"Shallow water model","title":"Shallow water model","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The shallow water model describes the evolution of a 2D flow described by its velocity and an interface height that conceptually represents pressure. A divergent flow affects the interface height which in turn can impose a pressure gradient force onto the flow. The dynamics include advection, forces, dissipation, and continuity.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The following description of the shallow water model largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: The Shallow Water Equations[2].","category":"page"},{"location":"shallowwater/#Shallow-water-equations","page":"Shallow water model","title":"Shallow water equations","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The shallow water equations of velocity mathbfu = (uv) and interface height eta (i.e. the deviation from the fluid's rest height H) are, formulated in terms of relative vorticity zeta = nabla times mathbfu, divergence mathcalD = nabla cdot mathbfu","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\nfracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) =\nnabla times mathbfF + (-1)^n+1nunabla^2nzeta \nfracpartial mathcalDpartial t - nabla times (mathbfu(zeta + f)) =\nnabla cdot mathbfF -nabla^2(tfrac12(u^2 + v^2) + geta) + (-1)^n+1nunabla^2nmathcalD \nfracpartial etapartial t + nabla cdot (mathbfuh) = F_eta\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"We denote timet, Coriolis parameter f, a forcing vector mathbfF = (F_uF_v), hyperdiffusion (-1)^n+1 nu nabla^2n (n is the hyperdiffusion order, see Horizontal diffusion), gravitational acceleration g, dynamic layer thickness h, and a forcing for the interface height F_eta. In the shallow water model the dynamics layer thickness h is","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"h = eta + H - H_b","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"that is, the layer thickness at rest H plus the interface height eta minus orography H_b.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"In the shallow water system the flow can be described through uv or zetamathcalD which are related through the stream function Psi and the velocity potential Phi (which is zero in the Barotropic vorticity equation).","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\nzeta = nabla^2 Psi \nmathcalD = nabla^2 Phi \nmathbfu = nabla^perp Psi + nabla Phi\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"With nabla^perp being the rotated gradient operator, in cartesian coordinates xy: nabla^perp = (-partial_y partial_x). See Derivatives in spherical coordinates for further details. Especially because the inversion of the Laplacian and the gradients of Psi Phi can be computed in a single pass, see U,V from vorticity and divergence.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The divergence/curl of the vorticity flux mathbfu(zeta + f) are combined with the divergence/curl of the forcing vector mathbfF, as","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\n- nabla cdot (mathbfu(zeta + f)) + nabla times mathbfF =\nnabla times (mathbfF + mathbfu_perp(zeta + f)) \nnabla times (mathbfu(zeta + f)) + nabla cdot mathbfF =\nnabla cdot (mathbfF + mathbfu_perp(zeta + f))\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"equivalently to how this is done in the Barotropic vorticity equation with mathbfu_perp = (v-u).","category":"page"},{"location":"shallowwater/#Algorithm","page":"Shallow water model","title":"Algorithm","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"0. Start with initial conditions of relative vorticity zeta_lm, divergence D_lm, and interface height eta_lm in spectral space and transform this model state to grid-point space:","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Invert the Laplacian of zeta_lm to obtain the stream function Psi_lm in spectral space\nInvert the Laplacian of D_lm to obtain the velocity potential Phi_lm in spectral space\nobtain velocities U_lm = (cos(theta)u)_lm V_lm = (cos(theta)v)_lm from nabla^perpPsi_lm + nablaPhi_lm\nTransform velocities U_lm, V_lm to grid-point space UV\nUnscale the cos(theta) factor to obtain uv\nTransform zeta_lm, D_lm, eta_lm to zeta D eta in grid-point space","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Now loop over","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Compute the forcing vector mathbfF = (F_uF_v) for u and v\nMultiply uv with zeta+f in grid-point space\nAdd A = F_u + v(zeta + f) and B = F_v - u(zeta + f)\nTransform these vector components to spectral space A_lm, B_lm\nCompute the curl of (AB)_lm in spectral space which is the tendency of zeta_lm\nCompute the divergence of (AB)_lm in spectral space which is the tendency of mathcalD_lm\nCompute the kinetic energy frac12(u^2 + v^2) and transform to spectral space\nAdd to the kinetic energy the \"geopotential\" geta_lm in spectral space to obtain the Bernoulli potential\nTake the Laplacian of the Bernoulli potential and subtract from the divergence tendency\nCompute the volume fluxes uhvh in grid-point space via h = eta + H - H_b\nTransform to spectral space and take the divergence for -nabla cdot (mathbfuh) which is the tendency for eta\nAdd possibly forcing F_eta for eta in spectral space\nCorrect the tendencies following the semi-implicit time integration to prevent fast gravity waves from causing numerical instabilities\nCompute the horizontal diffusion based on the zetamathcalD tendencies\nCompute a leapfrog time step as described in Time integration with a Robert-Asselin and Williams filter\nTransform the new spectral state of zeta_lm, mathcalD_lm, eta_lm to grid-point uvzetamathcalDeta as described in 0.\nPossibly do some output\nRepeat from 1.","category":"page"},{"location":"shallowwater/#implicit_swm","page":"Shallow water model","title":"Semi-implicit time integration","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Probably the biggest advantage of a spectral model is its ability to solve (parts of) the equations implicitly a low computational cost. The reason is that a linear operator can be easily inverted in spectral space, removing the necessity to solve large equation systems. An operation like Psi = nabla^-2zeta in grid-point space is costly because it requires a global communication, coupling all grid points. In spectral space nabla^2 is a diagonal operator, meaning that there is no communication between harmonics and its inversion is therefore easily done on a mode-by-mode basis of the harmonics.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"This can be made use of when facing time stepping constraints with explicit schemes, where ridiculuously small time steps to resolve fast waves would otherwise result in a horribly slow simulation. In the shallow water system there are gravity waves that propagate at a wave speed of sqrtgH (typically 300m/s), which, in order to not violate the CFL criterion for explicit time stepping, would need to be resolved. Therefore, treating the terms that are responsible for gravity waves implicitly would remove that time stepping constraint and allows us to run the simulation at the time step needed to resolve the advective motion of the atmosphere, which is usually one or two orders of magnitude longer than gravity waves.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"In the following we will describe how the semi implicit time integration can be combined with the Leapfrog time stepping and the Robert-Asselin and Williams filter for a large increase in numerical stability with gravity waves. Let V_i be the model state of all prognostic variables at time step i, the leapfrog time stepping is then","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"fracV_i+1 - V_i-12Delta t = N(V_i)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"with the right-hand side operator N evaluated at the current time step i. Now the idea is to split the terms in N into non-linear terms that are evaluated explicitly in N_E and into the linear terms N_I, solved implicitly, that are responsible for the gravity waves. We could already assume to evaluate N_I at i+1, but in fact, we can introduce alpha in 01 so that for alpha=0 we use i-1 (i.e. explicit), for alpha=12 it is centred implicit tfrac12N_I(V_i-1) + tfrac12N_I(V_i+1), and for alpha=1 a fully backwards scheme N_I(V_i+1) evaluated at i+1.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"fracV_i+1 - V_i-12Delta t = N_E(V_i) + alpha N_I(V_i+1) + (1-alpha)N_I(V_i-1)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Let delta V = tfracV_i+1 - V_i-12Delta t be the tendency we need for the Leapfrog time stepping. Introducing xi = 2alphaDelta t we have","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"delta V = N_E(V_i) + N_I(V_i-1) + xi N_I(delta V)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"because N_I is a linear operator. This is done so that we can solve for delta V by inverting N_I, but let us gather the other terms as G first.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"G = N_E(V_i) + N_I(V_i-1) = N(V_i) + N_I(V_i-1 - V_i)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"For the shallow water equations we will only make use of the last formulation, meaning we first evaluate the whole right-hand side N(V_i) at the current time step as we would do with fully explicit time stepping but then add the implicit terms N_I(V_i-1 - V_i) afterwards to move those terms from i to i-1. Note that we could also directly evaluate the implicit terms at i-1 as it is suggested in the previous formulation N_E(V_i) + N_I(V_i-1), the result would be the same. But in general it can be more efficient to do it one or the other way, and in fact it is also possible to combine both ways. This will be discussed in the semi-implicit time stepping for the primitive equations.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"We can now implicitly solve for delta V by","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"delta V = (1-xi N_I)^-1G","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"So what is N_I? In the shallow water system the gravity waves are caused by","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\nfracpartial mathcalDpartial t = -gnabla^2eta \nfracpartial etapartial t = -HmathcalD\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"which is a linearization of the equations around a state of rest with uniform constant layer thickness h = H. The continuity equation with the -nabla(mathbfuh) term, for example, is linearized to -nabla(mathbfuH) = -HmathcalD. The divergence and continuity equations can now be written following the delta V = G + xi N_I(delta V) formulation from above as a coupled system (The vorticity equation is zero for the linear gravity wave equation in the shallow water equations, hence no semi-implicit correction has to be made to the vorticity tendency).","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\ndelta mathcalD = G_mathcalD - xi g nabla^2 delta eta \ndelta eta = G_mathcaleta - xi H deltamathcalD\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"with","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\nG_mathcalD = N_mathcalD - xi g nabla^2 (eta_i-1 - eta_i) \nG_mathcaleta = N_eta - xi H (mathcalD_i-1 - mathcalD_i)\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Inserting the second equation into the first, we can first solve for delta mathcalD, and then for delta eta. Reminder that we do this in spectral space to every harmonic independently, so the Laplace operator nabla^2 = -l(l+1) takes the form of its eigenvalue -l(l+1) (normalized to unit sphere, as are the scaled shallow water equations) and its inversion is therefore just the inversion of this scalar.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"delta D = fracG_mathcalD - xi gnabla^2 G_eta1 - xi^2 H nabla^2 = S^-1(G_mathcalD - xi gnabla^2 G_eta) ","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Where the last formulation just makes it clear that S = 1 - xi^2 H nabla^2 is the operator to be inverted. delta eta is then obtained via insertion as written above. Equivalently, by adding a superscript l for every degree of the spherical harmonics, we have","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"delta mathcalD^l = fracG_mathcalD^l + xi g l(l+1) G_eta^l1 + xi^2 H l(l+1)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The idea of the semi-implicit time stepping is now as follows:","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Evaluate the right-hand side explicitly at time step i to obtain the explicit, preliminary tendencies N_mathcalDN_eta (and N_zeta without a need for semi-implicit correction)\nMove the implicit terms from i to i-1 when calculating G_mathcalD G_eta\nSolve for delta mathcalD, the new, corrected tendency for divergence.\nWith delta mathcalD obtain delta eta, the new, corrected tendency for eta.\nApply horizontal diffusion as a correction to N_zeta delta mathcalD as outlined in Horizontal diffusion.\nLeapfrog with tendencies that have been corrected for both semi-implicit and diffusion.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Some notes on the semi-implicit time stepping","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The inversion of the semi-implicit time stepping depends on delta t, that means every time the time step changes, the inversion has to be recalculated.\nYou may choose alpha = 12 to dampen gravity waves but initialisation shocks still usually kick off many gravity waves that propagate around the sphere for many days.\nWith increasing alpha 12 these waves are also slowed down, such that for alpha = 1 they quickly disappear in several hours.\nUsing the scaled shallow water equations the time step delta t has to be the scaled time step tildeDelta t = delta tR which is divided by the radius R. Then we use the normalized eigenvalues -l(l+1) which also omit the 1R^2 scaling, see scaled shallow water equations for more details.","category":"page"},{"location":"shallowwater/#scaled_swm","page":"Shallow water model","title":"Scaled shallow water equations","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Similar to the scaled barotropic vorticity equations, SpeedyWeather.jl scales in the shallow water equations. The vorticity and the divergence equation are scaled with R^2, the radius of the sphere squared, but the continuity equation is scaled with R. We also combine the vorticity flux and forcing into a single divergence/curl operation as mentioned in Shallow water equations above","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\nfracpartial tildezetapartial tildet =\ntildenabla times (tildemathbfF + mathbfu_perp(tildezeta + tildef)) +\n(-1)^n+1tildenutildenabla^2ntildezeta \nfracpartial tildemathcalDpartial tildet =\ntildenabla cdot (tildemathbfF + mathbfu_perp(tildezeta + tildef)) -\ntildenabla^2left(tfrac12(u^2 + v^2) + geta right) +\n(-1)^n+1tildenutildenabla^2ntildemathcalD \nfracpartial etapartial tildet =\n- tildenabla cdot (mathbfuh) + tildeF_eta\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"As in the scaled barotropic vorticity equations, one needs to scale the time step, the Coriolis force, the forcing and the diffusion coefficient, but then enjoys the luxury of working with dimensionless gradient operators. As before, SpeedyWeather.jl will scale vorticity and divergence just before the model integration starts and unscale them upon completion and for output. In the semi-implicit time integration we solve an equation that also has to be scaled. It is with radius squared scaling (because it is the tendency for the divergence equation which is also scaled with R^2)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"R^2 delta D = R^2fracG_mathcalD - xi gnabla^2 G_eta1 - xi^2 H nabla^2","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"As G_eta is only scaled with R we have","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"tildedelta D = fractildeG_mathcalD - tildexi gtildenabla^2 tildeG_eta1 - tildexi^2 H tildenabla^2","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The R^2 normalizes the Laplace operator in the numerator, but using the scaled G_eta we also scale xi (which is convenient, because the time step within is the one we use anyway). The denominator S does not actually change because xi^2nabla^2 = tildexi^2tildenabla^2 as xi^2 is scaled with 1R^2, but the Laplace operator with R^2. So overall we just have to use the scaled time step tildeDelta t and normalized eigenvalues for tildenabla^2.","category":"page"},{"location":"shallowwater/#References","page":"Shallow water model","title":"References","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"[1]: Geophysical Fluid Dynamics Laboratory, Idealized models with spectral dynamics","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"[2]: Geophysical Fluid Dynamics Laboratory, The Shallow Water Equations.","category":"page"},{"location":"extending/#New-model-setups","page":"Extending SpeedyWeather","title":"New model setups","text":"","category":"section"},{"location":"extending/","page":"Extending SpeedyWeather","title":"Extending SpeedyWeather","text":"more to come...","category":"page"},{"location":"spectral_transform/#Spherical-Harmonic-Transform","page":"Spherical harmonic transform","title":"Spherical Harmonic Transform","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space which can be any of the Implemented grids as defined by RingGrids. This includes the classical full Gaussian grid, a regular longitude-latitude grid called the full Clenshaw grid (FullClenshawGrid), ECMWF's octahedral Gaussian grid[Malardel2016], and HEALPix grids[Gorski2004]. SpeedyWeather.jl's spectral transform module SpeedyTransforms is grid-flexible and can be used with any of these, see Grids.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: SpeedyTransforms is a module too!\nSpeedyTransform is the underlying module that SpeedyWeather imports to transform between spectral and grid-point space, which also implements Derivatives in spherical coordinates. You can use this module independently of SpeedyWeather for spectral transforms, see SpeedyTransforms.","category":"page"},{"location":"spectral_transform/#Inspiration","page":"Spherical harmonic transform","title":"Inspiration","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform implemented by SpeedyWeather.jl follows largely Justin Willmert's CMB.jl and SphericalHarmonicTransforms.jl package and makes use of AssociatedLegendrePolynomials.jl and FFTW.jl for the Fourier transform. Justin described his work in a Blog series [Willmert2020].","category":"page"},{"location":"spectral_transform/#Spherical-harmonics","page":"Spherical harmonic transform","title":"Spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spherical harmonics Y_lm of degree l and order m over the longitude phi = (02pi) and colatitudes theta = (-pi2pi2), are","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Y_lm(phi theta) = lambda_l^m(sintheta) e^imphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with lambda_l^m being the pre-normalized associated Legendre polynomials, and e^imphi are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Latitudes versus colatitudes\nThe implementation of the spectral transforms in SpeedyWeather.jl uses colatitudes theta = (0pi) (0 at the north pole) but the dynamical core uses latitudes theta = (-pi2pi2) (pi2 at the north pole). Note: We may also use latitudes in the spherical harmonic transform in the future for consistency. ","category":"page"},{"location":"spectral_transform/#synthesis","page":"Spherical harmonic transform","title":"Synthesis (spectral to grid)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The synthesis (or inverse transform) takes the spectral coefficients a_lm and transforms them to grid-point values f(phitheta) (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics Y_lm with non-zero coefficients.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"f(phitheta) = sum_l=0^infty sum_m=-l^l a_lm Y_lm(phitheta)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We obtain an approximation with a finite set of a_lm by truncating the series in both degree l and order m somehow. Most commonly, a triangular truncation is applied, such that all degrees after l = l_max are discarded. Triangular because the retained array of the coefficients a_lm looks like a triangle. Other truncations like rhomboidal have been studied[Daley78] but are rarely used since. Choosing l_max also constrains m_max and determines the (horizontal) spectral resolution. In SpeedyWeather.jl this resolution as chosen as trunc when creating the SpectralGrid.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"For f being a real-valued there is a symmetry","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"a_l-m = (-1)^m a^*_l+m","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"meaning that the coefficients at -m and m are the same, but the sign of the real and imaginary component can be flipped, as denoted with the (-1)^m and the complex conjugate a_lm^*. As we are only dealing with real-valued fields anyway, we therefore never have to store the negative orders -m and end up with a lower triangular matrix of size (l_max+1) times (m_max+1) or technically (T+1)^2 where T is the truncation trunc. One is added here because the degree l and order m use 0-based indexing but sizes (and so is Julia's indexing) are 1-based.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"For correctness we want to mention here that vector quantities require one more degree l due to the recurrence relation in the Meridional derivative. Hence for practical reasons all spectral fields are represented as a lower triangular matrix of size (m_max + 2) times (m_max +1). And the scalar quantities would just not make use of that last degree, and its entries would be simply zero. We will, however, for the following sections ignore this and only discuss it again in Meridional derivative.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Another consequence of the symmetry mentioned above is that the zonal harmonics, meaning a_lm=0 have no imaginary component. Because these harmonics are zonally constant, a non-zero imaginary component would rotate them around the Earth's axis, which, well, doesn't actually change a real-valued field. ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Following the notation of [Willmert2020] we can therefore write the truncated synthesis as","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"f(phitheta) = sum_l=0^l_max sum_m=0^l (2-delta_m0) a_lm Y_lm(phitheta)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The (2-delta_m0) factor using the Kronecker delta is used here because of the symmetry we have to count both the m-m order pairs (hence the 2) except for the zonal harmonics which do not have a pair.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Another symmetry arises from the fact that the spherical harmonics are either symmetric or anti-symmetric around the Equator. There is an even/odd combination of degrees and orders so that the sign flips like a checkerboard","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Y_lm(phipi-theta) = (-1)^l+mY_lm(phiphi)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This means that one only has to compute the Legendre polynomials for one hemisphere and the other one follows with this equality.","category":"page"},{"location":"spectral_transform/#analysis","page":"Spherical harmonic transform","title":"Analysis (grid to spectral)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting in grid-point space we can transform a field f(lambdatheta) into the spectral space of the spherical harmonics by","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"a_lm = int_0^2pi int_0^pi f(phitheta) Y_lm(phitheta) sin theta dtheta dphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Note that this notation again uses colatitudes theta, for latitudes the sintheta becomes a costheta and the bounds have to be changed accordingly to (-fracpi2fracpi2). A discretization with N grid points at location (phi_itheta_i), indexed by i can be written as [Willmert2020]","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"hata_lm = sum_i f(phi_itheta_i) Y_lm(phi_itheta_i) sin theta_i Deltatheta Deltaphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The hat on a just means that it is an approximation, or an estimate of the true a_lm approx hata_lm. We can essentially make use of the same symmetries as already discussed in Synthesis. Splitting into the Fourier modes e^imphi and the Legendre polynomials lambda_l^m(costheta) (which are defined over -11 so the costheta argument maps them to colatitudes) we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"hata_lm = sum_j left sum_i f(phi_itheta_j) e^-imphi_i right lambda_lm(theta_j) sin theta_j Deltatheta Deltaphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"So the term in brackets can be separated out as long as the latitude theta_j is constant, which motivates us to restrict the spectral transform to grids with iso-latitude rings, see Grids. Furthermore, this term can be written as a fast Fourier transform, if the phi_i are equally spaced on the latitude ring j. Note that the in-ring index i can depend on the ring index j, so that one can have reduced grids, which have fewer grid points towards the poles, for example. Also the Legendre polynomials only have to be computed for the colatitudes theta_j (and in fact only one hemisphere, due to the north-south symmetry discussed in the Synthesis). It is therefore practical and efficient to design a spectral transform implementation for ring grids, but there is no need to hardcode a specific grid.","category":"page"},{"location":"spectral_transform/#Spectral-packing","page":"Spherical harmonic transform","title":"Spectral packing","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Spectral packing is the way how the coefficients a_lm of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree l and order m as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Every row represents an order l geq 0, starting from l=0 at the top. Every column represents an order m geq 0, starting from m=0 on the left. The coefficients of these spherical harmonics are directly mapped into a matrix a_lm as ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 \n a_10 a_11 \n a_20 a_12 a_22 \n a_30 a_13 a_23 a_33","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal are zero. Note that internally vector fields include an additional degree, such that l_max = m_max + 1 (see Derivatives in spherical coordinates for more information). The harmonics with a_l0 (the first column) are also called zonal harmonics as they are constant with longitude phi. The harmonics with a_ll (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into 2l sectors in longitude phi without a zero-crossing in latitude.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"For correctness it is mentioned here that SpeedyWeather.jl uses a LowerTriangularMatrix type to store the spherical harmonic coefficients. By doing so, the upper triangle is actually not explicitly stored and the data technically unravelled into a vector, but this is hidden as much as possible from the user. For more details see LowerTriangularMatrices.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Array indices\nFor a spectral field a note that due to Julia's 1-based indexing the coefficient a_lm is obtained via a[l+1,m+1]. Alternatively, we may index over 1-based l,m but a comment is usually added for clarification.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Fortran SPEEDY does not use the same spectral packing as SpeedyWeather.jl. The alternative packing lm therein uses l=m and m=l-m as summarized in the following table.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"degree l order m l=m m=l-m\n0 0 0 0\n1 0 0 1\n1 1 1 0\n2 0 0 2\n2 1 1 1\n2 2 2 0\n3 0 0 3\n... ... ... ...","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 a_10 a_20 a_30\n a_11 a_21 a_31 \n a_22 a_32 \n a_33 ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran SPEEDY.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with l leq l_max and m leq m_max are explicitly represented. This is usually described as Tm_max, with l_max = m_max (although in vector quantities require one more degree l in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with l_max = m_max = 31. Note that the degree l and order m are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.","category":"page"},{"location":"spectral_transform/#Available-horizontal-resolutions","page":"Spherical harmonic transform","title":"Available horizontal resolutions","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Technically, SpeedyWeather.jl supports arbitrarily chosen resolution parameter trunc when creating the SpectralGrid that refers to the highest non-zero degree l_max that is resolved in spectral space. SpeedyWeather.jl will always try to choose an easily-Fourier transformable[FFT] size of the grid, but as we use FFTW.jl there is quite some flexibility without performance sacrifice. However, this has traditionally lead to typical resolutions that we also use for testing we therefore recommend to use. They are as follows with more details below","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"trunc nlon nlat Delta x\n31 (default) 96 48 400 km\n42 128 64 312 km\n63 192 96 216 km\n85 256 128 165 km\n127 384 192 112 km\n170 512 256 85 km\n255 768 384 58 km\n341 1024 512 43 km\n511 1536 768 29 km\n682 2048 1024 22 km\n1024 3072 1536 14 km\n1365 4092 2048 11 km","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Some remarks on this table","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This assumes the default quadratic truncation, you can always adapt the grid resolution via the dealiasing option, see Matching spectral and grid resolution\nnlat refers to the total number of latitude rings, see Grids. With non-Gaussian grids, nlat will be one one less, e.g. 47 instead of 48 rings.\nnlon is the number of longitude points on the Full Gaussian Grid, for other grids there will be at most these number of points around the Equator.\nDelta x is the horizontal resolution. For a spectral model there are many ways of estimating this[9]. We use here the square root of the average area a grid cell covers, see Effective grid resolution","category":"page"},{"location":"spectral_transform/#Effective-grid-resolution","page":"Spherical harmonic transform","title":"Effective grid resolution","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"There are many ways to estimate the effective grid resolution of spectral models[9]. Some of them are based on the wavelength a given spectral resolution allows to represent, others on the total number of real variables per area. However, as many atmospheric models do represent a considerable amount of physics on the grid (see Parameterizations) there is also a good argument to include the actual grid resolution into this estimate and not just the spectral resolution. We therefore use the average grid cell area to estimate the resolution","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Delta x = sqrtfrac4pi R^2N","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with N number of grid points over a sphere with radius R. However, we have to acknowledge that this usually gives higher resolution compared to other methods of estimating the effective resolution, see [Randall2021] for a discussion. You may therefore need to be careful to make claims that, e.g. trunc=85 can resolve the atmospheric dynamics at a scale of 165km.","category":"page"},{"location":"spectral_transform/#Derivatives-in-spherical-coordinates","page":"Spherical harmonic transform","title":"Derivatives in spherical coordinates","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Horizontal gradients in spherical coordinates are defined for a scalar field A and the latitudes theta and longitudes lambda as","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla A = left(frac1Rcosthetafracpartial Apartial lambda frac1Rfracpartial Apartial theta right)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"However, the divergence of a vector field mathbfu = (uv) includes additional cos(theta) scalings","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla cdot mathbfu = frac1Rcosthetafracpartial upartial lambda +\nfrac1Rcosthetafracpartial (v costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"and similar for the curl","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla times mathbfu = frac1Rcosthetafracpartial vpartial lambda -\nfrac1Rcosthetafracpartial (u costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The radius of the sphere (i.e. Earth) is R. The zonal gradient scales with 1cos(theta) as the longitudes converge towards the poles (note that theta describes latitudes here, definitions using colatitudes replace the cos with a sin.)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting with a spectral field of vorticity zeta and divergence mathcalD one can obtain stream function Psi and velocity potential Phi by inverting the Laplace operator nabla^2:","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi = nabla^-2zeta quad Phi = nabla^-2mathcalD","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The velocities uv are then obtained from (uv) = nabla^botPsi + nablaPhi following the definition from above and nabla^bot = (-R^-1partial_theta (Rcostheta)^-1partial_lambda)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nu = -frac1Rpartial_thetaPsi + frac1Rcosthetapartial_lambdaPhi \nv = +frac1Rpartial_thetaPhi + frac1Rcosthetapartial_lambdaPsi\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Alternatively, we can use the velocities U = ucostheta V = vcostheta, which we do as the meridional gradient for spherical harmonics is easier implemented with a costheta-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with UV and not uv. From uv we can return to zeta mathcalD via","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nzeta = frac1Rcosthetapartial_lambda v - frac1Rcosthetapartial_theta (u costheta) \nmathcalD = frac1Rcosthetapartial_lambda u + frac1Rcosthetapartial_theta (v costheta)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Equivalently, we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU = -fraccosthetaRpartial_thetaPsi + frac1Rpartial_lambdaPhi \nV = +fraccosthetaRpartial_thetaPhi + frac1Rpartial_lambdaPsi \nzeta = frac1Rpartial_lambda left( fracVcos^2theta right) -\nfraccosthetaRpartial_theta left( fracUcos^2theta right) \nmathcalD = frac1Rpartial_lambda left( fracUcos^2theta right) +\nfraccosthetaRpartial_theta left( fracVcos^2theta right)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is a more convenient formulation because of the way how the Meridional derivative is implemented with a recursion relation, actually computing costheta partial_theta rather than partial_theta directly. The remaining cosine scalings in (UV)*cos^-2theta are done in grid-point space. If one wanted to get back to zeta mathcalD this is how it would be done, but it is often more convenient to unscale UV on the fly in the spectral transform to obtain uv and then divide again by costheta when any gradient (or divergence or curl) is taken. This is because other terms would need that single costheta unscaling too before a gradient is taken. How the operators nabla nabla times nabla cdot can be implemented with spherical harmonics is presented in the following sections.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out in this last formulation too.","category":"page"},{"location":"spectral_transform/#Zonal-derivative","page":"Spherical harmonic transform","title":"Zonal derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The zonal derivative of a scalar field Psi in spectral space is the zonal derivative of all its respective spherical harmonics Psi_lm(phitheta) (now we use phi for longitudes to avoid confusion with the Legendre polynomials lambda_lm)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"v_lm = frac1R cos(theta) fracpartialpartial phi left( lambda_l^m(costheta) e^imphi right) =\nfracimR cos(theta) lambda_l^m(costheta) e^imphi = fracimR cos(theta) Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"So for every spectral harmonic, cos(theta)v_lm is obtained from Psi_lm via a multiplication with imR. Unscaling the cos(theta)-factor is done after transforming the spectral coefficients v_lm into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as tildePsi = R^-1Psi such that the division by radius R in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order im. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number m times imaginary i.","category":"page"},{"location":"spectral_transform/#Meridional-derivative","page":"Spherical harmonic transform","title":"Meridional derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridional derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costheta fracdP_lmdtheta = -lepsilon_l+1mP_l+1m + (l+1)epsilon_lmP_l-1m","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with recursion factors","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"epsilon_lm = sqrtfracl^2-m^24l^2-1","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In the following we use the example of obtaining the zonal velocity u from the stream function Psi, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi(lambdatheta) = sum_lmPsi_lmP_lm(sintheta)e^imlambda","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"we multiply with -R^-1costhetapartial_theta to obtain","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costhetaleft(-frac1Rpartial_thetaPsi right) = -frac1Rsum_lmPsi_lme^imlambdacosthetapartial_theta P_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"at which point the recursion from above can be applied. Collecting terms proportional to P_lm then yields","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"(cos(theta)u)_lm = -frac1R(-(l-1)epsilon_lmPsi_l-1m + (l+2)epsilon_l+1mPsi_l+1m)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"To obtain the coefficient of each spherical harmonic lm of the meridional gradient of a spectral field, two coefficients at l-1m and l+1m have to be combined. This means that the coefficient of a gradient ((costheta) u)_lm is a linear combination of the coefficients of one higher and one lower degree Psi_l+1mPsi_l-1m. As the coefficient Psi_lm with ml are zero, the sectoral harmonics (l=m) of the gradients are obtained from the first off-diagonal only. However, the l=l_max harmonics of the gradients require the l_max-1 as well as the l_max+1 harmonics. As a consequence vector quantities like velocity components uv require one more degree l than scalar quantities like vorticity[Bourke72]. However, for easier compatibility all spectral fields in SpeedyWeather.jl use one more degree l, but scalar quantities should not make use of it. Equivalently, the last degree l is set to zero before the time integration, which only advances scalar quantities.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In SpeedyWeather.jl vector quantities like uv use therefore one more meridional mode than scalar quantities such as vorticity zeta or stream function Psi. The meridional derivative in SpeedyWeather.jl also omits the 1R-scaling as explained for the Zonal derivative and in Radius scaling.","category":"page"},{"location":"spectral_transform/#Divergence-and-curl-in-spherical-harmonics","page":"Spherical harmonic transform","title":"Divergence and curl in spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridional gradient as described above can be applied to scalars, such as Psi and Phi in the conversion to velocities (uv) = nabla^botPsi + nablaPhi, however, the operators curl nabla times and divergence nabla cdot in spherical coordinates involve a costheta scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform of vorticity zeta is","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac12piint_-tfracpi2^tfracpi2int_0^2pi zeta(lambdatheta)\nP_lm(sintheta) e^imlambda dlambda costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Given that Rzeta = cos^-1partial_lambda v - cos^-1partial_theta (u costheta), we therefore have to evaluate a meridional integral of the form","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"int P_lm frac1cos theta partial_theta(u costheta)) cos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which can be solved through integration by parts. As ucostheta = 0 at theta = pm tfracpi2 only the integral","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int partial_theta P_lm (u costheta) dtheta = -int costheta partial_theta P_lm\n(fracucostheta) costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"remains. Inserting the recurrence relation from the Meridional derivative turns this into","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int left(-l epsilon_l+1mP_l+1m + (l+1)epsilon_lm P_l-1m right) (fracucostheta)\ncos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Now we expand (tfracucostheta) but only the lm harmonic will project ontoP_lm. Let u^* = ucos^-1theta v^* = vcos^-1theta we then have in total","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nRzeta_lm = imv^*_lm + (l+1)epsilon_lmu^*_l-1m - lepsilon_l+1mu^*_l+1m \nRD_lm = imu^*_lm - (l+1)epsilon_lmv^*_l-1m + lepsilon_l+1mv^*_l+1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"And the divergence D is similar, but (uv) to (-vu). We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#Laplacian","page":"Spherical harmonic transform","title":"Laplacian","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral Laplacian is easily applied to the coefficients Psi_lm of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator nabla^2 in spherical coordinates with eigenvalues -l(l+1) divided by the radius squared R^2, i.e. nabla^2 Psi becomes tfrac-l(l+1)R^2Psi_lm in spectral space. For example, vorticity zeta and streamfunction Psi are related by zeta = nabla^2Psi in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree l and order m to","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac-l(l+1)R^2Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This can be easily inverted to obtain the stream function Psi from vorticity zeta instead. In order to avoid division by zero, we set Psi_00 here, given that the stream function is only defined up to a constant anyway.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nPsi_lm = fracR^2-l(l+1)zeta_lm quad foralllm 0\nPsi_00 = 0\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"See also Horizontal diffusion and Normalization of diffusion.","category":"page"},{"location":"spectral_transform/#U,V-from-vorticity-and-divergence","page":"Spherical harmonic transform","title":"U,V from vorticity and divergence","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity zeta and divergence D (which are prognostic variables) to U=ucostheta V=vcostheta. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree l (the meridional gradient). It is therefore computationally more efficient to compute UV directly from zetaD instead of calculating stream function and velocity potential first. In total we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU_lm = -fraciml(l+1)(RD)_lm + fracepsilon_l+1ml+1(Rzeta)_l+1m -\nfracepsilon_lml(Rzeta)_l-1m \nV_lm = -fraciml(l+1)(Rzeta)_lm - fracepsilon_l+1ml+1(RD)_l+1m +\nfracepsilon_lml(RD)_l-1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#References","page":"Spherical harmonic transform","title":"References","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Malardel2016]: Malardel S, Wedi N, Deconinck W, Diamantakis M, Kühnlein C, Mozdzynski G, Hamrud M, Smolarkiewicz P. A new grid for the IFS. ECMWF newsletter. 2016;146(23-28):321. doi: 10.21957/zwdu9u5i","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Gorski2004]: Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Willmert2020]: Justin Willmert, 2020. justinwillmert.comIntroduction to Associated Legendre Polynomials (Legendre.jl Series, Part I)\nCalculating Legendre Polynomials (Legendre.jl Series, Part II)\nPre-normalizing Legendre Polynomials (Legendre.jl Series, Part III)\nMaintaining numerical accuracy in the Legendre recurrences (Legendre.jl Series, Part IV)\nIntroducing Legendre.jl (Legendre.jl Series, Part V)\nNumerical Accuracy of the Spherical Harmonic Recurrence Coefficient (Legendre.jl Series Addendum)\nNotes on Calculating the Spherical Harmonics\nMore Notes on Calculating the Spherical Harmonics: Analysis of maps to harmonic coefficients","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Daley78]: Roger Daley & Yvon Bourassa (1978) Rhomboidal versus triangular spherical harmonic truncation: Some verification statistics, Atmosphere-Ocean, 16:2, 187-196, DOI: 10.1080/07055900.1978.9649026","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Randall2021]: David Randall, 2021. An Introduction to Numerical Modeling of the Atmosphere, Chapter 22.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Durran2010]: Dale Durran, 2010. Numerical Methods for Fluid Dynamics, Springer. In particular section 6.2, 6.4.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[GFDL]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[FFT]: Depending on the implementation of the Fast Fourier Transform (Cooley-Tukey algorithm, or or the Bluestein algorithm) easily Fourier-transformable can mean different things: Vectors of the length n that is a power of two, i.e. n = 2^i is certainly easily Fourier-transformable, but for most FFT implementations so are n = 2^i3^j5^k with ijk some positive integers. In fact, FFTW uses O(n log n) algorithms even for prime sizes.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Bourke72]: Bourke, W. An Efficient, One-Level, Primitive-Equation Spectral Model. Mon. Wea. Rev. 100, 683–689 (1972). doi:10.1175/1520-0493(1972)100<0683:AEOPSM>2.3.CO;2","category":"page"},{"location":"ringgrids/#RingGrids","page":"Submodule: RingGrids","title":"RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids defines several iso-latitude grids, which are mathematically described in the section on Grids. In brief, they include the regular latitude-longitude grids (here called FullClenshawGrid) as well as grids which latitudes are shifted to the Gaussian latitudes and reduced grids, meaning that they have a decreasing number of longitudinal points towards the poles to be more equal-area than full grids.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids defines and exports the following grids:","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"full grids: FullClenshawGrid, FullGaussianGrid, FullHEALPix, and FullOctaHEALPix\nreduced grids: OctahedralGaussianGrid, OctahedralClenshawGrid, OctaHEALPixGrid and HEALPixGrid","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"The following explanation of how to use these can be mostly applied to any of them, however, there are certain functions that are not defined, e.g. the full grids can be trivially converted to a Matrix (i.e. they are rectangular grids) but not the OctahedralGaussianGrid.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"note: What is a ring?\nWe use the term ring, short for iso-latitude ring, to refer to a sequence of grid points that all share the same latitude. A latitude-longitude grid is a ring grid, as it organises its grid-points into rings. However, other grids, like the cubed-sphere are not based on iso-latitude rings. SpeedyWeather.jl only works with ring grids because its a requirement for the Spherical Harmonic Transform to be efficient. See Grids.","category":"page"},{"location":"ringgrids/#Creating-data-on-a-RingGrid","page":"Submodule: RingGrids","title":"Creating data on a RingGrid","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Every grid in RingGrids has a grid.data field, which is a vector containing the data on the grid. The grid points are unravelled west to east then north to south, meaning that it starts at 90˚N and 0˚E then walks eastward for 360˚ before jumping on the next latitude ring further south, this way circling around the sphere till reaching the south pole. This may also be called ring order.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Data in a Matrix which follows this ring order can be put on a FullGaussianGrid like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"using SpeedyWeather.RingGrids\nmap = randn(Float32,8,4)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid = FullGaussianGrid(map)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"A full Gaussian grid has always 2N x N grid points, but a FullClenshawGrid has 2N x N-1, if those dimensions don't match, the creation will throw an error. To reobtain the data from a grid, you can access its data field which returns a normal Vector","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid.data","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Which can be reshaped to reobtain map from above. Alternatively you can Matrix(grid) to do this in one step","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"map == Matrix(FullGaussianGrid(map))","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"You can also use zeros,ones,rand,randn to create a grid, whereby nlat_half, i.e. the number of latitude rings on one hemisphere, Equator included, is used as a resolution parameter and here as a second argument.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"nlat_half = 4\ngrid = randn(OctahedralGaussianGrid{Float16},nlat_half)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"and any element type T can be used for OctahedralGaussianGrid{T} and similar for other grid types.","category":"page"},{"location":"ringgrids/#Visualising-RingGrid-data","page":"Submodule: RingGrids","title":"Visualising RingGrid data","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"As only the full grids can be reshaped into a matrix, the underlying data structure of any AbstractGrid is a vector. As shown in the examples above, one can therefore inspect the data as if it was a vector. But as that data has, through its <:AbstractGrid type, all the geometric information available to plot it on a map, RingGrids also exports plot function, based on UnicodePlots' heatmap.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"nlat_half = 24\ngrid = randn(OctahedralGaussianGrid,nlat_half)\nplot(grid)","category":"page"},{"location":"ringgrids/#Indexing-RingGrids","page":"Submodule: RingGrids","title":"Indexing RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"All RingGrids have a single index ij which follows the ring order. While this is obviously not super exciting here are some examples how to make better use of the information that the data sits on a grid.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"We obtain the latitudes of the rings of a grid by calling get_latd (get_lond is only defined for full grids, or use get_latdlonds for latitudes, longitudes per grid point not per ring)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid = randn(OctahedralClenshawGrid,5)\nlatd = get_latd(grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Now we could calculate Coriolis and add it on the grid as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"rotation = 7.29e-5 # angular frequency of Earth's rotation [rad/s]\ncoriolis = 2rotation*sind.(latd) # vector of coriolis parameters per latitude ring\n\nrings = eachring(grid)\nfor (j,ring) in enumerate(rings)\n f = coriolis[j]\n for ij in ring\n grid[ij] += f\n end\nend","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"eachring creates a vector of UnitRange indices, such that we can loop over the ring index j (j=1 being closest to the North pole) pull the coriolis parameter at that latitude and then loop over all in-ring indices i (changing longitudes) to do something on the grid. Something similar can be done to scale/unscale with the cosine of latitude for example. We can always loop over all grid-points like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"for ij in eachgridpoint(grid)\n grid[ij]\nend","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"or use eachindex instead.","category":"page"},{"location":"ringgrids/#Interpolation-on-RingGrids","page":"Submodule: RingGrids","title":"Interpolation on RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"In most cases we will want to use RingGrids so that our data directly comes with the geometric information of where the grid-point is one the sphere. We have seen how to use get_latd, get_lond, ... for that above. This information generally can also be used to interpolate our data from grid to another or to request an interpolated value on some coordinates. Using our data on grid which is an OctahedralGaussianGrid from above we can use the interpolate function to get it onto a FullGaussianGrid (or any other grid for purpose)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid = randn(OctahedralGaussianGrid{Float32},4)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(FullGaussianGrid,grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"By default this will linearly interpolate (it's an Anvil interpolator, see below) onto a grid with the same nlat_half, but we can also coarse-grain or fine-grain by specifying nlat_half directly as 2nd argument","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(FullGaussianGrid,6,grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"So we got from an 8-ring OctahedralGaussianGrid{Float16} to a 12-ring FullGaussianGrid{Float64}, so it did a conversion from Float16 to Float64 on the fly too, because the default precision is Float64 unless specified. interpolate(FullGaussianGrid{Float16},6,grid) would have interpolated onto a grid with element type Float16.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"One can also interpolate onto a given coordinate ˚N, ˚E like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(30.0,10.0,grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"we interpolated the data from grid onto 30˚N, 10˚E. To do this simultaneously for many coordinates they can be packed into a vector too","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate([30.0,40.0,50.0],[10.0,10.0,10.0],grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which returns the data on grid at 30˚N, 40˚N, 50˚N, and 10˚E respectively. Note how the interpolation here retains the element type of grid.","category":"page"},{"location":"ringgrids/#Performance-for-RingGrid-interpolation","page":"Submodule: RingGrids","title":"Performance for RingGrid interpolation","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Every time an interpolation like interpolate(30.0,10.0,grid) is called, several things happen, which are important to understand to know how to get the fastest interpolation out of this module in a given situation. Under the hood an interpolation takes three arguments","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"output vector\ninput grid\ninterpolator","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"The output vector is just an array into which the interpolated data is written, providing this prevents unnecessary allocation of memory in case the destination array of the interpolation already exists. The input grid contains the data which is subject to interpolation, it must come on a ring grid, however, its coordinate information is actually already in the interpolator. The interpolator knows about the geometry of the grid the data is coming on and the coordinates it is supposed to interpolate onto. It has therefore precalculated the indices that are needed to access the right data on the input grid and the weights it needs to apply in the actual interpolation operation. The only thing it does not know is the actual data values of that grid. So in the case you want to interpolate from grid A to grid B many times, you can just reuse the same interpolator. If you want to change the coordinates of the output grid but its total number of points remain constants then you can update the locator inside the interpolator and only else you will need to create a new interpolator. Let's look at this in practice. Say we have two grids an want to interpolate between them","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid_in = rand(HEALPixGrid,4)\ngrid_out = zeros(FullClenshawGrid,6)\ninterp = RingGrids.interpolator(grid_out,grid_in)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Now we have created an interpolator interp which knows about the geometry where to interpolate from and the coordinates there to interpolate to. It is also initialized, meaning it has precomputed the indices to of grid_in that are supposed to be used. It just does not know about the data of grid_in (and neither of grid_out which will be overwritten anyway). We can now do","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate!(grid_out,grid_in,interp)\ngrid_out","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which is identical to interpolate(grid_out,grid_in) but you can reuse interp for other data. The interpolation can also handle various element types (the interpolator interp does not have to be updated for this either)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid_out = zeros(FullClenshawGrid{Float16},6);\ninterpolate!(grid_out,grid_in,interp)\ngrid_out","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"and we have converted data from a HEALPixGrid{Float64} (Float64 is always default if not specified) to a FullClenshawGrid{Float16} including the type conversion Float64-Float16 on the fly. Technically there are three data types and their combinations possible: The input data will come with a type, the output array has an element type and the interpolator has precomputed weights with a given type. Say we want to go from Float16 data on an OctahedralGaussianGrid to Float16 on a FullClenshawGrid but using Float32 precision for the interpolation itself, we would do this by","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid_in = randn(OctahedralGaussianGrid{Float16},24)\ngrid_out = zeros(FullClenshawGrid{Float16},24)\ninterp = RingGrids.interpolator(Float32,grid_out,grid_in)\ninterpolate!(grid_out,grid_in,interp)\ngrid_out","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"As a last example we want to illustrate a situation where we would always want to interpolate onto 10 coordinates, but their locations may change. In order to avoid recreating an interpolator object we would do (AnvilInterpolator is described in Anvil interpolator)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"npoints = 10 # number of coordinates to interpolate onto\ninterp = AnvilInterpolator(Float32,HEALPixGrid,24,npoints)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"with the first argument being the number format used during interpolation, then the input grid type, its resolution in terms of nlat_half and then the number of points to interpolate onto. However, interp is not yet initialized as it does not know about the destination coordinates yet. Let's define them, but note that we already decided there's only 10 of them above.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"latds = collect(0.0:5.0:45.0)\nlonds = collect(-10.0:2.0:8.0)\nnothing # hide","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"now we can update the locator inside our interpolator as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids.update_locator!(interp,latds,londs)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"With data matching the input from above, a nlat_half=24 HEALPixGrid, and allocate 10-element output vector","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"output_vec = zeros(10)\ngrid_input = rand(HEALPixGrid,24)\nnothing # hide","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"we can use the interpolator as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate!(output_vec,grid_input,interp)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which is the approximately the same as doing it directly without creating an interpolator first and updating its locator","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(latds,londs,grid_input)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"but allows for a reuse of the interpolator. Note that the two output arrays are not exactly identical because we manually set our interpolator interp to use Float32 for the interpolation whereas the default is Float64.","category":"page"},{"location":"ringgrids/#Anvil-interpolator","page":"Submodule: RingGrids","title":"Anvil interpolator","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Currently the only interpolator implemented is a 4-point bilinear interpolator, which schematically works as follows. Anvil interpolation is the bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":" 0..............1 # fraction of distance Δab between a,b\n |< Δab >|\n\n0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x\n.Δy |\n. |\n.v x \n. |\n1 c ------ o ---- d\n\n |< Δcd >|\n 0...............1 # fraction of distance Δcd between c,d\n\n^ fraction of distance Δy between a-b and c-d.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"This interpolation is chosen as by definition of the ring grids, a and b share the same latitude, so do c and d, but the longitudes can be different for all four, a,b,c,d.","category":"page"},{"location":"ringgrids/#Function-index","page":"Submodule: RingGrids","title":"Function index","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Modules = [SpeedyWeather.RingGrids]","category":"page"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractFullGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractFullGrid","text":"abstract type AbstractFullGrid{T} <: AbstractGrid{T} end\n\nAn AbstractFullGrid is a horizontal grid with a constant number of longitude points across latitude rings. Different latitudes can be used, Gaussian latitudes, equi-angle latitdes, or others.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractGrid","text":"abstract type AbstractGrid{T} <: AbstractVector{T} end\n\nThe abstract supertype for all spatial grids on the sphere supported by SpeedyWeather.jl. Every new grid has to be of the form\n\nabstract type AbstractGridClass{T} <: AbstractGrid{T} end\nstruct MyNewGrid{T} <: AbstractGridClass{T}\n data::Vector{T} # all grid points unravelled into a vector\n nlat_half::Int # resolution: latitude rings on one hemisphere (Equator incl)\nend\n\nMyNewGrid should belong to a grid class like AbstractFullGrid, AbstractOctahedralGrid or AbstractHEALPixGrid (that already exist but you may introduce a new class of grids) that share certain features such as the number of longitude points per latitude ring and indexing, but may have different latitudes or offset rotations. Each new grid Grid (or grid class) then has to implement the following methods (as an example, see octahedral.jl)\n\nFundamental grid properties getnpoints # total number of grid points nlatodd # does the grid have an odd number of latitude rings? getnlat # total number of latitude rings getnlat_half # number of latitude rings on one hemisphere incl Equator\n\nIndexing getnlonmax # maximum number of longitudes points (at the Equator) getnlonperring # number of longitudes on ring j eachindexinring # a unit range that indexes all longitude points on a ring\n\nCoordinates getcolat # vector of colatitudes (radians) getcolatlon # vectors of colatitudes, longitudes (both radians)\n\nSpectral truncation truncationorder # linear, quadratic, cubic = 1,2,3 for grid gettruncation # spectral truncation given a grid resolution get_resolution # grid resolution given a spectral truncation\n\nQuadrature weights and solid angles getquadratureweights # = sinθ Δθ for grid points on ring j for meridional integration getsolidangle # = sinθ Δθ Δϕ, solid angle of grid points on ring j\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractHEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractHEALPixGrid","text":"abstract type AbstractHEALPixGrid{T} <: AbstractGrid{T} end\n\nAn AbstractHEALPixGrid is a horizontal grid similar to the standard HEALPixGrid, but different latitudes can be used, the default HEALPix latitudes or others.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractInterpolator","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractInterpolator","text":"abstract type AbstractInterpolator{NF,G} end\n\nSupertype for Interpolators. Every Interpolator <: AbstractInterpolator is expected to have two fields,\n\ngeometry, which describes the grid G to interpolate from\nlocator, which locates the indices on G and their weights to interpolate onto a new grid.\n\nNF is the number format used to calculate the interpolation, which can be different from the input data and/or the interpolated data on the new grid.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractLocator","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractLocator","text":"AbstractLocator{NF}\n\nSupertype of every Locator, which locates the indices on a grid to be used to perform an interpolation. E.g. AnvilLocator uses a 4-point stencil for every new coordinate to interpolate onto. Higher order stencils can be implemented by defining OtherLocator <: AbstractLocactor.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractOctaHEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractOctaHEALPixGrid","text":"abstract type AbstractOctaHEALPixGrid{T} <: AbstractGrid{T} end\n\nAn AbstractOctaHEALPixGrid is a horizontal grid similar to the standard OctahedralGrid, but the number of points in the ring closest to the Poles starts from 4 instead of 20, and the longitude of the first point in each ring is shifted as in HEALPixGrid. Also, different latitudes can be used.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractOctahedralGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractOctahedralGrid","text":"abstract type AbstractOctahedralGrid{T} <: AbstractGrid{T} end\n\nAn AbstractOctahedralGrid is a horizontal grid with 16+4i longitude points on the latitude ring i starting with i=1 around the pole. Different latitudes can be used, Gaussian latitudes, equi-angle latitdes, or others.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AnvilLocator","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AnvilLocator","text":"AnvilLocator{NF<:AbstractFloat} <: AbtractLocator\n\nContains arrays that locates grid points of a given field to be uses in an interpolation and their weights. This Locator is a 4-point average in an anvil-shaped grid-point arrangement between two latitude rings.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AnvilLocator-Union{Tuple{Integer}, Tuple{NF}} where NF<:AbstractFloat","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AnvilLocator","text":"L = AnvilLocator( ::Type{NF}, # number format used for the interpolation\n npoints::Integer # number of points to interpolate onto\n ) where {NF<:AbstractFloat}\n\nZero generator function for the 4-point average AnvilLocator. Use update_locator! to update the grid indices used for interpolation and their weights. The number format NF is the format used for the calculations within the interpolation, the input data and/or output data formats may differ.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.FullClenshawGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.FullClenshawGrid","text":"G = FullClenshawGrid{T}\n\nA FullClenshawGrid is a regular latitude-longitude grid with an odd number of nlat equi-spaced latitudes, the central latitude ring is on the Equator. The same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field data that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.FullGaussianGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.FullGaussianGrid","text":"G = FullGaussianGrid{T}\n\nA full Gaussian grid is a regular latitude-longitude grid that uses nlat Gaussian latitudes, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.FullHEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.FullHEALPixGrid","text":"G = FullHEALPixGrid{T}\n\nA full HEALPix grid is a regular latitude-longitude grid that uses nlat latitudes from the HEALPix grid, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.FullOctaHEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.FullOctaHEALPixGrid","text":"G = FullOctaHEALPixGrid{T}\n\nA full OctaHEALPix grid is a regular latitude-longitude grid that uses nlat OctaHEALPix latitudes, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.GridGeometry","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.GridGeometry","text":"GridGeometry{G<:AbstractGrid}\n\ncontains general precomputed arrays describing the grid of G.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.GridGeometry-Tuple{Type{<:SpeedyWeather.RingGrids.AbstractGrid}, Integer}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.GridGeometry","text":"G = GridGeometry( Grid::Type{<:AbstractGrid},\n nlat_half::Integer)\n\nPrecomputed arrays describing the geometry of the Grid with resolution nlat_half. Contains latitudes and longitudes of grid points, their ring index j and their unravelled indices ij.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.HEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.HEALPixGrid","text":"H = HEALPixGrid{T}\n\nA HEALPix grid with 12 faces, each nsidexnside grid points, each covering the same area. The number of latitude rings on one hemisphere (incl Equator) nlat_half is used as resolution parameter. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.OctaHEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.OctaHEALPixGrid","text":"H = OctaHEALPixGrid{T}\n\nA OctaHEALPix grid with 4 base faces, each nlat_halfxnlat_half grid points, each covering the same area. The values of all grid points are stored in a vector field data that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.OctahedralClenshawGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.OctahedralClenshawGrid","text":"G = OctahedralClenshawGrid{T}\n\nAn Octahedral Clenshaw grid that uses nlat equi-spaced latitudes. Like FullClenshawGrid, the central latitude ring is on the Equator. Like OctahedralGaussianGrid, the number of longitude points per latitude ring decreases towards the poles. Starting with 20 equi-spaced longitude points (starting at 0˚E) on the rings around the poles, each latitude ring towards the equator has consecuitively 4 more points, one for each face of the octahedron. E.g. 20,24,28,32,...nlon-4,nlon,nlon,nlon-4,...,32,28,24,20. The maximum number of longitue points is nlon. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.OctahedralGaussianGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.OctahedralGaussianGrid","text":"G = OctahedralGaussianGrid{T}\n\nAn Octahedral Gaussian grid that uses nlat Gaussian latitudes, but a decreasing number of longitude points per latitude ring towards the poles. Starting with 20 equi-spaced longitude points (starting at 0˚E) on the rings around the poles, each latitude ring towards the equator has consecuitively 4 more points, one for each face of the octahedron. E.g. 20,24,28,32,...nlon-4,nlon,nlon,nlon-4,...,32,28,24,20. The maximum number of longitue points is nlon. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.Matrix!-Tuple{AbstractMatrix, OctaHEALPixGrid}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.Matrix!","text":"Matrix!(M::AbstractMatrix,\n G::OctaHEALPixGrid;\n quadrant_rotation=(0,1,2,3),\n matrix_quadrant=((2,2),(1,2),(1,1),(2,1)),\n )\n\nSorts the gridpoints in G into the matrix M without interpolation. Every quadrant of the grid G is rotated as specified in quadrant_rotation, 0 is no rotation, 1 is 90˚ clockwise, 2 is 180˚ etc. Grid quadrants are counted eastward starting from 0˚E. The grid quadrants are moved into the matrix quadrant (i,j) as specified. Defaults are equivalent to centered at 0˚E and a rotation such that the North Pole is at M's midpoint.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.Matrix!-Tuple{AbstractMatrix, OctahedralClenshawGrid}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.Matrix!","text":"Matrix!(M::AbstractMatrix,\n G::OctahedralClenshawGrid;\n quadrant_rotation=(0,1,2,3),\n matrix_quadrant=((2,2),(1,2),(1,1),(2,1)),\n )\n\nSorts the gridpoints in G into the matrix M without interpolation. Every quadrant of the grid G is rotated as specified in quadrant_rotation, 0 is no rotation, 1 is 90˚ clockwise, 2 is 180˚ etc. Grid quadrants are counted eastward starting from 0˚E. The grid quadrants are moved into the matrix quadrant (i,j) as specified. Defaults are equivalent to centered at 0˚E and a rotation such that the North Pole is at M's midpoint.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.Matrix!-Union{Tuple{Vararg{Tuple{AbstractMatrix{T}, OctaHEALPixGrid}}}, Tuple{T}} where T","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.Matrix!","text":"Matrix!(MGs::Tuple{AbstractMatrix{T},OctaHEALPixGrid}...;kwargs...)\n\nLike Matrix!(::AbstractMatrix,::OctaHEALPixGrid) but for simultaneous processing of tuples ((M1,G1),(M2,G2),...) with matrices Mi and grids Gi. All matrices and grids have to be of the same size respectively.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.Matrix!-Union{Tuple{Vararg{Tuple{AbstractMatrix{T}, OctahedralClenshawGrid}}}, Tuple{T}} where T","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.Matrix!","text":"Matrix!(MGs::Tuple{AbstractMatrix{T},OctahedralClenshawGrid}...;kwargs...)\n\nLike Matrix!(::AbstractMatrix,::OctahedralClenshawGrid) but for simultaneous processing of tuples ((M1,G1),(M2,G2),...) with matrices Mi and grids Gi. All matrices and grids have to be of the same size respectively.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.anvil_average-NTuple{7, Any}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.anvil_average","text":"anvil_average(a, b, c, d, Δab, Δcd, Δy) -> Any\n\n\nThe bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate. See schematic:\n\n 0..............1 # fraction of distance Δab between a,b\n |< Δab >|\n\n 0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x\n .Δy |\n . |\n .v x \n . |\n 1 c ------ o ---- d\n\n |< Δcd >|\n 0...............1 # fraction of distance Δcd between c,d\n\n^ fraction of distance Δy between a-b and c-d.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.average_on_poles-Union{Tuple{NF}, Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}, Vector{<:UnitRange{<:Integer}}}} where NF<:AbstractFloat","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.average_on_poles","text":"average_on_poles(\n A::SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n rings::Vector{<:UnitRange{<:Integer}}\n) -> Tuple{Any, Any}\n\n\nComputes the average at the North and South pole from a given grid A and it's precomputed ring indices rings. The North pole average is an equally weighted average of all grid points on the northern-most ring. Similar for the South pole.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.average_on_poles-Union{Tuple{NF}, Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}, Vector{<:UnitRange{<:Integer}}}} where NF<:Integer","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.average_on_poles","text":"average_on_poles(\n A::SpeedyWeather.RingGrids.AbstractGrid{NF<:Integer},\n rings::Vector{<:UnitRange{<:Integer}}\n) -> Tuple{Any, Any}\n\n\nMethod for A::Abstract{T<:Integer} which rounds the averaged values to return the same number format NF.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.each_index_in_ring-Union{Tuple{Grid}, Tuple{Grid, Integer}} where Grid<:SpeedyWeather.RingGrids.AbstractGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.each_index_in_ring","text":"i = each_index_in_ring(grid,j)\n\nUnitRange i to access data on grid grid on ring j.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.eachgridpoint-Tuple{SpeedyWeather.RingGrids.AbstractGrid}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.eachgridpoint","text":"ijs = eachgridpoint(grid)\n\nUnitRange ijs to access each grid point on grid grid.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.eachring-Tuple{SpeedyWeather.RingGrids.AbstractGrid}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.eachring","text":"eachring(grid::SpeedyWeather.RingGrids.AbstractGrid) -> Any\n\n\nVector{UnitRange} rings to loop over every ring of grid grid and then each grid point per ring. To be used like\n\nrings = eachring(grid)\nfor ring in rings\n for ij in ring\n grid[ij]\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.eachring-Union{Tuple{Grid}, Tuple{Grid, Vararg{Grid}}} where Grid<:SpeedyWeather.RingGrids.AbstractGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.eachring","text":"eachring(\n grid1::SpeedyWeather.RingGrids.AbstractGrid,\n grids::Grid<:SpeedyWeather.RingGrids.AbstractGrid...\n) -> Any\n\n\nSame as eachring(grid) but performs a bounds check to assess that all grids in grids are of same size.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.extrema_in-Tuple{Vector, Real, Real}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.extrema_in","text":"true/false = extrema_in(v::Vector,a::Real,b::Real)\n\nFor every element vᵢ in v does a<=vi<=b hold?\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.get_nlons-Tuple{Type{<:SpeedyWeather.RingGrids.AbstractGrid}, Integer}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.get_nlons","text":"get_nlons(\n Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid},\n nlat_half::Integer;\n both_hemispheres\n) -> Any\n\n\nReturns a vector nlons for the number of longitude points per latitude ring, north to south. Provide grid Grid and its resolution parameter nlat_half. For both_hemisphere==false only the northern hemisphere (incl Equator) is returned.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.isdecreasing-Tuple{Vector}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.isdecreasing","text":"true/false = isdecreasing(v::Vector)\n\nCheck whether elements of a vector v are strictly decreasing.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.isincreasing-Tuple{Vector}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.isincreasing","text":"true/false = isincreasing(v::Vector)\n\nCheck whether elements of a vector v are strictly increasing.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.rotate_matrix_indices_180-Tuple{Integer, Integer, Integer}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.rotate_matrix_indices_180","text":"i_new,j_new = rotate_matrix_indices_180(i,j,s)\n\nRotate indices i,j of a square matrix of size s x s by 180˚.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.rotate_matrix_indices_270-Tuple{Integer, Integer, Integer}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.rotate_matrix_indices_270","text":"i_new,j_new = rotate_matrix_indices_270(i,j,s)\n\nRotate indices i,j of a square matrix of size s x s anti-clockwise by 270˚.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.rotate_matrix_indices_90-Tuple{Integer, Integer, Integer}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.rotate_matrix_indices_90","text":"i_new,j_new = rotate_matrix_indices_90(i,j,s)\n\nRotate indices i,j of a square matrix of size s x s anti-clockwise by 90˚.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.whichring-Tuple{Integer, Vector{UnitRange{Int64}}}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.whichring","text":"whichring(\n ij::Integer,\n rings::Vector{UnitRange{Int64}}\n) -> Int64\n\n\nObtain ring index j from gridpoint ij and Vector{UnitRange} describing rind indices as obtained from eachring(::Grid)\n\n\n\n\n\n","category":"method"},{"location":"#SpeedyWeather.jl-documentation","page":"Home","title":"SpeedyWeather.jl documentation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.","category":"page"},{"location":"#Overview","page":"Home","title":"Overview","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to simulate the general circulation of the atmosphere. The prognostic variables used are vorticity, divergence, temperature, surface pressure and specific humidity. Simple parameterizations represent various climate processes: Radiation, clouds, precipitation, surface fluxes, among others.","category":"page"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl defines ","category":"page"},{"location":"","page":"Home","title":"Home","text":"BarotropicModel for the 2D barotropic vorticity equation\nShallowWaterModel for the 2D shallow water equations\nPrimitiveDryModel for the 3D primitive equations without humidity\nPrimitiveWetModel for the 3D primitive equations with humidity","category":"page"},{"location":"","page":"Home","title":"Home","text":"and solves these equations in spherical coordinates as described in this documentation.","category":"page"},{"location":"#Manual-outline","page":"Home","title":"Manual outline","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"See the following pages of the documentation for more details","category":"page"},{"location":"","page":"Home","title":"Home","text":"Installation\nHow to run SpeedyWeather.jl\nSpherical harmonic transform\nGrids\nBarotropic model\nShallow water model\nPrimitive equation model\nParameterizations\nExtending SpeedyWeather\nNetCDF output","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the submodules","category":"page"},{"location":"","page":"Home","title":"Home","text":"RingGrids\nLowerTriangularMatrices \nSpeedyTransforms","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the original documentation by Molteni and Kucharski.","category":"page"},{"location":"#Developers","page":"Home","title":"Developers","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The development of SpeedyWeather.jl is lead by Milan Klöwer and current and past contributors include","category":"page"},{"location":"","page":"Home","title":"Home","text":"Tom Kimpson\nAlistair White\nMaximilian Gelbrecht\nDavid Meyer\nDaisuke Hotta\nNavid Constantinou","category":"page"},{"location":"","page":"Home","title":"Home","text":"Any contributions are always welcome!","category":"page"},{"location":"#Funding","page":"Home","title":"Funding","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"MK received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022. Since 2023 this project is also funded by the National Science Foundation NSF.","category":"page"},{"location":"time_integration/#Time-integration","page":"Time integration","title":"Time integration","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"SpeedyWeather.jl uses a leapfrog time scheme with a Robert's and Williams filter to dampen the computational mode and achieve 3rd order accuracy.","category":"page"},{"location":"time_integration/#Oscillation-equation","page":"Time integration","title":"Oscillation equation","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"fracdFdt = iomega F","category":"page"},{"location":"time_integration/#Implementation-details","page":"Time integration","title":"Implementation details","text":"","category":"section"}]
+}
diff --git a/previews/PR360/shallowwater/index.html b/previews/PR360/shallowwater/index.html
new file mode 100644
index 000000000..5630b4249
--- /dev/null
+++ b/previews/PR360/shallowwater/index.html
@@ -0,0 +1,36 @@
+
+Shallow water model · SpeedyWeather.jl
The shallow water model describes the evolution of a 2D flow described by its velocity and an interface height that conceptually represents pressure. A divergent flow affects the interface height which in turn can impose a pressure gradient force onto the flow. The dynamics include advection, forces, dissipation, and continuity.
The following description of the shallow water model largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: The Shallow Water Equations[2].
The shallow water equations of velocity $\mathbf{u} = (u,v)$ and interface height $\eta$ (i.e. the deviation from the fluid's rest height $H$) are, formulated in terms of relative vorticity $\zeta = \nabla \times \mathbf{u}$, divergence $\mathcal{D} = \nabla \cdot \mathbf{u}$
We denote time$t$, Coriolis parameter $f$, a forcing vector $\mathbf{F} = (F_u,F_v)$, hyperdiffusion $(-1)^{n+1} \nu \nabla^{2n}$ ($n$ is the hyperdiffusion order, see Horizontal diffusion), gravitational acceleration $g$, dynamic layer thickness $h$, and a forcing for the interface height $F_\eta$. In the shallow water model the dynamics layer thickness $h$ is
\[h = \eta + H - H_b\]
that is, the layer thickness at rest $H$ plus the interface height $\eta$ minus orography $H_b$.
In the shallow water system the flow can be described through $u,v$ or $\zeta,\mathcal{D}$ which are related through the stream function $\Psi$ and the velocity potential $\Phi$ (which is zero in the Barotropic vorticity equation).
With $\nabla^\perp$ being the rotated gradient operator, in cartesian coordinates $x,y$: $\nabla^\perp = (-\partial_y, \partial_x)$. See Derivatives in spherical coordinates for further details. Especially because the inversion of the Laplacian and the gradients of $\Psi, \Phi$ can be computed in a single pass, see U,V from vorticity and divergence.
The divergence/curl of the vorticity flux $\mathbf{u}(\zeta + f)$ are combined with the divergence/curl of the forcing vector $\mathbf{F}$, as
0. Start with initial conditions of relative vorticity $\zeta_{lm}$, divergence $D_{lm}$, and interface height $\eta_{lm}$ in spectral space and transform this model state to grid-point space:
Invert the Laplacian of $\zeta_{lm}$ to obtain the stream function $\Psi_{lm}$ in spectral space
Invert the Laplacian of $D_{lm}$ to obtain the velocity potential $\Phi_{lm}$ in spectral space
Probably the biggest advantage of a spectral model is its ability to solve (parts of) the equations implicitly a low computational cost. The reason is that a linear operator can be easily inverted in spectral space, removing the necessity to solve large equation systems. An operation like $\Psi = \nabla^{-2}\zeta$ in grid-point space is costly because it requires a global communication, coupling all grid points. In spectral space $\nabla^2$ is a diagonal operator, meaning that there is no communication between harmonics and its inversion is therefore easily done on a mode-by-mode basis of the harmonics.
This can be made use of when facing time stepping constraints with explicit schemes, where ridiculuously small time steps to resolve fast waves would otherwise result in a horribly slow simulation. In the shallow water system there are gravity waves that propagate at a wave speed of $\sqrt{gH}$ (typically 300m/s), which, in order to not violate the CFL criterion for explicit time stepping, would need to be resolved. Therefore, treating the terms that are responsible for gravity waves implicitly would remove that time stepping constraint and allows us to run the simulation at the time step needed to resolve the advective motion of the atmosphere, which is usually one or two orders of magnitude longer than gravity waves.
In the following we will describe how the semi implicit time integration can be combined with the Leapfrog time stepping and the Robert-Asselin and Williams filter for a large increase in numerical stability with gravity waves. Let $V_i$ be the model state of all prognostic variables at time step $i$, the leapfrog time stepping is then
with the right-hand side operator $N$ evaluated at the current time step $i$. Now the idea is to split the terms in $N$ into non-linear terms that are evaluated explicitly in $N_E$ and into the linear terms $N_I$, solved implicitly, that are responsible for the gravity waves. We could already assume to evaluate $N_I$ at $i+1$, but in fact, we can introduce $\alpha \in [0,1]$ so that for $\alpha=0$ we use $i-1$ (i.e. explicit), for $\alpha=1/2$ it is centred implicit $\tfrac{1}{2}N_I(V_{i-1}) + \tfrac{1}{2}N_I(V_{i+1})$, and for $\alpha=1$ a fully backwards scheme $N_I(V_{i+1})$ evaluated at $i+1$.
Let $\delta V = \tfrac{V_{i+1} - V_{i-1}}{2\Delta t}$ be the tendency we need for the Leapfrog time stepping. Introducing $\xi = 2\alpha\Delta t$ we have
\[\delta V = N_E(V_i) + N_I(V_{i-1}) + \xi N_I(\delta V)\]
because $N_I$ is a linear operator. This is done so that we can solve for $\delta V$ by inverting $N_I$, but let us gather the other terms as $G$ first.
For the shallow water equations we will only make use of the last formulation, meaning we first evaluate the whole right-hand side $N(V_i)$ at the current time step as we would do with fully explicit time stepping but then add the implicit terms $N_I(V_{i-1} - V_i)$ afterwards to move those terms from $i$ to $i-1$. Note that we could also directly evaluate the implicit terms at $i-1$ as it is suggested in the previous formulation $N_E(V_i) + N_I(V_{i-1})$, the result would be the same. But in general it can be more efficient to do it one or the other way, and in fact it is also possible to combine both ways. This will be discussed in the semi-implicit time stepping for the primitive equations.
We can now implicitly solve for $\delta V$ by
\[\delta V = (1-\xi N_I)^{-1}G\]
So what is $N_I$? In the shallow water system the gravity waves are caused by
which is a linearization of the equations around a state of rest with uniform constant layer thickness $h = H$. The continuity equation with the $-\nabla(\mathbf{u}h)$ term, for example, is linearized to $-\nabla(\mathbf{u}H) = -H\mathcal{D}$. The divergence and continuity equations can now be written following the $\delta V = G + \xi N_I(\delta V)$ formulation from above as a coupled system (The vorticity equation is zero for the linear gravity wave equation in the shallow water equations, hence no semi-implicit correction has to be made to the vorticity tendency).
Inserting the second equation into the first, we can first solve for $\delta \mathcal{D}$, and then for $\delta \eta$. Reminder that we do this in spectral space to every harmonic independently, so the Laplace operator $\nabla^2 = -l(l+1)$ takes the form of its eigenvalue $-l(l+1)$ (normalized to unit sphere, as are the scaled shallow water equations) and its inversion is therefore just the inversion of this scalar.
\[\delta D = \frac{G_\mathcal{D} - \xi g\nabla^2 G_\eta}{1 - \xi^2 H \nabla^2} =: S^{-1}(G_\mathcal{D} - \xi g\nabla^2 G_\eta) \]
Where the last formulation just makes it clear that $S = 1 - \xi^2 H \nabla^2$ is the operator to be inverted. $\delta \eta$ is then obtained via insertion as written above. Equivalently, by adding a superscript $l$ for every degree of the spherical harmonics, we have
\[\delta \mathcal{D}^l = \frac{G_\mathcal{D}^l + \xi g l(l+1) G_\eta^l}{1 + \xi^2 H l(l+1)}\]
The idea of the semi-implicit time stepping is now as follows:
Evaluate the right-hand side explicitly at time step $i$ to obtain the explicit, preliminary tendencies $N_\mathcal{D},N_\eta$ (and $N_\zeta$ without a need for semi-implicit correction)
Move the implicit terms from $i$ to $i-1$ when calculating $G_\mathcal{D}, G_\eta$
Solve for $\delta \mathcal{D}$, the new, corrected tendency for divergence.
With $\delta \mathcal{D}$ obtain $\delta \eta$, the new, corrected tendency for $\eta$.
Apply horizontal diffusion as a correction to $N_\zeta, \delta \mathcal{D}$ as outlined in Horizontal diffusion.
Leapfrog with tendencies that have been corrected for both semi-implicit and diffusion.
Some notes on the semi-implicit time stepping
The inversion of the semi-implicit time stepping depends on $\delta t$, that means every time the time step changes, the inversion has to be recalculated.
You may choose $\alpha = 1/2$ to dampen gravity waves but initialisation shocks still usually kick off many gravity waves that propagate around the sphere for many days.
With increasing $\alpha > 1/2$ these waves are also slowed down, such that for $\alpha = 1$ they quickly disappear in several hours.
Using the scaled shallow water equations the time step $\delta t$ has to be the scaled time step $\tilde{\Delta t} = \delta t/R$ which is divided by the radius $R$. Then we use the normalized eigenvalues $-l(l+1)$ which also omit the $1/R^2$ scaling, see scaled shallow water equations for more details.
Similar to the scaled barotropic vorticity equations, SpeedyWeather.jl scales in the shallow water equations. The vorticity and the divergence equation are scaled with $R^2$, the radius of the sphere squared, but the continuity equation is scaled with $R$. We also combine the vorticity flux and forcing into a single divergence/curl operation as mentioned in Shallow water equations above
As in the scaled barotropic vorticity equations, one needs to scale the time step, the Coriolis force, the forcing and the diffusion coefficient, but then enjoys the luxury of working with dimensionless gradient operators. As before, SpeedyWeather.jl will scale vorticity and divergence just before the model integration starts and unscale them upon completion and for output. In the semi-implicit time integration we solve an equation that also has to be scaled. It is with radius squared scaling (because it is the tendency for the divergence equation which is also scaled with $R^2$)
\[R^2 \delta D = R^2\frac{G_\mathcal{D} - \xi g\nabla^2 G_\eta}{1 - \xi^2 H \nabla^2}\]
The $R^2$ normalizes the Laplace operator in the numerator, but using the scaled $G_\eta$ we also scale $\xi$ (which is convenient, because the time step within is the one we use anyway). The denominator $S$ does not actually change because $\xi^2\nabla^2 = \tilde{\xi}^2\tilde{\nabla}^2$ as $\xi^2$ is scaled with $1/R^2$, but the Laplace operator with $R^2$. So overall we just have to use the scaled time step $\tilde{\Delta t}$ and normalized eigenvalues for $\tilde{\nabla}^2$.
The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space which can be any of the Implemented grids as defined by RingGrids. This includes the classical full Gaussian grid, a regular longitude-latitude grid called the full Clenshaw grid (FullClenshawGrid), ECMWF's octahedral Gaussian grid[Malardel2016], and HEALPix grids[Gorski2004]. SpeedyWeather.jl's spectral transform module SpeedyTransforms is grid-flexible and can be used with any of these, see Grids.
SpeedyTransforms is a module too!
SpeedyTransform is the underlying module that SpeedyWeather imports to transform between spectral and grid-point space, which also implements Derivatives in spherical coordinates. You can use this module independently of SpeedyWeather for spectral transforms, see SpeedyTransforms.
with $\lambda_l^m$ being the pre-normalized associated Legendre polynomials, and $e^{im\phi}$ are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.
Latitudes versus colatitudes
The implementation of the spectral transforms in SpeedyWeather.jl uses colatitudes $\theta = (0,\pi)$ (0 at the north pole) but the dynamical core uses latitudes $\theta = (-\pi/2,\pi/2)$ ($\pi/2$ at the north pole). Note: We may also use latitudes in the spherical harmonic transform in the future for consistency.
The synthesis (or inverse transform) takes the spectral coefficients $a_{lm}$ and transforms them to grid-point values $f(\phi,\theta)$ (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics $Y_{lm}$ with non-zero coefficients.
We obtain an approximation with a finite set of $a_{l,m}$ by truncating the series in both degree $l$ and order $m$ somehow. Most commonly, a triangular truncation is applied, such that all degrees after $l = l_{max}$ are discarded. Triangular because the retained array of the coefficients $a_{l,m}$ looks like a triangle. Other truncations like rhomboidal have been studied[Daley78] but are rarely used since. Choosing $l_{max}$ also constrains $m_{max}$ and determines the (horizontal) spectral resolution. In SpeedyWeather.jl this resolution as chosen as trunc when creating the SpectralGrid.
For $f$ being a real-valued there is a symmetry
\[a_{l,-m} = (-1)^m a^*_{l,+m},\]
meaning that the coefficients at $-m$ and $m$ are the same, but the sign of the real and imaginary component can be flipped, as denoted with the $(-1)^m$ and the complex conjugate $a_{l,m}^*$. As we are only dealing with real-valued fields anyway, we therefore never have to store the negative orders $-m$ and end up with a lower triangular matrix of size $(l_{max}+1) \times (m_{max}+1)$ or technically $(T+1)^2$ where $T$ is the truncation trunc. One is added here because the degree $l$ and order $m$ use 0-based indexing but sizes (and so is Julia's indexing) are 1-based.
For correctness we want to mention here that vector quantities require one more degree $l$ due to the recurrence relation in the Meridional derivative. Hence for practical reasons all spectral fields are represented as a lower triangular matrix of size $(m_{max} + 2) \times (m_{max} +1)$. And the scalar quantities would just not make use of that last degree, and its entries would be simply zero. We will, however, for the following sections ignore this and only discuss it again in Meridional derivative.
Another consequence of the symmetry mentioned above is that the zonal harmonics, meaning $a_{l,m=0}$ have no imaginary component. Because these harmonics are zonally constant, a non-zero imaginary component would rotate them around the Earth's axis, which, well, doesn't actually change a real-valued field.
Following the notation of [Willmert2020] we can therefore write the truncated synthesis as
The $(2-\delta_{m0})$ factor using the Kronecker $\delta$ is used here because of the symmetry we have to count both the $m,-m$ order pairs (hence the $2$) except for the zonal harmonics which do not have a pair.
Another symmetry arises from the fact that the spherical harmonics are either symmetric or anti-symmetric around the Equator. There is an even/odd combination of degrees and orders so that the sign flips like a checkerboard
Note that this notation again uses colatitudes $\theta$, for latitudes the $\sin\theta$ becomes a $\cos\theta$ and the bounds have to be changed accordingly to $(-\frac{\pi}{2},\frac{\pi}{2})$. A discretization with $N$ grid points at location $(\phi_i,\theta_i)$, indexed by $i$ can be written as [Willmert2020]
The hat on $a$ just means that it is an approximation, or an estimate of the true $a_{lm} \approx \hat{a}_{lm}$. We can essentially make use of the same symmetries as already discussed in Synthesis. Splitting into the Fourier modes $e^{im\phi}$ and the Legendre polynomials $\lambda_l^m(\cos\theta)$ (which are defined over $[-1,1]$ so the $\cos\theta$ argument maps them to colatitudes) we have
So the term in brackets can be separated out as long as the latitude $\theta_j$ is constant, which motivates us to restrict the spectral transform to grids with iso-latitude rings, see Grids. Furthermore, this term can be written as a fast Fourier transform, if the $\phi_i$ are equally spaced on the latitude ring $j$. Note that the in-ring index $i$ can depend on the ring index $j$, so that one can have reduced grids, which have fewer grid points towards the poles, for example. Also the Legendre polynomials only have to be computed for the colatitudes $\theta_j$ (and in fact only one hemisphere, due to the north-south symmetry discussed in the Synthesis). It is therefore practical and efficient to design a spectral transform implementation for ring grids, but there is no need to hardcode a specific grid.
Spectral packing is the way how the coefficients $a_{lm}$ of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree $l$ and order $m$ as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)
Every row represents an order $l \geq 0$, starting from $l=0$ at the top. Every column represents an order $m \geq 0$, starting from $m=0$ on the left. The coefficients of these spherical harmonics are directly mapped into a matrix $a_{lm}$ as
$m$
$l$
$a_{00}$
$a_{10}$
$a_{11}$
$a_{20}$
$a_{12}$
$a_{22}$
$a_{30}$
$a_{13}$
$a_{23}$
$a_{33}$
which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal are zero. Note that internally vector fields include an additional degree, such that $l_{max} = m_{max} + 1$ (see Derivatives in spherical coordinates for more information). The harmonics with $a_{l0}$ (the first column) are also called zonal harmonics as they are constant with longitude $\phi$. The harmonics with $a_{ll}$ (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into $2l$ sectors in longitude $\phi$ without a zero-crossing in latitude.
For correctness it is mentioned here that SpeedyWeather.jl uses a LowerTriangularMatrix type to store the spherical harmonic coefficients. By doing so, the upper triangle is actually not explicitly stored and the data technically unravelled into a vector, but this is hidden as much as possible from the user. For more details see LowerTriangularMatrices.
Array indices
For a spectral field a note that due to Julia's 1-based indexing the coefficient $a_{lm}$ is obtained via a[l+1,m+1]. Alternatively, we may index over 1-based l,m but a comment is usually added for clarification.
Fortran SPEEDY does not use the same spectral packing as SpeedyWeather.jl. The alternative packing $l',m'$ therein uses $l'=m$ and $m'=l-m$ as summarized in the following table.
degree $l$
order $m$
$l'=m$
$m'=l-m$
0
0
0
0
1
0
0
1
1
1
1
0
2
0
0
2
2
1
1
1
2
2
2
0
3
0
0
3
...
...
...
...
This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices
$m'$
$l'$
$a_{00}$
$a_{10}$
$a_{20}$
$a_{30}$
$a_{11}$
$a_{21}$
$a_{31}$
$a_{22}$
$a_{32}$
$a_{33}$
This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran SPEEDY.
SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with $l \leq l_{max}$ and $|m| \leq m_{max}$ are explicitly represented. This is usually described as $Tm_{max}$, with $l_{max} = m_{max}$ (although in vector quantities require one more degree $l$ in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with $l_{max} = m_{max} = 31$. Note that the degree $l$ and order $m$ are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.
Technically, SpeedyWeather.jl supports arbitrarily chosen resolution parameter trunc when creating the SpectralGrid that refers to the highest non-zero degree $l_{max}$ that is resolved in spectral space. SpeedyWeather.jl will always try to choose an easily-Fourier transformable[FFT] size of the grid, but as we use FFTW.jl there is quite some flexibility without performance sacrifice. However, this has traditionally lead to typical resolutions that we also use for testing we therefore recommend to use. They are as follows with more details below
trunc
nlon
nlat
$\Delta x$
31 (default)
96
48
400 km
42
128
64
312 km
63
192
96
216 km
85
256
128
165 km
127
384
192
112 km
170
512
256
85 km
255
768
384
58 km
341
1024
512
43 km
511
1536
768
29 km
682
2048
1024
22 km
1024
3072
1536
14 km
1365
4092
2048
11 km
Some remarks on this table
This assumes the default quadratic truncation, you can always adapt the grid resolution via the dealiasing option, see Matching spectral and grid resolution
nlat refers to the total number of latitude rings, see Grids. With non-Gaussian grids, nlat will be one one less, e.g. 47 instead of 48 rings.
nlon is the number of longitude points on the Full Gaussian Grid, for other grids there will be at most these number of points around the Equator.
$\Delta x$ is the horizontal resolution. For a spectral model there are many ways of estimating this[9]. We use here the square root of the average area a grid cell covers, see Effective grid resolution
There are many ways to estimate the effective grid resolution of spectral models[9]. Some of them are based on the wavelength a given spectral resolution allows to represent, others on the total number of real variables per area. However, as many atmospheric models do represent a considerable amount of physics on the grid (see Parameterizations) there is also a good argument to include the actual grid resolution into this estimate and not just the spectral resolution. We therefore use the average grid cell area to estimate the resolution
\[\Delta x = \sqrt{\frac{4\pi R^2}{N}}\]
with $N$ number of grid points over a sphere with radius $R$. However, we have to acknowledge that this usually gives higher resolution compared to other methods of estimating the effective resolution, see [Randall2021] for a discussion. You may therefore need to be careful to make claims that, e.g. trunc=85 can resolve the atmospheric dynamics at a scale of 165km.
The radius of the sphere (i.e. Earth) is $R$. The zonal gradient scales with $1/\cos(\theta)$ as the longitudes converge towards the poles (note that $\theta$ describes latitudes here, definitions using colatitudes replace the $\cos$ with a $\sin$.)
Starting with a spectral field of vorticity $\zeta$ and divergence $\mathcal{D}$ one can obtain stream function $\Psi$ and velocity potential $\Phi$ by inverting the Laplace operator $\nabla^2$:
The velocities $u,v$ are then obtained from $(u,v) = \nabla^\bot\Psi + \nabla\Phi$ following the definition from above and $\nabla^\bot = (-R^{-1}\partial_\theta, (R\cos\theta)^{-1}\partial_\lambda)$
Alternatively, we can use the velocities $U = u\cos\theta, V = v\cos\theta$, which we do as the meridional gradient for spherical harmonics is easier implemented with a $\cos\theta$-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with $U,V$ and not $u,v$. From $u,v$ we can return to $\zeta, \mathcal{D}$ via
\[\begin{aligned}
+\zeta &= \frac{1}{R\cos\theta}\partial_\lambda v - \frac{1}{R\cos\theta}\partial_\theta (u \cos\theta) \\
+\mathcal{D} &= \frac{1}{R\cos\theta}\partial_\lambda u + \frac{1}{R\cos\theta}\partial_\theta (v \cos\theta).
+\end{aligned}\]
which is a more convenient formulation because of the way how the Meridional derivative is implemented with a recursion relation, actually computing $\cos\theta \partial_\theta$ rather than $\partial_\theta$ directly. The remaining cosine scalings in $(U,V)*\cos^{-2}\theta$ are done in grid-point space. If one wanted to get back to $\zeta, \mathcal{D}$ this is how it would be done, but it is often more convenient to unscale $U,V$ on the fly in the spectral transform to obtain $u,v$ and then divide again by $\cos\theta$ when any gradient (or divergence or curl) is taken. This is because other terms would need that single $\cos\theta$ unscaling too before a gradient is taken. How the operators $\nabla, \nabla \times, \nabla \cdot$ can be implemented with spherical harmonics is presented in the following sections.
Also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out in this last formulation too.
The zonal derivative of a scalar field $\Psi$ in spectral space is the zonal derivative of all its respective spherical harmonics $\Psi_{lm}(\phi,\theta)$ (now we use $\phi$ for longitudes to avoid confusion with the Legendre polynomials $\lambda_{lm}$)
So for every spectral harmonic, $\cos(\theta)v_{lm}$ is obtained from $\Psi_{lm}$ via a multiplication with $im/R$. Unscaling the $\cos(\theta)$-factor is done after transforming the spectral coefficients $v_{lm}$ into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as $\tilde{\Psi} = R^{-1}\Psi$ such that the division by radius $R$ in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order $im$. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number $m$ times imaginary $i$.
The meridional derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]
In the following we use the example of obtaining the zonal velocity $u$ from the stream function $\Psi$, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion
To obtain the coefficient of each spherical harmonic $l,m$ of the meridional gradient of a spectral field, two coefficients at $l-1,m$ and $l+1,m$ have to be combined. This means that the coefficient of a gradient $((\cos\theta) u)_{lm}$ is a linear combination of the coefficients of one higher and one lower degree $\Psi_{l+1,m},\Psi_{l-1,m}$. As the coefficient $\Psi_{lm}$ with $m<l$ are zero, the sectoral harmonics ($l=m$) of the gradients are obtained from the first off-diagonal only. However, the $l=l_{max}$ harmonics of the gradients require the $l_{max}-1$ as well as the $l_{max}+1$ harmonics. As a consequence vector quantities like velocity components $u,v$ require one more degree $l$ than scalar quantities like vorticity[Bourke72]. However, for easier compatibility all spectral fields in SpeedyWeather.jl use one more degree $l$, but scalar quantities should not make use of it. Equivalently, the last degree $l$ is set to zero before the time integration, which only advances scalar quantities.
In SpeedyWeather.jl vector quantities like $u,v$ use therefore one more meridional mode than scalar quantities such as vorticity $\zeta$ or stream function $\Psi$. The meridional derivative in SpeedyWeather.jl also omits the $1/R$-scaling as explained for the Zonal derivative and in Radius scaling.
The meridional gradient as described above can be applied to scalars, such as $\Psi$ and $\Phi$ in the conversion to velocities $(u,v) = \nabla^\bot\Psi + \nabla\Phi$, however, the operators curl $\nabla \times$ and divergence $\nabla \cdot$ in spherical coordinates involve a $\cos\theta$ scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].
Given that $R\zeta = \cos^{-1}\partial_\lambda v - \cos^{-1}\partial_\theta (u \cos\theta)$, we therefore have to evaluate a meridional integral of the form
Now we expand $(\tfrac{u}{\cos\theta})$ but only the $l,m$ harmonic will project onto$P_{l,m}$. Let $u^* = u\cos^{-1}\theta, v^* = v\cos^{-1}\theta$ we then have in total
And the divergence $D$ is similar, but $(u,v) \to (-v,u)$. We have moved the scaling with the radius $R$ directly into $\zeta,D$ as further described in Radius scaling.
The spectral Laplacian is easily applied to the coefficients $\Psi_{lm}$ of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator $\nabla^2$ in spherical coordinates with eigenvalues $-l(l+1)$ divided by the radius squared $R^2$, i.e. $\nabla^2 \Psi$ becomes $\tfrac{-l(l+1)}{R^2}\Psi_{lm}$ in spectral space. For example, vorticity $\zeta$ and streamfunction $\Psi$ are related by $\zeta = \nabla^2\Psi$ in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree $l$ and order $m$ to
\[\zeta_{l,m} = \frac{-l(l+1)}{R^2}\Psi_{l,m}\]
This can be easily inverted to obtain the stream function $\Psi$ from vorticity $\zeta$ instead. In order to avoid division by zero, we set $\Psi_{0,0}$ here, given that the stream function is only defined up to a constant anyway.
After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity $\zeta$ and divergence $D$ (which are prognostic variables) to $U=u\cos\theta, V=v\cos\theta$. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree $l$ (the meridional gradient). It is therefore computationally more efficient to compute $U,V$ directly from $\zeta,D$ instead of calculating stream function and velocity potential first. In total we have
Malardel2016Malardel S, Wedi N, Deconinck W, Diamantakis M, Kühnlein C, Mozdzynski G, Hamrud M, Smolarkiewicz P. A new grid for the IFS. ECMWF newsletter. 2016;146(23-28):321. doi: 10.21957/zwdu9u5i
Gorski2004Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976
FFTDepending on the implementation of the Fast Fourier Transform (Cooley-Tukey algorithm, or or the Bluestein algorithm) easily Fourier-transformable can mean different things: Vectors of the length $n$ that is a power of two, i.e. $n = 2^i$ is certainly easily Fourier-transformable, but for most FFT implementations so are $n = 2^i3^j5^k$ with $i,j,k$ some positive integers. In fact, FFTW uses $O(n \log n)$ algorithms even for prime sizes.
SpeedyTransforms is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it) and can also be used without running simulations. It is just not put into its own respective repository.
S = SpectralTransform( alms::AbstractMatrix{Complex{NF}};
+ recompute_legendre::Bool=true,
+ Grid::Type{<:AbstractGrid}=DEFAULT_GRID)
Generator function for a SpectralTransform struct based on the size of the spectral coefficients alms and the grid Grid. Recomputes the Legendre polynomials by default.
Generator function for a SpectralTransform struct. With NF the number format, Grid the grid type <:AbstractGrid and spectral truncation trunc this function sets up necessary constants for the spetral transform. Also plans the Fourier transforms, retrieves the colatitudes, and preallocates the Legendre polynomials (if recompute_legendre == false) and quadrature weights.
S = SpectralTransform( map::AbstractGrid;
+ recompute_legendre::Bool=true)
Generator function for a SpectralTransform struct based on the size and grid type of gridded field map. Recomputes the Legendre polynomials by default.
Get U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.
Get U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.
Generic divergence function of vector u,v that writes into the output into div. Generic as it uses the kernel kernel such that curl, div, add or flipsign options are provided through kernel, but otherwise a single function is used.
Curl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.
Divergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.
get_recursion_factors( ::Type{NF}, # number format NF
+ lmax::Int, # max degree l of spherical harmonics (0-based here)
+ mmax::Int # max order m of spherical harmonics
+ ) where {NF<:AbstractFloat}
Returns a matrix of recursion factors ϵ up to degree lmax and order mmax of number format NF.
Spectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.
m = roundup_fft(n::Int;
+ small_primes::Vector{Int}=[2,3,5])
Returns an integer m >= n with only small prime factors 2, 3 (default, others can be specified with the keyword argument small_primes) to obtain an efficiently fourier-transformable number of longitudes, m = 2^i * 3^j * 5^k >= n, with i,j,k >=0.
Spectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Returns a spectral coefficient matrix alms_interp that is alms padded with zeros to interpolate in spectral space. If trunc is smaller or equal to the implicit truncation in alms obtained from its size than spectral_truncation is automatically called instead, returning alms_trunc, a coefficient matrix that is smaller than alms, implicitly setting higher degrees and orders to zero.
Smooth the spectral field A following A = (1-(1-c)∇²ⁿ) with power n of a normalised Laplacian so that the highest degree lmax is dampened by multiplication with c. Anti-diffusion for c>1.
Smooth the spectral field A following A_smooth = (1-c*∇²ⁿ)A with power n of a normalised Laplacian so that the highest degree lmax is dampened by multiplication with c. Anti-diffusion for c<0.
Create a spectral transform struct S2 similar to the input S, but for the corresponding full grid of the grid in S. The FFT is replanned and lon_offsets are set to 1 (i.e. no rotation). Solid angles for the Legendre transform are recomputed, but all other arrays fields for S, S2 point to the same place in memory, e.g. the Legendre polynomials aren't recomputed or stored twice.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.
Truncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.
Truncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.
Returns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.
Recursion factors ϵ as a function of degree l and order m (0-based) of the spherical harmonics. ϵ(l,m) = sqrt((l^2-m^2)/(4*l^2-1)) with default number format Float64.
Recursion factors ϵ as a function of degree l and order m (0-based) of the spherical harmonics. ϵ(l,m) = sqrt((l^2-m^2)/(4*l^2-1)) and then converted to number format NF.
Laplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.
Keyword arguments
add=true adds the ∇²(alms) to the output
flipsign=true computes -∇²(alms) instead
inverse=true computes ∇⁻²(alms) instead
Default is add=false, flipsign=false, inverse=false. These options can be combined.
The barotropic vorticity model describes the evolution of a 2D non-divergent flow with velocity components $\mathbf{u} = (u,v)$ through self-advection, forces and dissipation. Due to the non-divergent nature of the flow, it can be described by (the vertical component) of the relative vorticity $\zeta = \nabla \times \mathbf{u}$.
The dynamical core presented here to solve the barotropic vorticity equations largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2].
The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity $\zeta$ with advection, Coriolis force, forcing and diffusion in a single global layer on the sphere.
We denote time$t$, velocity vector $\mathbf{u} = (u, v)$, Coriolis parameter $f$, and hyperdiffusion $(-1)^{n+1} \nu \nabla^{2n} \zeta$ ($n$ is the hyperdiffusion order, see Horizontal diffusion). We also include a forcing vector $\mathbf{F} = (F_u,F_v)$ which acts on the zonal velocity $u$ and the meridional velocity $v$ and hence its curl $\nabla \times \mathbf{F}$ is a tendency for relative vorticity $\zeta$.
Starting with some relative vorticity $\zeta$, the Laplacian is inverted to obtain the stream function $\Psi$
\[\Psi = \nabla^{-2}\zeta\]
The zonal velocity $u$ and meridional velocity $v$ are then the (negative) meridional gradient and zonal gradient of $\Psi$
which is described in Derivatives in spherical coordinates. Using $u$ and $v$ we can then advect the absolute vorticity $\zeta + f$. In order to avoid to calculate both the curl and the divergence of a flux we rewrite the barotropic vorticity equation as
with $\mathbf{u}_\perp = (v,-u)$ the rotated velocity vector, because $-\nabla\cdot\mathbf{u} = \nabla \times \mathbf{u}_\perp$. This is the form that is solved in the BarotropicModel, as outlined in the following section.
In SpeedyWeather.jl we use hyerdiffusion through an $n$-th power Laplacian $(-1)^{n+1}\nabla^{2n}$ (hyper when $n>1$) which can be implemented as a multiplication of the spectral coefficients $\Psi_{lm}$ with $(-l(l+1))^nR^{-2n}$ (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the $(-l(l+1))^nR^{-2n}$ can be precomputed. Note the sign change $(-1)^{n+1}$ here is such that the dissipative nature of the diffusion operator is retained for $n$ odd and even.
In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step $\Delta t$ of variable $\zeta$ to obtain from time steps $i-1$ and $i$, the next time step $i+1$
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t d\zeta,\]
with $d\zeta$ being some tendency evaluated from $\zeta_i$. Now we want to add a diffusion term $(-1)^{n+1}\nu \nabla^{2n}\zeta$ with coefficient $\nu$, which however, is implicitly calculated from $\zeta_{i+1}$, then
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t (d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i+1})\]
As the application of $(-1)^{n+1}\nu\nabla^{2n}$ is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to
\[\zeta_{i+1} = \frac{\zeta_{i-1} + 2\Delta t d\zeta}{1 - 2\Delta (-1)^{n+1}\nu\nabla^{2n}},\]
and expand the numerator to
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t \frac{d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t (-1)^{n+1}\nu \nabla^{2n}},\]
Hence the diffusion can be applied implicitly by updating the tendency $d\zeta$ as
\[d\zeta \to \frac{d\zeta + (-1)^{n+1}\nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t \nu \nabla^{2n}}\]
which only depends on $\zeta_{i-1}$. Now let $D_\text{explicit} = (-1)^{n+1}\nu\nabla^{2n}$ be the explicit part and $D_\text{implicit} = 1 - (-1)^{n+1} 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are $D_\text{implicit} = 1 - 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic $l,m$ we do
Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power $n$.
In physics, the Laplace operator $\nabla^2$ is often used to represent diffusion due to viscosity in a fluid or diffusion that needs to be added to retain numerical stability. In both cases, the coefficient is $\nu$ of units $\text{m}^2\text{s}^{-1}$ and the full operator reads as $\nu \nabla^2$ with units $(\text{m}^2\text{s}^{-1})(\text{m}^{-2}) = \text{s}^{-1}$. This motivates us to normalize the Laplace operator by a constant of units $\text{m}^{-2}$ and the coefficient by its inverse such that it becomes a damping timescale of unit $\text{s}^{-1}$. Given the application in spectral space we decide to normalize by the largest eigenvalue $-l_\text{max}(l_\text{max}+1)$ such that all entries in the discrete spectral Laplace operator are in $[0,1]$. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized coefficient $\nu^* = l_\text{max}(l_\text{max}+1)\nu$ (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have
and the implicit part is accordingly $D^\text{implicit,n}_{l,m} = 1 - 2\Delta t D^\text{explicit,n}_{l,m}$. Note that the diffusion time scale $\nu^*$ is then also scaled by the radius, see next section.
Similar to a non-dimensionalization of the equations, SpeedyWeather.jl scales the barotropic vorticity equation with $R^2$ to obtain normalized gradient operators as follows. A scaling for vorticity $\zeta$ and stream function $\Psi$ is used that is
This is also convenient as vorticity is often $10^{-5}\text{ s}^{-1}$ in the atmosphere, but the streamfunction more like $10^5\text{ m}^2\text{ s}^{-1}$ and so this scaling brings both closer to 1 with a typical radius of the Earth of 6371km. The inversion of the Laplacians in order to obtain $\Psi$ from $\zeta$ therefore becomes
\[\tilde{\zeta} = \tilde{\nabla}^2 \tilde{\Psi}\]
where the dimensionless gradients simply omit the scaling with $1/R$, $\tilde{\nabla} = R\nabla$. The Barotropic vorticity equation scaled with $R^2$ is
$\mathbf{u} = (u,v)$, the velocity vector (no scaling applied)
$\tilde{f} = fR$, the scaled Coriolis parameter $f$
$\tilde{\mathbf{F}} = R\mathbf{F}$, the scaled forcing vector $\mathbf{F}$
$\tilde{\nu} = \nu^* R$, the scaled diffusion coefficient $\nu^*$, which itself is normalized to a damping time scale, see Normalization of diffusion.
So scaling with the radius squared means we can use dimensionless operators, however, this comes at the cost of needing to deal with both a time step in seconds as well as a scaled time step in seconds per meter, which can be confusing. Furthermore, some constants like Coriolis or the diffusion coefficient need to be scaled too during initialisation, which may be confusing too because values are not what users expect them to be. SpeedyWeather.jl follows the logic that the scaling to the prognostic variables is only applied just before the time integration and variables are unscaled for output and after the time integration finished. That way, the scaling is hidden as much as possible from the user. In hopefully many other cases it is clearly denoted that a variable or constant is scaled.
meaning we step from the previous time step $i-1$, leapfrogging over the current time step$i$ to the next time step $i+1$ by evaluating the tendencies on the right-hand side $RHS$ at the current time step $i$. The time stepping is done in spectral space. Once the right-hand side $RHS$ is evaluated, leapfrogging is a linear operation, meaning that its simply applied to every spectral coefficient $\zeta_{lm}$ as one would evaluate it on every grid point in grid-point models.
For the Leapfrog time integration two time steps of the prognostic variables have to be stored, $i-1$ and $i$. Time step $i$ is used to evaluate the tendencies which are then added to $i-1$ in a step that also swaps the indices for the next time step $i \to i-1$ and $i+1 \to i$, so that no additional memory than two time steps have to be stored at the same time.
The Leapfrog time integration has to be initialised with an Euler forward step in order to have a second time step $i+1$ available when starting from $i$ to actually leapfrog over. SpeedyWeather.jl therefore does two initial time steps that are different from the leapfrog time steps that follow and that have been described above.
an Euler forward step with $\Delta t/2$, then
one leapfrog time step with $\Delta t$, then
leapfrog with $2 \Delta t$ till the end
This is particularly done in a way that after 2. we have $t=0$ at $i-1$ and $t=\Delta t$ at $i$ available so that 3. can start the leapfrogging without any offset from the intuitive spacing $0,\Delta t, 2\Delta t, 3\Delta t,...$. The following schematic can be useful
time at step $i-1$
time at step $i$
time step at $i+1$
Initial conditions
$t = 0$
1: Euler
(T) $\quad t = 0$
$t=\Delta t/2$
2: Leapfrog with $\Delta t$
$t = 0$
(T) $\quad t = \Delta t/2$
$t = \Delta t$
3 to $n$: Leapfrog with $2\Delta t$
$t-\Delta t$
(T) $\qquad \quad \quad t$
$t+\Delta t$
The time step that is used to evaluate the tendencies is denoted with (T). It is always the time step furthest in time that is available.
The standard leapfrog time integration is often combined with a Robert-Asselin filter[Robert66][Asselin72] to dampen a computational mode. The idea is to start with a standard leapfrog step to obtain the next time step $i+1$ but then to correct the current time step $i$ by applying a filter which dampens the computational mode. The filter looks like a discrete Laplacian in time with a $(1, -2, 1)$ stencil, and so, maybe unsurprisingly, is efficient to filter out a "grid-scale oscillation" in time, aka the computational mode. Let $v$ be the unfiltered variable and $u$ be the filtered variable, $F$ the right-hand side tendency, then the standard leapfrog step is
\[v_{i+1} = u_{i-1} + 2\Delta tF(v_i)\]
Meaning we start with a filtered variable $u$ at the previous time step $i-1$, evaluate the tendency $F(v_i)$ based on the current time step $i$ to obtain an unfiltered next time step $v_{i+1}$. We then filter the current time step $i$ (which will become $i-1$ on the next iteration)
by adding a discrete Laplacian with coefficient $\tfrac{\nu}{2}$ to it, evaluated from the available filtered and unfiltered time steps centred around $i$: $v_{i-1}$ is not available anymore because it was overwritten by the filtering at the previous iteration, $u_i, u_{i+1}$ are not filtered yet when applying the Laplacian. The filter parameter $\nu$ is typically chosen between 0.01-0.2, with stronger filtering for higher values.
Williams[Williams2009] then proposed an additional filter step to regain accuracy that is otherwise lost with a strong Robert-Asselin filter[Amezcua2011][Williams2011]. Now let $w$ be unfiltered, $v$ be once filtered, and $u$ twice filtered, then
with the Williams filter parameter $\alpha \in [0.5,1]$. For $\alpha=1$ we're back with the Robert-Asselin filter (the first two lines).
The Laplacian in the parentheses is often called a displacement, meaning that the filtered value is displaced (or corrected) in the direction of the two surrounding time steps. The Williams filter now also applies the same displacement, but in the opposite direction to the next time step $i+1$ as a correction step (line 3 above) for a once-filtered value $v_{i+1}$ which will then be twice-filtered by the Robert-Asselin filter on the next iteration. For more details see the referenced publications.
The initial Euler step (see Time integration, Table) is not filtered. Both the the Robert-Asselin and Williams filter are then switched on for all following leapfrog time steps.
Robert66Robert, André. “The Integration of a Low Order Spectral Form of the Primitive Meteorological Equations.” Journal of the Meteorological Society of Japan 44 (1966): 237-245.
Williams2009Williams, P. D., 2009: A Proposed Modification to the Robert–Asselin Time Filter. Mon. Wea. Rev., 137, 2538–2546, 10.1175/2009MWR2724.1.
Amezcua2011Amezcua, J., E. Kalnay, and P. D. Williams, 2011: The Effects of the RAW Filter on the Climatology and Forecast Skill of the SPEEDY Model. Mon. Wea. Rev., 139, 608–619, doi:10.1175/2010MWR3530.1.
Williams2011Williams, P. D., 2011: The RAW Filter: An Improvement to the Robert–Asselin Filter in Semi-Implicit Integrations. Mon. Wea. Rev., 139, 1996–2007, doi:10.1175/2010MWR3601.1.
Settings
This document was generated with Documenter.jl version 0.27.25 on Friday 4 August 2023. Using Julia version 1.8.5.
diff --git a/previews/PR362/conventions/index.html b/previews/PR362/conventions/index.html
new file mode 100644
index 000000000..d8fb40984
--- /dev/null
+++ b/previews/PR362/conventions/index.html
@@ -0,0 +1,12 @@
+
+Style and convention guide · SpeedyWeather.jl
The prognostic variables in spectral space are called
vor # Vorticity of horizontal wind field
+ div # Divergence of horizontal wind field
+ temp # Absolute temperature [K]
+ pres_surf # Logarithm of surface pressure [log(Pa)]
+ humid # Specific humidity [g/kg]
their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are
We follow Julia's style guide and highlight here some important aspects of it.
Bang (!) convention. A function func does not change its input arguments, however, func! does.
Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.
Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup
but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.
Settings
This document was generated with Documenter.jl version 0.27.25 on Friday 4 August 2023. Using Julia version 1.8.5.
This document was generated with Documenter.jl version 0.27.25 on Friday 4 August 2023. Using Julia version 1.8.5.
diff --git a/previews/PR362/functions/index.html b/previews/PR362/functions/index.html
new file mode 100644
index 000000000..d07e05193
--- /dev/null
+++ b/previews/PR362/functions/index.html
@@ -0,0 +1,634 @@
+
+Function and type index · SpeedyWeather.jl
The BarotropicModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.
spectral_grid::SpectralGrid: dictates resolution for many other components
planet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics
atmosphere::SpeedyWeather.AbstractAtmosphere
forcing::SpeedyWeather.AbstractForcing{NF} where NF<:AbstractFloat
Mutable struct that contains all prognostic (copies thereof) and diagnostic variables in a single column needed to evaluate the physical parametrizations. For now the struct is mutable as we will reuse the struct to iterate over horizontal grid points. Every column vector has nlev entries, from [1] at the top to [end] at the lowermost model level at the planetary boundary layer.
nlev::Int64
nband::Int64
n_stratosphere_levels::Int64
jring::Int64
lond::AbstractFloat
latd::AbstractFloat
u::Vector{NF} where NF<:AbstractFloat
v::Vector{NF} where NF<:AbstractFloat
temp::Vector{NF} where NF<:AbstractFloat
humid::Vector{NF} where NF<:AbstractFloat
ln_pres::Vector{NF} where NF<:AbstractFloat
pres::Vector{NF} where NF<:AbstractFloat
u_tend::Vector{NF} where NF<:AbstractFloat
v_tend::Vector{NF} where NF<:AbstractFloat
temp_tend::Vector{NF} where NF<:AbstractFloat
humid_tend::Vector{NF} where NF<:AbstractFloat
geopot::Vector{NF} where NF<:AbstractFloat
flux_u_upward::Vector{NF} where NF<:AbstractFloat
flux_u_downward::Vector{NF} where NF<:AbstractFloat
flux_v_upward::Vector{NF} where NF<:AbstractFloat
flux_v_downward::Vector{NF} where NF<:AbstractFloat
flux_temp_upward::Vector{NF} where NF<:AbstractFloat
flux_temp_downward::Vector{NF} where NF<:AbstractFloat
flux_humid_upward::Vector{NF} where NF<:AbstractFloat
flux_humid_downward::Vector{NF} where NF<:AbstractFloat
sat_humid::Vector{NF} where NF<:AbstractFloat
sat_vap_pres::Vector{NF} where NF<:AbstractFloat
dry_static_energy::Vector{NF} where NF<:AbstractFloat
moist_static_energy::Vector{NF} where NF<:AbstractFloat
humid_half::Vector{NF} where NF<:AbstractFloat
sat_humid_half::Vector{NF} where NF<:AbstractFloat
sat_moist_static_energy::Vector{NF} where NF<:AbstractFloat
dry_static_energy_half::Vector{NF} where NF<:AbstractFloat
sat_moist_static_energy_half::Vector{NF} where NF<:AbstractFloat
conditional_instability::Bool
activate_convection::Bool
cloud_top::Int64
excess_humidity::AbstractFloat
cloud_base_mass_flux::AbstractFloat
precip_convection::AbstractFloat
net_flux_humid::Vector{NF} where NF<:AbstractFloat
net_flux_dry_static_energy::Vector{NF} where NF<:AbstractFloat
entrainment_profile::Vector{NF} where NF<:AbstractFloat
Create a struct Earth<:AbstractPlanet, with the following physical/orbital characteristics. Note that radius is not part of it as this should be chosen in SpectralGrid. Keyword arguments are
rotation::Float64: angular frequency of Earth's rotation [rad/s]
Create a struct EarthAtmosphere<:AbstractPlanet, with the following physical/chemical characteristics. Note that radius is not part of it as this should be chosen in SpectralGrid. Keyword arguments are
mol_mass_dry_air::Float64: molar mass of dry air [g/mol]
mol_mass_vapour::Float64: molar mass of water vapour [g/mol]
cₚ::Float64: specific heat at constant pressure [J/K/kg]
R_gas::Float64: universal gas constant [J/K/mol]
R_dry::Float64: specific gas constant for dry air [J/kg/K]
R_vapour::Float64: specific gas constant for water vapour [J/kg/K]
water_density::Float64: water density [kg/m³]
latent_heat_condensation::Float64: latent heat of condensation [J/g] for consistency with specific humidity [g/Kg], also called alhc
latent_heat_sublimation::Float64: latent heat of sublimation [J/g], also called alhs
Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF.
Construct Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. Pass on SpectralGrid to calculate the following fields
spectral_grid::SpectralGrid: SpectralGrid that defines spectral and grid resolution
Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid of the dynamical core
nlat_half::Int64: resolution parameter nlat_half of Grid, # of latitudes on one hemisphere (incl Equator)
nlon_max::Int64: maximum number of longitudes (at/around Equator)
nlon::Int64: =nlon_max, same (used for compatibility), TODO: still needed?
nlat::Int64: number of latitude rings
nlev::Int64: number of vertical levels
npoints::Int64: total number of grid points
radius::AbstractFloat: Planet's radius [m]
latd::Vector{Float64}: array of latitudes in degrees (90˚...-90˚)
lond::Vector{Float64}: array of longitudes in degrees (0...360˚), empty for non-full grids
londs::Vector{NF} where NF<:AbstractFloat: longitude (-180˚...180˚) for each grid point in ring order
latds::Vector{NF} where NF<:AbstractFloat: latitude (-90˚...˚90) for each grid point in ring order
sinlat::Vector{NF} where NF<:AbstractFloat: sin of latitudes
coslat::Vector{NF} where NF<:AbstractFloat: cos of latitudes
coslat⁻¹::Vector{NF} where NF<:AbstractFloat: = 1/cos(lat)
coslat²::Vector{NF} where NF<:AbstractFloat: = cos²(lat)
coslat⁻²::Vector{NF} where NF<:AbstractFloat: # = 1/cos²(lat)
σ_levels_half::Vector{NF} where NF<:AbstractFloat: σ at half levels, σ_k+1/2
σ_levels_full::Vector{NF} where NF<:AbstractFloat: σ at full levels, σₖ
σ_levels_thick::Vector{NF} where NF<:AbstractFloat: σ level thicknesses, σₖ₊₁ - σₖ
ln_σ_levels_full::Vector{NF} where NF<:AbstractFloat: log of σ at full levels, include surface (σ=1) as last element
Struct for horizontal hyper diffusion of vor, div, temp; implicitly in spectral space with a power of the Laplacian (default=4) and the strength controlled by time_scale. Options exist to scale the diffusion by resolution, and adaptive depending on the current vorticity maximum to increase diffusion in active layers. Furthermore the power can be decreased above the tapering_σ to power_stratosphere (default 2). For Barotropic, ShallowWater, the default non-adaptive constant-time scale hyper diffusion is used. Options are
trunc::Int64: spectral resolution
nlev::Int64: number of vertical levels
power::Float64: power of Laplacian
time_scale::Float64: diffusion time scales [hrs]
resolution_scaling::Float64: stronger diffusion with resolution? 0: constant with trunc, 1: (inverse) linear with trunc, etc
power_stratosphere::Float64: different power for tropopause/stratosphere
tapering_σ::Float64: linearly scale towards power_stratosphere above this σ
adaptive::Bool: adaptive = higher diffusion for layers with higher vorticity levels.
vor_max::Float64: above this (absolute) vorticity level [1/s], diffusion is increased
adaptive_strength::Float64: increase strength above vor_max by this factor times max(abs(vor))/vor_max
Struct that holds various precomputed arrays for the semi-implicit correction to prevent gravity waves from amplifying in the primitive equation model.
NetCDF output writer. Contains all output options and auxiliary fields for output interpolation. To be initialised with OutputWriter(::SpectralGrid,::Type{<:ModelSetup},kwargs...) to pass on the resolution information and the model type which chooses which variables to output. Options include
spectral_grid::SpectralGrid
output::Bool
path::String: [OPTION] path to output folder, run_???? will be created within
id::String: [OPTION] run identification number/string
run_path::String
filename::String: [OPTION] name of the output netcdf file
write_restart::Bool: [OPTION] also write restart file if output==true?
pkg_version::VersionNumber
startdate::Dates.DateTime
output_dt::Float64: [OPTION] output frequency, time step [hrs]
output_dt_sec::Int64: actual output time step [sec]
output_vars::Vector{Symbol}: [OPTION] which variables to output, u, v, vor, div, pres, temp, humid
missing_value::Union{Float32, Float64}: [OPTION] missing value to be used in netcdf output
compression_level::Int64: [OPTION] lossless compression level; 1=low but fast, 9=high but slow
keepbits::SpeedyWeather.Keepbits: [OPTION] mantissa bits to keep for every variable
The PrimitiveDryModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.
spectral_grid::SpectralGrid: dictates resolution for many other components
planet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics
The PrimitiveDryModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.
spectral_grid::SpectralGrid: dictates resolution for many other components
planet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics
The ShallowWaterModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.
spectral_grid::SpectralGrid: dictates resolution for many other components
planet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics
atmosphere::SpeedyWeather.AbstractAtmosphere
forcing::SpeedyWeather.AbstractForcing{NF} where NF<:AbstractFloat
Restart from a previous SpeedyWeather.jl simulation via the restart file restart.jld2 Applies interpolation in the horizontal but not in the vertical. restart.jld2 is identified by
path::String: path for restart file
id::Union{Int64, String}: run_id of restart file in run_????/restart.jld2
Create a struct that contains all parameters for the Galewsky et al, 2004 zonal jet intitial conditions for the shallow water model. Default values as in Galewsky.
latitude::Float64: jet latitude [˚N]
width::Float64: jet width [˚], default ≈ 19.29˚
umax::Float64: jet maximum velocity [m/s]
perturb_lat::Float64: perturbation latitude [˚N], position in jet by default
Create a struct that contains all parameters for the Jablonowski and Williamson, 2006 intitial conditions for the primitive equation model. Default values as in Jablonowski.
η₀::Float64: conversion from σ to Jablonowski's ηᵥ-coordinates
u₀::Float64: max amplitude of zonal wind [m/s]
perturb_lat::Float64: perturbation centred at [˚N]
perturb_lon::Float64: perturbation centred at [˚E]
perturb_uₚ::Float64: perturbation strength [m/s]
perturb_radius::Float64: radius of Gaussian perturbation in units of Earth's radius [1]
ΔT::Float64: temperature difference used for stratospheric lapse rate [K], Jablonowski uses ΔT = 4.8e5 [K]
Tmin::Float64: minimum temperature [K] of profile
pressure_on_orography::Bool: initialize pressure given the atmosphere.lapse_rate on orography?
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for primitive equation models. Updates grid vorticity, grid divergence, grid temperature, pressure (pres_grid) and the velocities u,v.
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities u,v.
Vertical sigma coordinates defined by their nlev+1 half levels σ_levels_half. Sigma coordinates are fraction of surface pressure (p/p0) and are sorted from top (stratosphere) to bottom (surface). The first half level is at 0 the last at 1. Evaluate a generalised logistic function with coefficients in P for the distribution of values in between. Default coefficients follow the L31 configuration historically used at ECMWF.
Performs the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.
Calculate the geopotential based on temp in a single column. This exclues the surface geopotential that would need to be added to the returned vector. Function not used in the dynamical core but for post-processing and analysis.
Update C::ColumnVariables by copying the prognostic variables from D::DiagnosticVariables at gridpoint index ij. Provide G::Geometry for coordinate information.
Checks existing run_???? folders in path to determine a 4-digit id number by counting up. E.g. if folder run_0001 exists it will return the string "0002". Does not create a folder for the returned run id.
Calculate thermodynamic quantities like saturation vapour pressure, saturation specific humidity, dry static energy, moist static energy and saturation moist static energy from the prognostic column variables.
Apply horizontal diffusion applied to vorticity, diffusion and temperature in the PrimitiveEquation models. Uses the constant diffusion for temperature but possibly adaptive diffusion for vorticity and divergence.
Apply horizontal diffusion to a 2D field A in spectral space by updating its tendency tendency with an implicitly calculated diffusion term. The implicit diffusion of the next time step is split into an explicit part ∇²ⁿ_expl and an implicit part ∇²ⁿ_impl, such that both can be calculated in a single forward step by using A as well as its tendency tendency.
implicit_correction!(
+ diagn::SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},
+ progn::SpeedyWeather.PrognosticLayerTimesteps{NF},
+ diagn_surface::SpeedyWeather.SurfaceVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},
+ progn_surface::SpeedyWeather.PrognosticSurfaceTimesteps{NF},
+ implicit::SpeedyWeather.ImplicitShallowWater
+)
+
Apply correction to the tendencies in diagn to prevent the gravity waves from amplifying. The correction is implicitly evaluated using the parameter implicit.α to switch between forward, centered implicit or backward evaluation of the gravity wave terms.
Precomputes the hyper diffusion terms in scheme for layer k based on the model time step in L, the vertical level sigma level in G, and the current (absolute) vorticity maximum level vor_max
initialize the JablonowskiRelaxation temperature relaxation by precomputing terms for the equilibrium temperature Teq and the frequency (strength of relaxation).
Calls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.
Calls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.
Calls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.
Creates a netcdf file on disk and the corresponding netcdf_file object preallocated with output variables and dimensions. write_output! then writes consecuitive time steps into this file.
Large-scale condensation for a column by relaxation back to a reference relative humidity if larger than that. Calculates the tendencies for specific humidity and temperature and integrates the large-scale precipitation vertically for output.
So that the second term inside the Laplace operator can be added to the geopotential. Rd is the gas constant, Tᵥ the virtual temperature and Tᵥ' its anomaly wrt to the average or reference temperature Tₖ, lnpₛ is the logarithm of surface pressure.
Linear virtual temperature for model::PrimitiveDry: Just copy over arrays from temp to temp_virt at timestep lf in spectral space as humidity is zero in this model.
with the static energy SE, the latent heat of condensation Lc, the geopotential Φ. As well as the saturation moist static energy which replaces Q with Q_sat
Compute tendencies for u,v,temp,humid from physical parametrizations. Extract for each vertical atmospheric column the prognostic variables (stored in diagn as they are grid-point transformed), loop over all grid-points, compute all parametrizations on a single-column basis, then write the tendencies back into a horizontal field of tendencies.
Returns Dates.CompoundPeriod rounding to either (days, hours), (hours, minutes), (minutes, seconds), or seconds with 1 decimal place accuracy for >10s and two for less. E.g.
Compute (1) the saturation vapour pressure as a function of temperature using the August-Roche-Magnus formula,
eᵢ(T) = e₀ * exp(Cᵢ * (T - T₀) / (T - Tᵢ)),
where T is in Kelvin and i = 1,2 for saturation with respect to water and ice, respectively. And (2) the saturation specific humidity according to the formula,
0.622 * e / (p - (1 - 0.622) * e),
where e is the saturation vapour pressure, p is the pressure, and 0.622 is the ratio of the molecular weight of water to dry air.
Sets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.
set_var!(progn::PrognosticVariables{NF},
+ varname::Symbol,
+ var::Vector{<:LowerTriangularMatrix};
+ lf::Integer=1) where NF
Sets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in spectral space.
Sets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.
set_var!(progn::PrognosticVariables{NF},
+ varname::Symbol,
+ var::Vector{<:AbstractGrid};
+ lf::Integer=1) where NF
Sets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.
Computes the tendency of the logarithm of surface pressure as
-(ū*px + v̄*py) - D̄
with ū,v̄ being the vertically averaged velocities; px, py the gradients of the logarithm of surface pressure ln(p_s) and D̄ the vertically averaged divergence.
Calculate ∇ln(p_s) in spectral space, convert to grid.
Multiply ū,v̄ with ∇ln(p_s) in grid-point space, convert to spectral.
D̄ is subtracted in spectral space.
Set tendency of the l=m=0 mode to 0 for better mass conservation.
+= because the tendencies already contain parameterizations and vertical advection. T' is the anomaly with respect to the reference/average temperature. Tᵥ is the virtual temperature used in the adiabatic term κTᵥ*Dlnp/Dt.
Virtual temperature in grid-point space: For the PrimitiveDry temperature and virtual temperature are the same (humidity=0). Just copy over the arrays.
Tendencies for vorticity and divergence. Excluding Bernoulli potential with geopotential and linear pressure gradient inside the Laplace operator, which are added later in spectral space.
+= because the tendencies already contain the parameterizations and vertical advection. f is coriolis, ζ relative vorticity, R the gas constant Tᵥ' the virtual temperature anomaly, ∇lnp the gradient of surface pressure and _x and _y its zonal/meridional components. The tendencies are then curled/dived to get the tendencies for vorticity/divergence in spectral space
with Fᵤ,Fᵥ from u_tend_grid/v_tend_grid that are assumed to be alread set in forcing!. Set div=false for the BarotropicModel which doesn't require the divergence tendency.
Write the parametrization tendencies from C::ColumnVariables into the horizontal fields of tendencies stored in D::DiagnosticVariables at gridpoint index ij.
Writes the variables from diagn of time step i at time time into outputter.netcdf_file. Simply escapes for no netcdf output of if output shouldn't be written on this time step. Interpolates onto output grid and resolution as specified in outputter, converts to output number format, truncates the mantissa for higher compression and applies lossless compression.
A restart file restart.jld2 with the prognostic variables is written to the output folder (or current path) that can be used to restart the model. restart.jld2 will then be used as initial conditions. The prognostic variables are bitrounded for compression and the 2nd leapfrog time step is discarded. Variables in restart file are unscaled.
The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and but let's start but how they can be used
The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object which defines the resolution in spectral and in grid-point space. The generator SpectralGrid() can take as a keyword argument Grid which can be any of the grids described below. The resolution of the grid, however, is not directly chosen, but determined from the spectral resolution trunc and the dealiasing factor. More in Matching spectral and grid resolution.
RingGrids is a module too!
While RingGrids is the underlying module that SpeedyWeather.jl uses for data structs on the sphere, the module can also be used independently of SpeedyWeather, for example to interpolate between data on different grids. See RingGrids
SpeedyWeather.jl's spectral transform supports all ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on these rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.
Is the FullClenshawGrid a longitude-latitude grid?
Short answer: Yes. The FullClenshawGrid is a specific longitude-latitude grid with equi-angle spacing. The most common grids for geoscientific data use regular spacings for 0-360˚E in longitude and 90˚N-90˚S. The FullClenshawGrid does that too, but it does not have a point on the North or South pole, and the central latitude ring sits exactly on the Equator. We name it Clenshaw following the Clenshaw-Curtis quadrature that is used in the Legendre transfrom in the same way as Gaussian refers to the Gaussian quadrature.
All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).
Currently the following full grids <: AbstractFullGrid are implemented
FullGaussianGrid, a full grid with Gaussian latitudes
FullClenshawGrid, a full grid with equi-angle latitudes
and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are
OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron
OctahedralClenshawGrid, similar but based on equi-angle latitudes
HEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces
OctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.
An overview of these grids is visualised here
Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.
All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half.
HEALPix grids do not use Nside as resolution parameter
The original formulation for HEALPix grids use $N_{side}$, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use $N_{side}$ for the documentation or within functions though.
A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation $T$ with a grid resolution $N$ (=nlat_half) such that $T + 1 = N$. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at $l_{max}=31$ in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid. In SpeedyWeather.jl the choice of the order of truncation is controlled with the dealiasing parameter in the SpectralGrid construction.
Let J be the total number of rings. Then we have
$T \approx J$ for linear truncation, i.e. dealiasing = 1
$\frac{3}{2}T \approx J$ for quadratic truncation, i.e. dealiasing = 2
$2T \approx J$ for cubic truncation, , i.e. dealiasing = 3
and in general $\frac{m+1}{2}T \approx J$ for m-th order truncation. So the higher the truncation order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. A quick overview of how the grid resolution changes when dealiasing is passed onto SpectralGrid on the FullGaussianGrid
trunc
dealiasing
FullGaussianGrid size
31
1
64x32
31
2
96x48
31
3
128x64
42
1
96x48
42
2
128x64
42
3
192x96
...
...
...
You will obtain this information every time you create a SpectralGrid(;Grid,trunc,dealiasing).
Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are $N_\varphi$ basepixels in zonal direction and $N_\theta$ basepixels in meridional direction. For $N_\varphi = 4$ and $N_\theta = 3$ we obtain the classical HEALPix grid with $N_\varphi N_\theta = 12$ basepixels shown above in Implemented grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always $2N$, so 32 at $N=16$) and there are polar caps above and below the equatorial zone with the border at $\cos(\theta) = 2/3$ ($\theta$ in colatitudes).
Following Górski, 2004[1], the $z=cos(\theta)$ colatitude of the $j$-th ring in the north polar cap, $j=1,...,N_{side}$ with $2N_{side} = N$ is
\[z = 1 - \frac{j^2}{3N_{side}^2}\]
and on that ring, the longitude $\phi$ of the $i$-th point ($i$ is the in-ring-index) is at
\[\phi = \frac{\pi}{2j}(i-\tfrac{1}{2})\]
The in-ring index $i$ goes from $i=1,...,4$ for the first (i.e. northern-most) ring, $i=1,...,8$ for the second ring and $i = 1,...,4j$ for the $j$-th ring in the northern polar cap.
In the north equatorial belt $j=N_{side},...,2N_{side}$ this changes to
\[z = \frac{4}{3} - \frac{2j}{3N_{side}}\]
and the longitudes change to ($i$ is always $i = 1,...,4N_{side}$ in the equatorial belt meaning the number of longitude points is constant here)
The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.
The cell boundaries are obtained by setting $i = k + 1/2$ or $i = k + 1/2 + j$ (half indices) into the equations above, such that $z(\phi,k)$, a function for the cosine of colatitude $z$ of index $k$ and the longitude $\phi$ is obtained. These are then (northern polar cap)
While the classic HEALPix grid is based on a dodecahedron, other choices for $N_\varphi$ and $N_\theta$ in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With $N_\varphi = 4$ and $N_\theta = 1$ we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, $2\pi$ around the Equator versus $\pi$ between the poles. $N_\varphi = 6, N_\theta = 2$ or $N_\varphi = 8, N_\theta = 3$ are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visualizations of these grids.
We call the $N_\varphi = 4, N_\theta = 1$ HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As $N_\theta = 1$ there is no equatorial belt which simplifies the grid. The latitude of the $j$-th isolatitude ring on the OctaHEALPixGrid is defined by
\[z = 1 - \frac{j^2}{N^2},\]
with $j=1,...,N$, and similarly for the southern hemisphere by symmetry. On this grid $N_{side} = N$ where $N$= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index $i = 1,...,4j$ are
\[\phi = \frac{\pi}{2j}(i - \tfrac{1}{2})\]
and again, the southern hemisphere grid points are obtained by symmetry.
The $3N_{side}^2$ in the denominator of the HEALPix grid came simply $N^2$ for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).
1Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976
Settings
This document was generated with Documenter.jl version 0.27.25 on Friday 4 August 2023. Using Julia version 1.8.5.
diff --git a/previews/PR362/how_to_run_speedy/index.html b/previews/PR362/how_to_run_speedy/index.html
new file mode 100644
index 000000000..15703a9e7
--- /dev/null
+++ b/previews/PR362/how_to_run_speedy/index.html
@@ -0,0 +1,57 @@
+
+How to run SpeedyWeather.jl · SpeedyWeather.jl
We want to use the barotropic model to simulate some free-decaying 2D turbulence on the sphere without rotation. We start by defining the SpectralGrid object. To have a resolution of about 100km, we choose a spectral resolution of T127 (see Available horizontal resolutions) and nlev=1 vertical levels. The SpectralGrid object will provide us with some more information
There are other options to create a planet but they are irrelevant for the barotropic vorticity equations. We also want to specify the initial conditions, randomly distributed vorticity is already defined
By default, the power of vorticity is spectrally distributed with $k^{-3}$, $k$ being the horizontal wavenumber, and the amplitude is $10^{-5}\text{ s}^{-1}$.
Now we want to construct a BarotropicModel with these
julia> model = BarotropicModel(;spectral_grid, initial_conditions, planet=still_earth);
The model contains all the parameters, but isn't initialized yet, which we can do with and then run it.
The run! command will always return the prognostic variables, which, by default, are plotted for surface relative vorticity with a unicode plot. The resolution of the plot is not necessarily representative but it lets us have a quick look at the result
Woohoo! I can see turbulence! You could pick up where this simulation stopped by simply doing run!(simulation,n_days=50) again. We didn't store any output, which you can do by run!(simulation,output=true), which will switch on NetCDF output with default settings. More options on output in NetCDF output.
As a second example, let's investigate the Galewsky et al.[1] test case for the shallow water equations with and without mountains. As the shallow water system has also only one level, we can reuse the SpectralGrid from Example 1.
Although the orography is zero, you have to pass on spectral_grid so that it can still initialize zero-arrays of the right size and element type. Awesome. This time the initial conditions should be set the the Galewsky et al.[1] zonal jet, which is already defined as
The jet sits at 45˚N with a maximum velocity of 80m/s and a perturbation as described in their paper. Now we construct a model, but this time a ShallowWaterModel
julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);
+julia> simulation = initialize!(model);
Oh yeah. That looks like the wobbly jet in their paper. Let's run it again for another 6 days but this time also store NetCDF output.
julia> run!(simulation,n_days=6,output=true)
+Weather is speedy: run 0002 100%|███████████████████████| Time: 0:00:12 (115.37 years/day)
The progress bar tells us that the simulation run got the identification "0002", meaning that data is stored in the folder /run_0002, so let's plot that data properly (and not just using UnicodePlots).
julia> using PyPlot, NCDatasets
+julia> ds = NCDataset("run_0002/output.nc");
+julia> ds["vor"]
+vor (384 × 192 × 1 × 25)
+ Datatype: Float32
+ Dimensions: lon × lat × lev × time
+ Attributes:
+ units = 1/s
+ missing_value = NaN
+ long_name = relative vorticity
+ _FillValue = NaN
Vorticity vor is stored as a 384x192x1x25 array, we may want to look at the first time step, which is the end of the previous simulation (time=6days) which we didn't store output for.
julia> vor = ds["vor"][:,:,1,1];
+julia> lat = ds["lat"][:];
+julia> lon = ds["lon"][:];
+julia> pcolormesh(lon,lat,vor')
Which looks like
You see that the unicode plot heavily coarse-grains the simulation, well it's unicode after all! And now the last time step, that means time=12days is
julia> vor = ds["vor"][:,:,1,25];
+julia> pcolormesh(lon,lat,vor')
The jet broke up into many small eddies, but the turbulence is still confined to the northern hemisphere, cool! How this may change when we add mountains (we had NoOrography above!), say Earth's orography, you may ask? Let's try it out! We create an EarthOrography struct like so
It will read the orography from file as shown, and there are some smoothing options too, but let's not change them. Same as before, create a model, initialize into a simulation, run. This time directly for 12 days so that we can compare with the last plot
julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);
+julia> simulation = initialize!(model);
+julia> run!(simulation,n_days=12,output=true)
+Weather is speedy: run 0003 100%|███████████████████████| Time: 0:00:35 (79.16 years/day)
This time the run got the id "0003", but otherwise we do as before.
Interesting! The initial conditions have zero velocity in the southern hemisphere, but still, one can see some imprint of the orography on vorticity. You can spot the coastline of Antarctica; the Andes and Greenland are somewhat visible too. Mountains also completely changed the flow after 12 days, probably not surprising!
[1] Galewsky, Scott, Polvani, 2004. An initial-value problem for testing numerical models of the global shallow-water equations, Tellus A. DOI: 10.3402/tellusa.v56i5.14436
Settings
This document was generated with Documenter.jl version 0.27.25 on Friday 4 August 2023. Using Julia version 1.8.5.
Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.
SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to simulate the general circulation of the atmosphere. The prognostic variables used are vorticity, divergence, temperature, surface pressure and specific humidity. Simple parameterizations represent various climate processes: Radiation, clouds, precipitation, surface fluxes, among others.
SpeedyWeather.jl defines
BarotropicModel for the 2D barotropic vorticity equation
ShallowWaterModel for the 2D shallow water equations
PrimitiveDryModel for the 3D primitive equations without humidity
PrimitiveWetModel for the 3D primitive equations with humidity
and solves these equations in spherical coordinates as described in this documentation.
MK received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022. Since 2023 this project is also funded by the National Science Foundation NSF.
Settings
This document was generated with Documenter.jl version 0.27.25 on Friday 4 August 2023. Using Julia version 1.8.5.
LowerTriangularMatrices is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.
This module defines LowerTriangularMatrix, a lower triangular matrix, which in contrast to LinearAlgebra.LowerTriangular does not store the entries above the diagonal. SpeedyWeather.jl uses LowerTriangularMatrix which is defined as a subtype of AbstractMatrix to store the spherical harmonic coefficients (see Spectral packing).
LowerTriangularMatrix supports two types of indexing: 1) by denoting two indices, column and row [l,m] or 2) by denoting a single index [lm]. The double index works as expected
But the single index skips the zero entries in the upper triangle, i.e.
julia> L[4]
+Float16(0.478)
which, important, is different from single indices of an AbstractMatrix
julia> Matrix(L)[4]
+Float16(0.0)
In performance-critical code a single index should be used, as this directly maps to the index of the underlying data vector. The double index is somewhat slower as it first has to be converted to the corresponding single index.
Consequently, many loops in SpeedyWeather.jl are build with the following structure
n,m = size(L)
+ij = 0
+for j in 1:m
+ for i in j:n
+ ij += 1
+ L[ij] = i+j
+ end
+end
which loops over all lower triangle entries of L::LowerTriangularMatrix and the single index ij is simply counted up. However, one could also use [i,j] as indices in the loop body or to perform any calculation (i+j here). An iterator over all entries in the lower triangle can be created by
for ij in eachindex(L)
+ # do something
+end
The setindex! functionality of matrixes will throw a BoundsError when trying to write into the upper triangle of a LowerTriangularMatrix, for example
julia> L[2,1] = 0 # valid index
+0
+
+julia> L[1,2] = 0 # invalid index in the upper triangle
+ERROR: BoundsError: attempt to access 3×3 LowerTriangularMatrix{Float32} at index [1, 2]
The LowerTriangularMatrices module's main purpose is not linear algebra, and it's implementation may not be efficient, however, many operations work as expected
Note, however, that the latter includes a conversion to Matrix, which is true for many operations, including inv or \. Hence when trying to do more sophisticated linear algebra with LowerTriangularMatrix we quickly leave lower triangular-land and go back to normal matrix-land.
L = LowerTriangularMatrix{T}(v::Vector{T},m::Int,n::Int)
A lower triangular matrix implementation that only stores the non-zero entries explicitly. L<:AbstractMatrix although in general we have L[i] != Matrix(L)[i], the former skips zero entries, tha latter includes them.
creates unit_range::UnitRange to loop over all non-zeros in the LowerTriangularMatrices provided as arguments. Checks bounds first. All LowerTriangularMatrix's need to be of the same size. Like eachindex but skips the upper triangle with zeros in L.
k = ij2k( i::Integer, # row index of matrix
+ j::Integer, # column index of matrix
+ m::Integer) # number of rows in matrix
Converts the index pair i,j of an mxn LowerTriangularMatrix L to a single index k that indexes the same element in the corresponding vector that stores only the lower triangle (the non-zero entries) of L.
SpeedyWeather.jl uses NetCDF to output the data of a simulation. The following describes the details of this and how to change the way in which the NetCDF output is written. There are many options to this available.
The output writer is a component of every Model, i.e. BarotropicModel, ShallowWaterModel, PrimitiveDryModel and PrimitiveWetModel, hence a non-default output writer can be passed on as a keyword argument to the model constructor
julia> using SpeedyWeather
+julia> spectral_grid = SpectralGrid()
+julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry)
+julia> model = PrimitiveDryModel(;spectral_grid, output=my_output_writer)
So after we have defined the grid through the SpectralGrid object we can use and change the implemented OutputWriter by passing on the following arguments
the spectral_grid has to be the first argument then the model type (Barotropic, ShallowWater, PrimitiveDry, PrimitiveWet) which helps the output writer to make default choices on which variables to output. However, we can also pass on further keyword arguments. So let's start with an example.
which will now output every hour. It is important to pass on the new output writer my_output_writer to the model constructor, otherwise it will not be part of your model and the default is used instead. Note that output_dt has to be understood as the minimum frequency or maximum output time step. Example, we run the model at a resolution of T85 and the time step is going to be 670s
This means that after 32 time steps 5h 57min and 20s will have passed where output will happen as the next time step would be >6h. The time axis of the NetCDF output will look like
This is so that we don't interpolate in time during output to hit exactly every 6 hours, but at the same time have a constant spacing in time between output time steps.
Say we want to run the model at a given horizontal resolution but want to output on another resolution, the OutputWriter takes as argument output_Grid<:AbstractFullGrid and nlat_half::Int. So for example output_Grid=FullClenshawGrid and nlat_half=48 will always interpolate onto a regular 192x95 longitude-latitude grid of 1.875˚ resolution, regardless the grid and resolution used for the model integration.
Note that by default the output is on the corresponding full of the grid used in the dynamical core so that interpolation only happens at most in the zonal direction as they share the location of the latitude rings. You can check this by
So the corresponding full grid of an OctahedralGaussianGrid is the FullGaussiangrid and the same resolution nlat_half is chosen by default in the output writer (which you can change though as shown above). Overview of the corresponding full grids
Grid
Corresponding full grid
FullGaussianGrid
FullGaussianGrid
FullClenshawGrid
FullClenshawGrid
OctahadralGaussianGrid
FullGaussianGrid
OctahedralClensawhGrid
FullClenshawGrid
HEALPixGrid
FullHEALPixGrid
OctaHEALPixGrid
FullOctaHEALPixGrid
The grids FullHEALPixGrid, FullOctaHEALPixGrid share the same latitude rings as their reduced grids, but have always as many longitude points as they are at most around the equator. These grids are not tested in the dynamical core (but you may use them experimentally) and mostly designed for output purposes.
That's easy by passing on path="/my/favourite/path/" and the folder run_* with * the identification of the run (that's the id keyword, which can be manually set but is also automatically determined as a number counting up depending on which folders already exist) will be created within.
which will be used instead of a 4 digit number like 0001, 0002 which is automatically determined if id is not provided. You will see the id of the run in the progress bar
Weather is speedy: run diffusion_test 100%|███████████████████████| Time: 0:00:12 (19.20 years/day)
and the run folder, here run_diffusion_test, is also named accordingly
Further options are described in the OutputWriter docstring, (also accessible via julia>?OutputWriter for example). Note that some fields are actual options, but others are derived from the options you provided or are arrays/objects the output writer needs, but shouldn't be passed on by the user. The actual options are declared as [OPTION] in the following
Missing docstring.
Missing docstring for OutputWriter. Check Documenter's build log for details.
Settings
This document was generated with Documenter.jl version 0.27.25 on Friday 4 August 2023. Using Julia version 1.8.5.
This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmosphere. Every section is followed by a brief description of implementation details.
The primitive equations are a hydrostatic approximation of the compressible Navier-Stokes equations for an ideal gas on a rotating sphere. We largely follow the idealised spectral dynamical core developed by GFDL[1] and documented therein[2].
The primitive equations solved by SpeedyWeather.jl for relative vorticity $\zeta$, divergence $\mathcal{D}$, logarithm of surface pressure $\ln p_s$, temperature $T$ and specific humidity $q$ are
with velocity $\mathbf{u} = (u,v)$, rotated velocity $\mathbf{u}_\perp = (v,-u)$, Coriolis parameter $f$, $W$ the vertical advection operator, dry air gas constant $R_d$, virtual temperature $T_v$, geopotential $\Phi$, pressure $p$, thermodynamic $\kappa = R\_d/c_p$ with $c_p$ the heat capacity at constant pressure. Horizontal hyper diffusion of the form $(-1)^{n+1}\nu\nabla^{2n}$ with coefficient $\nu$ and power $n$ is added for every variable that is advected, meaning $\zeta, \mathcal{D}, T, q$, but left out here for clarity, see Horizontal diffusion.
The parameterizations for the tendencies of $u,v,T,q$ from physical processes are denoted as $\mathcal{P}_\mathbf{u} = (\mathcal{P}_u, \mathcal{P}_v), \mathcal{P}_T, \mathcal{P}_q$ and are further described in the corresponding sections, see Parameterizations.
SpeedyWeather.jl implements a PrimitiveWet and a PrimitiveDry dynamical core. For a dry atmosphere, we have $q = 0$ and the virtual temperature $T_v = T$ equals the temperature (often called absolute to distinguish from the virtual temperature). The terms in the primitive equations and their discretizations are discussed in the following sections.
Virtual temperature is the temperature dry air would need to have to be as light as moist air. It is used in the dynamical core to include the effect of humidity on the density while replacing density through the ideal gas law with temperature.
We assume the atmosphere to be composed of two ideal gases: Dry air and water vapour. Given a specific humidity $q$ both gases mix, their pressures $p_d$, $p_w$ ($d$ for dry, $w$ for water vapour), and densities $\rho_d, \rho_w$ add in a given air parcel that has temperature $T$. The ideal gas law then holds for both gases
\[\begin{aligned}
+p_d &= \rho_d R_d T \\
+p_w &= \rho_w R_w T \\
+\end{aligned}\]
with the respective specific gas constants $R_d = R/m_d$ and $R_w = R/m_w$ obtained from the univeral gas constant $R$ divided by the molecular masses of the gas. The total pressure $p$ in the air parcel is
\[p = p_d + p_w = (\rho_d R_d + \rho_w R_w)T\]
We ultimately want to replace the density $\rho = \rho_w + \rho_d$ in the dynamical core, using the ideal gas law, with the temperature $T$, so that we never have to calculate the density explicitly. However, in order to not deal with two densities (dry air and water vapour) we would like to replace temperature with a virtual temperature that includes the effect of humidity on the density. So, whereever we use the ideal gas law to replace density with temperature, we would use the virtual temperature, which is a function of the absolute temperature and specific humidity, instead. A higher specific humidity in an air parcel lowers the density as water vapour is lighter than dry air. Consequently, the virtual temperature of moist air is higher than its absolute temperature because warmer air is lighter too at constant pressure. We therefore think of the virtual temperature as the temperature dry air would need to have to be as light as moist air.
Starting with the last equation, with some manipulation we can write the ideal gas law as total density $rho$ times a gas constant times the virtual temperature that is supposed to be a function of absolute temperature, humidity and some constants
as some constant that is positive for water vapour being lighter than dry air ($\tfrac{R_d}{R_w} = \tfrac{m_w}{m_d} < 1$) and
\[q = \frac{\rho_w}{\rho_w + \rho_d}\]
as the specific humidity. Given temperature $T$ and specific humidity $q$, we can therefore calculate the virtual temperature $T_v$ as
\[T_v = (1 + \mu q)T\]
For completeness we want to mention here that the above product, because it is a product of two variables $q,T$ has to be computed in grid-point space, see [Spectral Transform]. To obtain an approximation to the virtual temperature in spectral space without expensive transforms one can linearize
\[T_v = T + \mu q\bar{T}\]
With a global constant temperature $\bar{T}$, for example obtained from the $l=m=0$ mode, $\bar{T} = T_{0,0}\frac{1}{\sqrt{4\pi}}$ but depending on the normalization of the spherical harmonics that factor needs adjustment.
In the hydrostatic approximation the vertical momentum equation becomes
\[\frac{\partial p}{\partial z} = -\rho g,\]
meaning that the (negative) vertical pressure gradient is given by the density in that layer times the gravitational acceleration. The heavier the fluid the more the pressure will increase below. Inserting the ideal gas law
Note that we use the Virtual temperature here as we replaced the density through the ideal gas law with temperature. Given a vertical temperature profile $T_v$ and the (constant) surface geopotential $\Phi_s = gz_s$ where $z_s$ is the orography, we can integrate this equation from the surface to the top to obtain $\Phi_k$ on every layer $k$. The surface is at $k = N+\tfrac{1}{2}$ (see Vertical coordinates) with $N$ vertical levels. We can integrate the geopotential onto half levels as
RingGrids is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.
RingGrids defines several iso-latitude grids, which are mathematically described in the section on Grids. In brief, they include the regular latitude-longitude grids (here called FullClenshawGrid) as well as grids which latitudes are shifted to the Gaussian latitudes and reduced grids, meaning that they have a decreasing number of longitudinal points towards the poles to be more equal-area than full grids.
RingGrids defines and exports the following grids:
full grids: FullClenshawGrid, FullGaussianGrid, FullHEALPix, and FullOctaHEALPix
reduced grids: OctahedralGaussianGrid, OctahedralClenshawGrid, OctaHEALPixGrid and HEALPixGrid
The following explanation of how to use these can be mostly applied to any of them, however, there are certain functions that are not defined, e.g. the full grids can be trivially converted to a Matrix (i.e. they are rectangular grids) but not the OctahedralGaussianGrid.
What is a ring?
We use the term ring, short for iso-latitude ring, to refer to a sequence of grid points that all share the same latitude. A latitude-longitude grid is a ring grid, as it organises its grid-points into rings. However, other grids, like the cubed-sphere are not based on iso-latitude rings. SpeedyWeather.jl only works with ring grids because its a requirement for the Spherical Harmonic Transform to be efficient. See Grids.
Every grid in RingGrids has a grid.data field, which is a vector containing the data on the grid. The grid points are unravelled west to east then north to south, meaning that it starts at 90˚N and 0˚E then walks eastward for 360˚ before jumping on the next latitude ring further south, this way circling around the sphere till reaching the south pole. This may also be called ring order.
Data in a Matrix which follows this ring order can be put on a FullGaussianGrid like so
using SpeedyWeather.RingGrids
+map = randn(Float32,8,4)
A full Gaussian grid has always $2N$ x $N$ grid points, but a FullClenshawGrid has $2N$ x $N-1$, if those dimensions don't match, the creation will throw an error. To reobtain the data from a grid, you can access its data field which returns a normal Vector
Which can be reshaped to reobtain map from above. Alternatively you can Matrix(grid) to do this in one step
map == Matrix(FullGaussianGrid(map))
true
You can also use zeros,ones,rand,randn to create a grid, whereby nlat_half, i.e. the number of latitude rings on one hemisphere, Equator included, is used as a resolution parameter and here as a second argument.
As only the full grids can be reshaped into a matrix, the underlying data structure of any AbstractGrid is a vector. As shown in the examples above, one can therefore inspect the data as if it was a vector. But as that data has, through its <:AbstractGrid type, all the geometric information available to plot it on a map, RingGrids also exports plot function, based on UnicodePlots' heatmap.
All RingGrids have a single index ij which follows the ring order. While this is obviously not super exciting here are some examples how to make better use of the information that the data sits on a grid.
We obtain the latitudes of the rings of a grid by calling get_latd (get_lond is only defined for full grids, or use get_latdlonds for latitudes, longitudes per grid point not per ring)
Now we could calculate Coriolis and add it on the grid as follows
rotation = 7.29e-5 # angular frequency of Earth's rotation [rad/s]
+coriolis = 2rotation*sind.(latd) # vector of coriolis parameters per latitude ring
+
+rings = eachring(grid)
+for (j,ring) in enumerate(rings)
+ f = coriolis[j]
+ for ij in ring
+ grid[ij] += f
+ end
+end
eachring creates a vector of UnitRange indices, such that we can loop over the ring index j (j=1 being closest to the North pole) pull the coriolis parameter at that latitude and then loop over all in-ring indices i (changing longitudes) to do something on the grid. Something similar can be done to scale/unscale with the cosine of latitude for example. We can always loop over all grid-points like so
In most cases we will want to use RingGrids so that our data directly comes with the geometric information of where the grid-point is one the sphere. We have seen how to use get_latd, get_lond, ... for that above. This information generally can also be used to interpolate our data from grid to another or to request an interpolated value on some coordinates. Using our data on grid which is an OctahedralGaussianGrid from above we can use the interpolate function to get it onto a FullGaussianGrid (or any other grid for purpose)
By default this will linearly interpolate (it's an Anvil interpolator, see below) onto a grid with the same nlat_half, but we can also coarse-grain or fine-grain by specifying nlat_half directly as 2nd argument
So we got from an 8-ring OctahedralGaussianGrid{Float16} to a 12-ring FullGaussianGrid{Float64}, so it did a conversion from Float16 to Float64 on the fly too, because the default precision is Float64 unless specified. interpolate(FullGaussianGrid{Float16},6,grid) would have interpolated onto a grid with element type Float16.
One can also interpolate onto a given coordinate ˚N, ˚E like so
interpolate(30.0,10.0,grid)
0.16222367f0
we interpolated the data from grid onto 30˚N, 10˚E. To do this simultaneously for many coordinates they can be packed into a vector too
Every time an interpolation like interpolate(30.0,10.0,grid) is called, several things happen, which are important to understand to know how to get the fastest interpolation out of this module in a given situation. Under the hood an interpolation takes three arguments
output vector
input grid
interpolator
The output vector is just an array into which the interpolated data is written, providing this prevents unnecessary allocation of memory in case the destination array of the interpolation already exists. The input grid contains the data which is subject to interpolation, it must come on a ring grid, however, its coordinate information is actually already in the interpolator. The interpolator knows about the geometry of the grid the data is coming on and the coordinates it is supposed to interpolate onto. It has therefore precalculated the indices that are needed to access the right data on the input grid and the weights it needs to apply in the actual interpolation operation. The only thing it does not know is the actual data values of that grid. So in the case you want to interpolate from grid A to grid B many times, you can just reuse the same interpolator. If you want to change the coordinates of the output grid but its total number of points remain constants then you can update the locator inside the interpolator and only else you will need to create a new interpolator. Let's look at this in practice. Say we have two grids an want to interpolate between them
Now we have created an interpolator interp which knows about the geometry where to interpolate from and the coordinates there to interpolate to. It is also initialized, meaning it has precomputed the indices to of grid_in that are supposed to be used. It just does not know about the data of grid_in (and neither of grid_out which will be overwritten anyway). We can now do
which is identical to interpolate(grid_out,grid_in) but you can reuse interp for other data. The interpolation can also handle various element types (the interpolator interp does not have to be updated for this either)
and we have converted data from a HEALPixGrid{Float64} (Float64 is always default if not specified) to a FullClenshawGrid{Float16} including the type conversion Float64-Float16 on the fly. Technically there are three data types and their combinations possible: The input data will come with a type, the output array has an element type and the interpolator has precomputed weights with a given type. Say we want to go from Float16 data on an OctahedralGaussianGrid to Float16 on a FullClenshawGrid but using Float32 precision for the interpolation itself, we would do this by
As a last example we want to illustrate a situation where we would always want to interpolate onto 10 coordinates, but their locations may change. In order to avoid recreating an interpolator object we would do (AnvilInterpolator is described in Anvil interpolator)
npoints = 10 # number of coordinates to interpolate onto
+interp = AnvilInterpolator(Float32,HEALPixGrid,24,npoints)
with the first argument being the number format used during interpolation, then the input grid type, its resolution in terms of nlat_half and then the number of points to interpolate onto. However, interp is not yet initialized as it does not know about the destination coordinates yet. Let's define them, but note that we already decided there's only 10 of them above.
but allows for a reuse of the interpolator. Note that the two output arrays are not exactly identical because we manually set our interpolator interp to use Float32 for the interpolation whereas the default is Float64.
Currently the only interpolator implemented is a 4-point bilinear interpolator, which schematically works as follows. Anvil interpolation is the bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate.
0..............1 # fraction of distance Δab between a,b
+ |< Δab >|
+
+0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x
+.Δy |
+. |
+.v x
+. |
+1 c ------ o ---- d
+
+ |< Δcd >|
+ 0...............1 # fraction of distance Δcd between c,d
+
+^ fraction of distance Δy between a-b and c-d.
This interpolation is chosen as by definition of the ring grids, a and b share the same latitude, so do c and d, but the longitudes can be different for all four, a,b,c,d.
abstract type AbstractFullGrid{T} <: AbstractGrid{T} end
An AbstractFullGrid is a horizontal grid with a constant number of longitude points across latitude rings. Different latitudes can be used, Gaussian latitudes, equi-angle latitdes, or others.
abstract type AbstractGrid{T} <: AbstractVector{T} end
The abstract supertype for all spatial grids on the sphere supported by SpeedyWeather.jl. Every new grid has to be of the form
abstract type AbstractGridClass{T} <: AbstractGrid{T} end
+struct MyNewGrid{T} <: AbstractGridClass{T}
+ data::Vector{T} # all grid points unravelled into a vector
+ nlat_half::Int # resolution: latitude rings on one hemisphere (Equator incl)
+end
MyNewGrid should belong to a grid class like AbstractFullGrid, AbstractOctahedralGrid or AbstractHEALPixGrid (that already exist but you may introduce a new class of grids) that share certain features such as the number of longitude points per latitude ring and indexing, but may have different latitudes or offset rotations. Each new grid Grid (or grid class) then has to implement the following methods (as an example, see octahedral.jl)
Fundamental grid properties getnpoints # total number of grid points nlatodd # does the grid have an odd number of latitude rings? getnlat # total number of latitude rings getnlat_half # number of latitude rings on one hemisphere incl Equator
Indexing getnlonmax # maximum number of longitudes points (at the Equator) getnlonperring # number of longitudes on ring j eachindexinring # a unit range that indexes all longitude points on a ring
Coordinates getcolat # vector of colatitudes (radians) getcolatlon # vectors of colatitudes, longitudes (both radians)
Spectral truncation truncationorder # linear, quadratic, cubic = 1,2,3 for grid gettruncation # spectral truncation given a grid resolution get_resolution # grid resolution given a spectral truncation
Quadrature weights and solid angles getquadratureweights # = sinθ Δθ for grid points on ring j for meridional integration getsolidangle # = sinθ Δθ Δϕ, solid angle of grid points on ring j
abstract type AbstractHEALPixGrid{T} <: AbstractGrid{T} end
An AbstractHEALPixGrid is a horizontal grid similar to the standard HEALPixGrid, but different latitudes can be used, the default HEALPix latitudes or others.
Supertype of every Locator, which locates the indices on a grid to be used to perform an interpolation. E.g. AnvilLocator uses a 4-point stencil for every new coordinate to interpolate onto. Higher order stencils can be implemented by defining OtherLocator <: AbstractLocactor.
abstract type AbstractOctaHEALPixGrid{T} <: AbstractGrid{T} end
An AbstractOctaHEALPixGrid is a horizontal grid similar to the standard OctahedralGrid, but the number of points in the ring closest to the Poles starts from 4 instead of 20, and the longitude of the first point in each ring is shifted as in HEALPixGrid. Also, different latitudes can be used.
abstract type AbstractOctahedralGrid{T} <: AbstractGrid{T} end
An AbstractOctahedralGrid is a horizontal grid with 16+4i longitude points on the latitude ring i starting with i=1 around the pole. Different latitudes can be used, Gaussian latitudes, equi-angle latitdes, or others.
Contains arrays that locates grid points of a given field to be uses in an interpolation and their weights. This Locator is a 4-point average in an anvil-shaped grid-point arrangement between two latitude rings.
L = AnvilLocator( ::Type{NF}, # number format used for the interpolation
+ npoints::Integer # number of points to interpolate onto
+ ) where {NF<:AbstractFloat}
Zero generator function for the 4-point average AnvilLocator. Use update_locator! to update the grid indices used for interpolation and their weights. The number format NF is the format used for the calculations within the interpolation, the input data and/or output data formats may differ.
A FullClenshawGrid is a regular latitude-longitude grid with an odd number of nlat equi-spaced latitudes, the central latitude ring is on the Equator. The same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field data that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
A full Gaussian grid is a regular latitude-longitude grid that uses nlat Gaussian latitudes, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
A full HEALPix grid is a regular latitude-longitude grid that uses nlat latitudes from the HEALPix grid, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
A full OctaHEALPix grid is a regular latitude-longitude grid that uses nlat OctaHEALPix latitudes, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
G = GridGeometry( Grid::Type{<:AbstractGrid},
+ nlat_half::Integer)
Precomputed arrays describing the geometry of the Grid with resolution nlat_half. Contains latitudes and longitudes of grid points, their ring index j and their unravelled indices ij.
A HEALPix grid with 12 faces, each nsidexnside grid points, each covering the same area. The number of latitude rings on one hemisphere (incl Equator) nlat_half is used as resolution parameter. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
A OctaHEALPix grid with 4 base faces, each nlat_halfxnlat_half grid points, each covering the same area. The values of all grid points are stored in a vector field data that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
An Octahedral Clenshaw grid that uses nlat equi-spaced latitudes. Like FullClenshawGrid, the central latitude ring is on the Equator. Like OctahedralGaussianGrid, the number of longitude points per latitude ring decreases towards the poles. Starting with 20 equi-spaced longitude points (starting at 0˚E) on the rings around the poles, each latitude ring towards the equator has consecuitively 4 more points, one for each face of the octahedron. E.g. 20,24,28,32,...nlon-4,nlon,nlon,nlon-4,...,32,28,24,20. The maximum number of longitue points is nlon. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
An Octahedral Gaussian grid that uses nlat Gaussian latitudes, but a decreasing number of longitude points per latitude ring towards the poles. Starting with 20 equi-spaced longitude points (starting at 0˚E) on the rings around the poles, each latitude ring towards the equator has consecuitively 4 more points, one for each face of the octahedron. E.g. 20,24,28,32,...nlon-4,nlon,nlon,nlon-4,...,32,28,24,20. The maximum number of longitue points is nlon. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.
Sorts the gridpoints in G into the matrix M without interpolation. Every quadrant of the grid G is rotated as specified in quadrant_rotation, 0 is no rotation, 1 is 90˚ clockwise, 2 is 180˚ etc. Grid quadrants are counted eastward starting from 0˚E. The grid quadrants are moved into the matrix quadrant (i,j) as specified. Defaults are equivalent to centered at 0˚E and a rotation such that the North Pole is at M's midpoint.
Sorts the gridpoints in G into the matrix M without interpolation. Every quadrant of the grid G is rotated as specified in quadrant_rotation, 0 is no rotation, 1 is 90˚ clockwise, 2 is 180˚ etc. Grid quadrants are counted eastward starting from 0˚E. The grid quadrants are moved into the matrix quadrant (i,j) as specified. Defaults are equivalent to centered at 0˚E and a rotation such that the North Pole is at M's midpoint.
Like Matrix!(::AbstractMatrix,::OctaHEALPixGrid) but for simultaneous processing of tuples ((M1,G1),(M2,G2),...) with matrices Mi and grids Gi. All matrices and grids have to be of the same size respectively.
Like Matrix!(::AbstractMatrix,::OctahedralClenshawGrid) but for simultaneous processing of tuples ((M1,G1),(M2,G2),...) with matrices Mi and grids Gi. All matrices and grids have to be of the same size respectively.
The bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate. See schematic:
0..............1 # fraction of distance Δab between a,b
+ |< Δab >|
+
+ 0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x
+ .Δy |
+ . |
+ .v x
+ . |
+ 1 c ------ o ---- d
+
+ |< Δcd >|
+ 0...............1 # fraction of distance Δcd between c,d
Computes the average at the North and South pole from a given grid A and it's precomputed ring indices rings. The North pole average is an equally weighted average of all grid points on the northern-most ring. Similar for the South pole.
Returns a vector nlons for the number of longitude points per latitude ring, north to south. Provide grid Grid and its resolution parameter nlat_half. For both_hemisphere==false only the northern hemisphere (incl Equator) is returned.
This document was generated with Documenter.jl version 0.27.25 on Friday 4 August 2023. Using Julia version 1.8.5.
diff --git a/previews/PR362/search_index.js b/previews/PR362/search_index.js
new file mode 100644
index 000000000..ceac88c9c
--- /dev/null
+++ b/previews/PR362/search_index.js
@@ -0,0 +1,3 @@
+var documenterSearchIndex = {"docs":
+[{"location":"parameterizations/#parameterizations","page":"Parameterizations","title":"Parameterizations","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmosphere. Every section is followed by a brief description of implementation details.","category":"page"},{"location":"parameterizations/#Convection","page":"Parameterizations","title":"Convection","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Large-scale-condensation","page":"Parameterizations","title":"Large-scale condensation","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Clouds","page":"Parameterizations","title":"Clouds","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Short-wave-radiation","page":"Parameterizations","title":"Short-wave radiation","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Long-wave-radiation","page":"Parameterizations","title":"Long-wave radiation","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Surface-fluxes-of-momentum-and-energy","page":"Parameterizations","title":"Surface fluxes of momentum and energy","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parameterizations/#Vertical-diffusion","page":"Parameterizations","title":"Vertical diffusion","text":"","category":"section"},{"location":"parameterizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"development/#Development-notes","page":"Development notes","title":"Development notes","text":"","category":"section"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To run tests, from the path of your local clone of the repository do:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=. -e 'import Pkg; Pkg.test()'","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To install dependencies:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project -e 'import Pkg; Pkg.instantiate()`","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"then opening:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"you are able to using SpeedyWeather.","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To generate the docs:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=docs -e 'import Pkg; Pkg.develop(path=\".\"); Pkg.instantiate()'\njulia --project=docs docs/make.jl","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"If the docs are generated successfully, you view them by opening docs/build/index.html in your favorite browser.","category":"page"},{"location":"barotropic/#Barotropic-vorticity-model","page":"Barotropic model","title":"Barotropic vorticity model","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The barotropic vorticity model describes the evolution of a 2D non-divergent flow with velocity components mathbfu = (uv) through self-advection, forces and dissipation. Due to the non-divergent nature of the flow, it can be described by (the vertical component) of the relative vorticity zeta = nabla times mathbfu.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The dynamical core presented here to solve the barotropic vorticity equations largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2].","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Many concepts of the Shallow water model and the Primitive equation model are similar, such that for example horizontal diffusion and the Time integration are only explained here.","category":"page"},{"location":"barotropic/#Barotropic-vorticity-equation","page":"Barotropic model","title":"Barotropic vorticity equation","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity zeta with advection, Coriolis force, forcing and diffusion in a single global layer on the sphere.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"fracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) =\nnabla times mathbfF + (-1)^n+1nunabla^2nzeta","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"We denote timet, velocity vector mathbfu = (u v), Coriolis parameter f, and hyperdiffusion (-1)^n+1 nu nabla^2n zeta (n is the hyperdiffusion order, see Horizontal diffusion). We also include a forcing vector mathbfF = (F_uF_v) which acts on the zonal velocity u and the meridional velocity v and hence its curl nabla times mathbfF is a tendency for relative vorticity zeta.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Starting with some relative vorticity zeta, the Laplacian is inverted to obtain the stream function Psi","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Psi = nabla^-2zeta","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The zonal velocity u and meridional velocity v are then the (negative) meridional gradient and zonal gradient of Psi","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"beginaligned\nu = -frac1R fracpartial Psipartial theta \nv = frac1Rcos(theta) fracpartial Psipartial phi \nendaligned","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"which is described in Derivatives in spherical coordinates. Using u and v we can then advect the absolute vorticity zeta + f. In order to avoid to calculate both the curl and the divergence of a flux we rewrite the barotropic vorticity equation as","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"fracpartial zetapartial t =\nnabla times (mathbfF + mathbfu_perp(zeta + f)) + (-1)^n+1nunabla^2nzeta","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"with mathbfu_perp = (v-u) the rotated velocity vector, because -nablacdotmathbfu = nabla times mathbfu_perp. This is the form that is solved in the BarotropicModel, as outlined in the following section.","category":"page"},{"location":"barotropic/#Algorithm","page":"Barotropic model","title":"Algorithm","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"We briefly outline the algorithm that SpeedyWeather.jl uses in order to integrate the barotropic vorticity equation. As an initial step","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"0. Start with initial conditions of zeta_lm in spectral space and transform this model state to grid-point space:","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Invert the Laplacian of vorticity zeta_lm to obtain the stream function Psi_lm in spectral space\nobtain zonal velocity (cos(theta)u)_lm through a Meridional derivative\nobtain meridional velocity (cos(theta)v)_lm through a Zonal derivative\nTransform zonal and meridional velocity (cos(theta)u)_lm, (cos(theta)v)_lm to grid-point space\nUnscale the cos(theta) factor to obtain uv\nTransform zeta_lm to zeta in grid-point space","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Now loop over","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Compute the forcing vector mathbfF = (F_uF_v) for u and v\nMultiply uv with zeta+f in grid-point space\nAdd A = F_u + v(zeta + f) and B = F_v - u(zeta + f)\nTransform these vector components to spectral space A_lm, B_lm\nCompute the curl of (AB)_lm in spectral space which is the tendency of zeta_lm\nCompute the horizontal diffusion based on that tendency\nCompute a leapfrog time step as described in Time integration with a Robert-Asselin and Williams filter\nTransform the new spectral state of zeta_lm to grid-point uvzeta as described in 0.\nPossibly do some output\nRepeat from 1.","category":"page"},{"location":"barotropic/#diffusion","page":"Barotropic model","title":"Horizontal diffusion","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"In SpeedyWeather.jl we use hyerdiffusion through an n-th power Laplacian (-1)^n+1nabla^2n (hyper when n1) which can be implemented as a multiplication of the spectral coefficients Psi_lm with (-l(l+1))^nR^-2n (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the (-l(l+1))^nR^-2n can be precomputed. Note the sign change (-1)^n+1 here is such that the dissipative nature of the diffusion operator is retained for n odd and even.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step Delta t of variable zeta to obtain from time steps i-1 and i, the next time step i+1","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"zeta_i+1 = zeta_i-1 + 2Delta t dzeta","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"with dzeta being some tendency evaluated from zeta_i. Now we want to add a diffusion term (-1)^n+1nu nabla^2nzeta with coefficient nu, which however, is implicitly calculated from zeta_i+1, then","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"zeta_i+1 = zeta_i-1 + 2Delta t (dzeta + (-1)^n+1 nunabla^2nzeta_i+1)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"As the application of (-1)^n+1nunabla^2n is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"zeta_i+1 = fraczeta_i-1 + 2Delta t dzeta1 - 2Delta (-1)^n+1nunabla^2n","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"and expand the numerator to","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"zeta_i+1 = zeta_i-1 + 2Delta t fracdzeta + (-1)^n+1 nunabla^2nzeta_i-11 - 2Delta t (-1)^n+1nu nabla^2n","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Hence the diffusion can be applied implicitly by updating the tendency dzeta as","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"dzeta to fracdzeta + (-1)^n+1nunabla^2nzeta_i-11 - 2Delta t nu nabla^2n","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"which only depends on zeta_i-1. Now let D_textexplicit = (-1)^n+1nunabla^2n be the explicit part and D_textimplicit = 1 - (-1)^n+1 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are D_textimplicit = 1 - 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic lm we do","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"dzeta to D_textimplicit^-1(dzeta + D_textexplicitzeta_i-1)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power n.","category":"page"},{"location":"barotropic/#Normalization-of-diffusion","page":"Barotropic model","title":"Normalization of diffusion","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"In physics, the Laplace operator nabla^2 is often used to represent diffusion due to viscosity in a fluid or diffusion that needs to be added to retain numerical stability. In both cases, the coefficient is nu of units textm^2texts^-1 and the full operator reads as nu nabla^2 with units (textm^2texts^-1)(textm^-2) = texts^-1. This motivates us to normalize the Laplace operator by a constant of units textm^-2 and the coefficient by its inverse such that it becomes a damping timescale of unit texts^-1. Given the application in spectral space we decide to normalize by the largest eigenvalue -l_textmax(l_textmax+1) such that all entries in the discrete spectral Laplace operator are in 01. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized coefficient nu^* = l_textmax(l_textmax+1)nu (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have ","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"D^textexplicit_lm = -nu^* fracl(l+1)l_textmax(l_textmax+1)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"and the hyper-Laplacian of power n follows as","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"D^textexplicitn_lm = -nu^* left(fracl(l+1)l_textmax(l_textmax+1)right)^n","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"and the implicit part is accordingly D^textimplicitn_lm = 1 - 2Delta t D^textexplicitn_lm. Note that the diffusion time scale nu^* is then also scaled by the radius, see next section.","category":"page"},{"location":"barotropic/#scaling","page":"Barotropic model","title":"Radius scaling","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Similar to a non-dimensionalization of the equations, SpeedyWeather.jl scales the barotropic vorticity equation with R^2 to obtain normalized gradient operators as follows. A scaling for vorticity zeta and stream function Psi is used that is","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"tildezeta = zeta R tildePsi = Psi R^-1","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"This is also convenient as vorticity is often 10^-5text s^-1 in the atmosphere, but the streamfunction more like 10^5text m^2text s^-1 and so this scaling brings both closer to 1 with a typical radius of the Earth of 6371km. The inversion of the Laplacians in order to obtain Psi from zeta therefore becomes","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"tildezeta = tildenabla^2 tildePsi","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"where the dimensionless gradients simply omit the scaling with 1R, tildenabla = Rnabla. The Barotropic vorticity equation scaled with R^2 is","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"partial_tildettildezeta + tildenabla cdot (mathbfu(tildezeta + tildef)) =\nnabla times tildemathbfF + (-1)^n+1tildenutildenabla^2ntildezeta","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"with","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"tildet = tR^-1, the scaled time t\nmathbfu = (uv), the velocity vector (no scaling applied)\ntildef = fR, the scaled Coriolis parameter f\ntildemathbfF = RmathbfF, the scaled forcing vector mathbfF\ntildenu = nu^* R, the scaled diffusion coefficient nu^*, which itself is normalized to a damping time scale, see Normalization of diffusion.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"So scaling with the radius squared means we can use dimensionless operators, however, this comes at the cost of needing to deal with both a time step in seconds as well as a scaled time step in seconds per meter, which can be confusing. Furthermore, some constants like Coriolis or the diffusion coefficient need to be scaled too during initialisation, which may be confusing too because values are not what users expect them to be. SpeedyWeather.jl follows the logic that the scaling to the prognostic variables is only applied just before the time integration and variables are unscaled for output and after the time integration finished. That way, the scaling is hidden as much as possible from the user. In hopefully many other cases it is clearly denoted that a variable or constant is scaled.","category":"page"},{"location":"barotropic/#leapfrog","page":"Barotropic model","title":"Time integration","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"SpeedyWeather.jl is based on the Leapfrog time integration, which, for relative vorticity zeta, is in its simplest form","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"fraczeta_i+1 - zeta_i-12Delta t = RHS(zeta_i)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"meaning we step from the previous time step i-1, leapfrogging over the current time stepi to the next time step i+1 by evaluating the tendencies on the right-hand side RHS at the current time step i. The time stepping is done in spectral space. Once the right-hand side RHS is evaluated, leapfrogging is a linear operation, meaning that its simply applied to every spectral coefficient zeta_lm as one would evaluate it on every grid point in grid-point models.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"For the Leapfrog time integration two time steps of the prognostic variables have to be stored, i-1 and i. Time step i is used to evaluate the tendencies which are then added to i-1 in a step that also swaps the indices for the next time step i to i-1 and i+1 to i, so that no additional memory than two time steps have to be stored at the same time.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The Leapfrog time integration has to be initialised with an Euler forward step in order to have a second time step i+1 available when starting from i to actually leapfrog over. SpeedyWeather.jl therefore does two initial time steps that are different from the leapfrog time steps that follow and that have been described above.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"an Euler forward step with Delta t2, then\none leapfrog time step with Delta t, then\nleapfrog with 2 Delta t till the end","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"This is particularly done in a way that after 2. we have t=0 at i-1 and t=Delta t at i available so that 3. can start the leapfrogging without any offset from the intuitive spacing 0Delta t 2Delta t 3Delta t. The following schematic can be useful","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":" time at step i-1 time at step i time step at i+1\nInitial conditions t = 0 \n1: Euler (T) quad t = 0 t=Delta t2 \n2: Leapfrog with Delta t t = 0 (T) quad t = Delta t2 t = Delta t\n3 to n: Leapfrog with 2Delta t t-Delta t (T) qquad quad quad t t+Delta t","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The time step that is used to evaluate the tendencies is denoted with (T). It is always the time step furthest in time that is available.","category":"page"},{"location":"barotropic/#Robert-Asselin-and-Williams-filter","page":"Barotropic model","title":"Robert-Asselin and Williams filter","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The standard leapfrog time integration is often combined with a Robert-Asselin filter[Robert66][Asselin72] to dampen a computational mode. The idea is to start with a standard leapfrog step to obtain the next time step i+1 but then to correct the current time step i by applying a filter which dampens the computational mode. The filter looks like a discrete Laplacian in time with a (1 -2 1) stencil, and so, maybe unsurprisingly, is efficient to filter out a \"grid-scale oscillation\" in time, aka the computational mode. Let v be the unfiltered variable and u be the filtered variable, F the right-hand side tendency, then the standard leapfrog step is","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"v_i+1 = u_i-1 + 2Delta tF(v_i)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Meaning we start with a filtered variable u at the previous time step i-1, evaluate the tendency F(v_i) based on the current time step i to obtain an unfiltered next time step v_i+1. We then filter the current time step i (which will become i-1 on the next iteration)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"u_i = v_i + fracnu2(v_i+1 - 2v_i + u_i-1)","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"by adding a discrete Laplacian with coefficient tfracnu2 to it, evaluated from the available filtered and unfiltered time steps centred around i: v_i-1 is not available anymore because it was overwritten by the filtering at the previous iteration, u_i u_i+1 are not filtered yet when applying the Laplacian. The filter parameter nu is typically chosen between 0.01-0.2, with stronger filtering for higher values.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"Williams[Williams2009] then proposed an additional filter step to regain accuracy that is otherwise lost with a strong Robert-Asselin filter[Amezcua2011][Williams2011]. Now let w be unfiltered, v be once filtered, and u twice filtered, then","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"beginaligned\nw_i+1 = u_i-1 + 2Delta tF(v_i) \nu_i = v_i + fracnualpha2(w_i+1 - 2v_i + u_i-1) \nv_i+1 = w_i+1 - fracnu(1-alpha)2(w_i+1 - 2v_i + u_i-1)\nendaligned","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"with the Williams filter parameter alpha in 051. For alpha=1 we're back with the Robert-Asselin filter (the first two lines).","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The Laplacian in the parentheses is often called a displacement, meaning that the filtered value is displaced (or corrected) in the direction of the two surrounding time steps. The Williams filter now also applies the same displacement, but in the opposite direction to the next time step i+1 as a correction step (line 3 above) for a once-filtered value v_i+1 which will then be twice-filtered by the Robert-Asselin filter on the next iteration. For more details see the referenced publications.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"The initial Euler step (see Time integration, Table) is not filtered. Both the the Robert-Asselin and Williams filter are then switched on for all following leapfrog time steps.","category":"page"},{"location":"barotropic/#References","page":"Barotropic model","title":"References","text":"","category":"section"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[1]: Geophysical Fluid Dynamics Laboratory, Idealized models with spectral dynamics","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[2]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[Robert66]: Robert, André. “The Integration of a Low Order Spectral Form of the Primitive Meteorological Equations.” Journal of the Meteorological Society of Japan 44 (1966): 237-245.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[Asselin72]: ASSELIN, R., 1972: Frequency Filter for Time Integrations. Mon. Wea. Rev., 100, 487–490, doi:10.1175/1520-0493(1972)100<0487:FFFTI>2.3.CO;2","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[Williams2009]: Williams, P. D., 2009: A Proposed Modification to the Robert–Asselin Time Filter. Mon. Wea. Rev., 137, 2538–2546, 10.1175/2009MWR2724.1.","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[Amezcua2011]: Amezcua, J., E. Kalnay, and P. D. Williams, 2011: The Effects of the RAW Filter on the Climatology and Forecast Skill of the SPEEDY Model. Mon. Wea. Rev., 139, 608–619, doi:10.1175/2010MWR3530.1. ","category":"page"},{"location":"barotropic/","page":"Barotropic model","title":"Barotropic model","text":"[Williams2011]: Williams, P. D., 2011: The RAW Filter: An Improvement to the Robert–Asselin Filter in Semi-Implicit Integrations. Mon. Wea. Rev., 139, 1996–2007, doi:10.1175/2010MWR3601.1. ","category":"page"},{"location":"installation/#Installation","page":"Installation","title":"Installation","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"SpeedyWeather.jl is registered in the Julia Registry. In most cases just open the Julia REPL and type","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia> using Pkg\njulia> Pkg.add(\"SpeedyWeather\")","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"or, equivalently, (] opens the package manager)","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia>] add SpeedyWeather","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"which will automatically install the latest release and all necessary dependencies. If you run into any troubles please raise an issue.","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"However, you may want to make use of the latest features, then install directly from the main branch with","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia> Pkg.add(url=\"https://github.com/SpeedyWeather/SpeedyWeather.jl\",rev=\"main\")","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"other branches than main can be similarly installed. You can also type, equivalently,","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"julia>] add https://github.com/SpeedyWeather/SpeedyWeather.jl#main","category":"page"},{"location":"installation/#Compatibility-with-Julia-versions","page":"Installation","title":"Compatibility with Julia versions","text":"","category":"section"},{"location":"installation/","page":"Installation","title":"Installation","text":"SpeedyWeather.jl usually lives on the latest minor release and/or its predecessor. At the moment (June 2023) this means ","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"Julia v1.8\nJulia v1.9","category":"page"},{"location":"installation/","page":"Installation","title":"Installation","text":"are supported, but we dropped the support of earlier versions.","category":"page"},{"location":"output/#NetCDF-output","page":"NetCDF output","title":"NetCDF output","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"SpeedyWeather.jl uses NetCDF to output the data of a simulation. The following describes the details of this and how to change the way in which the NetCDF output is written. There are many options to this available.","category":"page"},{"location":"output/#Accessing-the-NetCDF-output-writer","page":"NetCDF output","title":"Accessing the NetCDF output writer","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"The output writer is a component of every Model, i.e. BarotropicModel, ShallowWaterModel, PrimitiveDryModel and PrimitiveWetModel, hence a non-default output writer can be passed on as a keyword argument to the model constructor","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> using SpeedyWeather\njulia> spectral_grid = SpectralGrid()\njulia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry)\njulia> model = PrimitiveDryModel(;spectral_grid, output=my_output_writer)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"So after we have defined the grid through the SpectralGrid object we can use and change the implemented OutputWriter by passing on the following arguments","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, kwargs...)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"the spectral_grid has to be the first argument then the model type (Barotropic, ShallowWater, PrimitiveDry, PrimitiveWet) which helps the output writer to make default choices on which variables to output. However, we can also pass on further keyword arguments. So let's start with an example.","category":"page"},{"location":"output/#Example-1:-NetCDF-output-every-hour","page":"NetCDF output","title":"Example 1: NetCDF output every hour","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"If we want to increase the frequency of the output we can choose output_dt (default =6 in hours) like so","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, output_dt=1)\njulia> model = PrimitiveDryModel(;spectral_grid, output=my_output_writer)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"which will now output every hour. It is important to pass on the new output writer my_output_writer to the model constructor, otherwise it will not be part of your model and the default is used instead. Note that output_dt has to be understood as the minimum frequency or maximum output time step. Example, we run the model at a resolution of T85 and the time step is going to be 670s","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> spectral_grid = SpectralGrid(trunc=85)\njulia> time_stepper = Leapfrog(spectral_grid)\nLeapfrog{Float32}:\n...\n Δt_sec::Int64 = 670\n...","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This means that after 32 time steps 5h 57min and 20s will have passed where output will happen as the next time step would be >6h. The time axis of the NetCDF output will look like","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> using NCDatasets\njulia> ds = NCDataset(\"run_0001/output.nc\");\njulia> ds[\"time\"][:]\n5-element Vector{Dates.DateTime}:\n 2000-01-01T00:00:00\n 2000-01-01T05:57:20\n 2000-01-01T11:54:40\n 2000-01-01T17:52:00\n 2000-01-01T23:49:20\n\njulia> diff(ds[\"time\"][:])\n4-element Vector{Dates.Millisecond}:\n 21440000 milliseconds\n 21440000 milliseconds\n 21440000 milliseconds\n 21440000 milliseconds","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This is so that we don't interpolate in time during output to hit exactly every 6 hours, but at the same time have a constant spacing in time between output time steps.","category":"page"},{"location":"output/#Example-2:-Output-onto-a-higher/lower-resolution-grid","page":"NetCDF output","title":"Example 2: Output onto a higher/lower resolution grid","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Say we want to run the model at a given horizontal resolution but want to output on another resolution, the OutputWriter takes as argument output_Grid<:AbstractFullGrid and nlat_half::Int. So for example output_Grid=FullClenshawGrid and nlat_half=48 will always interpolate onto a regular 192x95 longitude-latitude grid of 1.875˚ resolution, regardless the grid and resolution used for the model integration.","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, output_Grid=FullClenshawGrid, nlat_half=48)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Note that by default the output is on the corresponding full of the grid used in the dynamical core so that interpolation only happens at most in the zonal direction as they share the location of the latitude rings. You can check this by","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> RingGrids.full_grid(OctahedralGaussianGrid)\nFullGaussianGrid","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"So the corresponding full grid of an OctahedralGaussianGrid is the FullGaussiangrid and the same resolution nlat_half is chosen by default in the output writer (which you can change though as shown above). Overview of the corresponding full grids","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Grid Corresponding full grid\nFullGaussianGrid FullGaussianGrid\nFullClenshawGrid FullClenshawGrid\nOctahadralGaussianGrid FullGaussianGrid\nOctahedralClensawhGrid FullClenshawGrid\nHEALPixGrid FullHEALPixGrid\nOctaHEALPixGrid FullOctaHEALPixGrid","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"The grids FullHEALPixGrid, FullOctaHEALPixGrid share the same latitude rings as their reduced grids, but have always as many longitude points as they are at most around the equator. These grids are not tested in the dynamical core (but you may use them experimentally) and mostly designed for output purposes.","category":"page"},{"location":"output/#Example-3:-Changing-the-output-path-or-identification","page":"NetCDF output","title":"Example 3: Changing the output path or identification","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"That's easy by passing on path=\"/my/favourite/path/\" and the folder run_* with * the identification of the run (that's the id keyword, which can be manually set but is also automatically determined as a number counting up depending on which folders already exist) will be created within.","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> path = pwd()\n\"/Users/milan\"\njulia> my_output_writer = OutputWriter(spectral_grid, PrimitiveDry, path=path)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"This folder must already exist. If you want to give your run a name/identification you can pass on id","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"julia> my_output_writer = OutputWriter(spectral_grid,PrimitiveDry,id=\"diffusion_test\");","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"which will be used instead of a 4 digit number like 0001, 0002 which is automatically determined if id is not provided. You will see the id of the run in the progress bar","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Weather is speedy: run diffusion_test 100%|███████████████████████| Time: 0:00:12 (19.20 years/day)","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"and the run folder, here run_diffusion_test, is also named accordingly","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"shell> ls\n...\nrun_diffusion_test\n...","category":"page"},{"location":"output/#Further-options","page":"NetCDF output","title":"Further options","text":"","category":"section"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"Further options are described in the OutputWriter docstring, (also accessible via julia>?OutputWriter for example). Note that some fields are actual options, but others are derived from the options you provided or are arrays/objects the output writer needs, but shouldn't be passed on by the user. The actual options are declared as [OPTION] in the following","category":"page"},{"location":"output/","page":"NetCDF output","title":"NetCDF output","text":"OutputWriter","category":"page"},{"location":"functions/#Function-and-type-index","page":"Function and type index","title":"Function and type index","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"Modules = [SpeedyWeather]","category":"page"},{"location":"functions/#SpeedyWeather.AbstractDevice","page":"Function and type index","title":"SpeedyWeather.AbstractDevice","text":"abstract type AbstractDevice\n\nSupertype of all devices SpeedyWeather.jl can ran on\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.BarotropicModel","page":"Function and type index","title":"SpeedyWeather.BarotropicModel","text":"The BarotropicModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.\n\nspectral_grid::SpectralGrid: dictates resolution for many other components\nplanet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics\natmosphere::SpeedyWeather.AbstractAtmosphere\nforcing::SpeedyWeather.AbstractForcing{NF} where NF<:AbstractFloat\ninitial_conditions::SpeedyWeather.InitialConditions\ntime_stepping::SpeedyWeather.TimeStepper{NF} where NF<:AbstractFloat\nspectral_transform::SpectralTransform\nhorizontal_diffusion::SpeedyWeather.HorizontalDiffusion{NF} where NF<:AbstractFloat\nimplicit::SpeedyWeather.AbstractImplicit{NF} where NF<:AbstractFloat\ngeometry::Geometry\nconstants::DynamicsConstants\ndevice_setup::SpeedyWeather.DeviceSetup\noutput::SpeedyWeather.AbstractOutputWriter\nfeedback::SpeedyWeather.AbstractFeedback\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.CPUDevice","page":"Function and type index","title":"SpeedyWeather.CPUDevice","text":"CPUDevice <: AbstractDevice\n\nIndicates that SpeedyWeather.jl runs on a single CPU \n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Clock","page":"Function and type index","title":"SpeedyWeather.Clock","text":"Clock struct keeps track of the model time, how many days to integrate for and how many time steps this takes\n\ntime::Dates.DateTime: current model time\nn_days::Float64: number of days to integrate for, set in run!(::Simulation)\nn_timesteps::Int64: number of time steps to integrate for, set in initialize!(::Clock,::TimeStepper)\n\n.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ColumnVariables","page":"Function and type index","title":"SpeedyWeather.ColumnVariables","text":"Mutable struct that contains all prognostic (copies thereof) and diagnostic variables in a single column needed to evaluate the physical parametrizations. For now the struct is mutable as we will reuse the struct to iterate over horizontal grid points. Every column vector has nlev entries, from [1] at the top to [end] at the lowermost model level at the planetary boundary layer.\n\nnlev::Int64\nnband::Int64\nn_stratosphere_levels::Int64\njring::Int64\nlond::AbstractFloat\nlatd::AbstractFloat\nu::Vector{NF} where NF<:AbstractFloat\nv::Vector{NF} where NF<:AbstractFloat\ntemp::Vector{NF} where NF<:AbstractFloat\nhumid::Vector{NF} where NF<:AbstractFloat\nln_pres::Vector{NF} where NF<:AbstractFloat\npres::Vector{NF} where NF<:AbstractFloat\nu_tend::Vector{NF} where NF<:AbstractFloat\nv_tend::Vector{NF} where NF<:AbstractFloat\ntemp_tend::Vector{NF} where NF<:AbstractFloat\nhumid_tend::Vector{NF} where NF<:AbstractFloat\ngeopot::Vector{NF} where NF<:AbstractFloat\nflux_u_upward::Vector{NF} where NF<:AbstractFloat\nflux_u_downward::Vector{NF} where NF<:AbstractFloat\nflux_v_upward::Vector{NF} where NF<:AbstractFloat\nflux_v_downward::Vector{NF} where NF<:AbstractFloat\nflux_temp_upward::Vector{NF} where NF<:AbstractFloat\nflux_temp_downward::Vector{NF} where NF<:AbstractFloat\nflux_humid_upward::Vector{NF} where NF<:AbstractFloat\nflux_humid_downward::Vector{NF} where NF<:AbstractFloat\nsat_humid::Vector{NF} where NF<:AbstractFloat\nsat_vap_pres::Vector{NF} where NF<:AbstractFloat\ndry_static_energy::Vector{NF} where NF<:AbstractFloat\nmoist_static_energy::Vector{NF} where NF<:AbstractFloat\nhumid_half::Vector{NF} where NF<:AbstractFloat\nsat_humid_half::Vector{NF} where NF<:AbstractFloat\nsat_moist_static_energy::Vector{NF} where NF<:AbstractFloat\ndry_static_energy_half::Vector{NF} where NF<:AbstractFloat\nsat_moist_static_energy_half::Vector{NF} where NF<:AbstractFloat\nconditional_instability::Bool\nactivate_convection::Bool\ncloud_top::Int64\nexcess_humidity::AbstractFloat\ncloud_base_mass_flux::AbstractFloat\nprecip_convection::AbstractFloat\nnet_flux_humid::Vector{NF} where NF<:AbstractFloat\nnet_flux_dry_static_energy::Vector{NF} where NF<:AbstractFloat\nentrainment_profile::Vector{NF} where NF<:AbstractFloat\nprecip_large_scale::AbstractFloat\nwvi::Matrix{NF} where NF<:AbstractFloat\ntau2::Matrix{NF} where NF<:AbstractFloat\ndfabs::Vector{NF} where NF<:AbstractFloat\nfsfcd::AbstractFloat\nst4a::Matrix{NF} where NF<:AbstractFloat\nflux::Vector{NF} where NF<:AbstractFloat\nfsfcu::AbstractFloat\nts::AbstractFloat\nfsfc::AbstractFloat\nftop::AbstractFloat\nstratc::Vector{NF} where NF<:AbstractFloat\ntyear::AbstractFloat\ncsol::AbstractFloat\ntopsr::AbstractFloat\nfsol::AbstractFloat\nozupp::AbstractFloat\nozone::AbstractFloat\nzenit::AbstractFloat\nstratz::AbstractFloat\nalbsfc::AbstractFloat\nssrd::AbstractFloat\nssr::AbstractFloat\ntsr::AbstractFloat\ntend_t_rsw::Vector{NF} where NF<:AbstractFloat\nnorm_pres::AbstractFloat\nicltop::Int64\ncloudc::AbstractFloat\nclstr::AbstractFloat\nqcloud::AbstractFloat\nfmask::AbstractFloat\nrel_hum::Vector{NF} where NF<:AbstractFloat\ngrad_dry_static_energy::AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.DeviceSetup","page":"Function and type index","title":"SpeedyWeather.DeviceSetup","text":"DeviceSetup{S<:AbstractDevice}\n\nHolds information about the device the model is running on and workgroup size. \n\ndevice::AbstractDevice: Device the model is running on \ndevice_KA::KernelAbstractions.Device: Device for use with KernelAbstractions\nn: workgroup size \n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.DiagnosticVariables","page":"Function and type index","title":"SpeedyWeather.DiagnosticVariables","text":"DiagnosticVariables{Grid<:AbstractGrid,NF<:AbstractFloat}\n\nStruct holding the diagnostic variables.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.DynamicsConstants","page":"Function and type index","title":"SpeedyWeather.DynamicsConstants","text":"Struct holding constants needed at runtime for the dynamical core in number format NF.\n\nradius::AbstractFloat: Radius of Planet [m]\nrotation::AbstractFloat: Angular frequency of Planet's rotation [1/s]\ngravity::AbstractFloat: Gravitational acceleration [m/s^2]\nlayer_thickness::AbstractFloat: shallow water layer thickness [m]\nR_dry::AbstractFloat: specific gas constant for dry air [J/kg/K]\nR_vapour::AbstractFloat: specific gas constant for water vapour [J/kg/K]\nμ_virt_temp::AbstractFloat: used in Tv = T(1+μq) for virt temp Tv(T,q) calculation\ncₚ::AbstractFloat: specific heat at constant pressure [J/K/kg]\nκ::AbstractFloat: = R_dry/cₚ, gas const for air over heat capacity\nwater_density::AbstractFloat: water density [kg/m³]\nf_coriolis::Vector{NF} where NF<:AbstractFloat: coriolis frequency 1/s = 2Ωsin(lat)radius\nσ_lnp_A::Vector{NF} where NF<:AbstractFloat: σ-related factor A needed for adiabatic terms\nσ_lnp_B::Vector{NF} where NF<:AbstractFloat: σ-related factor B needed for adiabatic terms\nΔp_geopot_half::Vector{NF} where NF<:AbstractFloat: = R*(ln(pk+1) - ln(pk+1/2)), for half level geopotential\nΔp_geopot_full::Vector{NF} where NF<:AbstractFloat: = R*(ln(pk+1/2) - ln(pk)), for full level geopotential\ntemp_ref_profile::Vector{NF} where NF<:AbstractFloat: reference temperature profile\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.DynamicsConstants-Tuple{SpectralGrid, SpeedyWeather.AbstractPlanet, SpeedyWeather.AbstractAtmosphere, Geometry}","page":"Function and type index","title":"SpeedyWeather.DynamicsConstants","text":"DynamicsConstants(\n spectral_grid::SpectralGrid,\n planet::SpeedyWeather.AbstractPlanet,\n atmosphere::SpeedyWeather.AbstractAtmosphere,\n geometry::Geometry\n) -> Any\n\n\nGenerator function for a DynamicsConstants struct.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.DynamicsVariables","page":"Function and type index","title":"SpeedyWeather.DynamicsVariables","text":"DynamicsVariables{Grid<:AbstractGrid,NF<:AbstractFloat}\n\nStruct holding intermediate quantities for the dynamics of a given layer.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Earth","page":"Function and type index","title":"SpeedyWeather.Earth","text":"Create a struct Earth<:AbstractPlanet, with the following physical/orbital characteristics. Note that radius is not part of it as this should be chosen in SpectralGrid. Keyword arguments are\n\nrotation::Float64: angular frequency of Earth's rotation [rad/s]\ngravity::Float64: gravitational acceleration [m/s^2]\ndaily_cycle::Bool: switch on/off daily cycle\nlength_of_day::Float64: [hrs] in a day\nseasonal_cycle::Bool: switch on/off seasonal cycle\nlength_of_year::Float64: [days] in a year\nequinox::Dates.DateTime: time of spring equinox (year irrelevant)\naxial_tilt::Float64: angle [˚] rotation axis tilt wrt to orbit\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.EarthAtmosphere","page":"Function and type index","title":"SpeedyWeather.EarthAtmosphere","text":"Create a struct EarthAtmosphere<:AbstractPlanet, with the following physical/chemical characteristics. Note that radius is not part of it as this should be chosen in SpectralGrid. Keyword arguments are\n\nmol_mass_dry_air::Float64: molar mass of dry air [g/mol]\nmol_mass_vapour::Float64: molar mass of water vapour [g/mol]\ncₚ::Float64: specific heat at constant pressure [J/K/kg]\nR_gas::Float64: universal gas constant [J/K/mol]\nR_dry::Float64: specific gas constant for dry air [J/kg/K]\nR_vapour::Float64: specific gas constant for water vapour [J/kg/K]\nwater_density::Float64: water density [kg/m³]\nlatent_heat_condensation::Float64: latent heat of condensation [J/g] for consistency with specific humidity [g/Kg], also called alhc\nlatent_heat_sublimation::Float64: latent heat of sublimation [J/g], also called alhs\nstefan_boltzmann::Float64: stefan-Boltzmann constant [W/m²/K⁴]\nlapse_rate::Float64: moist adiabatic temperature lapse rate -dTdz [K/km]\ntemp_ref::Float64: absolute temperature at surface z=0 [K]\ntemp_top::Float64: absolute temperature in stratosphere [K]\nΔT_stratosphere::Float64: for stratospheric lapse rate [K] after Jablonowski\nσ_tropopause::Float64: start of the stratosphere in sigma coordinates\nσ_boundary_layer::Float64: top of the planetary boundary layer in sigma coordinates\nscale_height::Float64: scale height for pressure [km]\npres_ref::Float64: surface pressure [hPa]\nscale_height_humid::Float64: scale height for specific humidity [km]\nrelhumid_ref::Float64: relative humidity of near-surface air [1]\nwater_pres_ref::Float64: saturation water vapour pressure [Pa]\nlayer_thickness::Float64: layer thickness for the shallow water model [km]\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.EarthOrography","page":"Function and type index","title":"SpeedyWeather.EarthOrography","text":"Earth's orography read from file, with smoothing.\n\npath::String: path to the folder containing the orography file, pkg path default\nfile::String: filename of orography\nfile_Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: Grid the orography file comes on\nscale::Float64: scale orography by a factor\nsmoothing::Bool: smooth the orography field?\nsmoothing_power::Float64: power of Laplacian for smoothing\nsmoothing_strength::Float64: highest degree l is multiplied by\nsmoothing_truncation::Int64: resolution of orography in spectral trunc\norography::SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat: height [m] on grid-point space.\ngeopot_surf::LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat: surface geopotential, height*gravity [m²/s²]\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.EarthOrography-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.EarthOrography","text":"EarthOrography(\n spectral_grid::SpectralGrid;\n kwargs...\n) -> Any\n\n\nGenerator function pulling the resolution information from spectral_grid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.Feedback","page":"Function and type index","title":"SpeedyWeather.Feedback","text":"Feedback() -> Feedback\nFeedback(verbose::Bool) -> Feedback\nFeedback(verbose::Bool, debug::Bool) -> Feedback\n\n\nGenerator function for a Feedback struct.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Feedback-2","page":"Function and type index","title":"SpeedyWeather.Feedback","text":"Feedback struct that contains options and object for command-line feedback like the progress meter.\n\nverbose::Bool: print feedback to REPL?\ndebug::Bool: check for NaRs in the prognostic variables\noutput::Bool: write a progress.txt file? State synced with OutputWriter.output\nid::Union{Int64, String}: identification of run, taken from ::OutputWriter\nrun_path::String: path to run folder, taken from ::OutputWriter\nprogress_meter::ProgressMeter.Progress: struct containing everything progress related\nprogress_txt::Union{Nothing, IOStream}: txt is a Nothing in case of no output\nnars_detected::Bool: did Infs/NaNs occur in the simulation?\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.GPUDevice","page":"Function and type index","title":"SpeedyWeather.GPUDevice","text":"GPUDevice <: AbstractDevice\n\nIndicates that SpeedyWeather.jl runs on a single GPU\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.GenLogisticCoefs","page":"Function and type index","title":"SpeedyWeather.GenLogisticCoefs","text":"Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF.\n\nFollowing the notation of https://en.wikipedia.org/wiki/Generalisedlogisticfunction (Dec 15 2021).\n\nChange default parameters for more/fewer levels in the stratosphere vs troposphere vs boundary layer.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Geometry","page":"Function and type index","title":"SpeedyWeather.Geometry","text":"Construct Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. Pass on SpectralGrid to calculate the following fields\n\nspectral_grid::SpectralGrid: SpectralGrid that defines spectral and grid resolution\nGrid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid of the dynamical core\nnlat_half::Int64: resolution parameter nlat_half of Grid, # of latitudes on one hemisphere (incl Equator)\nnlon_max::Int64: maximum number of longitudes (at/around Equator)\nnlon::Int64: =nlon_max, same (used for compatibility), TODO: still needed?\nnlat::Int64: number of latitude rings\nnlev::Int64: number of vertical levels\nnpoints::Int64: total number of grid points\nradius::AbstractFloat: Planet's radius [m]\nlatd::Vector{Float64}: array of latitudes in degrees (90˚...-90˚)\nlond::Vector{Float64}: array of longitudes in degrees (0...360˚), empty for non-full grids\nlonds::Vector{NF} where NF<:AbstractFloat: longitude (-180˚...180˚) for each grid point in ring order\nlatds::Vector{NF} where NF<:AbstractFloat: latitude (-90˚...˚90) for each grid point in ring order\nsinlat::Vector{NF} where NF<:AbstractFloat: sin of latitudes\ncoslat::Vector{NF} where NF<:AbstractFloat: cos of latitudes\ncoslat⁻¹::Vector{NF} where NF<:AbstractFloat: = 1/cos(lat)\ncoslat²::Vector{NF} where NF<:AbstractFloat: = cos²(lat)\ncoslat⁻²::Vector{NF} where NF<:AbstractFloat: # = 1/cos²(lat)\nσ_levels_half::Vector{NF} where NF<:AbstractFloat: σ at half levels, σ_k+1/2\nσ_levels_full::Vector{NF} where NF<:AbstractFloat: σ at full levels, σₖ\nσ_levels_thick::Vector{NF} where NF<:AbstractFloat: σ level thicknesses, σₖ₊₁ - σₖ\nln_σ_levels_full::Vector{NF} where NF<:AbstractFloat: log of σ at full levels, include surface (σ=1) as last element\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Geometry-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.Geometry","text":"Geometry(spectral_grid::SpectralGrid) -> Any\n\n\nGenerator function for Geometry struct based on spectral_grid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.GridVariables","page":"Function and type index","title":"SpeedyWeather.GridVariables","text":"GridVariables{NF<:AbstractFloat}\n\nStruct holding the prognostic spectral variables of a given layer in grid point space.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.HeldSuarez","page":"Function and type index","title":"SpeedyWeather.HeldSuarez","text":"Struct that defines the temperature relaxation from Held and Suarez, 1996 BAMS\n\nnlat::Int64: number of latitude rings\nnlev::Int64: number of vertical levels\nσb::Float64: sigma coordinate below which faster surface relaxation is applied\nrelax_time_slow::Float64: time scale [hrs] for slow global relaxation\nrelax_time_fast::Float64: time scale [hrs] for faster tropical surface relaxation\nTmin::Float64: minimum equilibrium temperature [K]\nTmax::Float64: maximum equilibrium temperature [K]\nΔTy::Float64: meridional temperature gradient [K]\nΔθz::Float64: vertical temperature gradient [K]\nκ::Base.RefValue{NF} where NF<:AbstractFloat\np₀::Base.RefValue{NF} where NF<:AbstractFloat\ntemp_relax_freq::Matrix{NF} where NF<:AbstractFloat\ntemp_equil_a::Vector{NF} where NF<:AbstractFloat\ntemp_equil_b::Vector{NF} where NF<:AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.HeldSuarez-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.HeldSuarez","text":"HeldSuarez(SG::SpectralGrid; kwargs...) -> Any\n\n\ncreate a HeldSuarez temperature relaxation with arrays allocated given spectral_grid\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.HyperDiffusion","page":"Function and type index","title":"SpeedyWeather.HyperDiffusion","text":"Struct for horizontal hyper diffusion of vor, div, temp; implicitly in spectral space with a power of the Laplacian (default=4) and the strength controlled by time_scale. Options exist to scale the diffusion by resolution, and adaptive depending on the current vorticity maximum to increase diffusion in active layers. Furthermore the power can be decreased above the tapering_σ to power_stratosphere (default 2). For Barotropic, ShallowWater, the default non-adaptive constant-time scale hyper diffusion is used. Options are\n\ntrunc::Int64: spectral resolution\nnlev::Int64: number of vertical levels\npower::Float64: power of Laplacian\ntime_scale::Float64: diffusion time scales [hrs]\nresolution_scaling::Float64: stronger diffusion with resolution? 0: constant with trunc, 1: (inverse) linear with trunc, etc\npower_stratosphere::Float64: different power for tropopause/stratosphere\ntapering_σ::Float64: linearly scale towards power_stratosphere above this σ\nadaptive::Bool: adaptive = higher diffusion for layers with higher vorticity levels.\nvor_max::Float64: above this (absolute) vorticity level [1/s], diffusion is increased\nadaptive_strength::Float64: increase strength above vor_max by this factor times max(abs(vor))/vor_max\n∇²ⁿ_2D::Vector\n∇²ⁿ_2D_implicit::Vector\n∇²ⁿ::Array{Vector{NF}, 1} where NF\n∇²ⁿ_implicit::Array{Vector{NF}, 1} where NF\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.HyperDiffusion-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.HyperDiffusion","text":"HyperDiffusion(\n spectral_grid::SpectralGrid;\n kwargs...\n) -> Any\n\n\nGenerator function based on the resolutin in spectral_grid. Passes on keyword arguments.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.ImplicitPrimitiveEq","page":"Function and type index","title":"SpeedyWeather.ImplicitPrimitiveEq","text":"Struct that holds various precomputed arrays for the semi-implicit correction to prevent gravity waves from amplifying in the primitive equation model.\n\ntrunc::Int64: spectral resolution\nnlev::Int64: number of vertical levels\nα::Float64: time-step coefficient: 0=explicit, 0.5=centred implicit, 1=backward implicit\ntemp_profile::Vector{NF} where NF<:AbstractFloat: vertical temperature profile, obtained from diagn\nξ::Base.RefValue{NF} where NF<:AbstractFloat: time step 2α*Δt packed in RefValue for mutability\nR::Matrix{NF} where NF<:AbstractFloat: divergence: operator for the geopotential calculation\nU::Vector{NF} where NF<:AbstractFloat: divergence: the -RdTₖ∇² term excl the eigenvalues from ∇² for divergence\nL::Matrix{NF} where NF<:AbstractFloat: temperature: operator for the TₖD + κTₖDlnps/Dt term\nW::Vector{NF} where NF<:AbstractFloat: pressure: vertical averaging of the -D̄ term in the log surface pres equation\nL0::Vector{NF} where NF<:AbstractFloat: components to construct L, 1/ 2Δσ\nL1::Matrix{NF} where NF<:AbstractFloat: vert advection term in the temperature equation (below+above)\nL2::Vector{NF} where NF<:AbstractFloat: factor in front of the divsumabove term\nL3::Matrix{NF} where NF<:AbstractFloat: sumabove operator itself\nL4::Vector{NF} where NF<:AbstractFloat: factor in front of div term in Dlnps/Dt\nS::Matrix{NF} where NF<:AbstractFloat: for every l the matrix to be inverted\nS⁻¹::Array{NF, 3} where NF<:AbstractFloat: combined inverted operator: S = 1 - ξ²(RL + UW)\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ImplicitPrimitiveEq-Tuple{SpectralGrid, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.ImplicitPrimitiveEq","text":"ImplicitPrimitiveEq(\n spectral_grid::SpectralGrid,\n kwargs...\n) -> Any\n\n\nGenerator using the resolution from SpectralGrid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.ImplicitShallowWater","page":"Function and type index","title":"SpeedyWeather.ImplicitShallowWater","text":"Struct that holds various precomputed arrays for the semi-implicit correction to prevent gravity waves from amplifying in the shallow water model.\n\ntrunc::Int64\nα::Float64: coefficient for semi-implicit computations to filter gravity waves\nH::Base.RefValue{NF} where NF<:AbstractFloat\nξH::Base.RefValue{NF} where NF<:AbstractFloat\ng∇²::Vector{NF} where NF<:AbstractFloat\nξg∇²::Vector{NF} where NF<:AbstractFloat\nS⁻¹::Vector{NF} where NF<:AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ImplicitShallowWater-Tuple{SpectralGrid, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.ImplicitShallowWater","text":"ImplicitShallowWater(\n spectral_grid::SpectralGrid,\n kwargs...\n) -> Any\n\n\nGenerator using the resolution from spectral_grid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.JablonowskiRelaxation","page":"Function and type index","title":"SpeedyWeather.JablonowskiRelaxation","text":"HeldSuarez-like temperature relaxation, but towards the Jablonowski temperature profile with increasing temperatures in the stratosphere.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.JablonowskiRelaxation-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.JablonowskiRelaxation","text":"JablonowskiRelaxation(SG::SpectralGrid; kwargs...) -> Any\n\n\ncreate a JablonowskiRelaxation temperature relaxation with arrays allocated given spectral_grid\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.Keepbits","page":"Function and type index","title":"SpeedyWeather.Keepbits","text":"Number of mantissa bits to keep for each prognostic variable when compressed for netCDF and .jld2 data output.\n\nu::Int64\nv::Int64\nvor::Int64\ndiv::Int64\ntemp::Int64\npres::Int64\nhumid::Int64\nprecip_cond::Int64\nprecip_conv::Int64\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Leapfrog","page":"Function and type index","title":"SpeedyWeather.Leapfrog","text":"Leapfrog time stepping defined by the following fields\n\ntrunc::Int64: spectral resolution (max degree of spherical harmonics)\nΔt_at_T31::Float64: time step in minutes for T31, scale linearly to trunc\nradius::Any: radius of sphere [m], used for scaling\nrobert_filter::Any: Robert (1966) time filter coefficeint to suppress comput. mode\nwilliams_filter::Any: Williams time filter (Amezcua 2011) coefficient for 3rd order acc\nΔt_sec::Int64: time step Δt [s] at specified resolution\nΔt::Any: time step Δt [s/m] at specified resolution, scaled by 1/radius\nΔt_hrs::Float64: convert time step Δt from minutes to hours\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Leapfrog-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.Leapfrog","text":"Leapfrog(spectral_grid::SpectralGrid; kwargs...) -> Any\n\n\nGenerator function for a Leapfrog struct using spectral_grid for the resolution information.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.LinearDrag","page":"Function and type index","title":"SpeedyWeather.LinearDrag","text":"Linear boundary layer drag Following Held and Suarez, 1996 BAMS\n\nσb::Float64\ntime_scale::Float64\nnlev::Int64\ndrag_coefs::Vector{NF} where NF<:AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.LinearDrag-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.LinearDrag","text":"LinearDrag(SG::SpectralGrid; kwargs...) -> Any\n\n\nGenerator function using nlev from SG::SpectralGrid\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.MagnusCoefs","page":"Function and type index","title":"SpeedyWeather.MagnusCoefs","text":"Parameters for computing saturation vapour pressure using the August-Roche-Magnus formula,\n\neᵢ(T) = e₀ * exp(Cᵢ * (T - T₀) / (T - Tᵢ)),\n\nwhere T is in Kelvin and i = 1,2 for saturation with respect to water and ice, respectively.\n\ne₀::AbstractFloat: Saturation vapour pressure at 0°C [Pa]\nT₀::AbstractFloat: 0°C in Kelvin\nT₁::AbstractFloat\nT₂::AbstractFloat\nC₁::AbstractFloat\nC₂::AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.NoBoundaryLayerDrag","page":"Function and type index","title":"SpeedyWeather.NoBoundaryLayerDrag","text":"Concrete type that disables the boundary layer drag scheme.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.NoOrography","page":"Function and type index","title":"SpeedyWeather.NoOrography","text":"Orography with zero height in orography and zero surface geopotential geopot_surf.\n\norography::SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat: height [m] on grid-point space.\ngeopot_surf::LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat: surface geopotential, height*gravity [m²/s²]\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.NoOrography-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.NoOrography","text":"NoOrography(spectral_grid::SpectralGrid) -> NoOrography\n\n\nGenerator function pulling the resolution information from spectral_grid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.OutputWriter","page":"Function and type index","title":"SpeedyWeather.OutputWriter","text":"NetCDF output writer. Contains all output options and auxiliary fields for output interpolation. To be initialised with OutputWriter(::SpectralGrid,::Type{<:ModelSetup},kwargs...) to pass on the resolution information and the model type which chooses which variables to output. Options include\n\nspectral_grid::SpectralGrid\noutput::Bool\npath::String: [OPTION] path to output folder, run_???? will be created within\nid::String: [OPTION] run identification number/string\nrun_path::String\nfilename::String: [OPTION] name of the output netcdf file\nwrite_restart::Bool: [OPTION] also write restart file if output==true?\npkg_version::VersionNumber\nstartdate::Dates.DateTime\noutput_dt::Float64: [OPTION] output frequency, time step [hrs]\noutput_dt_sec::Int64: actual output time step [sec]\noutput_vars::Vector{Symbol}: [OPTION] which variables to output, u, v, vor, div, pres, temp, humid\nmissing_value::Union{Float32, Float64}: [OPTION] missing value to be used in netcdf output\ncompression_level::Int64: [OPTION] lossless compression level; 1=low but fast, 9=high but slow\nkeepbits::SpeedyWeather.Keepbits: [OPTION] mantissa bits to keep for every variable\noutput_every_n_steps::Int64\ntimestep_counter::Int64\noutput_counter::Int64\nnetcdf_file::Union{Nothing, NetCDF.NcFile}\ninput_Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}\nas_matrix::Bool: [OPTION] sort grid points into a matrix (interpolation-free), for OctahedralClenshawGrid, OctaHEALPixGrid only\nquadrant_rotation::NTuple{4, Int64}\nmatrix_quadrant::NTuple{4, Tuple{Int64, Int64}}\noutput_Grid::Type{<:SpeedyWeather.RingGrids.AbstractFullGrid}: [OPTION] the grid used for output, full grids only\nnlat_half::Int64: [OPTION] the resolution of the output grid, default: same nlat_half as in the dynamical core\nnlon::Int64\nnlat::Int64\nnpoints::Int64\nnlev::Int64\ninterpolator::SpeedyWeather.RingGrids.AbstractInterpolator\nu::Matrix{NF} where NF<:Union{Float32, Float64}\nv::Matrix{NF} where NF<:Union{Float32, Float64}\nvor::Matrix{NF} where NF<:Union{Float32, Float64}\ndiv::Matrix{NF} where NF<:Union{Float32, Float64}\ntemp::Matrix{NF} where NF<:Union{Float32, Float64}\npres::Matrix{NF} where NF<:Union{Float32, Float64}\nhumid::Matrix{NF} where NF<:Union{Float32, Float64}\nprecip_cond::Matrix{NF} where NF<:Union{Float32, Float64}\nprecip_conv::Matrix{NF} where NF<:Union{Float32, Float64}\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.PrimitiveDryModel","page":"Function and type index","title":"SpeedyWeather.PrimitiveDryModel","text":"The PrimitiveDryModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.\n\nspectral_grid::SpectralGrid: dictates resolution for many other components\nplanet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics\natmosphere::SpeedyWeather.AbstractAtmosphere\ninitial_conditions::SpeedyWeather.InitialConditions\norography::SpeedyWeather.AbstractOrography{NF} where NF<:AbstractFloat\nphysics::Bool\nboundary_layer_drag::SpeedyWeather.BoundaryLayerDrag{NF} where NF<:AbstractFloat\ntemperature_relaxation::SpeedyWeather.TemperatureRelaxation{NF} where NF<:AbstractFloat\nstatic_energy_diffusion::SpeedyWeather.VerticalDiffusion{NF} where NF<:AbstractFloat\nvertical_advection::SpeedyWeather.VerticalAdvection{NF} where NF<:AbstractFloat\ntime_stepping::SpeedyWeather.TimeStepper{NF} where NF<:AbstractFloat\nspectral_transform::SpectralTransform\nhorizontal_diffusion::SpeedyWeather.HorizontalDiffusion{NF} where NF<:AbstractFloat\nimplicit::SpeedyWeather.AbstractImplicit{NF} where NF<:AbstractFloat\ngeometry::Geometry\nconstants::DynamicsConstants\ndevice_setup::SpeedyWeather.DeviceSetup\noutput::SpeedyWeather.AbstractOutputWriter\nfeedback::SpeedyWeather.AbstractFeedback\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.PrimitiveWetModel","page":"Function and type index","title":"SpeedyWeather.PrimitiveWetModel","text":"The PrimitiveDryModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.\n\nspectral_grid::SpectralGrid: dictates resolution for many other components\nplanet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics\natmosphere::SpeedyWeather.AbstractAtmosphere\ninitial_conditions::SpeedyWeather.InitialConditions\norography::SpeedyWeather.AbstractOrography{NF} where NF<:AbstractFloat\nphysics::Bool\nthermodynamics::SpeedyWeather.Thermodynamics{NF} where NF<:AbstractFloat\nboundary_layer_drag::SpeedyWeather.BoundaryLayerDrag{NF} where NF<:AbstractFloat\ntemperature_relaxation::SpeedyWeather.TemperatureRelaxation{NF} where NF<:AbstractFloat\nstatic_energy_diffusion::SpeedyWeather.VerticalDiffusion{NF} where NF<:AbstractFloat\nlarge_scale_condensation::SpeedyWeather.AbstractCondensation{NF} where NF<:AbstractFloat\nvertical_advection::SpeedyWeather.VerticalAdvection{NF} where NF<:AbstractFloat\ntime_stepping::SpeedyWeather.TimeStepper{NF} where NF<:AbstractFloat\nspectral_transform::SpectralTransform\nhorizontal_diffusion::SpeedyWeather.HorizontalDiffusion{NF} where NF<:AbstractFloat\nimplicit::SpeedyWeather.AbstractImplicit{NF} where NF<:AbstractFloat\ngeometry::Geometry\nconstants::DynamicsConstants\ndevice_setup::SpeedyWeather.DeviceSetup\noutput::SpeedyWeather.AbstractOutputWriter\nfeedback::SpeedyWeather.AbstractFeedback\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ShallowWaterModel","page":"Function and type index","title":"SpeedyWeather.ShallowWaterModel","text":"The ShallowWaterModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration.\n\nspectral_grid::SpectralGrid: dictates resolution for many other components\nplanet::SpeedyWeather.AbstractPlanet: contains physical and orbital characteristics\natmosphere::SpeedyWeather.AbstractAtmosphere\nforcing::SpeedyWeather.AbstractForcing{NF} where NF<:AbstractFloat\ninitial_conditions::SpeedyWeather.InitialConditions\norography::SpeedyWeather.AbstractOrography{NF} where NF<:AbstractFloat\ntime_stepping::SpeedyWeather.TimeStepper{NF} where NF<:AbstractFloat\nspectral_transform::SpectralTransform\nhorizontal_diffusion::SpeedyWeather.HorizontalDiffusion{NF} where NF<:AbstractFloat\nimplicit::SpeedyWeather.AbstractImplicit{NF} where NF<:AbstractFloat\ngeometry::Geometry\nconstants::DynamicsConstants\ndevice_setup::SpeedyWeather.DeviceSetup\noutput::SpeedyWeather.AbstractOutputWriter\nfeedback::SpeedyWeather.AbstractFeedback\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Simulation","page":"Function and type index","title":"SpeedyWeather.Simulation","text":"Simulation is a container struct to be used with run!(::Simulation). It contains\n\nprognostic_variables::PrognosticVariables: define the current state of the model\ndiagnostic_variables::DiagnosticVariables: contain the tendencies and auxiliary arrays to compute them\nmodel::SpeedyWeather.ModelSetup: all parameters, constant at runtime\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.SpectralGrid","page":"Function and type index","title":"SpeedyWeather.SpectralGrid","text":"Defines the horizontal spectral resolution and corresponding grid and the vertical coordinate for SpeedyWeather.jl. Options are\n\nNF::Type{<:AbstractFloat}: number format used throughout the model\ntrunc::Int64: horizontal resolution as the maximum degree of spherical harmonics\nGrid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: horizontal grid used for calculations in grid-point space\ndealiasing::Float64: how to match spectral with grid resolution: dealiasing factor, 1=linear, 2=quadratic, 3=cubic grid\nradius::Float64: radius of the sphere [m]\nnlat_half::Int64: number of latitude rings on one hemisphere (Equator incl)\nnpoints::Int64: total number of grid points in the horizontal\nnlev::Int64: number of vertical levels\nvertical_coordinates::SpeedyWeather.VerticalCoordinates: coordinates used to discretize the vertical\n\nnlat_half and npoints should not be chosen but are derived from trunc, Grid and dealiasing.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.SpeedyCondensation","page":"Function and type index","title":"SpeedyWeather.SpeedyCondensation","text":"Large scale condensation as in Fortran SPEEDY with default values from therein.\n\nnlev::Int64: number of vertical levels\nthreshold_boundary_layer::Float64: Relative humidity threshold for boundary layer\nthreshold_range::Float64: Vertical range of relative humidity threshold\nthreshold_max::Float64: Maximum relative humidity threshold [1]\ntime_scale::Float64: Relaxation time for humidity [hrs]\nn_stratosphere_levels::Base.RefValue{Int64}\nhumid_tend_max::Vector{NF} where NF<:AbstractFloat\nrelative_threshold::Vector{NF} where NF<:AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.SpectralTransform-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"SpectralTransform(\n spectral_grid::SpectralGrid;\n recompute_legendre,\n kwargs...\n) -> SpectralTransform\n\n\nGenerator function for a SpectralTransform struct pulling in parameters from a SpectralGrid struct.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.StartFromFile","page":"Function and type index","title":"SpeedyWeather.StartFromFile","text":"Restart from a previous SpeedyWeather.jl simulation via the restart file restart.jld2 Applies interpolation in the horizontal but not in the vertical. restart.jld2 is identified by\n\npath::String: path for restart file\nid::Union{Int64, String}: run_id of restart file in run_????/restart.jld2\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.StartWithRandomVorticity","page":"Function and type index","title":"SpeedyWeather.StartWithRandomVorticity","text":"Start with random vorticity as initial conditions\n\npower::Float64: Power of the spectral distribution k^power\namplitude::Float64: (approximate) amplitude in [1/s], used as standard deviation of spherical harmonic coefficients\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.StaticEnergyDiffusion","page":"Function and type index","title":"SpeedyWeather.StaticEnergyDiffusion","text":"Diffusion of dry static energy: A relaxation towards a reference gradient of static energy wrt to geopotential, see Fortran SPEEDY documentation.\n\ntime_scale::Float64: time scale [hrs] for strength\nstatic_energy_lapse_rate::Float64: [1] ∂SE/∂Φ, vertical gradient of static energy SE with geopotential Φ\nFstar::Base.RefValue{NF} where NF<:AbstractFloat\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.Tendencies","page":"Function and type index","title":"SpeedyWeather.Tendencies","text":"Tendencies{Grid<:AbstractGrid,NF<:AbstractFloat}\n\nStruct holding the tendencies of the prognostic spectral variables for a given layer.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ZonalJet","page":"Function and type index","title":"SpeedyWeather.ZonalJet","text":"Create a struct that contains all parameters for the Galewsky et al, 2004 zonal jet intitial conditions for the shallow water model. Default values as in Galewsky.\n\nlatitude::Float64: jet latitude [˚N]\nwidth::Float64: jet width [˚], default ≈ 19.29˚\numax::Float64: jet maximum velocity [m/s]\nperturb_lat::Float64: perturbation latitude [˚N], position in jet by default\nperturb_lon::Float64: perturbation longitude [˚E]\nperturb_xwidth::Float64: perturbation zonal extent [˚], default ≈ 19.1˚\nperturb_ywidth::Float64: perturbation meridinoal extent [˚], default ≈ 3.8˚\nperturb_height::Float64: perturbation amplitude [m]\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ZonalRidge","page":"Function and type index","title":"SpeedyWeather.ZonalRidge","text":"Zonal ridge orography after Jablonowski and Williamson, 2006.\n\nη₀::Float64: conversion from σ to Jablonowski's ηᵥ-coordinates\nu₀::Float64: max amplitude of zonal wind [m/s] that scales orography height\norography::SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat: height [m] on grid-point space.\ngeopot_surf::LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat: surface geopotential, height*gravity [m²/s²]\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.ZonalRidge-Tuple{SpectralGrid}","page":"Function and type index","title":"SpeedyWeather.ZonalRidge","text":"ZonalRidge(spectral_grid::SpectralGrid; kwargs...) -> Any\n\n\nGenerator function pulling the resolution information from spectral_grid.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.ZonalWind","page":"Function and type index","title":"SpeedyWeather.ZonalWind","text":"Create a struct that contains all parameters for the Jablonowski and Williamson, 2006 intitial conditions for the primitive equation model. Default values as in Jablonowski.\n\nη₀::Float64: conversion from σ to Jablonowski's ηᵥ-coordinates\nu₀::Float64: max amplitude of zonal wind [m/s]\nperturb_lat::Float64: perturbation centred at [˚N]\nperturb_lon::Float64: perturbation centred at [˚E]\nperturb_uₚ::Float64: perturbation strength [m/s]\nperturb_radius::Float64: radius of Gaussian perturbation in units of Earth's radius [1]\nΔT::Float64: temperature difference used for stratospheric lapse rate [K], Jablonowski uses ΔT = 4.8e5 [K]\nTmin::Float64: minimum temperature [K] of profile\npressure_on_orography::Bool: initialize pressure given the atmosphere.lapse_rate on orography?\n\n\n\n\n\n","category":"type"},{"location":"functions/#Base.copy!-Tuple{PrognosticVariables, PrognosticVariables}","page":"Function and type index","title":"Base.copy!","text":"copy!(progn_new::PrognosticVariables, progn_old::PrognosticVariables)\n\nCopies entries of progn_old into progn_new. Only copies those variables that are present in the model of both progn_new and progn_old.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.Device-Tuple{}","page":"Function and type index","title":"SpeedyWeather.Device","text":"Device()\n\nReturn default used device for internal purposes, either CPUDevice or GPUDevice if a GPU is available.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.DeviceArray-Tuple{SpeedyWeather.GPUDevice, Any}","page":"Function and type index","title":"SpeedyWeather.DeviceArray","text":"DeviceArray(device::AbstractDevice, x)\n\nAdapts x to a CuArray when device<:GPUDevice is used, otherwise a regular Array. Uses adapt, thus also can return SubArrays etc.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.DeviceArrayNotAdapt-Tuple{SpeedyWeather.GPUDevice, Any}","page":"Function and type index","title":"SpeedyWeather.DeviceArrayNotAdapt","text":"DeviceArrayNotAdapt(device::AbstractDevice, x)\n\nReturns a CuArray when device<:GPUDevice is used, otherwise a regular Array. Doesn't uses adapt, therefore always returns CuArray/Array\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.Device_KernelAbstractions-Tuple{SpeedyWeather.CPUDevice}","page":"Function and type index","title":"SpeedyWeather.Device_KernelAbstractions","text":"Device_KernelAbstractions(::AbstractDevice)\n\nReturn used device for use with KernelAbstractions\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.Device_KernelAbstractions-Tuple{}","page":"Function and type index","title":"SpeedyWeather.Device_KernelAbstractions","text":"Device_KernelAbstractions()\n\nReturn default used device for KernelAbstractions, either CPU or CUDADevice if a GPU is available\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!-Tuple{DiagnosticVariables, PrognosticVariables, Int64, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!(\n diagn::DiagnosticVariables,\n progn::PrognosticVariables,\n lf::Int64,\n model::SpeedyWeather.ModelSetup\n)\n\n\nPropagate the spectral state of progn to diagn using time step/leapfrog index lf. Function barrier that calls gridded! for the respective model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticLayerTimesteps, Int64, Barotropic}","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::Barotropic\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticLayerTimesteps, Int64, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::PrimitiveEquation\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for primitive equation models. Updates grid vorticity, grid divergence, grid temperature, pressure (pres_grid) and the velocities u,v.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticLayerTimesteps, Int64, ShallowWater}","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n lf::Int64,\n model::ShallowWater\n)\n\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities u,v.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather._scale_lat!-Union{Tuple{NF}, Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}, AbstractVector}} where NF<:AbstractFloat","page":"Function and type index","title":"SpeedyWeather._scale_lat!","text":"_scale_lat!(\n A::SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n v::AbstractVector\n)\n\n\nGeneric latitude scaling applied to A in-place with latitude-like vector v.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.allocate-Union{Tuple{Model}, Tuple{Type{PrognosticVariables}, SpectralGrid, Type{Model}}} where Model<:SpeedyWeather.ModelSetup","page":"Function and type index","title":"SpeedyWeather.allocate","text":"allocate(\n _::Type{PrognosticVariables},\n spectral_grid::SpectralGrid,\n _::Type{Model<:SpeedyWeather.ModelSetup}\n) -> PrognosticVariables\n\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.bernoulli_potential!-Union{Tuple{NF}, Tuple{SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, SpectralTransform}} where NF","page":"Function and type index","title":"SpeedyWeather.bernoulli_potential!","text":"bernoulli_potential!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},\n S::SpectralTransform\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nComputes the Laplace operator ∇² of the Bernoulli potential B in spectral space.\n\ncomputes the kinetic energy KE = ½(u²+v²) on the grid\ntransforms KE to spectral space\nadds geopotential for the Bernoulli potential in spectral space\ntakes the Laplace operator.\n\nThis version is used for both ShallowWater and PrimitiveEquation, only the geopotential calculation in geopotential! differs.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.boundary_layer_drag!-Tuple{ColumnVariables, LinearDrag}","page":"Function and type index","title":"SpeedyWeather.boundary_layer_drag!","text":"boundary_layer_drag!(\n column::ColumnVariables,\n scheme::LinearDrag\n)\n\n\nCompute tendency for boundary layer drag of a column and add to its tendencies fields\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.boundary_layer_drag!-Tuple{ColumnVariables, SpeedyWeather.NoBoundaryLayerDrag, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.boundary_layer_drag!","text":"NoBoundaryLayer scheme just passes.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.clip_negatives!-Union{Tuple{AbstractArray{T}}, Tuple{T}} where T","page":"Function and type index","title":"SpeedyWeather.clip_negatives!","text":"clip_negatives!(A::AbstractArray)\n\nSet all negative entries a in A to zero.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.create_output_folder-Tuple{String, Union{Int64, String}}","page":"Function and type index","title":"SpeedyWeather.create_output_folder","text":"create_output_folder(\n path::String,\n id::Union{Int64, String}\n) -> String\n\n\nCreates a new folder run_* with the identification id. Also returns the full path run_path of that folder.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.default_sigma_coordinates-Tuple{Integer}","page":"Function and type index","title":"SpeedyWeather.default_sigma_coordinates","text":"default_sigma_coordinates(nlev::Integer) -> Any\n\n\nVertical sigma coordinates defined by their nlev+1 half levels σ_levels_half. Sigma coordinates are fraction of surface pressure (p/p0) and are sorted from top (stratosphere) to bottom (surface). The first half level is at 0 the last at 1. Evaluate a generalised logistic function with coefficients in P for the distribution of values in between. Default coefficients follow the L31 configuration historically used at ECMWF.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.dry_static_energy!-Tuple{ColumnVariables, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.dry_static_energy!","text":"dry_static_energy!(\n column::ColumnVariables,\n constants::DynamicsConstants\n)\n\n\nCompute the dry static energy SE = cₚT + Φ (latent heat times temperature plus geopotential) for the column.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.dynamics_tendencies!","page":"Function and type index","title":"SpeedyWeather.dynamics_tendencies!","text":"dynamics_tendencies!(\n diagn::DiagnosticVariables,\n progn::PrognosticVariables,\n model::PrimitiveEquation\n) -> Any\ndynamics_tendencies!(\n diagn::DiagnosticVariables,\n progn::PrognosticVariables,\n model::PrimitiveEquation,\n lf::Int64\n) -> Any\n\n\nCalculate all tendencies for the PrimitiveEquation model (wet or dry).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.dynamics_tendencies!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, Dates.DateTime, Barotropic}","page":"Function and type index","title":"SpeedyWeather.dynamics_tendencies!","text":"dynamics_tendencies!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n time::Dates.DateTime,\n model::Barotropic\n)\n\n\nCalculate all tendencies for the BarotropicModel.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.dynamics_tendencies!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.SurfaceVariables, LowerTriangularMatrix, Dates.DateTime, ShallowWater}","page":"Function and type index","title":"SpeedyWeather.dynamics_tendencies!","text":"dynamics_tendencies!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surface::SpeedyWeather.SurfaceVariables,\n pres::LowerTriangularMatrix,\n time::Dates.DateTime,\n model::ShallowWater\n)\n\n\nCalculate all tendencies for the ShallowWaterModel.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.first_timesteps!-Tuple{PrognosticVariables, DiagnosticVariables, SpeedyWeather.ModelSetup, SpeedyWeather.AbstractOutputWriter}","page":"Function and type index","title":"SpeedyWeather.first_timesteps!","text":"first_timesteps!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n model::SpeedyWeather.ModelSetup,\n output::SpeedyWeather.AbstractOutputWriter\n) -> typeof(time)\n\n\nPerforms the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.flipsign!-Tuple{AbstractArray}","page":"Function and type index","title":"SpeedyWeather.flipsign!","text":"flipgsign!(A::AbstractArray)\n\nLike -A but in-place.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.flux_divergence!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, SpeedyWeather.RingGrids.AbstractGrid{NF}, SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Geometry{NF}, SpectralTransform{NF}}} where NF","page":"Function and type index","title":"SpeedyWeather.flux_divergence!","text":"flux_divergence!(\n A_tend::LowerTriangularMatrix{Complex{NF}},\n A_grid::SpeedyWeather.RingGrids.AbstractGrid{NF},\n diagn::SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},\n G::Geometry{NF},\n S::SpectralTransform{NF};\n add,\n flipsign\n)\n\n\nComputes ∇⋅((u,v)*A) with the option to add/overwrite A_tend and to flip_sign of the flux divergence by doing so.\n\nA_tend = ∇⋅((u,v)*A) for add=false, flip_sign=false\nA_tend = -∇⋅((u,v)*A) for add=false, flip_sign=true\nA_tend += ∇⋅((u,v)*A) for add=true, flip_sign=false\nA_tend -= ∇⋅((u,v)*A) for add=true, flip_sign=true\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.fluxes_to_tendencies!-Tuple{ColumnVariables, Geometry, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.fluxes_to_tendencies!","text":"fluxes_to_tendencies!(\n column::ColumnVariables,\n geometry::Geometry,\n constants::DynamicsConstants\n)\n\n\nConvert the fluxes on half levels to tendencies on full levels.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.generalised_logistic-Tuple{Any, SpeedyWeather.GenLogisticCoefs}","page":"Function and type index","title":"SpeedyWeather.generalised_logistic","text":"Generalised logistic function based on the coefficients in coefs.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.geopotential!-Tuple{DiagnosticVariables, SpeedyWeather.AbstractOrography, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.geopotential!","text":"geopotential!(\n diagn::DiagnosticVariables,\n O::SpeedyWeather.AbstractOrography,\n C::DynamicsConstants\n)\n\n\nCompute spectral geopotential geopot from spectral temperature temp and spectral surface geopotential geopot_surf (orography*gravity).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.geopotential!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, LowerTriangularMatrix, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.geopotential!","text":"geopotential!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n pres::LowerTriangularMatrix,\n C::DynamicsConstants\n) -> Any\n\n\ncalculates the geopotential in the ShallowWaterModel as g*η, i.e. gravity times the interface displacement (field pres)\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.geopotential!-Tuple{Vector, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.geopotential!","text":"geopotential!(temp::Vector, C::DynamicsConstants) -> Vector\n\n\nCalculate the geopotential based on temp in a single column. This exclues the surface geopotential that would need to be added to the returned vector. Function not used in the dynamical core but for post-processing and analysis.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_column!-Tuple{ColumnVariables, DiagnosticVariables, Int64, Geometry}","page":"Function and type index","title":"SpeedyWeather.get_column!","text":"Recalculate ring index if not provided.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_column!-Tuple{ColumnVariables, DiagnosticVariables, Integer, Integer, Geometry}","page":"Function and type index","title":"SpeedyWeather.get_column!","text":"get_column!(\n C::ColumnVariables,\n D::DiagnosticVariables,\n ij::Integer,\n jring::Integer,\n G::Geometry\n)\n\n\nUpdate C::ColumnVariables by copying the prognostic variables from D::DiagnosticVariables at gridpoint index ij. Provide G::Geometry for coordinate information.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_full_output_file_path-Tuple{OutputWriter}","page":"Function and type index","title":"SpeedyWeather.get_full_output_file_path","text":"get_full_output_file_path(output::OutputWriter) -> String\n\n\nReturns the full path of the output file after it was created.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_run_id-Tuple{String, String}","page":"Function and type index","title":"SpeedyWeather.get_run_id","text":"get_run_id(path::String, id::String) -> String\n\n\nChecks existing run_???? folders in path to determine a 4-digit id number by counting up. E.g. if folder run_0001 exists it will return the string \"0002\". Does not create a folder for the returned run id.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_thermodynamics!-Tuple{ColumnVariables, PrimitiveDry}","page":"Function and type index","title":"SpeedyWeather.get_thermodynamics!","text":"get_thermodynamics!(\n column::ColumnVariables,\n model::PrimitiveDry\n)\n\n\nCalculate the dry static energy for the primitive dry model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_thermodynamics!-Tuple{ColumnVariables, PrimitiveWet}","page":"Function and type index","title":"SpeedyWeather.get_thermodynamics!","text":"get_thermodynamics!(\n column::ColumnVariables,\n model::PrimitiveWet\n)\n\n\nCalculate thermodynamic quantities like saturation vapour pressure, saturation specific humidity, dry static energy, moist static energy and saturation moist static energy from the prognostic column variables.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.get_var-Tuple{PrognosticVariables, Symbol}","page":"Function and type index","title":"SpeedyWeather.get_var","text":"get_var(progn::PrognosticVariables, var_name::Symbol; lf::Integer=1)\n\nReturns the prognostic variable var_name at leapfrog index lf as a Vector{LowerTriangularMatrices}.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.has-Tuple{Type{<:SpeedyWeather.ModelSetup}, Symbol}","page":"Function and type index","title":"SpeedyWeather.has","text":"has(\n M::Type{<:SpeedyWeather.ModelSetup},\n var_name::Symbol\n) -> Bool\n\n\nReturns true if the model M has a prognostic variable var_name, false otherwise. The default fallback is that all variables are included. \n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.horizontal_diffusion!","page":"Function and type index","title":"SpeedyWeather.horizontal_diffusion!","text":"horizontal_diffusion!(\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::PrimitiveEquation\n) -> Union{Nothing, Bool}\nhorizontal_diffusion!(\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::PrimitiveEquation,\n lf::Int64\n) -> Union{Nothing, Bool}\n\n\nApply horizontal diffusion applied to vorticity, diffusion and temperature in the PrimitiveEquation models. Uses the constant diffusion for temperature but possibly adaptive diffusion for vorticity and divergence.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.horizontal_diffusion!-2","page":"Function and type index","title":"SpeedyWeather.horizontal_diffusion!","text":"horizontal_diffusion!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n model::Barotropic\n)\nhorizontal_diffusion!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n model::Barotropic,\n lf::Int64\n)\n\n\nApply horizontal diffusion to vorticity in the Barotropic models.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.horizontal_diffusion!-3","page":"Function and type index","title":"SpeedyWeather.horizontal_diffusion!","text":"horizontal_diffusion!(\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::ShallowWater\n)\nhorizontal_diffusion!(\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::ShallowWater,\n lf::Int64\n)\n\n\nApply horizontal diffusion to vorticity and diffusion in the ShallowWater models.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.horizontal_diffusion!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, AbstractVector{NF}, AbstractVector{NF}}} where NF<:AbstractFloat","page":"Function and type index","title":"SpeedyWeather.horizontal_diffusion!","text":"horizontal_diffusion!(\n tendency::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n A::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n ∇²ⁿ_expl::AbstractArray{NF<:AbstractFloat, 1},\n ∇²ⁿ_impl::AbstractArray{NF<:AbstractFloat, 1}\n)\n\n\nApply horizontal diffusion to a 2D field A in spectral space by updating its tendency tendency with an implicitly calculated diffusion term. The implicit diffusion of the next time step is split into an explicit part ∇²ⁿ_expl and an implicit part ∇²ⁿ_impl, such that both can be calculated in a single forward step by using A as well as its tendency tendency.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.implicit_correction!-Tuple{DiagnosticVariables, SpeedyWeather.ImplicitPrimitiveEq, PrognosticVariables}","page":"Function and type index","title":"SpeedyWeather.implicit_correction!","text":"implicit_correction!(\n diagn::DiagnosticVariables,\n implicit::SpeedyWeather.ImplicitPrimitiveEq,\n progn::PrognosticVariables\n) -> Any\n\n\nApply the implicit corrections to dampen gravity waves in the primitive equation models.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.implicit_correction!-Union{Tuple{NF}, Tuple{SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, SpeedyWeather.PrognosticLayerTimesteps{NF}, SpeedyWeather.SurfaceVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, SpeedyWeather.PrognosticSurfaceTimesteps{NF}, SpeedyWeather.ImplicitShallowWater}} where NF","page":"Function and type index","title":"SpeedyWeather.implicit_correction!","text":"implicit_correction!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},\n progn::SpeedyWeather.PrognosticLayerTimesteps{NF},\n diagn_surface::SpeedyWeather.SurfaceVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF},\n progn_surface::SpeedyWeather.PrognosticSurfaceTimesteps{NF},\n implicit::SpeedyWeather.ImplicitShallowWater\n)\n\n\nApply correction to the tendencies in diagn to prevent the gravity waves from amplifying. The correction is implicitly evaluated using the parameter implicit.α to switch between forward, centered implicit or backward evaluation of the gravity wave terms.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initial_conditions!-Tuple{PrognosticVariables, StartFromFile, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.initial_conditions!","text":"initial_conditions!(\n progn_new::PrognosticVariables,\n initial_conditions::StartFromFile,\n model::SpeedyWeather.ModelSetup\n) -> PrognosticVariables\n\n\nRestart from a previous SpeedyWeather.jl simulation via the restart file restart.jld2 Applies interpolation in the horizontal but not in the vertical.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initial_conditions!-Tuple{PrognosticVariables, ZonalJet, ShallowWater}","page":"Function and type index","title":"SpeedyWeather.initial_conditions!","text":"initial_conditions!(\n progn::PrognosticVariables,\n initial_conditions::ZonalJet,\n model::ShallowWater\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nInitial conditions from Galewsky, 2004, Tellus\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initial_conditions!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, StartWithRandomVorticity, SpeedyWeather.ModelSetup}} where NF","page":"Function and type index","title":"SpeedyWeather.initial_conditions!","text":"initial_conditions!(\n progn::PrognosticVariables{NF},\n initial_conditions::StartWithRandomVorticity,\n model::SpeedyWeather.ModelSetup\n)\n\n\nStart with random vorticity as initial conditions\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initial_conditions!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, ZonalWind, PrimitiveEquation}} where NF","page":"Function and type index","title":"SpeedyWeather.initial_conditions!","text":"initial_conditions!(\n progn::PrognosticVariables{NF},\n initial_conditions::ZonalWind,\n model::PrimitiveEquation\n)\n\n\nInitial conditions from Jablonowski and Williamson, 2006, QJR Meteorol. Soc\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initial_conditions-Tuple{Model} where Model","page":"Function and type index","title":"SpeedyWeather.initial_conditions","text":"initial_conditions(model) -> PrognosticVariables\n\n\nAllocate the prognostic variables and then set to initial conditions.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::HyperDiffusion,\n k::Int64,\n G::Geometry,\n L::SpeedyWeather.TimeStepper\n)\ninitialize!(\n scheme::HyperDiffusion,\n k::Int64,\n G::Geometry,\n L::SpeedyWeather.TimeStepper,\n vor_max::Real\n)\n\n\nPrecomputes the hyper diffusion terms in scheme for layer k based on the model time step in L, the vertical level sigma level in G, and the current (absolute) vorticity maximum level vor_max\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{Barotropic}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(model::Barotropic) -> SpeedyWeather.Simulation\n\n\nCalls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping!.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{EarthOrography, SpeedyWeather.AbstractPlanet, SpectralTransform, Geometry}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n orog::EarthOrography,\n P::SpeedyWeather.AbstractPlanet,\n S::SpectralTransform,\n G::Geometry\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nInitialize the arrays orography,geopot_surf in orog by reading the orography field from file.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{Feedback, SpeedyWeather.Clock, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n feedback::Feedback,\n clock::SpeedyWeather.Clock,\n model::SpeedyWeather.ModelSetup\n) -> Union{Nothing, IOStream}\n\n\nInitializes the a Feedback struct.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{HeldSuarez, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(scheme::HeldSuarez, model::PrimitiveEquation)\n\n\ninitialize the HeldSuarez temperature relaxation by precomputing terms for the equilibrium temperature Teq.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{HyperDiffusion, SpeedyWeather.DiagnosticVariablesLayer, Geometry, SpeedyWeather.TimeStepper}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::HyperDiffusion,\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n G::Geometry,\n L::SpeedyWeather.TimeStepper\n)\n\n\nPre-function to other initialize!(::HyperDiffusion) initialisors that calculates the (absolute) vorticity maximum for the layer of diagn.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{HyperDiffusion, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::HyperDiffusion,\n model::SpeedyWeather.ModelSetup\n)\n\n\nPrecomputes the hyper diffusion terms in scheme based on the model time step, and possibly with a changing strength/power in the vertical.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{HyperDiffusion, SpeedyWeather.TimeStepper}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::HyperDiffusion,\n L::SpeedyWeather.TimeStepper\n)\n\n\nPrecomputes the 2D hyper diffusion terms in scheme based on the model time step.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{JablonowskiRelaxation, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::JablonowskiRelaxation,\n model::PrimitiveEquation\n)\n\n\ninitialize the JablonowskiRelaxation temperature relaxation by precomputing terms for the equilibrium temperature Teq and the frequency (strength of relaxation).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{LinearDrag, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(scheme::LinearDrag, model::PrimitiveEquation)\n\n\nPrecomputes the drag coefficients for this BoundaryLayerDrag scheme.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{NoTemperatureRelaxation, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::NoTemperatureRelaxation,\n model::PrimitiveEquation\n)\n\n\njust passes, does not need any initialization.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{PrimitiveDry}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(model::PrimitiveDry) -> SpeedyWeather.Simulation\n\n\nCalls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{PrimitiveWet}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(model::PrimitiveWet) -> SpeedyWeather.Simulation\n\n\nCalls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{ShallowWater}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(model::ShallowWater) -> SpeedyWeather.Simulation\n\n\nCalls all initialize! functions for components of model, except for model.output and model.feedback which are always called at in time_stepping! and model.implicit which is done in first_timesteps!.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyCondensation, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::SpeedyCondensation,\n model::PrimitiveEquation\n)\n\n\nInitialize the SpeedyCondensation scheme.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyWeather.Clock, SpeedyWeather.TimeStepper}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n clock::SpeedyWeather.Clock,\n time_stepping::SpeedyWeather.TimeStepper\n) -> SpeedyWeather.Clock\n\n\nInitialize the clock with the time step Δt in the time_stepping.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyWeather.ImplicitPrimitiveEq, Integer, Real, DiagnosticVariables, Geometry, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n implicit::SpeedyWeather.ImplicitPrimitiveEq,\n i::Integer,\n dt::Real,\n diagn::DiagnosticVariables,\n geometry::Geometry,\n constants::DynamicsConstants\n)\n\n\nReinitialize implicit occasionally based on time step i and implicit.recalculate.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyWeather.ImplicitPrimitiveEq, Real, DiagnosticVariables, Geometry, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n implicit::SpeedyWeather.ImplicitPrimitiveEq,\n dt::Real,\n diagn::DiagnosticVariables,\n geometry::Geometry,\n constants::DynamicsConstants\n)\n\n\nInitialize the implicit terms for the PrimitiveEquation models.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyWeather.ImplicitShallowWater, Real, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n implicit::SpeedyWeather.ImplicitShallowWater,\n dt::Real,\n constants::DynamicsConstants\n)\n\n\nUpdate the implicit terms in implicit for the shallow water model as they depend on the time step dt.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{SpeedyWeather.NoBoundaryLayerDrag, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"NoBoundaryLayer scheme does not need any initialisation.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Tuple{ZonalRidge, SpeedyWeather.AbstractPlanet, SpectralTransform, Geometry}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n orog::ZonalRidge,\n P::SpeedyWeather.AbstractPlanet,\n S::SpectralTransform,\n G::Geometry\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nInitialize the arrays orography,geopot_surf in orog following Jablonowski and Williamson, 2006.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Union{Tuple{Model}, Tuple{output_NF}, Tuple{OutputWriter{output_NF, Model}, SpeedyWeather.AbstractFeedback, SpeedyWeather.TimeStepper, DiagnosticVariables, Model}} where {output_NF, Model}","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n output::OutputWriter{output_NF, Model},\n feedback::SpeedyWeather.AbstractFeedback,\n time_stepping::SpeedyWeather.TimeStepper,\n diagn::DiagnosticVariables,\n model\n)\n\n\nCreates a netcdf file on disk and the corresponding netcdf_file object preallocated with output variables and dimensions. write_output! then writes consecuitive time steps into this file.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize!-Union{Tuple{NF}, Tuple{SpeedyWeather.StaticEnergyDiffusion{NF}, PrimitiveEquation}} where NF","page":"Function and type index","title":"SpeedyWeather.initialize!","text":"initialize!(\n scheme::SpeedyWeather.StaticEnergyDiffusion{NF},\n model::PrimitiveEquation\n) -> Any\n\n\nInitialize dry static energy diffusion.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.initialize_geopotential-Tuple{Vector, Vector, Real}","page":"Function and type index","title":"SpeedyWeather.initialize_geopotential","text":"initialize_geopotential(\n σ_levels_full::Vector,\n σ_levels_half::Vector,\n R_dry::Real\n) -> Tuple{Vector{Float64}, Vector{Float64}}\n\n\nPrecomputes constants for the vertical integration of the geopotential, defined as\n\nΦ_{k+1/2} = Φ_{k+1} + R*T_{k+1}*(ln(p_{k+1}) - ln(p_{k+1/2})) (half levels) Φ_k = Φ_{k+1/2} + R*T_k*(ln(p_{k+1/2}) - ln(p_k)) (full levels)\n\nSame formula but k → k-1/2.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.isdecreasing-Tuple{Vector}","page":"Function and type index","title":"SpeedyWeather.isdecreasing","text":"true/false = isdecreasing(v::Vector)\n\nCheck whether elements of a vector v are strictly decreasing.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.isincreasing-Tuple{Vector}","page":"Function and type index","title":"SpeedyWeather.isincreasing","text":"true/false = isincreasing(v::Vector)\n\nCheck whether elements of a vector v are strictly increasing.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.large_scale_condensation!-Tuple{ColumnVariables, PrimitiveDry}","page":"Function and type index","title":"SpeedyWeather.large_scale_condensation!","text":"large_scale_condensation!(\n column::ColumnVariables,\n model::PrimitiveDry\n)\n\n\nNo condensation in a PrimitiveDry model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.large_scale_condensation!-Tuple{ColumnVariables, PrimitiveWet}","page":"Function and type index","title":"SpeedyWeather.large_scale_condensation!","text":"Function barrier only.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.large_scale_condensation!-Union{Tuple{NF}, Tuple{ColumnVariables{NF}, SpeedyCondensation, Geometry, DynamicsConstants, SpeedyWeather.AbstractAtmosphere, SpeedyWeather.TimeStepper}} where NF","page":"Function and type index","title":"SpeedyWeather.large_scale_condensation!","text":"large_scale_condensation!(\n column::ColumnVariables{NF},\n scheme::SpeedyCondensation,\n geometry::Geometry,\n constants::DynamicsConstants,\n atmosphere::SpeedyWeather.AbstractAtmosphere,\n time_stepping::SpeedyWeather.TimeStepper\n)\n\n\nLarge-scale condensation for a column by relaxation back to a reference relative humidity if larger than that. Calculates the tendencies for specific humidity and temperature and integrates the large-scale precipitation vertically for output.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.launch_kernel!-Tuple{SpeedyWeather.DeviceSetup, Any, Any, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.launch_kernel!","text":"launch_kernel!(device_setup::DeviceSetup, kernel!, ndrange, kernel_args...)\n\nLaunches the kernel! on the device_setup with ndrange computations over the kernel and arguments kernel_args\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.leapfrog!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, Real, Int64, Leapfrog{NF}}} where NF<:AbstractFloat","page":"Function and type index","title":"SpeedyWeather.leapfrog!","text":"leapfrog!(\n A_old::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n A_new::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n tendency::LowerTriangularMatrix{Complex{NF<:AbstractFloat}},\n dt::Real,\n lf::Int64,\n L::Leapfrog{NF<:AbstractFloat}\n)\n\n\nPerforms one leapfrog time step with (lf=2) or without (lf=1) Robert+Williams filter (see Williams (2009), Montly Weather Review, Eq. 7-9).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.linear_pressure_gradient!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticSurfaceTimesteps, Int64, DynamicsConstants, SpeedyWeather.ImplicitPrimitiveEq}","page":"Function and type index","title":"SpeedyWeather.linear_pressure_gradient!","text":"linear_pressure_gradient!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surface::SpeedyWeather.PrognosticSurfaceTimesteps,\n lf::Int64,\n C::DynamicsConstants,\n I::SpeedyWeather.ImplicitPrimitiveEq\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nAdd the linear contribution of the pressure gradient to the geopotential. The pressure gradient in the divergence equation takes the form\n\n-∇⋅(Rd*Tᵥ*∇lnpₛ) = -∇⋅(Rd*Tᵥ'*∇lnpₛ) - ∇²(Rd*Tₖ*lnpₛ)\n\nSo that the second term inside the Laplace operator can be added to the geopotential. Rd is the gas constant, Tᵥ the virtual temperature and Tᵥ' its anomaly wrt to the average or reference temperature Tₖ, lnpₛ is the logarithm of surface pressure.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.linear_virtual_temperature!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticLayerTimesteps, DynamicsConstants, Int64}","page":"Function and type index","title":"SpeedyWeather.linear_virtual_temperature!","text":"linear_virtual_temperature!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n constants::DynamicsConstants,\n lf::Int64\n) -> Any\n\n\nCalculates a linearised virtual temperature Tᵥ as\n\nTᵥ = T + Tₖμq\n\nWith absolute temperature T, layer-average temperarture Tₖ (computed in temperature_average!), specific humidity q and\n\nμ = (1-ξ)/ξ, ξ = R_dry/R_vapour.\n\nin spectral space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.linear_virtual_temperature!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.PrognosticLayerTimesteps, PrimitiveDry, Integer}","page":"Function and type index","title":"SpeedyWeather.linear_virtual_temperature!","text":"linear_virtual_temperature!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n progn::SpeedyWeather.PrognosticLayerTimesteps,\n model::PrimitiveDry,\n lf::Integer\n) -> LowerTriangularMatrix{Complex{NF}} where NF<:AbstractFloat\n\n\nLinear virtual temperature for model::PrimitiveDry: Just copy over arrays from temp to temp_virt at timestep lf in spectral space as humidity is zero in this model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.load_trajectory-Tuple{Union{String, Symbol}, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.load_trajectory","text":"load_trajectory(\n var_name::Union{String, Symbol},\n model::SpeedyWeather.ModelSetup\n) -> Any\n\n\nLoads a var_name trajectory of the model M that has been saved in a netCDF file during the time stepping.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.moist_static_energy!-Tuple{ColumnVariables, SpeedyWeather.Thermodynamics}","page":"Function and type index","title":"SpeedyWeather.moist_static_energy!","text":"moist_static_energy!(\n column::ColumnVariables,\n thermodynamics::SpeedyWeather.Thermodynamics\n)\n\n\nCompute the moist static energy\n\nMSE = SE + Lc*Q = cₚT + Φ + Lc*Q\n\nwith the static energy SE, the latent heat of condensation Lc, the geopotential Φ. As well as the saturation moist static energy which replaces Q with Q_sat\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.nans-Tuple","page":"Function and type index","title":"SpeedyWeather.nans","text":"A = nans(dims...)\n\nAllocate A::Array{Float64} with NaNs.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.nans-Union{Tuple{T}, Tuple{Type{T}, Vararg{Any}}} where T","page":"Function and type index","title":"SpeedyWeather.nans","text":"A = nans(T,dims...)\n\nAllocate array A with NaNs of type T. Similar to zeros(T,dims...).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.nar_detection!-Tuple{Feedback, PrognosticVariables}","page":"Function and type index","title":"SpeedyWeather.nar_detection!","text":"nar_detection!(\n feedback::Feedback,\n progn::PrognosticVariables\n) -> Union{Nothing, Bool}\n\n\nDetect NaR (Not-a-Real) in the prognostic variables.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.parameterization_tendencies!-Tuple{DiagnosticVariables, Dates.DateTime, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.parameterization_tendencies!","text":"parameterization_tendencies!(\n diagn::DiagnosticVariables,\n time::Dates.DateTime,\n model::PrimitiveEquation\n) -> Any\n\n\nCompute tendencies for u,v,temp,humid from physical parametrizations. Extract for each vertical atmospheric column the prognostic variables (stored in diagn as they are grid-point transformed), loop over all grid-points, compute all parametrizations on a single-column basis, then write the tendencies back into a horizontal field of tendencies.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.pressure_on_orography!-Tuple{PrognosticVariables, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.pressure_on_orography!","text":"pressure_on_orography!(\n progn::PrognosticVariables,\n model::PrimitiveEquation\n)\n\n\nInitialize surface pressure on orography by integrating the hydrostatic equation with the reference temperature lapse rate.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.progress!-Tuple{Feedback}","page":"Function and type index","title":"SpeedyWeather.progress!","text":"progress!(feedback::Feedback)\n\n\nCalls the progress meter and writes every 5% progress increase to txt.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.progress_finish!-Tuple{Feedback}","page":"Function and type index","title":"SpeedyWeather.progress_finish!","text":"progress_finish!(F::Feedback)\n\n\nFinalises the progress meter and the progress txt file.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.readable_secs-Tuple{Real}","page":"Function and type index","title":"SpeedyWeather.readable_secs","text":"readable_secs(secs::Real) -> Dates.CompoundPeriod\n\n\nReturns Dates.CompoundPeriod rounding to either (days, hours), (hours, minutes), (minutes, seconds), or seconds with 1 decimal place accuracy for >10s and two for less. E.g.\n\njulia> readable_secs(12345)\n3 hours, 26 minutes\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.remaining_time-Tuple{ProgressMeter.Progress}","page":"Function and type index","title":"SpeedyWeather.remaining_time","text":"remaining_time(p::ProgressMeter.Progress) -> String\n\n\nEstimates the remaining time from a ProgresssMeter.Progress. Adapted from ProgressMeter.jl\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.reset_column!-Union{Tuple{ColumnVariables{NF}}, Tuple{NF}} where NF","page":"Function and type index","title":"SpeedyWeather.reset_column!","text":"reset_column!(column::ColumnVariables{NF})\n\n\nSet the accumulators (tendencies but also vertical sums and similar) back to zero for column to be reused at other grid points.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.run!-Tuple{SpeedyWeather.Simulation}","page":"Function and type index","title":"SpeedyWeather.run!","text":"run!(\n simulation::SpeedyWeather.Simulation;\n initialize,\n n_days,\n startdate,\n output\n) -> PrognosticVariables\n\n\nRun a SpeedyWeather.jl simulation. The simulation.model is assumed to be initialized, otherwise use initialize=true as keyword argument.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.saturation_humidity!-Tuple{ColumnVariables, SpeedyWeather.Thermodynamics}","page":"Function and type index","title":"SpeedyWeather.saturation_humidity!","text":"saturation_humidity!(\n column::ColumnVariables,\n thermodynamics::SpeedyWeather.Thermodynamics\n)\n\n\nCompute (1) the saturation vapour pressure as a function of temperature using the August-Roche-Magnus formula,\n\neᵢ(T) = e₀ * exp(Cᵢ * (T - T₀) / (T - Tᵢ)),\n\nwhere T is in Kelvin and i = 1,2 for saturation with respect to water and ice, respectively. And (2) the saturation specific humidity according to the formula,\n\n0.622 * e / (p - (1 - 0.622) * e),\n\nwhere e is the saturation vapour pressure, p is the pressure, and 0.622 is the ratio of the molecular weight of water to dry air.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.scale!-Tuple{PrognosticVariables, Real}","page":"Function and type index","title":"SpeedyWeather.scale!","text":"scale!(progn::PrognosticVariables, scale::Real) -> Real\n\n\nScales the prognostic variables vorticity and divergence with the Earth's radius which is used in the dynamical core.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.scale!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Real}} where NF","page":"Function and type index","title":"SpeedyWeather.scale!","text":"scale!(\n progn::PrognosticVariables{NF},\n var::Symbol,\n scale::Real\n)\n\n\nScale the variable var inside progn with scalar scale.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_divergence!-Tuple{PrognosticVariables, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.set_divergence!","text":"set_divergence!(progn::PrognosticVariables, varargs...; kwargs...)\n\nSee set_var!\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_humidity!-Tuple{PrognosticVariables, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.set_humidity!","text":"set_humidity!(progn::PrognosticVariables, varargs...; kwargs...)\n\nSee set_var!\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_pressure!-Tuple{PrognosticVariables, AbstractMatrix}","page":"Function and type index","title":"SpeedyWeather.set_pressure!","text":"set_pressure!(progn::PrognosticVariables{NF}, \n pressure::AbstractMatrix, \n Grid::Type{<:AbstractGrid}, \n lf::Integer=1) where NF\n\nSets the prognostic variable with the surface pressure in grid space at leapfrog index lf.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_pressure!-Tuple{PrognosticVariables, LowerTriangularMatrix}","page":"Function and type index","title":"SpeedyWeather.set_pressure!","text":"set_pressure!(progn::PrognosticVariables{NF}, \n pressure::LowerTriangularMatrix;\n lf::Integer=1) where NF\n\nSets the prognostic variable with the surface pressure in spectral space at leapfrog index lf.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_pressure!-Tuple{PrognosticVariables, SpeedyWeather.RingGrids.AbstractGrid, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.set_pressure!","text":"set_pressure!(progn::PrognosticVariables{NF}, \n pressure::AbstractGrid, \n M::ModelSetup;\n lf::Integer=1) where NF\n\nSets the prognostic variable with the surface pressure in grid space at leapfrog index lf.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_pressure!-Tuple{PrognosticVariables, SpeedyWeather.RingGrids.AbstractGrid}","page":"Function and type index","title":"SpeedyWeather.set_pressure!","text":"set_pressure!(progn::PrognosticVariables{NF}, \n pressure::AbstractGrid, \n lf::Integer=1) where NF\n\nSets the prognostic variable with the surface pressure in grid space at leapfrog index lf.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_temperature!-Tuple{PrognosticVariables, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.set_temperature!","text":"set_temperature!(progn::PrognosticVariables, varargs...; kwargs...)\n\nSee set_var!\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_var!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Number}} where NF","page":"Function and type index","title":"SpeedyWeather.set_var!","text":"function set_var!(progn::PrognosticVariables{NF}, \n varname::Symbol, \n s::Number;\n lf::Integer=1) where NF\n\nSets all values of prognostic variable varname at leapfrog index lf to the scalar s.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_var!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Vector{<:AbstractMatrix}}, Tuple{PrognosticVariables{NF}, Symbol, Vector{<:AbstractMatrix}, Type{<:SpeedyWeather.RingGrids.AbstractGrid}}} where NF","page":"Function and type index","title":"SpeedyWeather.set_var!","text":"set_var!(progn::PrognosticVariables{NF}, \n varname::Symbol, \n var::Vector{<:AbstractMatrix}, \n Grid::Type{<:AbstractGrid}=FullGaussianGrid;\n lf::Integer=1) where NF\n\nSets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_var!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Vector{<:LowerTriangularMatrix}}} where NF","page":"Function and type index","title":"SpeedyWeather.set_var!","text":"set_var!(progn::PrognosticVariables{NF}, \n varname::Symbol, \n var::Vector{<:LowerTriangularMatrix};\n lf::Integer=1) where NF\n\nSets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in spectral space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_var!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Vector{<:SpeedyWeather.RingGrids.AbstractGrid}, SpeedyWeather.ModelSetup}} where NF","page":"Function and type index","title":"SpeedyWeather.set_var!","text":"set_var!(progn::PrognosticVariables{NF}, \n varname::Symbol, \n var::Vector{<:AbstractGrid}, \n M::ModelSetup;\n lf::Integer=1) where NF\n\nSets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_var!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, Symbol, Vector{<:SpeedyWeather.RingGrids.AbstractGrid}}} where NF","page":"Function and type index","title":"SpeedyWeather.set_var!","text":"set_var!(progn::PrognosticVariables{NF}, \n varname::Symbol, \n var::Vector{<:AbstractGrid};\n lf::Integer=1) where NF\n\nSets the prognostic variable with the name varname in all layers at leapfrog index lf with values given in var a vector with all information for all layers in grid space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.set_vorticity!-Tuple{PrognosticVariables, Vararg{Any}}","page":"Function and type index","title":"SpeedyWeather.set_vorticity!","text":"set_vorticity!(progn::PrognosticVariables, varargs...; kwargs...)\n\nSee set_var!\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.sigma_okay-Tuple{Integer, AbstractVector}","page":"Function and type index","title":"SpeedyWeather.sigma_okay","text":"sigma_okay(nlev::Integer, σ_half::AbstractVector) -> Bool\n\n\nCheck that nlev and σ_half match.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.speedstring-Tuple{Any, Any}","page":"Function and type index","title":"SpeedyWeather.speedstring","text":"speedstring(sec_per_iter, dt_in_sec) -> String\n\n\ndefine a ProgressMeter.speedstring method that also takes a time step dt_in_sec to translate sec/iteration to days/days-like speeds.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.static_energy_diffusion!-Union{Tuple{NF}, Tuple{ColumnVariables{NF}, SpeedyWeather.StaticEnergyDiffusion}} where NF","page":"Function and type index","title":"SpeedyWeather.static_energy_diffusion!","text":"static_energy_diffusion!(\n column::ColumnVariables{NF},\n scheme::SpeedyWeather.StaticEnergyDiffusion\n)\n\n\nApply dry static energy diffusion.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.surface_pressure_tendency!-Tuple{SpeedyWeather.SurfaceVariables, SpectralTransform}","page":"Function and type index","title":"SpeedyWeather.surface_pressure_tendency!","text":"surface_pressure_tendency!( Prog::PrognosticVariables,\n Diag::DiagnosticVariables,\n lf::Int,\n M::PrimitiveEquation)\n\nComputes the tendency of the logarithm of surface pressure as\n\n-(ū*px + v̄*py) - D̄\n\nwith ū,v̄ being the vertically averaged velocities; px, py the gradients of the logarithm of surface pressure ln(p_s) and D̄ the vertically averaged divergence.\n\nCalculate ∇ln(p_s) in spectral space, convert to grid.\nMultiply ū,v̄ with ∇ln(p_s) in grid-point space, convert to spectral.\nD̄ is subtracted in spectral space.\nSet tendency of the l=m=0 mode to 0 for better mass conservation.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_anomaly!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.ImplicitPrimitiveEq}","page":"Function and type index","title":"SpeedyWeather.temperature_anomaly!","text":"Convert absolute and virtual temperature to anomalies wrt to the reference profile\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_average!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, LowerTriangularMatrix, SpectralTransform}","page":"Function and type index","title":"SpeedyWeather.temperature_average!","text":"temperature_average!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n temp::LowerTriangularMatrix,\n S::SpectralTransform\n) -> Any\n\n\nCalculates the average temperature of a layer from the l=m=0 harmonic and stores the result in diagn.temp_average\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_relaxation!-Tuple{ColumnVariables, JablonowskiRelaxation}","page":"Function and type index","title":"SpeedyWeather.temperature_relaxation!","text":"temperature_relaxation!(\n column::ColumnVariables,\n scheme::JablonowskiRelaxation\n)\n\n\nApply HeldSuarez-like temperature relaxation to the Jablonowski and Williamson vertical profile.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_relaxation!-Tuple{ColumnVariables, NoTemperatureRelaxation}","page":"Function and type index","title":"SpeedyWeather.temperature_relaxation!","text":"temperature_relaxation!(\n column::ColumnVariables,\n scheme::NoTemperatureRelaxation\n)\n\n\njust passes.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_relaxation!-Union{Tuple{NF}, Tuple{ColumnVariables{NF}, HeldSuarez}} where NF","page":"Function and type index","title":"SpeedyWeather.temperature_relaxation!","text":"temperature_relaxation!(\n column::ColumnVariables{NF},\n scheme::HeldSuarez\n)\n\n\nApply temperature relaxation following Held and Suarez 1996, BAMS.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_tendency!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, DynamicsConstants, Geometry, SpectralTransform, SpeedyWeather.ImplicitPrimitiveEq}","page":"Function and type index","title":"SpeedyWeather.temperature_tendency!","text":"temperature_tendency!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n C::DynamicsConstants,\n G::Geometry,\n S::SpectralTransform,\n I::SpeedyWeather.ImplicitPrimitiveEq\n)\n\n\nCompute the temperature tendency\n\n∂T/∂t += -∇⋅((u,v)*T') + T'D + κTᵥ*Dlnp/Dt\n\n+= because the tendencies already contain parameterizations and vertical advection. T' is the anomaly with respect to the reference/average temperature. Tᵥ is the virtual temperature used in the adiabatic term κTᵥ*Dlnp/Dt.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.temperature_tendency!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.temperature_tendency!","text":"temperature_tendency!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::PrimitiveEquation\n)\n\n\nFunction barrier to unpack model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.time_stepping!-Tuple{PrognosticVariables, DiagnosticVariables, SpeedyWeather.ModelSetup}","page":"Function and type index","title":"SpeedyWeather.time_stepping!","text":"time_stepping!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n model::SpeedyWeather.ModelSetup\n) -> PrognosticVariables\n\n\nMain time loop that that initializes output and feedback, loops over all time steps and calls the output and feedback functions.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.timestep!","page":"Function and type index","title":"SpeedyWeather.timestep!","text":"timestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n dt::Real,\n i::Integer,\n model::Barotropic\n)\ntimestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n dt::Real,\n i::Integer,\n model::Barotropic,\n lf1::Int64\n)\ntimestep!(\n progn::PrognosticVariables,\n diagn::DiagnosticVariables,\n dt::Real,\n i::Integer,\n model::Barotropic,\n lf1::Int64,\n lf2::Int64\n)\n\n\nCalculate a single time step for the model <: Barotropic.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.timestep!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, PrimitiveEquation}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, PrimitiveEquation, Int64}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, PrimitiveEquation, Int64, Int64}} where NF<:AbstractFloat","page":"Function and type index","title":"SpeedyWeather.timestep!","text":"timestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::PrimitiveEquation\n) -> Any\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::PrimitiveEquation,\n lf1::Int64\n) -> Any\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::PrimitiveEquation,\n lf1::Int64,\n lf2::Int64\n) -> Any\n\n\nCalculate a single time step for the model<:PrimitiveEquation\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.timestep!-Union{Tuple{NF}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, ShallowWater}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, ShallowWater, Int64}, Tuple{PrognosticVariables{NF}, DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, Real, Integer, ShallowWater, Int64, Int64}} where NF<:AbstractFloat","page":"Function and type index","title":"SpeedyWeather.timestep!","text":"timestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::ShallowWater\n) -> Union{Nothing, SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat}\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::ShallowWater,\n lf1::Int64\n) -> Union{Nothing, SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat}\ntimestep!(\n progn::PrognosticVariables{NF<:AbstractFloat},\n diagn::DiagnosticVariables{NF<:AbstractFloat, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n dt::Real,\n i::Integer,\n model::ShallowWater,\n lf1::Int64,\n lf2::Int64\n) -> Union{Nothing, SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat}\n\n\nCalculate a single time step for the model <: ShallowWater.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.underflow!-Union{Tuple{T}, Tuple{AbstractArray{T}, Real}} where T","page":"Function and type index","title":"SpeedyWeather.underflow!","text":"underflow!(A::AbstractArray,ϵ::Real)\n\nUnderflows element a in A to zero if abs(a) < ϵ.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.unscale!-Tuple{AbstractArray, Real}","page":"Function and type index","title":"SpeedyWeather.unscale!","text":"unscale!(variable::AbstractArray, scale::Real) -> Any\n\n\nUndo the radius-scaling for any variable. Method used for netcdf output.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.unscale!-Tuple{PrognosticVariables}","page":"Function and type index","title":"SpeedyWeather.unscale!","text":"unscale!(progn::PrognosticVariables) -> Int64\n\n\nUndo the radius-scaling of vorticity and divergence from scale!(progn,scale::Real).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vertical_integration!-Union{Tuple{NF}, Tuple{DiagnosticVariables{NF, Grid} where Grid<:SpeedyWeather.RingGrids.AbstractGrid{NF}, PrognosticVariables{NF}, Int64, Geometry{NF}}} where NF","page":"Function and type index","title":"SpeedyWeather.vertical_integration!","text":"vertical_integration!(Diag::DiagnosticVariables,G::Geometry)\n\nCalculates the vertically averaged (weighted by the thickness of the σ level) velocities (*coslat) and divergence. E.g.\n\nu_mean = ∑_k=1^nlev Δσ_k * u_k\n\nu,v are averaged in grid-point space, divergence in spectral space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.virtual_temperature!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, LowerTriangularMatrix, DynamicsConstants}","page":"Function and type index","title":"SpeedyWeather.virtual_temperature!","text":"virtual_temperature!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n temp::LowerTriangularMatrix,\n constants::DynamicsConstants\n)\n\n\nCalculates the virtual temperature Tᵥ as\n\nTᵥ = T(1+μq)\n\nWith absolute temperature T, specific humidity q and\n\nμ = (1-ξ)/ξ, ξ = R_dry/R_vapour.\n\nin grid-point space.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.virtual_temperature!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, LowerTriangularMatrix, PrimitiveDry}","page":"Function and type index","title":"SpeedyWeather.virtual_temperature!","text":"virtual_temperature!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n temp::LowerTriangularMatrix,\n model::PrimitiveDry\n) -> SpeedyWeather.RingGrids.AbstractGrid{NF} where NF<:AbstractFloat\n\n\nVirtual temperature in grid-point space: For the PrimitiveDry temperature and virtual temperature are the same (humidity=0). Just copy over the arrays.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.volume_flux_divergence!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.SurfaceVariables, SpeedyWeather.AbstractOrography, DynamicsConstants, Geometry, SpectralTransform}","page":"Function and type index","title":"SpeedyWeather.volume_flux_divergence!","text":"volume_flux_divergence!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surface::SpeedyWeather.SurfaceVariables,\n orog::SpeedyWeather.AbstractOrography,\n constants::DynamicsConstants,\n G::Geometry,\n S::SpectralTransform\n)\n\n\nComputes the (negative) divergence of the volume fluxes uh,vh for the continuity equation, -∇⋅(uh,vh).\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vordiv_tendencies!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.SurfaceVariables, DynamicsConstants, Geometry, SpectralTransform}","page":"Function and type index","title":"SpeedyWeather.vordiv_tendencies!","text":"vordiv_tendencies!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surf::SpeedyWeather.SurfaceVariables,\n C::DynamicsConstants,\n G::Geometry,\n S::SpectralTransform\n)\n\n\nTendencies for vorticity and divergence. Excluding Bernoulli potential with geopotential and linear pressure gradient inside the Laplace operator, which are added later in spectral space.\n\nu_tend += v*(f+ζ) - RTᵥ'*∇lnp_x\nv_tend += -u*(f+ζ) - RTᵥ'*∇lnp_y\n\n+= because the tendencies already contain the parameterizations and vertical advection. f is coriolis, ζ relative vorticity, R the gas constant Tᵥ' the virtual temperature anomaly, ∇lnp the gradient of surface pressure and _x and _y its zonal/meridional components. The tendencies are then curled/dived to get the tendencies for vorticity/divergence in spectral space\n\n∂ζ/∂t = ∇×(u_tend,v_tend)\n∂D/∂t = ∇⋅(u_tend,v_tend) + ...\n\n+ ... because there's more terms added later for divergence.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vordiv_tendencies!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, SpeedyWeather.SurfaceVariables, PrimitiveEquation}","page":"Function and type index","title":"SpeedyWeather.vordiv_tendencies!","text":"vordiv_tendencies!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n surf::SpeedyWeather.SurfaceVariables,\n model::PrimitiveEquation\n)\n\n\nFunction barrier to unpack model.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vorticity_flux!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, Barotropic}","page":"Function and type index","title":"SpeedyWeather.vorticity_flux!","text":"vorticity_flux!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::Barotropic\n)\n\n\nVorticity flux tendency in the barotropic vorticity equation\n\n∂ζ/∂t = ∇×(u_tend,v_tend)\n\nwith\n\nu_tend = Fᵤ + v*(ζ+f) v_tend = Fᵥ - u*(ζ+f)\n\nwith Fᵤ,Fᵥ the forcing from forcing! already in u_tend_grid/v_tend_grid and vorticity ζ, coriolis f.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vorticity_flux!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, ShallowWater}","page":"Function and type index","title":"SpeedyWeather.vorticity_flux!","text":"vorticity_flux!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n model::ShallowWater\n)\n\n\nVorticity flux tendency in the shallow water equations\n\n∂ζ/∂t = ∇×(u_tend,v_tend) ∂D/∂t = ∇⋅(u_tend,v_tend)\n\nwith\n\nu_tend = Fᵤ + v*(ζ+f) v_tend = Fᵥ - u*(ζ+f)\n\nwith Fᵤ,Fᵥ the forcing from forcing! already in u_tend_grid/v_tend_grid and vorticity ζ, coriolis f.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.vorticity_flux_curldiv!-Tuple{SpeedyWeather.DiagnosticVariablesLayer, DynamicsConstants, Geometry, SpectralTransform}","page":"Function and type index","title":"SpeedyWeather.vorticity_flux_curldiv!","text":"vorticity_flux_curldiv!(\n diagn::SpeedyWeather.DiagnosticVariablesLayer,\n C::DynamicsConstants,\n G::Geometry,\n S::SpectralTransform;\n div\n)\n\n\nCompute the vorticity advection as the curl/div of the vorticity fluxes\n\n∂ζ/∂t = ∇×(u_tend,v_tend) ∂D/∂t = ∇⋅(u_tend,v_tend)\n\nwith\n\nu_tend = Fᵤ + v*(ζ+f) v_tend = Fᵥ - u*(ζ+f)\n\nwith Fᵤ,Fᵥ from u_tend_grid/v_tend_grid that are assumed to be alread set in forcing!. Set div=false for the BarotropicModel which doesn't require the divergence tendency.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.workgroup_size-Tuple{SpeedyWeather.AbstractDevice}","page":"Function and type index","title":"SpeedyWeather.workgroup_size","text":"workgroup_size(dev::AbstractDevice)\n\nReturns a workgroup size depending on dev. WIP: Will be expanded in the future to also include grid information. \n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.write_column_tendencies!-Tuple{DiagnosticVariables, ColumnVariables, Int64}","page":"Function and type index","title":"SpeedyWeather.write_column_tendencies!","text":"write_column_tendencies!(\n D::DiagnosticVariables,\n C::ColumnVariables,\n ij::Int64\n)\n\n\nWrite the parametrization tendencies from C::ColumnVariables into the horizontal fields of tendencies stored in D::DiagnosticVariables at gridpoint index ij.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.write_netcdf_time!-Tuple{OutputWriter, Dates.DateTime}","page":"Function and type index","title":"SpeedyWeather.write_netcdf_time!","text":"write_netcdf_time!(\n output::OutputWriter,\n time::Dates.DateTime\n)\n\n\nWrite the current time time::DateTime to the netCDF file in output::OutputWriter.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.write_netcdf_variables!-Union{Tuple{Model}, Tuple{Grid}, Tuple{NF}, Tuple{OutputWriter, DiagnosticVariables{NF, Grid, Model}}} where {NF, Grid, Model}","page":"Function and type index","title":"SpeedyWeather.write_netcdf_variables!","text":"write_netcdf_variables!(\n output::OutputWriter,\n diagn::DiagnosticVariables{NF, Grid, Model}\n)\n\n\nWrite diagnostic variables from diagn to the netCDF file in output::OutputWriter.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.write_output!-Tuple{OutputWriter, Dates.DateTime, DiagnosticVariables}","page":"Function and type index","title":"SpeedyWeather.write_output!","text":"write_output!(\n outputter::OutputWriter,\n time::Dates.DateTime,\n diagn::DiagnosticVariables\n)\n\n\nWrites the variables from diagn of time step i at time time into outputter.netcdf_file. Simply escapes for no netcdf output of if output shouldn't be written on this time step. Interpolates onto output grid and resolution as specified in outputter, converts to output number format, truncates the mantissa for higher compression and applies lossless compression.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.write_restart_file-Tuple{PrognosticVariables, OutputWriter}","page":"Function and type index","title":"SpeedyWeather.write_restart_file","text":"write_restart_file(\n progn::PrognosticVariables,\n output::OutputWriter\n) -> Union{Nothing, String}\n\n\nA restart file restart.jld2 with the prognostic variables is written to the output folder (or current path) that can be used to restart the model. restart.jld2 will then be used as initial conditions. The prognostic variables are bitrounded for compression and the 2nd leapfrog time step is discarded. Variables in restart file are unscaled.\n\n\n\n\n\n","category":"method"},{"location":"functions/#SpeedyWeather.zero_tendencies!-Tuple{DiagnosticVariables}","page":"Function and type index","title":"SpeedyWeather.zero_tendencies!","text":"zero_tendencies!(diagn::DiagnosticVariables)\n\n\nSet the tendencies in diagn to zero.\n\n\n\n\n\n","category":"method"},{"location":"how_to_run_speedy/#How-to-run-SpeedyWeather.jl","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Creating a SpeedyWeather.jl simulation and running it consists conceptually of 4 steps","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Create a SpectralGrid which defines the grid and spectral resolution\nCreate a model\nInitialize a model to obtain a Simulation.\nRun the simulation.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"In the following we will describe these steps in more detail, but let's start with some examples first.","category":"page"},{"location":"how_to_run_speedy/#Example-1:-2D-turbulence-on-a-non-rotating-sphere","page":"How to run SpeedyWeather.jl","title":"Example 1: 2D turbulence on a non-rotating sphere","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"We want to use the barotropic model to simulate some free-decaying 2D turbulence on the sphere without rotation. We start by defining the SpectralGrid object. To have a resolution of about 100km, we choose a spectral resolution of T127 (see Available horizontal resolutions) and nlev=1 vertical levels. The SpectralGrid object will provide us with some more information","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> spectral_grid = SpectralGrid(trunc=127,nlev=1)\nSpectralGrid:\n Spectral: T127 LowerTriangularMatrix{Complex{Float32}}, radius = 6.371e6 m\n Grid: 40320-element, 192-ring OctahedralGaussianGrid{Float32} (quadratic)\n Resolution: 112km (average)\n Vertical: 1-level SigmaCoordinates","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"We could have specified further options, but let's ignore that for now. Next step we create a planet that's like Earth but not rotating","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> still_earth = Earth(rotation=0)\nMain.SpeedyWeather.Earth\n rotation: Float64 0.0\n gravity: Float64 9.81\n daily_cycle: Bool true\n length_of_day: Float64 24.0\n seasonal_cycle: Bool true\n length_of_year: Float64 365.25\n equinox: Dates.DateTime\n axial_tilt: Float64 23.4","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"There are other options to create a planet but they are irrelevant for the barotropic vorticity equations. We also want to specify the initial conditions, randomly distributed vorticity is already defined","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> initial_conditions = StartWithRandomVorticity()\nStartWithRandomVorticity\n power_law: Float64 -3.0\n amplitude: Float64 1.0e-5","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"By default, the power of vorticity is spectrally distributed with k^-3, k being the horizontal wavenumber, and the amplitude is 10^-5text s^-1.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Now we want to construct a BarotropicModel with these","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> model = BarotropicModel(;spectral_grid, initial_conditions, planet=still_earth);","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The model contains all the parameters, but isn't initialized yet, which we can do with and then run it.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> simulation = initialize!(model);\njulia> run!(simulation,n_days=30)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The run! command will always return the prognostic variables, which, by default, are plotted for surface relative vorticity with a unicode plot. The resolution of the plot is not necessarily representative but it lets us have a quick look at the result","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Barotropic vorticity unicode plot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Woohoo! I can see turbulence! You could pick up where this simulation stopped by simply doing run!(simulation,n_days=50) again. We didn't store any output, which you can do by run!(simulation,output=true), which will switch on NetCDF output with default settings. More options on output in NetCDF output.","category":"page"},{"location":"how_to_run_speedy/#Example-2:-Shallow-water-with-mountains","page":"How to run SpeedyWeather.jl","title":"Example 2: Shallow water with mountains","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"As a second example, let's investigate the Galewsky et al.[1] test case for the shallow water equations with and without mountains. As the shallow water system has also only one level, we can reuse the SpectralGrid from Example 1.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> spectral_grid = SpectralGrid(trunc=127,nlev=1)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Now as a first simulation, we want to disable any orography, so we create a NoOrography","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> orography = NoOrography(spectral_grid)\nNoOrography{Float32, OctahedralGaussianGrid{Float32}}","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Although the orography is zero, you have to pass on spectral_grid so that it can still initialize zero-arrays of the right size and element type. Awesome. This time the initial conditions should be set the the Galewsky et al.[1] zonal jet, which is already defined as","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> initial_conditions = ZonalJet()\nZonalJet\n latitude: Float64 45.0\n width: Float64 19.28571428571429\n umax: Float64 80.0\n perturb_lat: Float64 45.0\n perturb_lon: Float64 270.0\n perturb_xwidth: Float64 19.098593171027442\n perturb_ywidth: Float64 3.819718634205488\n perturb_height: Float64 120.0","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The jet sits at 45˚N with a maximum velocity of 80m/s and a perturbation as described in their paper. Now we construct a model, but this time a ShallowWaterModel","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);\njulia> simulation = initialize!(model);","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet unicode plot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Oh yeah. That looks like the wobbly jet in their paper. Let's run it again for another 6 days but this time also store NetCDF output.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> run!(simulation,n_days=6,output=true)\nWeather is speedy: run 0002 100%|███████████████████████| Time: 0:00:12 (115.37 years/day)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The progress bar tells us that the simulation run got the identification \"0002\", meaning that data is stored in the folder /run_0002, so let's plot that data properly (and not just using UnicodePlots).","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> using PyPlot, NCDatasets\njulia> ds = NCDataset(\"run_0002/output.nc\");\njulia> ds[\"vor\"]\nvor (384 × 192 × 1 × 25)\n Datatype: Float32\n Dimensions: lon × lat × lev × time\n Attributes:\n units = 1/s\n missing_value = NaN\n long_name = relative vorticity\n _FillValue = NaN","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Vorticity vor is stored as a 384x192x1x25 array, we may want to look at the first time step, which is the end of the previous simulation (time=6days) which we didn't store output for.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> vor = ds[\"vor\"][:,:,1,1];\njulia> lat = ds[\"lat\"][:];\njulia> lon = ds[\"lon\"][:];\njulia> pcolormesh(lon,lat,vor')","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Which looks like","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet pyplot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"You see that the unicode plot heavily coarse-grains the simulation, well it's unicode after all! And now the last time step, that means time=12days is","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> vor = ds[\"vor\"][:,:,1,25];\njulia> pcolormesh(lon,lat,vor')","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet pyplot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The jet broke up into many small eddies, but the turbulence is still confined to the northern hemisphere, cool! How this may change when we add mountains (we had NoOrography above!), say Earth's orography, you may ask? Let's try it out! We create an EarthOrography struct like so","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> orography = EarthOrography(spectral_grid)\nEarthOrography{Float32, OctahedralGaussianGrid{Float32}}:\n path::String = SpeedyWeather.jl/input_data\n file::String = orography_F512.nc\n scale::Float64 = 1.0\n smoothing::Bool = true\n smoothing_power::Float64 = 1.0\n smoothing_strength::Float64 = 0.1\n smoothing_truncation::Int64 = 85","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"It will read the orography from file as shown, and there are some smoothing options too, but let's not change them. Same as before, create a model, initialize into a simulation, run. This time directly for 12 days so that we can compare with the last plot","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"julia> model = ShallowWaterModel(;spectral_grid, orography, initial_conditions);\njulia> simulation = initialize!(model);\njulia> run!(simulation,n_days=12,output=true)\nWeather is speedy: run 0003 100%|███████████████████████| Time: 0:00:35 (79.16 years/day)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"This time the run got the id \"0003\", but otherwise we do as before.","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"(Image: Galewsky jet pyplot)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Interesting! The initial conditions have zero velocity in the southern hemisphere, but still, one can see some imprint of the orography on vorticity. You can spot the coastline of Antarctica; the Andes and Greenland are somewhat visible too. Mountains also completely changed the flow after 12 days, probably not surprising!","category":"page"},{"location":"how_to_run_speedy/#SpectralGrid","page":"How to run SpeedyWeather.jl","title":"SpectralGrid","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object. We have seen some examples above, now let's look into the details","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"SpectralGrid","category":"page"},{"location":"how_to_run_speedy/#References","page":"How to run SpeedyWeather.jl","title":"References","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"[1] Galewsky, Scott, Polvani, 2004. An initial-value problem for testing numerical models of the global shallow-water equations, Tellus A. DOI: 10.3402/tellusa.v56i5.14436","category":"page"},{"location":"speedytransforms/#SpeedyTransforms","page":"Submodule: SpeedyTransforms","title":"SpeedyTransforms","text":"","category":"section"},{"location":"speedytransforms/","page":"Submodule: SpeedyTransforms","title":"Submodule: SpeedyTransforms","text":"SpeedyTransforms is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"speedytransforms/#Example-transforms","page":"Submodule: SpeedyTransforms","title":"Example transforms","text":"","category":"section"},{"location":"speedytransforms/","page":"Submodule: SpeedyTransforms","title":"Submodule: SpeedyTransforms","text":"julia> using SpeedyWeather\njulia> alms = zeros(ComplexF64,3,3) # spectral coefficients\njulia> alms[2,2] = 1 # only l=1,m=1 harmonic\njulia> map = gridded(alms) # convert to grid space\n8×4 Matrix{Float64}:\n -0.324541 -0.600363 -0.600363 -0.324541\n -0.134429 -0.248678 -0.248678 -0.134429\n 0.134429 0.248678 0.248678 0.134429\n 0.324541 0.600363 0.600363 0.324541\n 0.324541 0.600363 0.600363 0.324541\n 0.134429 0.248678 0.248678 0.134429\n -0.134429 -0.248678 -0.248678 -0.134429\n -0.324541 -0.600363 -0.600363 -0.324541\n \njulia> spectral(map) # back to spectral space\n3×3 Matrix{ComplexF64}:\n 0.0+0.0im 0.0+0.0im 0.0+0.0im\n 0.0+0.0im 1.0+3.60727e-17im 0.0+0.0im\n 0.0+0.0im 0.0+0.0im 0.0+0.0im","category":"page"},{"location":"speedytransforms/","page":"Submodule: SpeedyTransforms","title":"Submodule: SpeedyTransforms","text":"and we have successfully reobtained the l=m=1 spherical harmonic.","category":"page"},{"location":"speedytransforms/#Functions-and-type-index","page":"Submodule: SpeedyTransforms","title":"Functions and type index","text":"","category":"section"},{"location":"speedytransforms/","page":"Submodule: SpeedyTransforms","title":"Submodule: SpeedyTransforms","text":"Modules = [SpeedyWeather.SpeedyTransforms]","category":"page"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.SpectralTransform","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"S = SpectralTransform{NF<:AbstractFloat}(...)\n\nSpectralTransform struct that contains all parameters and preallocated arrays for the spectral transform.\n\n\n\n\n\n","category":"type"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.SpectralTransform-Union{Tuple{AbstractArray{Complex{NF}, 2}}, Tuple{NF}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"S = SpectralTransform( alms::AbstractMatrix{Complex{NF}};\n recompute_legendre::Bool=true,\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID)\n\nGenerator function for a SpectralTransform struct based on the size of the spectral coefficients alms and the grid Grid. Recomputes the Legendre polynomials by default.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.SpectralTransform-Union{Tuple{NF}, Tuple{Type{NF}, Type{<:SpeedyWeather.RingGrids.AbstractGrid}, Int64, Int64}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"SpectralTransform(\n ::Type{NF},\n Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid},\n lmax::Int64,\n mmax::Int64;\n recompute_legendre,\n legendre_shortcut,\n dealiasing\n) -> SpectralTransform\n\n\nGenerator function for a SpectralTransform struct. With NF the number format, Grid the grid type <:AbstractGrid and spectral truncation lmax,mmax this function sets up necessary constants for the spetral transform. Also plans the Fourier transforms, retrieves the colatitudes, and preallocates the Legendre polynomials (if recompute_legendre == false) and quadrature weights.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.SpectralTransform-Union{Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}}, Tuple{NF}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"S = SpectralTransform( map::AbstractGrid;\n recompute_legendre::Bool=true)\n\nGenerator function for a SpectralTransform struct based on the size and grid type of gridded field map. Recomputes the Legendre polynomials by default.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.UV_from_vor!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.UV_from_vor!","text":"UV_from_vor!( U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.UV_from_vordiv!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.UV_from_vordiv!","text":"UV_from_vordiv!(U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n div::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms._divergence!-Union{Tuple{NF}, Tuple{Any, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms._divergence!","text":"_divergence!( kernel,\n div::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGeneric divergence function of vector u,v that writes into the output into div. Generic as it uses the kernel kernel such that curl, div, add or flipsign options are provided through kernel, but otherwise a single function is used.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.curl!-Tuple{LowerTriangularMatrix, LowerTriangularMatrix, LowerTriangularMatrix, SpectralTransform}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.curl!","text":"curl!( curl::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform;\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nCurl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.divergence!-Tuple{LowerTriangularMatrix, LowerTriangularMatrix, LowerTriangularMatrix, SpectralTransform}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.divergence!","text":"divergence!(div::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform{NF};\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nDivergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.get_recursion_factors-Union{Tuple{NF}, Tuple{Type{NF}, Int64, Int64}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.get_recursion_factors","text":"get_recursion_factors( ::Type{NF}, # number format NF\n lmax::Int, # max degree l of spherical harmonics (0-based here)\n mmax::Int # max order m of spherical harmonics\n ) where {NF<:AbstractFloat}\n\nReturns a matrix of recursion factors ϵ up to degree lmax and order mmax of number format NF.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.gridded!-Union{Tuple{NF}, Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!( map::AbstractGrid,\n alms::LowerTriangularMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.gridded-Union{Tuple{AbstractMatrix{T}}, Tuple{T}, Tuple{NF}} where {NF, T<:Complex{NF}}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.gridded","text":"map = gridded( alms::AbstractMatrix;\n recompute_legendre::Bool=true,\n grid::Type{<:AbstractGrid}=DEFAULT_GRID)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.gridded-Union{Tuple{NF}, Tuple{AbstractMatrix, SpectralTransform{NF}}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.gridded","text":"map = gridded( alms::AbstractMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.is_power_2-Tuple{Integer}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.is_power_2","text":"true/false = is_power_2(i::Integer)\n\nChecks whether an integer i is a power of 2, i.e. i = 2^k, with k = 0,1,2,3,....\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.roundup_fft-Union{Tuple{Integer}, Tuple{T}} where T<:Integer","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.roundup_fft","text":"m = roundup_fft(n::Int;\n small_primes::Vector{Int}=[2,3,5])\n\nReturns an integer m >= n with only small prime factors 2, 3 (default, others can be specified with the keyword argument small_primes) to obtain an efficiently fourier-transformable number of longitudes, m = 2^i * 3^j * 5^k >= n, with i,j,k >=0.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, SpeedyWeather.RingGrids.AbstractGrid{NF}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral!","text":"spectral!( alms::LowerTriangularMatrix,\n map::AbstractGrid,\n S::SpectralTransform)\n\nSpectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral-Tuple{AbstractMatrix}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral","text":"alms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral-Union{Tuple{NF}, Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}, SpectralTransform{NF}}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral","text":"alms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nSpectral transform (grid to spectral) map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral-Union{Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}}, Tuple{NF}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral","text":"alms = spectral( map::AbstractGrid;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to Grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_interpolation-Union{Tuple{NF}, Tuple{Type{NF}, LowerTriangularMatrix, Integer, Integer}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_interpolation","text":"alms_interp = spectral_interpolation( ::Type{NF},\n alms::LowerTriangularMatrix,\n ltrunc::Integer,\n mtrunc::Integer\n ) where NF\n\nReturns a spectral coefficient matrix alms_interp that is alms padded with zeros to interpolate in spectral space. If trunc is smaller or equal to the implicit truncation in alms obtained from its size than spectral_truncation is automatically called instead, returning alms_trunc, a coefficient matrix that is smaller than alms, implicitly setting higher degrees and orders to zero.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_smoothing!-Tuple{LowerTriangularMatrix, Real}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_smoothing!","text":"spectral_smoothing!(A::LowerTriangularMatrix,c;power=1)\n\nSmooth the spectral field A following A = (1-(1-c)∇²ⁿ) with power n of a normalised Laplacian so that the highest degree lmax is dampened by multiplication with c. Anti-diffusion for c>1.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_smoothing-Tuple{LowerTriangularMatrix, Real}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_smoothing","text":"A_smooth = spectral_smoothing(A::LowerTriangularMatrix,c;power=1)\n\nSmooth the spectral field A following A_smooth = (1-c*∇²ⁿ)A with power n of a normalised Laplacian so that the highest degree lmax is dampened by multiplication with c. Anti-diffusion for c<0.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_truncation!-Tuple{AbstractMatrix, Int64}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms,trunc)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_truncation!-Tuple{AbstractMatrix}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms)\n\nTruncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_truncation!-Union{Tuple{NF}, Tuple{AbstractMatrix{NF}, Integer, Integer}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms::AbstractMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_truncation!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{NF}, Integer, Integer}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms::LowerTriangularMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.spectral_truncation-Union{Tuple{NF}, Tuple{Type{NF}, LowerTriangularMatrix, Integer, Integer}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation","text":"alms_trunc = spectral_truncation(alms,trunc)\n\nReturns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.ϵlm-Tuple{Int64, Int64}","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.ϵlm","text":"ϵ = ϵ(l,m)\n\nRecursion factors ϵ as a function of degree l and order m (0-based) of the spherical harmonics. ϵ(l,m) = sqrt((l^2-m^2)/(4*l^2-1)) with default number format Float64.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.ϵlm-Union{Tuple{NF}, Tuple{Type{NF}, Int64, Int64}} where NF","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.ϵlm","text":"ϵ = ϵ(NF,l,m)\n\nRecursion factors ϵ as a function of degree l and order m (0-based) of the spherical harmonics. ϵ(l,m) = sqrt((l^2-m^2)/(4*l^2-1)) and then converted to number format NF.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.∇²!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.∇²!","text":"∇²!( ∇²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false,\n inverse::Bool=false)\n\nLaplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.\n\nKeyword arguments\n\nadd=true adds the ∇²(alms) to the output\nflipsign=true computes -∇²(alms) instead\ninverse=true computes ∇⁻²(alms) instead\n\nDefault is add=false, flipsign=false, inverse=false. These options can be combined.\n\n\n\n\n\n","category":"method"},{"location":"speedytransforms/#SpeedyWeather.SpeedyTransforms.∇⁻²!-Union{Tuple{NF}, Tuple{LowerTriangularMatrix{Complex{NF}}, LowerTriangularMatrix{Complex{NF}}, SpectralTransform{NF}}} where NF<:AbstractFloat","page":"Submodule: SpeedyTransforms","title":"SpeedyWeather.SpeedyTransforms.∇⁻²!","text":"∇⁻²!( ∇⁻²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false)\n\nCalls ∇²!(∇⁻²alms, alms, S; add, flipsign, inverse=true).\n\n\n\n\n\n","category":"method"},{"location":"grids/#Grids","page":"Grids","title":"Grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and but let's start but how they can be used","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"julia> spectral_grid = SpectralGrid(Grid = FullGaussianGrid)\nSpectralGrid:\n Spectral: T31 LowerTriangularMatrix{Complex{Float32}}, radius = 6.371e6 m\n Grid: 4608-element, 48-ring FullGaussianGrid{Float32} (quadratic)\n Resolution: 333km (average)\n Vertical: 8-level SigmaCoordinates","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The life of every SpeedyWeather.jl simulation starts with a SpectralGrid object which defines the resolution in spectral and in grid-point space. The generator SpectralGrid() can take as a keyword argument Grid which can be any of the grids described below. The resolution of the grid, however, is not directly chosen, but determined from the spectral resolution trunc and the dealiasing factor. More in Matching spectral and grid resolution.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: RingGrids is a module too!\nWhile RingGrids is the underlying module that SpeedyWeather.jl uses for data structs on the sphere, the module can also be used independently of SpeedyWeather, for example to interpolate between data on different grids. See RingGrids","category":"page"},{"location":"grids/#Ring-based-equi-longitude-grids","page":"Grids","title":"Ring-based equi-longitude grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"SpeedyWeather.jl's spectral transform supports all ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on these rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: Is the FullClenshawGrid a longitude-latitude grid?\nShort answer: Yes. The FullClenshawGrid is a specific longitude-latitude grid with equi-angle spacing. The most common grids for geoscientific data use regular spacings for 0-360˚E in longitude and 90˚N-90˚S. The FullClenshawGrid does that too, but it does not have a point on the North or South pole, and the central latitude ring sits exactly on the Equator. We name it Clenshaw following the Clenshaw-Curtis quadrature that is used in the Legendre transfrom in the same way as Gaussian refers to the Gaussian quadrature.","category":"page"},{"location":"grids/#Implemented-grids","page":"Grids","title":"Implemented grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Currently the following full grids <: AbstractFullGrid are implemented","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"FullGaussianGrid, a full grid with Gaussian latitudes\nFullClenshawGrid, a full grid with equi-angle latitudes","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron\nOctahedralClenshawGrid, similar but based on equi-angle latitudes\nHEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces\nOctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"An overview of these grids is visualised here","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"(Image: Overview of implemented grids in SpeedyWeather.jl)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.","category":"page"},{"location":"grids/#Grid-resolution","page":"Grids","title":"Grid resolution","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half. ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: HEALPix grids do not use Nside as resolution parameter\nThe original formulation for HEALPix grids use N_side, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use N_side for the documentation or within functions though.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Related: Effective grid resolution and Available horizontal resolutions.","category":"page"},{"location":"grids/#Matching-spectral-and-grid-resolution","page":"Grids","title":"Matching spectral and grid resolution","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation T with a grid resolution N (=nlat_half) such that T + 1 = N. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at l_max=31 in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid. In SpeedyWeather.jl the choice of the order of truncation is controlled with the dealiasing parameter in the SpectralGrid construction.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Let J be the total number of rings. Then we have","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"T approx J for linear truncation, i.e. dealiasing = 1\nfrac32T approx J for quadratic truncation, i.e. dealiasing = 2\n2T approx J for cubic truncation, , i.e. dealiasing = 3","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and in general fracm+12T approx J for m-th order truncation. So the higher the truncation order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. A quick overview of how the grid resolution changes when dealiasing is passed onto SpectralGrid on the FullGaussianGrid","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"trunc dealiasing FullGaussianGrid size\n31 1 64x32\n31 2 96x48\n31 3 128x64\n42 1 96x48\n42 2 128x64\n42 3 192x96\n... ... ...","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"You will obtain this information every time you create a SpectralGrid(;Grid,trunc,dealiasing).","category":"page"},{"location":"grids/#FullGaussianGrid","page":"Grids","title":"Full Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#FullClenshawGrid","page":"Grids","title":"Full Clenshaw-Curtis grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#Octahedral-Gaussian-grid","page":"Grids","title":"Octahedral Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#HEALPix-grid","page":"Grids","title":"HEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are N_varphi basepixels in zonal direction and N_theta basepixels in meridional direction. For N_varphi = 4 and N_theta = 3 we obtain the classical HEALPix grid with N_varphi N_theta = 12 basepixels shown above in Implemented grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always 2N, so 32 at N=16) and there are polar caps above and below the equatorial zone with the border at cos(theta) = 23 (theta in colatitudes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Following Górski, 2004[1], the z=cos(theta) colatitude of the j-th ring in the north polar cap, j=1N_side with 2N_side = N is ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^23N_side^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and on that ring, the longitude phi of the i-th point (i is the in-ring-index) is at","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i-tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The in-ring index i goes from i=14 for the first (i.e. northern-most) ring, i=18 for the second ring and i = 14j for the j-th ring in the northern polar cap.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"In the north equatorial belt j=N_side2N_side this changes to","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac43 - frac2j3N_side","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and the longitudes change to (i is always i = 14N_side in the equatorial belt meaning the number of longitude points is constant here)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2N_side(i - fracs2) quad s = (j - N_side + 1) mod 2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The cell boundaries are obtained by setting i = k + 12 or i = k + 12 + j (half indices) into the equations above, such that z(phik), a function for the cosine of colatitude z of index k and the longitude phi is obtained. These are then (northern polar cap)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^23N_side^2left(fracpi2phi_tright)^2 quad z = 1 - frack^23N_side^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with phi_t = phi mod tfracpi2 and in the equatorial belt","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac23-frac4k3N_side pm frac8phi3pi","category":"page"},{"location":"grids/#OctaHEALPix-grid","page":"Grids","title":"OctaHEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"While the classic HEALPix grid is based on a dodecahedron, other choices for N_varphi and N_theta in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With N_varphi = 4 and N_theta = 1 we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, 2pi around the Equator versus pi between the poles. N_varphi = 6 N_theta = 2 or N_varphi = 8 N_theta = 3 are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visualizations of these grids.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"We call the N_varphi = 4 N_theta = 1 HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As N_theta = 1 there is no equatorial belt which simplifies the grid. The latitude of the j-th isolatitude ring on the OctaHEALPixGrid is defined by","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^2N^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with j=1N, and similarly for the southern hemisphere by symmetry. On this grid N_side = N where N= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index i = 14j are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i - tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and again, the southern hemisphere grid points are obtained by symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries-2","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Similar to the grid cell boundaries for the HEALPix grid, the OctaHEALPix grid's boundaries are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^2N^2left(fracpi2phi_tright)^2 quad z = 1 - frack^2N^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The 3N_side^2 in the denominator of the HEALPix grid came simply N^2 for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).","category":"page"},{"location":"grids/#References","page":"Grids","title":"References","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"[1]: Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976","category":"page"},{"location":"primitiveequation/#Primitive-equation-model","page":"Primitive equation model","title":"Primitive equation model","text":"","category":"section"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"The primitive equations are a hydrostatic approximation of the compressible Navier-Stokes equations for an ideal gas on a rotating sphere. We largely follow the idealised spectral dynamical core developed by GFDL[1] and documented therein[2].","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"The primitive equations solved by SpeedyWeather.jl for relative vorticity zeta, divergence mathcalD, logarithm of surface pressure ln p_s, temperature T and specific humidity q are","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"beginaligned\nfracpartial zetapartial t = nabla times (mathbfmathcalP_mathbfu\n+ (f+zeta)mathbfu_perp - W(mathbfu) - R_dT_vnabla ln p_s) \nfracpartial mathcalDpartial t = nabla cdot (mathcalP_mathbfu\n+ (f+zeta)mathbfu_perp - W(mathbfu) - R_dT_vnabla ln p_s) - nabla^2(frac12(u^2 + v^2) + Phi) \nfracpartial ln p_spartial t = -frac1p_s nabla cdot int_0^p_s mathbfudp \nfracpartial Tpartial t = mathcalP_T -nablacdot(mathbfuT) + TmathcalD - W(T) + kappa T_v fracD ln pDt \nfracpartial qpartial t = mathcalP_q -nablacdot(mathbfuq) + qmathcalD - W(q)\nendaligned","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"with velocity mathbfu = (uv), rotated velocity mathbfu_perp = (v-u), Coriolis parameter f, W the vertical advection operator, dry air gas constant R_d, virtual temperature T_v, geopotential Phi, pressure p, thermodynamic kappa = R_dc_p with c_p the heat capacity at constant pressure. Horizontal hyper diffusion of the form (-1)^n+1nunabla^2n with coefficient nu and power n is added for every variable that is advected, meaning zeta mathcalD T q, but left out here for clarity, see Horizontal diffusion.","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"The parameterizations for the tendencies of uvTq from physical processes are denoted as mathcalP_mathbfu = (mathcalP_u mathcalP_v) mathcalP_T mathcalP_q and are further described in the corresponding sections, see Parameterizations.","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"SpeedyWeather.jl implements a PrimitiveWet and a PrimitiveDry dynamical core. For a dry atmosphere, we have q = 0 and the virtual temperature T_v = T equals the temperature (often called absolute to distinguish from the virtual temperature). The terms in the primitive equations and their discretizations are discussed in the following sections. ","category":"page"},{"location":"primitiveequation/#Virtual-temperature","page":"Primitive equation model","title":"Virtual temperature","text":"","category":"section"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"info: In short: Virtual temperature\nVirtual temperature is the temperature dry air would need to have to be as light as moist air. It is used in the dynamical core to include the effect of humidity on the density while replacing density through the ideal gas law with temperature.","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"We assume the atmosphere to be composed of two ideal gases: Dry air and water vapour. Given a specific humidity q both gases mix, their pressures p_d, p_w (d for dry, w for water vapour), and densities rho_d rho_w add in a given air parcel that has temperature T. The ideal gas law then holds for both gases","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"beginaligned\np_d = rho_d R_d T \np_w = rho_w R_w T \nendaligned","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"with the respective specific gas constants R_d = Rm_d and R_w = Rm_w obtained from the univeral gas constant R divided by the molecular masses of the gas. The total pressure p in the air parcel is","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"p = p_d + p_w = (rho_d R_d + rho_w R_w)T","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"We ultimately want to replace the density rho = rho_w + rho_d in the dynamical core, using the ideal gas law, with the temperature T, so that we never have to calculate the density explicitly. However, in order to not deal with two densities (dry air and water vapour) we would like to replace temperature with a virtual temperature that includes the effect of humidity on the density. So, whereever we use the ideal gas law to replace density with temperature, we would use the virtual temperature, which is a function of the absolute temperature and specific humidity, instead. A higher specific humidity in an air parcel lowers the density as water vapour is lighter than dry air. Consequently, the virtual temperature of moist air is higher than its absolute temperature because warmer air is lighter too at constant pressure. We therefore think of the virtual temperature as the temperature dry air would need to have to be as light as moist air.","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Starting with the last equation, with some manipulation we can write the ideal gas law as total density rho times a gas constant times the virtual temperature that is supposed to be a function of absolute temperature, humidity and some constants","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"p = (rho R_d + rho_w (R_w - R_d)) T = rho R_d (1 +\nfrac1 - tfracR_dR_wtfracR_dR_w fracrho_wrho_w + rho_d)T","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Now we identify","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"mu = frac1 - tfracR_dR_wtfracR_dR_w","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"as some constant that is positive for water vapour being lighter than dry air (tfracR_dR_w = tfracm_wm_d 1) and","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"q = fracrho_wrho_w + rho_d","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"as the specific humidity. Given temperature T and specific humidity q, we can therefore calculate the virtual temperature T_v as","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"T_v = (1 + mu q)T","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"For completeness we want to mention here that the above product, because it is a product of two variables qT has to be computed in grid-point space, see [Spectral Transform]. To obtain an approximation to the virtual temperature in spectral space without expensive transforms one can linearize","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"T_v = T + mu qbarT","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"With a global constant temperature barT, for example obtained from the l=m=0 mode, barT = T_00frac1sqrt4pi but depending on the normalization of the spherical harmonics that factor needs adjustment.","category":"page"},{"location":"primitiveequation/#Vertical-coordinates","page":"Primitive equation model","title":"Vertical coordinates","text":"","category":"section"},{"location":"primitiveequation/#General","page":"Primitive equation model","title":"General","text":"","category":"section"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Let Psi(xyzt) ","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"SpeedyWeather.jl currently uses sigma coordinates for the vertical. ","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"sigma = fracpp_s","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"p_k = sigma_kp_s","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Delta p_k = p_k+1 - p_k = Delta sigma_k p_s","category":"page"},{"location":"primitiveequation/#Geopotential","page":"Primitive equation model","title":"Geopotential","text":"","category":"section"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"In the hydrostatic approximation the vertical momentum equation becomes","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"fracpartial ppartial z = -rho g","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"meaning that the (negative) vertical pressure gradient is given by the density in that layer times the gravitational acceleration. The heavier the fluid the more the pressure will increase below. Inserting the ideal gas law","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"fracpartial gzpartial p = -fracR_dT_vp","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"with the geopotential Phi = gz we can write this in terms of the logarithm of pressure","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"fracpartial Phipartial ln p = -R_dT_v","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Note that we use the Virtual temperature here as we replaced the density through the ideal gas law with temperature. Given a vertical temperature profile T_v and the (constant) surface geopotential Phi_s = gz_s where z_s is the orography, we can integrate this equation from the surface to the top to obtain Phi_k on every layer k. The surface is at k = N+tfrac12 (see Vertical coordinates) with N vertical levels. We can integrate the geopotential onto half levels as","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"Phi_k-tfrac12 = Phi_k+tfrac12 + R_dT^v_k(ln p_k+12 - ln p_k-12)","category":"page"},{"location":"primitiveequation/#Surface-pressure-tendency","page":"Primitive equation model","title":"Surface pressure tendency","text":"","category":"section"},{"location":"primitiveequation/#Vertical-advection","page":"Primitive equation model","title":"Vertical advection","text":"","category":"section"},{"location":"primitiveequation/#Pressure-gradient-force","page":"Primitive equation model","title":"Pressure gradient force","text":"","category":"section"},{"location":"primitiveequation/#Temperature-equation","page":"Primitive equation model","title":"Temperature equation","text":"","category":"section"},{"location":"primitiveequation/#implicit_primitive","page":"Primitive equation model","title":"Semi-implicit time stepping","text":"","category":"section"},{"location":"primitiveequation/#Horizontal-diffusion","page":"Primitive equation model","title":"Horizontal diffusion","text":"","category":"section"},{"location":"primitiveequation/#Algorithm","page":"Primitive equation model","title":"Algorithm","text":"","category":"section"},{"location":"primitiveequation/#Scaled-primitive-equations","page":"Primitive equation model","title":"Scaled primitive equations","text":"","category":"section"},{"location":"primitiveequation/#References","page":"Primitive equation model","title":"References","text":"","category":"section"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"[1]: Geophysical Fluid Dynamics Laboratory, Idealized models with spectral dynamics","category":"page"},{"location":"primitiveequation/","page":"Primitive equation model","title":"Primitive equation model","text":"[2]: Geophysical Fluid Dynamics Laboratory, The Spectral Dynamical Core","category":"page"},{"location":"lowertriangularmatrices/#lowertriangularmatrices","page":"Submodule: LowerTriangularMatrices","title":"LowerTriangularMatrices","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"LowerTriangularMatrices is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"This module defines LowerTriangularMatrix, a lower triangular matrix, which in contrast to LinearAlgebra.LowerTriangular does not store the entries above the diagonal. SpeedyWeather.jl uses LowerTriangularMatrix which is defined as a subtype of AbstractMatrix to store the spherical harmonic coefficients (see Spectral packing). ","category":"page"},{"location":"lowertriangularmatrices/#Creation-of-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Creation of LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"A LowerTriangularMatrix can be created using zeros,ones,rand, or randn","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> using SpeedyWeather.LowerTriangularMatrices\n\njulia> L = rand(LowerTriangularMatrix{Float32},5,5)\n5×5 LowerTriangularMatrix{Float32}:\n 0.912744 0.0 0.0 0.0 0.0\n 0.0737592 0.230592 0.0 0.0 0.0\n 0.799679 0.0765255 0.888098 0.0 0.0\n 0.670835 0.997938 0.505276 0.492966 0.0\n 0.949321 0.193692 0.793623 0.152817 0.357968","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"or the undef initializor LowerTriangularMatrix{Float32}(undef,3,3). The element type is arbitrary though, you can use any type T too.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Alternatively, it can be created through conversion from Matrix, which drops the upper triangle entries and sets them to zero","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> M = rand(Float16,3,3)\n3×3 Matrix{Float16}:\n 0.2222 0.694 0.3452\n 0.2158 0.04443 0.274\n 0.9746 0.793 0.6294\n\njulia> LowerTriangularMatrix(M)\n3×3 LowerTriangularMatrix{Float16}:\n 0.2222 0.0 0.0\n 0.2158 0.04443 0.0\n 0.9746 0.793 0.6294","category":"page"},{"location":"lowertriangularmatrices/#Indexing-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Indexing LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"LowerTriangularMatrix supports two types of indexing: 1) by denoting two indices, column and row [l,m] or 2) by denoting a single index [lm]. The double index works as expected","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L\n3×3 LowerTriangularMatrix{Float16}:\n 0.1499 0.0 0.0\n 0.1177 0.478 0.0\n 0.1709 0.756 0.3223\n\njulia> L[2,2]\nFloat16(0.478)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"But the single index skips the zero entries in the upper triangle, i.e.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L[4]\nFloat16(0.478)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"which, important, is different from single indices of an AbstractMatrix","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> Matrix(L)[4]\nFloat16(0.0)","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"In performance-critical code a single index should be used, as this directly maps to the index of the underlying data vector. The double index is somewhat slower as it first has to be converted to the corresponding single index.","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Consequently, many loops in SpeedyWeather.jl are build with the following structure","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"n,m = size(L)\nij = 0\nfor j in 1:m\n for i in j:n\n ij += 1\n L[ij] = i+j\n end\nend","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"which loops over all lower triangle entries of L::LowerTriangularMatrix and the single index ij is simply counted up. However, one could also use [i,j] as indices in the loop body or to perform any calculation (i+j here). An iterator over all entries in the lower triangle can be created by","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"for ij in eachindex(L)\n # do something\nend","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"The setindex! functionality of matrixes will throw a BoundsError when trying to write into the upper triangle of a LowerTriangularMatrix, for example","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L[2,1] = 0 # valid index\n0\n\njulia> L[1,2] = 0 # invalid index in the upper triangle\nERROR: BoundsError: attempt to access 3×3 LowerTriangularMatrix{Float32} at index [1, 2]","category":"page"},{"location":"lowertriangularmatrices/#Linear-algebra-with-LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"Linear algebra with LowerTriangularMatrix","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"The LowerTriangularMatrices module's main purpose is not linear algebra, and it's implementation may not be efficient, however, many operations work as expected","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"julia> L = rand(LowerTriangularMatrix{Float32},3,3)\n3×3 LowerTriangularMatrix{Float32}:\n 0.57649 0.0 0.0\n 0.348685 0.875371 0.0\n 0.881923 0.850552 0.998306\n\njulia> L + L\n3×3 LowerTriangularMatrix{Float32}:\n 1.15298 0.0 0.0\n 0.697371 1.75074 0.0\n 1.76385 1.7011 1.99661\n\njulia> L * L\n3×3 Matrix{Float32}:\n 0.332341 0.0 0.0\n 0.506243 0.766275 0.0\n 1.68542 1.59366 0.996616","category":"page"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Note, however, that the latter includes a conversion to Matrix, which is true for many operations, including inv or \\. Hence when trying to do more sophisticated linear algebra with LowerTriangularMatrix we quickly leave lower triangular-land and go back to normal matrix-land.","category":"page"},{"location":"lowertriangularmatrices/#Function-and-type-index","page":"Submodule: LowerTriangularMatrices","title":"Function and type index","text":"","category":"section"},{"location":"lowertriangularmatrices/","page":"Submodule: LowerTriangularMatrices","title":"Submodule: LowerTriangularMatrices","text":"Modules = [SpeedyWeather.LowerTriangularMatrices]","category":"page"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix","text":"L = LowerTriangularMatrix{T}(v::Vector{T},m::Int,n::Int)\n\nA lower triangular matrix implementation that only stores the non-zero entries explicitly. L<:AbstractMatrix although in general we have L[i] != Matrix(L)[i], the former skips zero entries, tha latter includes them.\n\n\n\n\n\n","category":"type"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix-Union{Tuple{AbstractMatrix{T}}, Tuple{T}} where T","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.LowerTriangularMatrix","text":"L = LowerTriangularMatrix(M)\n\nCreate a LowerTriangularMatrix L from Matrix M by copying over the non-zero elements in M.\n\n\n\n\n\n","category":"method"},{"location":"lowertriangularmatrices/#Base.fill!-Union{Tuple{T}, Tuple{LowerTriangularMatrix{T}, Any}} where T","page":"Submodule: LowerTriangularMatrices","title":"Base.fill!","text":"fill!(L::LowerTriangularMatrix,x)\n\nFills the elements of L with x. Faster than fill!(::AbstractArray,x) as only the non-zero elements in L are assigned with x.\n\n\n\n\n\n","category":"method"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.eachharmonic-Tuple{LowerTriangularMatrix}","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.eachharmonic","text":"unit_range = eachharmonic(L::LowerTriangular)\n\ncreates unit_range::UnitRange to loop over all non-zeros in a LowerTriangularMatrix L. Like eachindex but skips the upper triangle with zeros in L.\n\n\n\n\n\n","category":"method"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.eachharmonic-Tuple{Vararg{LowerTriangularMatrix}}","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.eachharmonic","text":"unit_range = eachharmonic(Ls::LowerTriangularMatrix...)\n\ncreates unit_range::UnitRange to loop over all non-zeros in the LowerTriangularMatrices provided as arguments. Checks bounds first. All LowerTriangularMatrix's need to be of the same size. Like eachindex but skips the upper triangle with zeros in L.\n\n\n\n\n\n","category":"method"},{"location":"lowertriangularmatrices/#SpeedyWeather.LowerTriangularMatrices.ij2k-Tuple{Integer, Integer, Integer}","page":"Submodule: LowerTriangularMatrices","title":"SpeedyWeather.LowerTriangularMatrices.ij2k","text":"k = ij2k( i::Integer, # row index of matrix\n j::Integer, # column index of matrix\n m::Integer) # number of rows in matrix\n\nConverts the index pair i,j of an mxn LowerTriangularMatrix L to a single index k that indexes the same element in the corresponding vector that stores only the lower triangle (the non-zero entries) of L.\n\n\n\n\n\n","category":"method"},{"location":"conventions/#Style-and-convention-guide","page":"Style and convention guide","title":"Style and convention guide","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"In SpeedyWeather.jl we've been following the several conventions that are documented here.","category":"page"},{"location":"conventions/#Variable-naming","page":"Style and convention guide","title":"Variable naming","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The prognostic variables in spectral space are called","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" vor # Vorticity of horizontal wind field\n div # Divergence of horizontal wind field\n temp # Absolute temperature [K]\n pres_surf # Logarithm of surface pressure [log(Pa)]\n humid # Specific humidity [g/kg]","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" u\n v\n geopot\n ...","category":"page"},{"location":"conventions/#Preallocation","page":"Style and convention guide","title":"Preallocation","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"All arrays representing variables are preallocated and grouped into two structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" Prog::PrognosticVariables\n Diag::DiagnosticVariables","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The Diag struct contains further structs which represent the grid-point transformations of the prognostic variables and their tendencies.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" gridvars::GridVariables\n tendencies::Tendencies\n ...","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Constant arrays are grouped into several structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Boundaries","category":"page"},{"location":"conventions/#Julian-conventions","page":"Style and convention guide","title":"Julian conventions","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"We follow Julia's style guide and highlight here some important aspects of it.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Bang (!) convention. A function func does not change its input arguments, however, func! does.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.","category":"page"},{"location":"shallowwater/#Shallow-water-model","page":"Shallow water model","title":"Shallow water model","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The shallow water model describes the evolution of a 2D flow described by its velocity and an interface height that conceptually represents pressure. A divergent flow affects the interface height which in turn can impose a pressure gradient force onto the flow. The dynamics include advection, forces, dissipation, and continuity.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The following description of the shallow water model largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: The Shallow Water Equations[2].","category":"page"},{"location":"shallowwater/#Shallow-water-equations","page":"Shallow water model","title":"Shallow water equations","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The shallow water equations of velocity mathbfu = (uv) and interface height eta (i.e. the deviation from the fluid's rest height H) are, formulated in terms of relative vorticity zeta = nabla times mathbfu, divergence mathcalD = nabla cdot mathbfu","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\nfracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) =\nnabla times mathbfF + (-1)^n+1nunabla^2nzeta \nfracpartial mathcalDpartial t - nabla times (mathbfu(zeta + f)) =\nnabla cdot mathbfF -nabla^2(tfrac12(u^2 + v^2) + geta) + (-1)^n+1nunabla^2nmathcalD \nfracpartial etapartial t + nabla cdot (mathbfuh) = F_eta\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"We denote timet, Coriolis parameter f, a forcing vector mathbfF = (F_uF_v), hyperdiffusion (-1)^n+1 nu nabla^2n (n is the hyperdiffusion order, see Horizontal diffusion), gravitational acceleration g, dynamic layer thickness h, and a forcing for the interface height F_eta. In the shallow water model the dynamics layer thickness h is","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"h = eta + H - H_b","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"that is, the layer thickness at rest H plus the interface height eta minus orography H_b.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"In the shallow water system the flow can be described through uv or zetamathcalD which are related through the stream function Psi and the velocity potential Phi (which is zero in the Barotropic vorticity equation).","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\nzeta = nabla^2 Psi \nmathcalD = nabla^2 Phi \nmathbfu = nabla^perp Psi + nabla Phi\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"With nabla^perp being the rotated gradient operator, in cartesian coordinates xy: nabla^perp = (-partial_y partial_x). See Derivatives in spherical coordinates for further details. Especially because the inversion of the Laplacian and the gradients of Psi Phi can be computed in a single pass, see U,V from vorticity and divergence.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The divergence/curl of the vorticity flux mathbfu(zeta + f) are combined with the divergence/curl of the forcing vector mathbfF, as","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\n- nabla cdot (mathbfu(zeta + f)) + nabla times mathbfF =\nnabla times (mathbfF + mathbfu_perp(zeta + f)) \nnabla times (mathbfu(zeta + f)) + nabla cdot mathbfF =\nnabla cdot (mathbfF + mathbfu_perp(zeta + f))\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"equivalently to how this is done in the Barotropic vorticity equation with mathbfu_perp = (v-u).","category":"page"},{"location":"shallowwater/#Algorithm","page":"Shallow water model","title":"Algorithm","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"0. Start with initial conditions of relative vorticity zeta_lm, divergence D_lm, and interface height eta_lm in spectral space and transform this model state to grid-point space:","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Invert the Laplacian of zeta_lm to obtain the stream function Psi_lm in spectral space\nInvert the Laplacian of D_lm to obtain the velocity potential Phi_lm in spectral space\nobtain velocities U_lm = (cos(theta)u)_lm V_lm = (cos(theta)v)_lm from nabla^perpPsi_lm + nablaPhi_lm\nTransform velocities U_lm, V_lm to grid-point space UV\nUnscale the cos(theta) factor to obtain uv\nTransform zeta_lm, D_lm, eta_lm to zeta D eta in grid-point space","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Now loop over","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Compute the forcing vector mathbfF = (F_uF_v) for u and v\nMultiply uv with zeta+f in grid-point space\nAdd A = F_u + v(zeta + f) and B = F_v - u(zeta + f)\nTransform these vector components to spectral space A_lm, B_lm\nCompute the curl of (AB)_lm in spectral space which is the tendency of zeta_lm\nCompute the divergence of (AB)_lm in spectral space which is the tendency of mathcalD_lm\nCompute the kinetic energy frac12(u^2 + v^2) and transform to spectral space\nAdd to the kinetic energy the \"geopotential\" geta_lm in spectral space to obtain the Bernoulli potential\nTake the Laplacian of the Bernoulli potential and subtract from the divergence tendency\nCompute the volume fluxes uhvh in grid-point space via h = eta + H - H_b\nTransform to spectral space and take the divergence for -nabla cdot (mathbfuh) which is the tendency for eta\nAdd possibly forcing F_eta for eta in spectral space\nCorrect the tendencies following the semi-implicit time integration to prevent fast gravity waves from causing numerical instabilities\nCompute the horizontal diffusion based on the zetamathcalD tendencies\nCompute a leapfrog time step as described in Time integration with a Robert-Asselin and Williams filter\nTransform the new spectral state of zeta_lm, mathcalD_lm, eta_lm to grid-point uvzetamathcalDeta as described in 0.\nPossibly do some output\nRepeat from 1.","category":"page"},{"location":"shallowwater/#implicit_swm","page":"Shallow water model","title":"Semi-implicit time integration","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Probably the biggest advantage of a spectral model is its ability to solve (parts of) the equations implicitly a low computational cost. The reason is that a linear operator can be easily inverted in spectral space, removing the necessity to solve large equation systems. An operation like Psi = nabla^-2zeta in grid-point space is costly because it requires a global communication, coupling all grid points. In spectral space nabla^2 is a diagonal operator, meaning that there is no communication between harmonics and its inversion is therefore easily done on a mode-by-mode basis of the harmonics.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"This can be made use of when facing time stepping constraints with explicit schemes, where ridiculuously small time steps to resolve fast waves would otherwise result in a horribly slow simulation. In the shallow water system there are gravity waves that propagate at a wave speed of sqrtgH (typically 300m/s), which, in order to not violate the CFL criterion for explicit time stepping, would need to be resolved. Therefore, treating the terms that are responsible for gravity waves implicitly would remove that time stepping constraint and allows us to run the simulation at the time step needed to resolve the advective motion of the atmosphere, which is usually one or two orders of magnitude longer than gravity waves.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"In the following we will describe how the semi implicit time integration can be combined with the Leapfrog time stepping and the Robert-Asselin and Williams filter for a large increase in numerical stability with gravity waves. Let V_i be the model state of all prognostic variables at time step i, the leapfrog time stepping is then","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"fracV_i+1 - V_i-12Delta t = N(V_i)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"with the right-hand side operator N evaluated at the current time step i. Now the idea is to split the terms in N into non-linear terms that are evaluated explicitly in N_E and into the linear terms N_I, solved implicitly, that are responsible for the gravity waves. We could already assume to evaluate N_I at i+1, but in fact, we can introduce alpha in 01 so that for alpha=0 we use i-1 (i.e. explicit), for alpha=12 it is centred implicit tfrac12N_I(V_i-1) + tfrac12N_I(V_i+1), and for alpha=1 a fully backwards scheme N_I(V_i+1) evaluated at i+1.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"fracV_i+1 - V_i-12Delta t = N_E(V_i) + alpha N_I(V_i+1) + (1-alpha)N_I(V_i-1)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Let delta V = tfracV_i+1 - V_i-12Delta t be the tendency we need for the Leapfrog time stepping. Introducing xi = 2alphaDelta t we have","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"delta V = N_E(V_i) + N_I(V_i-1) + xi N_I(delta V)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"because N_I is a linear operator. This is done so that we can solve for delta V by inverting N_I, but let us gather the other terms as G first.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"G = N_E(V_i) + N_I(V_i-1) = N(V_i) + N_I(V_i-1 - V_i)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"For the shallow water equations we will only make use of the last formulation, meaning we first evaluate the whole right-hand side N(V_i) at the current time step as we would do with fully explicit time stepping but then add the implicit terms N_I(V_i-1 - V_i) afterwards to move those terms from i to i-1. Note that we could also directly evaluate the implicit terms at i-1 as it is suggested in the previous formulation N_E(V_i) + N_I(V_i-1), the result would be the same. But in general it can be more efficient to do it one or the other way, and in fact it is also possible to combine both ways. This will be discussed in the semi-implicit time stepping for the primitive equations.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"We can now implicitly solve for delta V by","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"delta V = (1-xi N_I)^-1G","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"So what is N_I? In the shallow water system the gravity waves are caused by","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\nfracpartial mathcalDpartial t = -gnabla^2eta \nfracpartial etapartial t = -HmathcalD\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"which is a linearization of the equations around a state of rest with uniform constant layer thickness h = H. The continuity equation with the -nabla(mathbfuh) term, for example, is linearized to -nabla(mathbfuH) = -HmathcalD. The divergence and continuity equations can now be written following the delta V = G + xi N_I(delta V) formulation from above as a coupled system (The vorticity equation is zero for the linear gravity wave equation in the shallow water equations, hence no semi-implicit correction has to be made to the vorticity tendency).","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\ndelta mathcalD = G_mathcalD - xi g nabla^2 delta eta \ndelta eta = G_mathcaleta - xi H deltamathcalD\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"with","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\nG_mathcalD = N_mathcalD - xi g nabla^2 (eta_i-1 - eta_i) \nG_mathcaleta = N_eta - xi H (mathcalD_i-1 - mathcalD_i)\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Inserting the second equation into the first, we can first solve for delta mathcalD, and then for delta eta. Reminder that we do this in spectral space to every harmonic independently, so the Laplace operator nabla^2 = -l(l+1) takes the form of its eigenvalue -l(l+1) (normalized to unit sphere, as are the scaled shallow water equations) and its inversion is therefore just the inversion of this scalar.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"delta D = fracG_mathcalD - xi gnabla^2 G_eta1 - xi^2 H nabla^2 = S^-1(G_mathcalD - xi gnabla^2 G_eta) ","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Where the last formulation just makes it clear that S = 1 - xi^2 H nabla^2 is the operator to be inverted. delta eta is then obtained via insertion as written above. Equivalently, by adding a superscript l for every degree of the spherical harmonics, we have","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"delta mathcalD^l = fracG_mathcalD^l + xi g l(l+1) G_eta^l1 + xi^2 H l(l+1)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The idea of the semi-implicit time stepping is now as follows:","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Evaluate the right-hand side explicitly at time step i to obtain the explicit, preliminary tendencies N_mathcalDN_eta (and N_zeta without a need for semi-implicit correction)\nMove the implicit terms from i to i-1 when calculating G_mathcalD G_eta\nSolve for delta mathcalD, the new, corrected tendency for divergence.\nWith delta mathcalD obtain delta eta, the new, corrected tendency for eta.\nApply horizontal diffusion as a correction to N_zeta delta mathcalD as outlined in Horizontal diffusion.\nLeapfrog with tendencies that have been corrected for both semi-implicit and diffusion.","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Some notes on the semi-implicit time stepping","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The inversion of the semi-implicit time stepping depends on delta t, that means every time the time step changes, the inversion has to be recalculated.\nYou may choose alpha = 12 to dampen gravity waves but initialisation shocks still usually kick off many gravity waves that propagate around the sphere for many days.\nWith increasing alpha 12 these waves are also slowed down, such that for alpha = 1 they quickly disappear in several hours.\nUsing the scaled shallow water equations the time step delta t has to be the scaled time step tildeDelta t = delta tR which is divided by the radius R. Then we use the normalized eigenvalues -l(l+1) which also omit the 1R^2 scaling, see scaled shallow water equations for more details.","category":"page"},{"location":"shallowwater/#scaled_swm","page":"Shallow water model","title":"Scaled shallow water equations","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"Similar to the scaled barotropic vorticity equations, SpeedyWeather.jl scales in the shallow water equations. The vorticity and the divergence equation are scaled with R^2, the radius of the sphere squared, but the continuity equation is scaled with R. We also combine the vorticity flux and forcing into a single divergence/curl operation as mentioned in Shallow water equations above","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"beginaligned\nfracpartial tildezetapartial tildet =\ntildenabla times (tildemathbfF + mathbfu_perp(tildezeta + tildef)) +\n(-1)^n+1tildenutildenabla^2ntildezeta \nfracpartial tildemathcalDpartial tildet =\ntildenabla cdot (tildemathbfF + mathbfu_perp(tildezeta + tildef)) -\ntildenabla^2left(tfrac12(u^2 + v^2) + geta right) +\n(-1)^n+1tildenutildenabla^2ntildemathcalD \nfracpartial etapartial tildet =\n- tildenabla cdot (mathbfuh) + tildeF_eta\nendaligned","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"As in the scaled barotropic vorticity equations, one needs to scale the time step, the Coriolis force, the forcing and the diffusion coefficient, but then enjoys the luxury of working with dimensionless gradient operators. As before, SpeedyWeather.jl will scale vorticity and divergence just before the model integration starts and unscale them upon completion and for output. In the semi-implicit time integration we solve an equation that also has to be scaled. It is with radius squared scaling (because it is the tendency for the divergence equation which is also scaled with R^2)","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"R^2 delta D = R^2fracG_mathcalD - xi gnabla^2 G_eta1 - xi^2 H nabla^2","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"As G_eta is only scaled with R we have","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"tildedelta D = fractildeG_mathcalD - tildexi gtildenabla^2 tildeG_eta1 - tildexi^2 H tildenabla^2","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"The R^2 normalizes the Laplace operator in the numerator, but using the scaled G_eta we also scale xi (which is convenient, because the time step within is the one we use anyway). The denominator S does not actually change because xi^2nabla^2 = tildexi^2tildenabla^2 as xi^2 is scaled with 1R^2, but the Laplace operator with R^2. So overall we just have to use the scaled time step tildeDelta t and normalized eigenvalues for tildenabla^2.","category":"page"},{"location":"shallowwater/#References","page":"Shallow water model","title":"References","text":"","category":"section"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"[1]: Geophysical Fluid Dynamics Laboratory, Idealized models with spectral dynamics","category":"page"},{"location":"shallowwater/","page":"Shallow water model","title":"Shallow water model","text":"[2]: Geophysical Fluid Dynamics Laboratory, The Shallow Water Equations.","category":"page"},{"location":"extending/#New-model-setups","page":"Extending SpeedyWeather","title":"New model setups","text":"","category":"section"},{"location":"extending/","page":"Extending SpeedyWeather","title":"Extending SpeedyWeather","text":"more to come...","category":"page"},{"location":"spectral_transform/#Spherical-Harmonic-Transform","page":"Spherical harmonic transform","title":"Spherical Harmonic Transform","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space which can be any of the Implemented grids as defined by RingGrids. This includes the classical full Gaussian grid, a regular longitude-latitude grid called the full Clenshaw grid (FullClenshawGrid), ECMWF's octahedral Gaussian grid[Malardel2016], and HEALPix grids[Gorski2004]. SpeedyWeather.jl's spectral transform module SpeedyTransforms is grid-flexible and can be used with any of these, see Grids.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: SpeedyTransforms is a module too!\nSpeedyTransform is the underlying module that SpeedyWeather imports to transform between spectral and grid-point space, which also implements Derivatives in spherical coordinates. You can use this module independently of SpeedyWeather for spectral transforms, see SpeedyTransforms.","category":"page"},{"location":"spectral_transform/#Inspiration","page":"Spherical harmonic transform","title":"Inspiration","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform implemented by SpeedyWeather.jl follows largely Justin Willmert's CMB.jl and SphericalHarmonicTransforms.jl package and makes use of AssociatedLegendrePolynomials.jl and FFTW.jl for the Fourier transform. Justin described his work in a Blog series [Willmert2020].","category":"page"},{"location":"spectral_transform/#Spherical-harmonics","page":"Spherical harmonic transform","title":"Spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spherical harmonics Y_lm of degree l and order m over the longitude phi = (02pi) and colatitudes theta = (-pi2pi2), are","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Y_lm(phi theta) = lambda_l^m(sintheta) e^imphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with lambda_l^m being the pre-normalized associated Legendre polynomials, and e^imphi are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Latitudes versus colatitudes\nThe implementation of the spectral transforms in SpeedyWeather.jl uses colatitudes theta = (0pi) (0 at the north pole) but the dynamical core uses latitudes theta = (-pi2pi2) (pi2 at the north pole). Note: We may also use latitudes in the spherical harmonic transform in the future for consistency. ","category":"page"},{"location":"spectral_transform/#synthesis","page":"Spherical harmonic transform","title":"Synthesis (spectral to grid)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The synthesis (or inverse transform) takes the spectral coefficients a_lm and transforms them to grid-point values f(phitheta) (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics Y_lm with non-zero coefficients.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"f(phitheta) = sum_l=0^infty sum_m=-l^l a_lm Y_lm(phitheta)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We obtain an approximation with a finite set of a_lm by truncating the series in both degree l and order m somehow. Most commonly, a triangular truncation is applied, such that all degrees after l = l_max are discarded. Triangular because the retained array of the coefficients a_lm looks like a triangle. Other truncations like rhomboidal have been studied[Daley78] but are rarely used since. Choosing l_max also constrains m_max and determines the (horizontal) spectral resolution. In SpeedyWeather.jl this resolution as chosen as trunc when creating the SpectralGrid.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"For f being a real-valued there is a symmetry","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"a_l-m = (-1)^m a^*_l+m","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"meaning that the coefficients at -m and m are the same, but the sign of the real and imaginary component can be flipped, as denoted with the (-1)^m and the complex conjugate a_lm^*. As we are only dealing with real-valued fields anyway, we therefore never have to store the negative orders -m and end up with a lower triangular matrix of size (l_max+1) times (m_max+1) or technically (T+1)^2 where T is the truncation trunc. One is added here because the degree l and order m use 0-based indexing but sizes (and so is Julia's indexing) are 1-based.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"For correctness we want to mention here that vector quantities require one more degree l due to the recurrence relation in the Meridional derivative. Hence for practical reasons all spectral fields are represented as a lower triangular matrix of size (m_max + 2) times (m_max +1). And the scalar quantities would just not make use of that last degree, and its entries would be simply zero. We will, however, for the following sections ignore this and only discuss it again in Meridional derivative.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Another consequence of the symmetry mentioned above is that the zonal harmonics, meaning a_lm=0 have no imaginary component. Because these harmonics are zonally constant, a non-zero imaginary component would rotate them around the Earth's axis, which, well, doesn't actually change a real-valued field. ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Following the notation of [Willmert2020] we can therefore write the truncated synthesis as","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"f(phitheta) = sum_l=0^l_max sum_m=0^l (2-delta_m0) a_lm Y_lm(phitheta)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The (2-delta_m0) factor using the Kronecker delta is used here because of the symmetry we have to count both the m-m order pairs (hence the 2) except for the zonal harmonics which do not have a pair.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Another symmetry arises from the fact that the spherical harmonics are either symmetric or anti-symmetric around the Equator. There is an even/odd combination of degrees and orders so that the sign flips like a checkerboard","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Y_lm(phipi-theta) = (-1)^l+mY_lm(phiphi)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This means that one only has to compute the Legendre polynomials for one hemisphere and the other one follows with this equality.","category":"page"},{"location":"spectral_transform/#analysis","page":"Spherical harmonic transform","title":"Analysis (grid to spectral)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting in grid-point space we can transform a field f(lambdatheta) into the spectral space of the spherical harmonics by","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"a_lm = int_0^2pi int_0^pi f(phitheta) Y_lm(phitheta) sin theta dtheta dphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Note that this notation again uses colatitudes theta, for latitudes the sintheta becomes a costheta and the bounds have to be changed accordingly to (-fracpi2fracpi2). A discretization with N grid points at location (phi_itheta_i), indexed by i can be written as [Willmert2020]","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"hata_lm = sum_i f(phi_itheta_i) Y_lm(phi_itheta_i) sin theta_i Deltatheta Deltaphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The hat on a just means that it is an approximation, or an estimate of the true a_lm approx hata_lm. We can essentially make use of the same symmetries as already discussed in Synthesis. Splitting into the Fourier modes e^imphi and the Legendre polynomials lambda_l^m(costheta) (which are defined over -11 so the costheta argument maps them to colatitudes) we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"hata_lm = sum_j left sum_i f(phi_itheta_j) e^-imphi_i right lambda_lm(theta_j) sin theta_j Deltatheta Deltaphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"So the term in brackets can be separated out as long as the latitude theta_j is constant, which motivates us to restrict the spectral transform to grids with iso-latitude rings, see Grids. Furthermore, this term can be written as a fast Fourier transform, if the phi_i are equally spaced on the latitude ring j. Note that the in-ring index i can depend on the ring index j, so that one can have reduced grids, which have fewer grid points towards the poles, for example. Also the Legendre polynomials only have to be computed for the colatitudes theta_j (and in fact only one hemisphere, due to the north-south symmetry discussed in the Synthesis). It is therefore practical and efficient to design a spectral transform implementation for ring grids, but there is no need to hardcode a specific grid.","category":"page"},{"location":"spectral_transform/#Spectral-packing","page":"Spherical harmonic transform","title":"Spectral packing","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Spectral packing is the way how the coefficients a_lm of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree l and order m as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Every row represents an order l geq 0, starting from l=0 at the top. Every column represents an order m geq 0, starting from m=0 on the left. The coefficients of these spherical harmonics are directly mapped into a matrix a_lm as ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 \n a_10 a_11 \n a_20 a_12 a_22 \n a_30 a_13 a_23 a_33","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal are zero. Note that internally vector fields include an additional degree, such that l_max = m_max + 1 (see Derivatives in spherical coordinates for more information). The harmonics with a_l0 (the first column) are also called zonal harmonics as they are constant with longitude phi. The harmonics with a_ll (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into 2l sectors in longitude phi without a zero-crossing in latitude.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"For correctness it is mentioned here that SpeedyWeather.jl uses a LowerTriangularMatrix type to store the spherical harmonic coefficients. By doing so, the upper triangle is actually not explicitly stored and the data technically unravelled into a vector, but this is hidden as much as possible from the user. For more details see LowerTriangularMatrices.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Array indices\nFor a spectral field a note that due to Julia's 1-based indexing the coefficient a_lm is obtained via a[l+1,m+1]. Alternatively, we may index over 1-based l,m but a comment is usually added for clarification.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Fortran SPEEDY does not use the same spectral packing as SpeedyWeather.jl. The alternative packing lm therein uses l=m and m=l-m as summarized in the following table.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"degree l order m l=m m=l-m\n0 0 0 0\n1 0 0 1\n1 1 1 0\n2 0 0 2\n2 1 1 1\n2 2 2 0\n3 0 0 3\n... ... ... ...","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 a_10 a_20 a_30\n a_11 a_21 a_31 \n a_22 a_32 \n a_33 ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran SPEEDY.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with l leq l_max and m leq m_max are explicitly represented. This is usually described as Tm_max, with l_max = m_max (although in vector quantities require one more degree l in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with l_max = m_max = 31. Note that the degree l and order m are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.","category":"page"},{"location":"spectral_transform/#Available-horizontal-resolutions","page":"Spherical harmonic transform","title":"Available horizontal resolutions","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Technically, SpeedyWeather.jl supports arbitrarily chosen resolution parameter trunc when creating the SpectralGrid that refers to the highest non-zero degree l_max that is resolved in spectral space. SpeedyWeather.jl will always try to choose an easily-Fourier transformable[FFT] size of the grid, but as we use FFTW.jl there is quite some flexibility without performance sacrifice. However, this has traditionally lead to typical resolutions that we also use for testing we therefore recommend to use. They are as follows with more details below","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"trunc nlon nlat Delta x\n31 (default) 96 48 400 km\n42 128 64 312 km\n63 192 96 216 km\n85 256 128 165 km\n127 384 192 112 km\n170 512 256 85 km\n255 768 384 58 km\n341 1024 512 43 km\n511 1536 768 29 km\n682 2048 1024 22 km\n1024 3072 1536 14 km\n1365 4092 2048 11 km","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Some remarks on this table","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This assumes the default quadratic truncation, you can always adapt the grid resolution via the dealiasing option, see Matching spectral and grid resolution\nnlat refers to the total number of latitude rings, see Grids. With non-Gaussian grids, nlat will be one one less, e.g. 47 instead of 48 rings.\nnlon is the number of longitude points on the Full Gaussian Grid, for other grids there will be at most these number of points around the Equator.\nDelta x is the horizontal resolution. For a spectral model there are many ways of estimating this[9]. We use here the square root of the average area a grid cell covers, see Effective grid resolution","category":"page"},{"location":"spectral_transform/#Effective-grid-resolution","page":"Spherical harmonic transform","title":"Effective grid resolution","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"There are many ways to estimate the effective grid resolution of spectral models[9]. Some of them are based on the wavelength a given spectral resolution allows to represent, others on the total number of real variables per area. However, as many atmospheric models do represent a considerable amount of physics on the grid (see Parameterizations) there is also a good argument to include the actual grid resolution into this estimate and not just the spectral resolution. We therefore use the average grid cell area to estimate the resolution","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Delta x = sqrtfrac4pi R^2N","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with N number of grid points over a sphere with radius R. However, we have to acknowledge that this usually gives higher resolution compared to other methods of estimating the effective resolution, see [Randall2021] for a discussion. You may therefore need to be careful to make claims that, e.g. trunc=85 can resolve the atmospheric dynamics at a scale of 165km.","category":"page"},{"location":"spectral_transform/#Derivatives-in-spherical-coordinates","page":"Spherical harmonic transform","title":"Derivatives in spherical coordinates","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Horizontal gradients in spherical coordinates are defined for a scalar field A and the latitudes theta and longitudes lambda as","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla A = left(frac1Rcosthetafracpartial Apartial lambda frac1Rfracpartial Apartial theta right)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"However, the divergence of a vector field mathbfu = (uv) includes additional cos(theta) scalings","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla cdot mathbfu = frac1Rcosthetafracpartial upartial lambda +\nfrac1Rcosthetafracpartial (v costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"and similar for the curl","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla times mathbfu = frac1Rcosthetafracpartial vpartial lambda -\nfrac1Rcosthetafracpartial (u costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The radius of the sphere (i.e. Earth) is R. The zonal gradient scales with 1cos(theta) as the longitudes converge towards the poles (note that theta describes latitudes here, definitions using colatitudes replace the cos with a sin.)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting with a spectral field of vorticity zeta and divergence mathcalD one can obtain stream function Psi and velocity potential Phi by inverting the Laplace operator nabla^2:","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi = nabla^-2zeta quad Phi = nabla^-2mathcalD","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The velocities uv are then obtained from (uv) = nabla^botPsi + nablaPhi following the definition from above and nabla^bot = (-R^-1partial_theta (Rcostheta)^-1partial_lambda)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nu = -frac1Rpartial_thetaPsi + frac1Rcosthetapartial_lambdaPhi \nv = +frac1Rpartial_thetaPhi + frac1Rcosthetapartial_lambdaPsi\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Alternatively, we can use the velocities U = ucostheta V = vcostheta, which we do as the meridional gradient for spherical harmonics is easier implemented with a costheta-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with UV and not uv. From uv we can return to zeta mathcalD via","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nzeta = frac1Rcosthetapartial_lambda v - frac1Rcosthetapartial_theta (u costheta) \nmathcalD = frac1Rcosthetapartial_lambda u + frac1Rcosthetapartial_theta (v costheta)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Equivalently, we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU = -fraccosthetaRpartial_thetaPsi + frac1Rpartial_lambdaPhi \nV = +fraccosthetaRpartial_thetaPhi + frac1Rpartial_lambdaPsi \nzeta = frac1Rpartial_lambda left( fracVcos^2theta right) -\nfraccosthetaRpartial_theta left( fracUcos^2theta right) \nmathcalD = frac1Rpartial_lambda left( fracUcos^2theta right) +\nfraccosthetaRpartial_theta left( fracVcos^2theta right)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is a more convenient formulation because of the way how the Meridional derivative is implemented with a recursion relation, actually computing costheta partial_theta rather than partial_theta directly. The remaining cosine scalings in (UV)*cos^-2theta are done in grid-point space. If one wanted to get back to zeta mathcalD this is how it would be done, but it is often more convenient to unscale UV on the fly in the spectral transform to obtain uv and then divide again by costheta when any gradient (or divergence or curl) is taken. This is because other terms would need that single costheta unscaling too before a gradient is taken. How the operators nabla nabla times nabla cdot can be implemented with spherical harmonics is presented in the following sections.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out in this last formulation too.","category":"page"},{"location":"spectral_transform/#Zonal-derivative","page":"Spherical harmonic transform","title":"Zonal derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The zonal derivative of a scalar field Psi in spectral space is the zonal derivative of all its respective spherical harmonics Psi_lm(phitheta) (now we use phi for longitudes to avoid confusion with the Legendre polynomials lambda_lm)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"v_lm = frac1R cos(theta) fracpartialpartial phi left( lambda_l^m(costheta) e^imphi right) =\nfracimR cos(theta) lambda_l^m(costheta) e^imphi = fracimR cos(theta) Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"So for every spectral harmonic, cos(theta)v_lm is obtained from Psi_lm via a multiplication with imR. Unscaling the cos(theta)-factor is done after transforming the spectral coefficients v_lm into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as tildePsi = R^-1Psi such that the division by radius R in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order im. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number m times imaginary i.","category":"page"},{"location":"spectral_transform/#Meridional-derivative","page":"Spherical harmonic transform","title":"Meridional derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridional derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costheta fracdP_lmdtheta = -lepsilon_l+1mP_l+1m + (l+1)epsilon_lmP_l-1m","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with recursion factors","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"epsilon_lm = sqrtfracl^2-m^24l^2-1","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In the following we use the example of obtaining the zonal velocity u from the stream function Psi, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi(lambdatheta) = sum_lmPsi_lmP_lm(sintheta)e^imlambda","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"we multiply with -R^-1costhetapartial_theta to obtain","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costhetaleft(-frac1Rpartial_thetaPsi right) = -frac1Rsum_lmPsi_lme^imlambdacosthetapartial_theta P_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"at which point the recursion from above can be applied. Collecting terms proportional to P_lm then yields","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"(cos(theta)u)_lm = -frac1R(-(l-1)epsilon_lmPsi_l-1m + (l+2)epsilon_l+1mPsi_l+1m)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"To obtain the coefficient of each spherical harmonic lm of the meridional gradient of a spectral field, two coefficients at l-1m and l+1m have to be combined. This means that the coefficient of a gradient ((costheta) u)_lm is a linear combination of the coefficients of one higher and one lower degree Psi_l+1mPsi_l-1m. As the coefficient Psi_lm with ml are zero, the sectoral harmonics (l=m) of the gradients are obtained from the first off-diagonal only. However, the l=l_max harmonics of the gradients require the l_max-1 as well as the l_max+1 harmonics. As a consequence vector quantities like velocity components uv require one more degree l than scalar quantities like vorticity[Bourke72]. However, for easier compatibility all spectral fields in SpeedyWeather.jl use one more degree l, but scalar quantities should not make use of it. Equivalently, the last degree l is set to zero before the time integration, which only advances scalar quantities.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In SpeedyWeather.jl vector quantities like uv use therefore one more meridional mode than scalar quantities such as vorticity zeta or stream function Psi. The meridional derivative in SpeedyWeather.jl also omits the 1R-scaling as explained for the Zonal derivative and in Radius scaling.","category":"page"},{"location":"spectral_transform/#Divergence-and-curl-in-spherical-harmonics","page":"Spherical harmonic transform","title":"Divergence and curl in spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridional gradient as described above can be applied to scalars, such as Psi and Phi in the conversion to velocities (uv) = nabla^botPsi + nablaPhi, however, the operators curl nabla times and divergence nabla cdot in spherical coordinates involve a costheta scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform of vorticity zeta is","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac12piint_-tfracpi2^tfracpi2int_0^2pi zeta(lambdatheta)\nP_lm(sintheta) e^imlambda dlambda costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Given that Rzeta = cos^-1partial_lambda v - cos^-1partial_theta (u costheta), we therefore have to evaluate a meridional integral of the form","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"int P_lm frac1cos theta partial_theta(u costheta)) cos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which can be solved through integration by parts. As ucostheta = 0 at theta = pm tfracpi2 only the integral","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int partial_theta P_lm (u costheta) dtheta = -int costheta partial_theta P_lm\n(fracucostheta) costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"remains. Inserting the recurrence relation from the Meridional derivative turns this into","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int left(-l epsilon_l+1mP_l+1m + (l+1)epsilon_lm P_l-1m right) (fracucostheta)\ncos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Now we expand (tfracucostheta) but only the lm harmonic will project ontoP_lm. Let u^* = ucos^-1theta v^* = vcos^-1theta we then have in total","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nRzeta_lm = imv^*_lm + (l+1)epsilon_lmu^*_l-1m - lepsilon_l+1mu^*_l+1m \nRD_lm = imu^*_lm - (l+1)epsilon_lmv^*_l-1m + lepsilon_l+1mv^*_l+1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"And the divergence D is similar, but (uv) to (-vu). We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#Laplacian","page":"Spherical harmonic transform","title":"Laplacian","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral Laplacian is easily applied to the coefficients Psi_lm of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator nabla^2 in spherical coordinates with eigenvalues -l(l+1) divided by the radius squared R^2, i.e. nabla^2 Psi becomes tfrac-l(l+1)R^2Psi_lm in spectral space. For example, vorticity zeta and streamfunction Psi are related by zeta = nabla^2Psi in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree l and order m to","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac-l(l+1)R^2Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This can be easily inverted to obtain the stream function Psi from vorticity zeta instead. In order to avoid division by zero, we set Psi_00 here, given that the stream function is only defined up to a constant anyway.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nPsi_lm = fracR^2-l(l+1)zeta_lm quad foralllm 0\nPsi_00 = 0\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"See also Horizontal diffusion and Normalization of diffusion.","category":"page"},{"location":"spectral_transform/#U,V-from-vorticity-and-divergence","page":"Spherical harmonic transform","title":"U,V from vorticity and divergence","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity zeta and divergence D (which are prognostic variables) to U=ucostheta V=vcostheta. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree l (the meridional gradient). It is therefore computationally more efficient to compute UV directly from zetaD instead of calculating stream function and velocity potential first. In total we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU_lm = -fraciml(l+1)(RD)_lm + fracepsilon_l+1ml+1(Rzeta)_l+1m -\nfracepsilon_lml(Rzeta)_l-1m \nV_lm = -fraciml(l+1)(Rzeta)_lm - fracepsilon_l+1ml+1(RD)_l+1m +\nfracepsilon_lml(RD)_l-1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#References","page":"Spherical harmonic transform","title":"References","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Malardel2016]: Malardel S, Wedi N, Deconinck W, Diamantakis M, Kühnlein C, Mozdzynski G, Hamrud M, Smolarkiewicz P. A new grid for the IFS. ECMWF newsletter. 2016;146(23-28):321. doi: 10.21957/zwdu9u5i","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Gorski2004]: Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Willmert2020]: Justin Willmert, 2020. justinwillmert.comIntroduction to Associated Legendre Polynomials (Legendre.jl Series, Part I)\nCalculating Legendre Polynomials (Legendre.jl Series, Part II)\nPre-normalizing Legendre Polynomials (Legendre.jl Series, Part III)\nMaintaining numerical accuracy in the Legendre recurrences (Legendre.jl Series, Part IV)\nIntroducing Legendre.jl (Legendre.jl Series, Part V)\nNumerical Accuracy of the Spherical Harmonic Recurrence Coefficient (Legendre.jl Series Addendum)\nNotes on Calculating the Spherical Harmonics\nMore Notes on Calculating the Spherical Harmonics: Analysis of maps to harmonic coefficients","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Daley78]: Roger Daley & Yvon Bourassa (1978) Rhomboidal versus triangular spherical harmonic truncation: Some verification statistics, Atmosphere-Ocean, 16:2, 187-196, DOI: 10.1080/07055900.1978.9649026","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Randall2021]: David Randall, 2021. An Introduction to Numerical Modeling of the Atmosphere, Chapter 22.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Durran2010]: Dale Durran, 2010. Numerical Methods for Fluid Dynamics, Springer. In particular section 6.2, 6.4.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[GFDL]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[FFT]: Depending on the implementation of the Fast Fourier Transform (Cooley-Tukey algorithm, or or the Bluestein algorithm) easily Fourier-transformable can mean different things: Vectors of the length n that is a power of two, i.e. n = 2^i is certainly easily Fourier-transformable, but for most FFT implementations so are n = 2^i3^j5^k with ijk some positive integers. In fact, FFTW uses O(n log n) algorithms even for prime sizes.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[Bourke72]: Bourke, W. An Efficient, One-Level, Primitive-Equation Spectral Model. Mon. Wea. Rev. 100, 683–689 (1972). doi:10.1175/1520-0493(1972)100<0683:AEOPSM>2.3.CO;2","category":"page"},{"location":"ringgrids/#RingGrids","page":"Submodule: RingGrids","title":"RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it and so does SpeedyTransforms) and can also be used without running simulations. It is just not put into its own respective repository.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids defines several iso-latitude grids, which are mathematically described in the section on Grids. In brief, they include the regular latitude-longitude grids (here called FullClenshawGrid) as well as grids which latitudes are shifted to the Gaussian latitudes and reduced grids, meaning that they have a decreasing number of longitudinal points towards the poles to be more equal-area than full grids.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids defines and exports the following grids:","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"full grids: FullClenshawGrid, FullGaussianGrid, FullHEALPix, and FullOctaHEALPix\nreduced grids: OctahedralGaussianGrid, OctahedralClenshawGrid, OctaHEALPixGrid and HEALPixGrid","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"The following explanation of how to use these can be mostly applied to any of them, however, there are certain functions that are not defined, e.g. the full grids can be trivially converted to a Matrix (i.e. they are rectangular grids) but not the OctahedralGaussianGrid.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"note: What is a ring?\nWe use the term ring, short for iso-latitude ring, to refer to a sequence of grid points that all share the same latitude. A latitude-longitude grid is a ring grid, as it organises its grid-points into rings. However, other grids, like the cubed-sphere are not based on iso-latitude rings. SpeedyWeather.jl only works with ring grids because its a requirement for the Spherical Harmonic Transform to be efficient. See Grids.","category":"page"},{"location":"ringgrids/#Creating-data-on-a-RingGrid","page":"Submodule: RingGrids","title":"Creating data on a RingGrid","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Every grid in RingGrids has a grid.data field, which is a vector containing the data on the grid. The grid points are unravelled west to east then north to south, meaning that it starts at 90˚N and 0˚E then walks eastward for 360˚ before jumping on the next latitude ring further south, this way circling around the sphere till reaching the south pole. This may also be called ring order.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Data in a Matrix which follows this ring order can be put on a FullGaussianGrid like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"using SpeedyWeather.RingGrids\nmap = randn(Float32,8,4)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid = FullGaussianGrid(map)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"A full Gaussian grid has always 2N x N grid points, but a FullClenshawGrid has 2N x N-1, if those dimensions don't match, the creation will throw an error. To reobtain the data from a grid, you can access its data field which returns a normal Vector","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid.data","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Which can be reshaped to reobtain map from above. Alternatively you can Matrix(grid) to do this in one step","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"map == Matrix(FullGaussianGrid(map))","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"You can also use zeros,ones,rand,randn to create a grid, whereby nlat_half, i.e. the number of latitude rings on one hemisphere, Equator included, is used as a resolution parameter and here as a second argument.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"nlat_half = 4\ngrid = randn(OctahedralGaussianGrid{Float16},nlat_half)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"and any element type T can be used for OctahedralGaussianGrid{T} and similar for other grid types.","category":"page"},{"location":"ringgrids/#Visualising-RingGrid-data","page":"Submodule: RingGrids","title":"Visualising RingGrid data","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"As only the full grids can be reshaped into a matrix, the underlying data structure of any AbstractGrid is a vector. As shown in the examples above, one can therefore inspect the data as if it was a vector. But as that data has, through its <:AbstractGrid type, all the geometric information available to plot it on a map, RingGrids also exports plot function, based on UnicodePlots' heatmap.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"nlat_half = 24\ngrid = randn(OctahedralGaussianGrid,nlat_half)\nplot(grid)","category":"page"},{"location":"ringgrids/#Indexing-RingGrids","page":"Submodule: RingGrids","title":"Indexing RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"All RingGrids have a single index ij which follows the ring order. While this is obviously not super exciting here are some examples how to make better use of the information that the data sits on a grid.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"We obtain the latitudes of the rings of a grid by calling get_latd (get_lond is only defined for full grids, or use get_latdlonds for latitudes, longitudes per grid point not per ring)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid = randn(OctahedralClenshawGrid,5)\nlatd = get_latd(grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Now we could calculate Coriolis and add it on the grid as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"rotation = 7.29e-5 # angular frequency of Earth's rotation [rad/s]\ncoriolis = 2rotation*sind.(latd) # vector of coriolis parameters per latitude ring\n\nrings = eachring(grid)\nfor (j,ring) in enumerate(rings)\n f = coriolis[j]\n for ij in ring\n grid[ij] += f\n end\nend","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"eachring creates a vector of UnitRange indices, such that we can loop over the ring index j (j=1 being closest to the North pole) pull the coriolis parameter at that latitude and then loop over all in-ring indices i (changing longitudes) to do something on the grid. Something similar can be done to scale/unscale with the cosine of latitude for example. We can always loop over all grid-points like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"for ij in eachgridpoint(grid)\n grid[ij]\nend","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"or use eachindex instead.","category":"page"},{"location":"ringgrids/#Interpolation-on-RingGrids","page":"Submodule: RingGrids","title":"Interpolation on RingGrids","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"In most cases we will want to use RingGrids so that our data directly comes with the geometric information of where the grid-point is one the sphere. We have seen how to use get_latd, get_lond, ... for that above. This information generally can also be used to interpolate our data from grid to another or to request an interpolated value on some coordinates. Using our data on grid which is an OctahedralGaussianGrid from above we can use the interpolate function to get it onto a FullGaussianGrid (or any other grid for purpose)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid = randn(OctahedralGaussianGrid{Float32},4)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(FullGaussianGrid,grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"By default this will linearly interpolate (it's an Anvil interpolator, see below) onto a grid with the same nlat_half, but we can also coarse-grain or fine-grain by specifying nlat_half directly as 2nd argument","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(FullGaussianGrid,6,grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"So we got from an 8-ring OctahedralGaussianGrid{Float16} to a 12-ring FullGaussianGrid{Float64}, so it did a conversion from Float16 to Float64 on the fly too, because the default precision is Float64 unless specified. interpolate(FullGaussianGrid{Float16},6,grid) would have interpolated onto a grid with element type Float16.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"One can also interpolate onto a given coordinate ˚N, ˚E like so","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(30.0,10.0,grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"we interpolated the data from grid onto 30˚N, 10˚E. To do this simultaneously for many coordinates they can be packed into a vector too","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate([30.0,40.0,50.0],[10.0,10.0,10.0],grid)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which returns the data on grid at 30˚N, 40˚N, 50˚N, and 10˚E respectively. Note how the interpolation here retains the element type of grid.","category":"page"},{"location":"ringgrids/#Performance-for-RingGrid-interpolation","page":"Submodule: RingGrids","title":"Performance for RingGrid interpolation","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Every time an interpolation like interpolate(30.0,10.0,grid) is called, several things happen, which are important to understand to know how to get the fastest interpolation out of this module in a given situation. Under the hood an interpolation takes three arguments","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"output vector\ninput grid\ninterpolator","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"The output vector is just an array into which the interpolated data is written, providing this prevents unnecessary allocation of memory in case the destination array of the interpolation already exists. The input grid contains the data which is subject to interpolation, it must come on a ring grid, however, its coordinate information is actually already in the interpolator. The interpolator knows about the geometry of the grid the data is coming on and the coordinates it is supposed to interpolate onto. It has therefore precalculated the indices that are needed to access the right data on the input grid and the weights it needs to apply in the actual interpolation operation. The only thing it does not know is the actual data values of that grid. So in the case you want to interpolate from grid A to grid B many times, you can just reuse the same interpolator. If you want to change the coordinates of the output grid but its total number of points remain constants then you can update the locator inside the interpolator and only else you will need to create a new interpolator. Let's look at this in practice. Say we have two grids an want to interpolate between them","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid_in = rand(HEALPixGrid,4)\ngrid_out = zeros(FullClenshawGrid,6)\ninterp = RingGrids.interpolator(grid_out,grid_in)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Now we have created an interpolator interp which knows about the geometry where to interpolate from and the coordinates there to interpolate to. It is also initialized, meaning it has precomputed the indices to of grid_in that are supposed to be used. It just does not know about the data of grid_in (and neither of grid_out which will be overwritten anyway). We can now do","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate!(grid_out,grid_in,interp)\ngrid_out","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which is identical to interpolate(grid_out,grid_in) but you can reuse interp for other data. The interpolation can also handle various element types (the interpolator interp does not have to be updated for this either)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid_out = zeros(FullClenshawGrid{Float16},6);\ninterpolate!(grid_out,grid_in,interp)\ngrid_out","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"and we have converted data from a HEALPixGrid{Float64} (Float64 is always default if not specified) to a FullClenshawGrid{Float16} including the type conversion Float64-Float16 on the fly. Technically there are three data types and their combinations possible: The input data will come with a type, the output array has an element type and the interpolator has precomputed weights with a given type. Say we want to go from Float16 data on an OctahedralGaussianGrid to Float16 on a FullClenshawGrid but using Float32 precision for the interpolation itself, we would do this by","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"grid_in = randn(OctahedralGaussianGrid{Float16},24)\ngrid_out = zeros(FullClenshawGrid{Float16},24)\ninterp = RingGrids.interpolator(Float32,grid_out,grid_in)\ninterpolate!(grid_out,grid_in,interp)\ngrid_out","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"As a last example we want to illustrate a situation where we would always want to interpolate onto 10 coordinates, but their locations may change. In order to avoid recreating an interpolator object we would do (AnvilInterpolator is described in Anvil interpolator)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"npoints = 10 # number of coordinates to interpolate onto\ninterp = AnvilInterpolator(Float32,HEALPixGrid,24,npoints)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"with the first argument being the number format used during interpolation, then the input grid type, its resolution in terms of nlat_half and then the number of points to interpolate onto. However, interp is not yet initialized as it does not know about the destination coordinates yet. Let's define them, but note that we already decided there's only 10 of them above.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"latds = collect(0.0:5.0:45.0)\nlonds = collect(-10.0:2.0:8.0)\nnothing # hide","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"now we can update the locator inside our interpolator as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"RingGrids.update_locator!(interp,latds,londs)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"With data matching the input from above, a nlat_half=24 HEALPixGrid, and allocate 10-element output vector","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"output_vec = zeros(10)\ngrid_input = rand(HEALPixGrid,24)\nnothing # hide","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"we can use the interpolator as follows","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate!(output_vec,grid_input,interp)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"which is the approximately the same as doing it directly without creating an interpolator first and updating its locator","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"interpolate(latds,londs,grid_input)","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"but allows for a reuse of the interpolator. Note that the two output arrays are not exactly identical because we manually set our interpolator interp to use Float32 for the interpolation whereas the default is Float64.","category":"page"},{"location":"ringgrids/#Anvil-interpolator","page":"Submodule: RingGrids","title":"Anvil interpolator","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Currently the only interpolator implemented is a 4-point bilinear interpolator, which schematically works as follows. Anvil interpolation is the bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":" 0..............1 # fraction of distance Δab between a,b\n |< Δab >|\n\n0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x\n.Δy |\n. |\n.v x \n. |\n1 c ------ o ---- d\n\n |< Δcd >|\n 0...............1 # fraction of distance Δcd between c,d\n\n^ fraction of distance Δy between a-b and c-d.","category":"page"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"This interpolation is chosen as by definition of the ring grids, a and b share the same latitude, so do c and d, but the longitudes can be different for all four, a,b,c,d.","category":"page"},{"location":"ringgrids/#Function-index","page":"Submodule: RingGrids","title":"Function index","text":"","category":"section"},{"location":"ringgrids/","page":"Submodule: RingGrids","title":"Submodule: RingGrids","text":"Modules = [SpeedyWeather.RingGrids]","category":"page"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractFullGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractFullGrid","text":"abstract type AbstractFullGrid{T} <: AbstractGrid{T} end\n\nAn AbstractFullGrid is a horizontal grid with a constant number of longitude points across latitude rings. Different latitudes can be used, Gaussian latitudes, equi-angle latitdes, or others.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractGrid","text":"abstract type AbstractGrid{T} <: AbstractVector{T} end\n\nThe abstract supertype for all spatial grids on the sphere supported by SpeedyWeather.jl. Every new grid has to be of the form\n\nabstract type AbstractGridClass{T} <: AbstractGrid{T} end\nstruct MyNewGrid{T} <: AbstractGridClass{T}\n data::Vector{T} # all grid points unravelled into a vector\n nlat_half::Int # resolution: latitude rings on one hemisphere (Equator incl)\nend\n\nMyNewGrid should belong to a grid class like AbstractFullGrid, AbstractOctahedralGrid or AbstractHEALPixGrid (that already exist but you may introduce a new class of grids) that share certain features such as the number of longitude points per latitude ring and indexing, but may have different latitudes or offset rotations. Each new grid Grid (or grid class) then has to implement the following methods (as an example, see octahedral.jl)\n\nFundamental grid properties getnpoints # total number of grid points nlatodd # does the grid have an odd number of latitude rings? getnlat # total number of latitude rings getnlat_half # number of latitude rings on one hemisphere incl Equator\n\nIndexing getnlonmax # maximum number of longitudes points (at the Equator) getnlonperring # number of longitudes on ring j eachindexinring # a unit range that indexes all longitude points on a ring\n\nCoordinates getcolat # vector of colatitudes (radians) getcolatlon # vectors of colatitudes, longitudes (both radians)\n\nSpectral truncation truncationorder # linear, quadratic, cubic = 1,2,3 for grid gettruncation # spectral truncation given a grid resolution get_resolution # grid resolution given a spectral truncation\n\nQuadrature weights and solid angles getquadratureweights # = sinθ Δθ for grid points on ring j for meridional integration getsolidangle # = sinθ Δθ Δϕ, solid angle of grid points on ring j\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractHEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractHEALPixGrid","text":"abstract type AbstractHEALPixGrid{T} <: AbstractGrid{T} end\n\nAn AbstractHEALPixGrid is a horizontal grid similar to the standard HEALPixGrid, but different latitudes can be used, the default HEALPix latitudes or others.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractInterpolator","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractInterpolator","text":"abstract type AbstractInterpolator{NF,G} end\n\nSupertype for Interpolators. Every Interpolator <: AbstractInterpolator is expected to have two fields,\n\ngeometry, which describes the grid G to interpolate from\nlocator, which locates the indices on G and their weights to interpolate onto a new grid.\n\nNF is the number format used to calculate the interpolation, which can be different from the input data and/or the interpolated data on the new grid.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractLocator","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractLocator","text":"AbstractLocator{NF}\n\nSupertype of every Locator, which locates the indices on a grid to be used to perform an interpolation. E.g. AnvilLocator uses a 4-point stencil for every new coordinate to interpolate onto. Higher order stencils can be implemented by defining OtherLocator <: AbstractLocactor.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractOctaHEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractOctaHEALPixGrid","text":"abstract type AbstractOctaHEALPixGrid{T} <: AbstractGrid{T} end\n\nAn AbstractOctaHEALPixGrid is a horizontal grid similar to the standard OctahedralGrid, but the number of points in the ring closest to the Poles starts from 4 instead of 20, and the longitude of the first point in each ring is shifted as in HEALPixGrid. Also, different latitudes can be used.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AbstractOctahedralGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AbstractOctahedralGrid","text":"abstract type AbstractOctahedralGrid{T} <: AbstractGrid{T} end\n\nAn AbstractOctahedralGrid is a horizontal grid with 16+4i longitude points on the latitude ring i starting with i=1 around the pole. Different latitudes can be used, Gaussian latitudes, equi-angle latitdes, or others.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AnvilLocator","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AnvilLocator","text":"AnvilLocator{NF<:AbstractFloat} <: AbtractLocator\n\nContains arrays that locates grid points of a given field to be uses in an interpolation and their weights. This Locator is a 4-point average in an anvil-shaped grid-point arrangement between two latitude rings.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.AnvilLocator-Union{Tuple{Integer}, Tuple{NF}} where NF<:AbstractFloat","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.AnvilLocator","text":"L = AnvilLocator( ::Type{NF}, # number format used for the interpolation\n npoints::Integer # number of points to interpolate onto\n ) where {NF<:AbstractFloat}\n\nZero generator function for the 4-point average AnvilLocator. Use update_locator! to update the grid indices used for interpolation and their weights. The number format NF is the format used for the calculations within the interpolation, the input data and/or output data formats may differ.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.FullClenshawGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.FullClenshawGrid","text":"G = FullClenshawGrid{T}\n\nA FullClenshawGrid is a regular latitude-longitude grid with an odd number of nlat equi-spaced latitudes, the central latitude ring is on the Equator. The same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field data that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.FullGaussianGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.FullGaussianGrid","text":"G = FullGaussianGrid{T}\n\nA full Gaussian grid is a regular latitude-longitude grid that uses nlat Gaussian latitudes, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.FullHEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.FullHEALPixGrid","text":"G = FullHEALPixGrid{T}\n\nA full HEALPix grid is a regular latitude-longitude grid that uses nlat latitudes from the HEALPix grid, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.FullOctaHEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.FullOctaHEALPixGrid","text":"G = FullOctaHEALPixGrid{T}\n\nA full OctaHEALPix grid is a regular latitude-longitude grid that uses nlat OctaHEALPix latitudes, and the same nlon longitudes for every latitude ring. The grid points are closer in zonal direction around the poles. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.GridGeometry","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.GridGeometry","text":"GridGeometry{G<:AbstractGrid}\n\ncontains general precomputed arrays describing the grid of G.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.GridGeometry-Tuple{Type{<:SpeedyWeather.RingGrids.AbstractGrid}, Integer}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.GridGeometry","text":"G = GridGeometry( Grid::Type{<:AbstractGrid},\n nlat_half::Integer)\n\nPrecomputed arrays describing the geometry of the Grid with resolution nlat_half. Contains latitudes and longitudes of grid points, their ring index j and their unravelled indices ij.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.HEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.HEALPixGrid","text":"H = HEALPixGrid{T}\n\nA HEALPix grid with 12 faces, each nsidexnside grid points, each covering the same area. The number of latitude rings on one hemisphere (incl Equator) nlat_half is used as resolution parameter. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.OctaHEALPixGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.OctaHEALPixGrid","text":"H = OctaHEALPixGrid{T}\n\nA OctaHEALPix grid with 4 base faces, each nlat_halfxnlat_half grid points, each covering the same area. The values of all grid points are stored in a vector field data that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.OctahedralClenshawGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.OctahedralClenshawGrid","text":"G = OctahedralClenshawGrid{T}\n\nAn Octahedral Clenshaw grid that uses nlat equi-spaced latitudes. Like FullClenshawGrid, the central latitude ring is on the Equator. Like OctahedralGaussianGrid, the number of longitude points per latitude ring decreases towards the poles. Starting with 20 equi-spaced longitude points (starting at 0˚E) on the rings around the poles, each latitude ring towards the equator has consecuitively 4 more points, one for each face of the octahedron. E.g. 20,24,28,32,...nlon-4,nlon,nlon,nlon-4,...,32,28,24,20. The maximum number of longitue points is nlon. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.OctahedralGaussianGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.OctahedralGaussianGrid","text":"G = OctahedralGaussianGrid{T}\n\nAn Octahedral Gaussian grid that uses nlat Gaussian latitudes, but a decreasing number of longitude points per latitude ring towards the poles. Starting with 20 equi-spaced longitude points (starting at 0˚E) on the rings around the poles, each latitude ring towards the equator has consecuitively 4 more points, one for each face of the octahedron. E.g. 20,24,28,32,...nlon-4,nlon,nlon,nlon-4,...,32,28,24,20. The maximum number of longitue points is nlon. The values of all grid points are stored in a vector field v that unravels the data 0 to 360˚, then ring by ring, which are sorted north to south.\n\n\n\n\n\n","category":"type"},{"location":"ringgrids/#SpeedyWeather.RingGrids.Matrix!-Tuple{AbstractMatrix, OctaHEALPixGrid}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.Matrix!","text":"Matrix!(M::AbstractMatrix,\n G::OctaHEALPixGrid;\n quadrant_rotation=(0,1,2,3),\n matrix_quadrant=((2,2),(1,2),(1,1),(2,1)),\n )\n\nSorts the gridpoints in G into the matrix M without interpolation. Every quadrant of the grid G is rotated as specified in quadrant_rotation, 0 is no rotation, 1 is 90˚ clockwise, 2 is 180˚ etc. Grid quadrants are counted eastward starting from 0˚E. The grid quadrants are moved into the matrix quadrant (i,j) as specified. Defaults are equivalent to centered at 0˚E and a rotation such that the North Pole is at M's midpoint.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.Matrix!-Tuple{AbstractMatrix, OctahedralClenshawGrid}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.Matrix!","text":"Matrix!(M::AbstractMatrix,\n G::OctahedralClenshawGrid;\n quadrant_rotation=(0,1,2,3),\n matrix_quadrant=((2,2),(1,2),(1,1),(2,1)),\n )\n\nSorts the gridpoints in G into the matrix M without interpolation. Every quadrant of the grid G is rotated as specified in quadrant_rotation, 0 is no rotation, 1 is 90˚ clockwise, 2 is 180˚ etc. Grid quadrants are counted eastward starting from 0˚E. The grid quadrants are moved into the matrix quadrant (i,j) as specified. Defaults are equivalent to centered at 0˚E and a rotation such that the North Pole is at M's midpoint.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.Matrix!-Union{Tuple{Vararg{Tuple{AbstractMatrix{T}, OctaHEALPixGrid}}}, Tuple{T}} where T","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.Matrix!","text":"Matrix!(MGs::Tuple{AbstractMatrix{T},OctaHEALPixGrid}...;kwargs...)\n\nLike Matrix!(::AbstractMatrix,::OctaHEALPixGrid) but for simultaneous processing of tuples ((M1,G1),(M2,G2),...) with matrices Mi and grids Gi. All matrices and grids have to be of the same size respectively.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.Matrix!-Union{Tuple{Vararg{Tuple{AbstractMatrix{T}, OctahedralClenshawGrid}}}, Tuple{T}} where T","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.Matrix!","text":"Matrix!(MGs::Tuple{AbstractMatrix{T},OctahedralClenshawGrid}...;kwargs...)\n\nLike Matrix!(::AbstractMatrix,::OctahedralClenshawGrid) but for simultaneous processing of tuples ((M1,G1),(M2,G2),...) with matrices Mi and grids Gi. All matrices and grids have to be of the same size respectively.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.anvil_average-NTuple{7, Any}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.anvil_average","text":"anvil_average(a, b, c, d, Δab, Δcd, Δy) -> Any\n\n\nThe bilinear average of a,b,c,d which are values at grid points in an anvil-shaped configuration at location x, which is denoted by Δab,Δcd,Δy, the fraction of distances between a-b,c-d, and ab-cd, respectively. Note that a,c and b,d do not necessarily share the same longitude/x-coordinate. See schematic:\n\n 0..............1 # fraction of distance Δab between a,b\n |< Δab >|\n\n 0^ a -------- o - b # anvil-shaped average of a,b,c,d at location x\n .Δy |\n . |\n .v x \n . |\n 1 c ------ o ---- d\n\n |< Δcd >|\n 0...............1 # fraction of distance Δcd between c,d\n\n^ fraction of distance Δy between a-b and c-d.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.average_on_poles-Union{Tuple{NF}, Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}, Vector{<:UnitRange{<:Integer}}}} where NF<:AbstractFloat","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.average_on_poles","text":"average_on_poles(\n A::SpeedyWeather.RingGrids.AbstractGrid{NF<:AbstractFloat},\n rings::Vector{<:UnitRange{<:Integer}}\n) -> Tuple{Any, Any}\n\n\nComputes the average at the North and South pole from a given grid A and it's precomputed ring indices rings. The North pole average is an equally weighted average of all grid points on the northern-most ring. Similar for the South pole.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.average_on_poles-Union{Tuple{NF}, Tuple{SpeedyWeather.RingGrids.AbstractGrid{NF}, Vector{<:UnitRange{<:Integer}}}} where NF<:Integer","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.average_on_poles","text":"average_on_poles(\n A::SpeedyWeather.RingGrids.AbstractGrid{NF<:Integer},\n rings::Vector{<:UnitRange{<:Integer}}\n) -> Tuple{Any, Any}\n\n\nMethod for A::Abstract{T<:Integer} which rounds the averaged values to return the same number format NF.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.each_index_in_ring-Union{Tuple{Grid}, Tuple{Grid, Integer}} where Grid<:SpeedyWeather.RingGrids.AbstractGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.each_index_in_ring","text":"i = each_index_in_ring(grid,j)\n\nUnitRange i to access data on grid grid on ring j.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.eachgridpoint-Tuple{SpeedyWeather.RingGrids.AbstractGrid}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.eachgridpoint","text":"ijs = eachgridpoint(grid)\n\nUnitRange ijs to access each grid point on grid grid.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.eachring-Tuple{SpeedyWeather.RingGrids.AbstractGrid}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.eachring","text":"eachring(grid::SpeedyWeather.RingGrids.AbstractGrid) -> Any\n\n\nVector{UnitRange} rings to loop over every ring of grid grid and then each grid point per ring. To be used like\n\nrings = eachring(grid)\nfor ring in rings\n for ij in ring\n grid[ij]\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.eachring-Union{Tuple{Grid}, Tuple{Grid, Vararg{Grid}}} where Grid<:SpeedyWeather.RingGrids.AbstractGrid","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.eachring","text":"eachring(\n grid1::SpeedyWeather.RingGrids.AbstractGrid,\n grids::Grid<:SpeedyWeather.RingGrids.AbstractGrid...\n) -> Any\n\n\nSame as eachring(grid) but performs a bounds check to assess that all grids in grids are of same size.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.extrema_in-Tuple{Vector, Real, Real}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.extrema_in","text":"true/false = extrema_in(v::Vector,a::Real,b::Real)\n\nFor every element vᵢ in v does a<=vi<=b hold?\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.get_nlons-Tuple{Type{<:SpeedyWeather.RingGrids.AbstractGrid}, Integer}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.get_nlons","text":"get_nlons(\n Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid},\n nlat_half::Integer;\n both_hemispheres\n) -> Any\n\n\nReturns a vector nlons for the number of longitude points per latitude ring, north to south. Provide grid Grid and its resolution parameter nlat_half. For both_hemisphere==false only the northern hemisphere (incl Equator) is returned.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.isdecreasing-Tuple{Vector}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.isdecreasing","text":"true/false = isdecreasing(v::Vector)\n\nCheck whether elements of a vector v are strictly decreasing.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.isincreasing-Tuple{Vector}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.isincreasing","text":"true/false = isincreasing(v::Vector)\n\nCheck whether elements of a vector v are strictly increasing.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.rotate_matrix_indices_180-Tuple{Integer, Integer, Integer}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.rotate_matrix_indices_180","text":"i_new,j_new = rotate_matrix_indices_180(i,j,s)\n\nRotate indices i,j of a square matrix of size s x s by 180˚.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.rotate_matrix_indices_270-Tuple{Integer, Integer, Integer}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.rotate_matrix_indices_270","text":"i_new,j_new = rotate_matrix_indices_270(i,j,s)\n\nRotate indices i,j of a square matrix of size s x s anti-clockwise by 270˚.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.rotate_matrix_indices_90-Tuple{Integer, Integer, Integer}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.rotate_matrix_indices_90","text":"i_new,j_new = rotate_matrix_indices_90(i,j,s)\n\nRotate indices i,j of a square matrix of size s x s anti-clockwise by 90˚.\n\n\n\n\n\n","category":"method"},{"location":"ringgrids/#SpeedyWeather.RingGrids.whichring-Tuple{Integer, Vector{UnitRange{Int64}}}","page":"Submodule: RingGrids","title":"SpeedyWeather.RingGrids.whichring","text":"whichring(\n ij::Integer,\n rings::Vector{UnitRange{Int64}}\n) -> Int64\n\n\nObtain ring index j from gridpoint ij and Vector{UnitRange} describing rind indices as obtained from eachring(::Grid)\n\n\n\n\n\n","category":"method"},{"location":"#SpeedyWeather.jl-documentation","page":"Home","title":"SpeedyWeather.jl documentation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.","category":"page"},{"location":"#Overview","page":"Home","title":"Overview","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to simulate the general circulation of the atmosphere. The prognostic variables used are vorticity, divergence, temperature, surface pressure and specific humidity. Simple parameterizations represent various climate processes: Radiation, clouds, precipitation, surface fluxes, among others.","category":"page"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl defines ","category":"page"},{"location":"","page":"Home","title":"Home","text":"BarotropicModel for the 2D barotropic vorticity equation\nShallowWaterModel for the 2D shallow water equations\nPrimitiveDryModel for the 3D primitive equations without humidity\nPrimitiveWetModel for the 3D primitive equations with humidity","category":"page"},{"location":"","page":"Home","title":"Home","text":"and solves these equations in spherical coordinates as described in this documentation.","category":"page"},{"location":"#Manual-outline","page":"Home","title":"Manual outline","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"See the following pages of the documentation for more details","category":"page"},{"location":"","page":"Home","title":"Home","text":"Installation\nHow to run SpeedyWeather.jl\nSpherical harmonic transform\nGrids\nBarotropic model\nShallow water model\nPrimitive equation model\nParameterizations\nExtending SpeedyWeather\nNetCDF output","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the submodules","category":"page"},{"location":"","page":"Home","title":"Home","text":"RingGrids\nLowerTriangularMatrices \nSpeedyTransforms","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the original documentation by Molteni and Kucharski.","category":"page"},{"location":"#Developers","page":"Home","title":"Developers","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The development of SpeedyWeather.jl is lead by Milan Klöwer and current and past contributors include","category":"page"},{"location":"","page":"Home","title":"Home","text":"Tom Kimpson\nAlistair White\nMaximilian Gelbrecht\nDavid Meyer\nDaisuke Hotta\nNavid Constantinou","category":"page"},{"location":"","page":"Home","title":"Home","text":"Any contributions are always welcome!","category":"page"},{"location":"#Funding","page":"Home","title":"Funding","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"MK received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022. Since 2023 this project is also funded by the National Science Foundation NSF.","category":"page"},{"location":"time_integration/#Time-integration","page":"Time integration","title":"Time integration","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"SpeedyWeather.jl uses a leapfrog time scheme with a Robert's and Williams filter to dampen the computational mode and achieve 3rd order accuracy.","category":"page"},{"location":"time_integration/#Oscillation-equation","page":"Time integration","title":"Oscillation equation","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"fracdFdt = iomega F","category":"page"},{"location":"time_integration/#Implementation-details","page":"Time integration","title":"Implementation details","text":"","category":"section"}]
+}
diff --git a/previews/PR362/shallowwater/index.html b/previews/PR362/shallowwater/index.html
new file mode 100644
index 000000000..8dc61fa94
--- /dev/null
+++ b/previews/PR362/shallowwater/index.html
@@ -0,0 +1,36 @@
+
+Shallow water model · SpeedyWeather.jl
The shallow water model describes the evolution of a 2D flow described by its velocity and an interface height that conceptually represents pressure. A divergent flow affects the interface height which in turn can impose a pressure gradient force onto the flow. The dynamics include advection, forces, dissipation, and continuity.
The following description of the shallow water model largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: The Shallow Water Equations[2].
The shallow water equations of velocity $\mathbf{u} = (u,v)$ and interface height $\eta$ (i.e. the deviation from the fluid's rest height $H$) are, formulated in terms of relative vorticity $\zeta = \nabla \times \mathbf{u}$, divergence $\mathcal{D} = \nabla \cdot \mathbf{u}$
We denote time$t$, Coriolis parameter $f$, a forcing vector $\mathbf{F} = (F_u,F_v)$, hyperdiffusion $(-1)^{n+1} \nu \nabla^{2n}$ ($n$ is the hyperdiffusion order, see Horizontal diffusion), gravitational acceleration $g$, dynamic layer thickness $h$, and a forcing for the interface height $F_\eta$. In the shallow water model the dynamics layer thickness $h$ is
\[h = \eta + H - H_b\]
that is, the layer thickness at rest $H$ plus the interface height $\eta$ minus orography $H_b$.
In the shallow water system the flow can be described through $u,v$ or $\zeta,\mathcal{D}$ which are related through the stream function $\Psi$ and the velocity potential $\Phi$ (which is zero in the Barotropic vorticity equation).
With $\nabla^\perp$ being the rotated gradient operator, in cartesian coordinates $x,y$: $\nabla^\perp = (-\partial_y, \partial_x)$. See Derivatives in spherical coordinates for further details. Especially because the inversion of the Laplacian and the gradients of $\Psi, \Phi$ can be computed in a single pass, see U,V from vorticity and divergence.
The divergence/curl of the vorticity flux $\mathbf{u}(\zeta + f)$ are combined with the divergence/curl of the forcing vector $\mathbf{F}$, as
0. Start with initial conditions of relative vorticity $\zeta_{lm}$, divergence $D_{lm}$, and interface height $\eta_{lm}$ in spectral space and transform this model state to grid-point space:
Invert the Laplacian of $\zeta_{lm}$ to obtain the stream function $\Psi_{lm}$ in spectral space
Invert the Laplacian of $D_{lm}$ to obtain the velocity potential $\Phi_{lm}$ in spectral space
Probably the biggest advantage of a spectral model is its ability to solve (parts of) the equations implicitly a low computational cost. The reason is that a linear operator can be easily inverted in spectral space, removing the necessity to solve large equation systems. An operation like $\Psi = \nabla^{-2}\zeta$ in grid-point space is costly because it requires a global communication, coupling all grid points. In spectral space $\nabla^2$ is a diagonal operator, meaning that there is no communication between harmonics and its inversion is therefore easily done on a mode-by-mode basis of the harmonics.
This can be made use of when facing time stepping constraints with explicit schemes, where ridiculuously small time steps to resolve fast waves would otherwise result in a horribly slow simulation. In the shallow water system there are gravity waves that propagate at a wave speed of $\sqrt{gH}$ (typically 300m/s), which, in order to not violate the CFL criterion for explicit time stepping, would need to be resolved. Therefore, treating the terms that are responsible for gravity waves implicitly would remove that time stepping constraint and allows us to run the simulation at the time step needed to resolve the advective motion of the atmosphere, which is usually one or two orders of magnitude longer than gravity waves.
In the following we will describe how the semi implicit time integration can be combined with the Leapfrog time stepping and the Robert-Asselin and Williams filter for a large increase in numerical stability with gravity waves. Let $V_i$ be the model state of all prognostic variables at time step $i$, the leapfrog time stepping is then
with the right-hand side operator $N$ evaluated at the current time step $i$. Now the idea is to split the terms in $N$ into non-linear terms that are evaluated explicitly in $N_E$ and into the linear terms $N_I$, solved implicitly, that are responsible for the gravity waves. We could already assume to evaluate $N_I$ at $i+1$, but in fact, we can introduce $\alpha \in [0,1]$ so that for $\alpha=0$ we use $i-1$ (i.e. explicit), for $\alpha=1/2$ it is centred implicit $\tfrac{1}{2}N_I(V_{i-1}) + \tfrac{1}{2}N_I(V_{i+1})$, and for $\alpha=1$ a fully backwards scheme $N_I(V_{i+1})$ evaluated at $i+1$.
Let $\delta V = \tfrac{V_{i+1} - V_{i-1}}{2\Delta t}$ be the tendency we need for the Leapfrog time stepping. Introducing $\xi = 2\alpha\Delta t$ we have
\[\delta V = N_E(V_i) + N_I(V_{i-1}) + \xi N_I(\delta V)\]
because $N_I$ is a linear operator. This is done so that we can solve for $\delta V$ by inverting $N_I$, but let us gather the other terms as $G$ first.
For the shallow water equations we will only make use of the last formulation, meaning we first evaluate the whole right-hand side $N(V_i)$ at the current time step as we would do with fully explicit time stepping but then add the implicit terms $N_I(V_{i-1} - V_i)$ afterwards to move those terms from $i$ to $i-1$. Note that we could also directly evaluate the implicit terms at $i-1$ as it is suggested in the previous formulation $N_E(V_i) + N_I(V_{i-1})$, the result would be the same. But in general it can be more efficient to do it one or the other way, and in fact it is also possible to combine both ways. This will be discussed in the semi-implicit time stepping for the primitive equations.
We can now implicitly solve for $\delta V$ by
\[\delta V = (1-\xi N_I)^{-1}G\]
So what is $N_I$? In the shallow water system the gravity waves are caused by
which is a linearization of the equations around a state of rest with uniform constant layer thickness $h = H$. The continuity equation with the $-\nabla(\mathbf{u}h)$ term, for example, is linearized to $-\nabla(\mathbf{u}H) = -H\mathcal{D}$. The divergence and continuity equations can now be written following the $\delta V = G + \xi N_I(\delta V)$ formulation from above as a coupled system (The vorticity equation is zero for the linear gravity wave equation in the shallow water equations, hence no semi-implicit correction has to be made to the vorticity tendency).
Inserting the second equation into the first, we can first solve for $\delta \mathcal{D}$, and then for $\delta \eta$. Reminder that we do this in spectral space to every harmonic independently, so the Laplace operator $\nabla^2 = -l(l+1)$ takes the form of its eigenvalue $-l(l+1)$ (normalized to unit sphere, as are the scaled shallow water equations) and its inversion is therefore just the inversion of this scalar.
\[\delta D = \frac{G_\mathcal{D} - \xi g\nabla^2 G_\eta}{1 - \xi^2 H \nabla^2} =: S^{-1}(G_\mathcal{D} - \xi g\nabla^2 G_\eta) \]
Where the last formulation just makes it clear that $S = 1 - \xi^2 H \nabla^2$ is the operator to be inverted. $\delta \eta$ is then obtained via insertion as written above. Equivalently, by adding a superscript $l$ for every degree of the spherical harmonics, we have
\[\delta \mathcal{D}^l = \frac{G_\mathcal{D}^l + \xi g l(l+1) G_\eta^l}{1 + \xi^2 H l(l+1)}\]
The idea of the semi-implicit time stepping is now as follows:
Evaluate the right-hand side explicitly at time step $i$ to obtain the explicit, preliminary tendencies $N_\mathcal{D},N_\eta$ (and $N_\zeta$ without a need for semi-implicit correction)
Move the implicit terms from $i$ to $i-1$ when calculating $G_\mathcal{D}, G_\eta$
Solve for $\delta \mathcal{D}$, the new, corrected tendency for divergence.
With $\delta \mathcal{D}$ obtain $\delta \eta$, the new, corrected tendency for $\eta$.
Apply horizontal diffusion as a correction to $N_\zeta, \delta \mathcal{D}$ as outlined in Horizontal diffusion.
Leapfrog with tendencies that have been corrected for both semi-implicit and diffusion.
Some notes on the semi-implicit time stepping
The inversion of the semi-implicit time stepping depends on $\delta t$, that means every time the time step changes, the inversion has to be recalculated.
You may choose $\alpha = 1/2$ to dampen gravity waves but initialisation shocks still usually kick off many gravity waves that propagate around the sphere for many days.
With increasing $\alpha > 1/2$ these waves are also slowed down, such that for $\alpha = 1$ they quickly disappear in several hours.
Using the scaled shallow water equations the time step $\delta t$ has to be the scaled time step $\tilde{\Delta t} = \delta t/R$ which is divided by the radius $R$. Then we use the normalized eigenvalues $-l(l+1)$ which also omit the $1/R^2$ scaling, see scaled shallow water equations for more details.
Similar to the scaled barotropic vorticity equations, SpeedyWeather.jl scales in the shallow water equations. The vorticity and the divergence equation are scaled with $R^2$, the radius of the sphere squared, but the continuity equation is scaled with $R$. We also combine the vorticity flux and forcing into a single divergence/curl operation as mentioned in Shallow water equations above
As in the scaled barotropic vorticity equations, one needs to scale the time step, the Coriolis force, the forcing and the diffusion coefficient, but then enjoys the luxury of working with dimensionless gradient operators. As before, SpeedyWeather.jl will scale vorticity and divergence just before the model integration starts and unscale them upon completion and for output. In the semi-implicit time integration we solve an equation that also has to be scaled. It is with radius squared scaling (because it is the tendency for the divergence equation which is also scaled with $R^2$)
\[R^2 \delta D = R^2\frac{G_\mathcal{D} - \xi g\nabla^2 G_\eta}{1 - \xi^2 H \nabla^2}\]
The $R^2$ normalizes the Laplace operator in the numerator, but using the scaled $G_\eta$ we also scale $\xi$ (which is convenient, because the time step within is the one we use anyway). The denominator $S$ does not actually change because $\xi^2\nabla^2 = \tilde{\xi}^2\tilde{\nabla}^2$ as $\xi^2$ is scaled with $1/R^2$, but the Laplace operator with $R^2$. So overall we just have to use the scaled time step $\tilde{\Delta t}$ and normalized eigenvalues for $\tilde{\nabla}^2$.
The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space which can be any of the Implemented grids as defined by RingGrids. This includes the classical full Gaussian grid, a regular longitude-latitude grid called the full Clenshaw grid (FullClenshawGrid), ECMWF's octahedral Gaussian grid[Malardel2016], and HEALPix grids[Gorski2004]. SpeedyWeather.jl's spectral transform module SpeedyTransforms is grid-flexible and can be used with any of these, see Grids.
SpeedyTransforms is a module too!
SpeedyTransform is the underlying module that SpeedyWeather imports to transform between spectral and grid-point space, which also implements Derivatives in spherical coordinates. You can use this module independently of SpeedyWeather for spectral transforms, see SpeedyTransforms.
with $\lambda_l^m$ being the pre-normalized associated Legendre polynomials, and $e^{im\phi}$ are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.
Latitudes versus colatitudes
The implementation of the spectral transforms in SpeedyWeather.jl uses colatitudes $\theta = (0,\pi)$ (0 at the north pole) but the dynamical core uses latitudes $\theta = (-\pi/2,\pi/2)$ ($\pi/2$ at the north pole). Note: We may also use latitudes in the spherical harmonic transform in the future for consistency.
The synthesis (or inverse transform) takes the spectral coefficients $a_{lm}$ and transforms them to grid-point values $f(\phi,\theta)$ (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics $Y_{lm}$ with non-zero coefficients.
We obtain an approximation with a finite set of $a_{l,m}$ by truncating the series in both degree $l$ and order $m$ somehow. Most commonly, a triangular truncation is applied, such that all degrees after $l = l_{max}$ are discarded. Triangular because the retained array of the coefficients $a_{l,m}$ looks like a triangle. Other truncations like rhomboidal have been studied[Daley78] but are rarely used since. Choosing $l_{max}$ also constrains $m_{max}$ and determines the (horizontal) spectral resolution. In SpeedyWeather.jl this resolution as chosen as trunc when creating the SpectralGrid.
For $f$ being a real-valued there is a symmetry
\[a_{l,-m} = (-1)^m a^*_{l,+m},\]
meaning that the coefficients at $-m$ and $m$ are the same, but the sign of the real and imaginary component can be flipped, as denoted with the $(-1)^m$ and the complex conjugate $a_{l,m}^*$. As we are only dealing with real-valued fields anyway, we therefore never have to store the negative orders $-m$ and end up with a lower triangular matrix of size $(l_{max}+1) \times (m_{max}+1)$ or technically $(T+1)^2$ where $T$ is the truncation trunc. One is added here because the degree $l$ and order $m$ use 0-based indexing but sizes (and so is Julia's indexing) are 1-based.
For correctness we want to mention here that vector quantities require one more degree $l$ due to the recurrence relation in the Meridional derivative. Hence for practical reasons all spectral fields are represented as a lower triangular matrix of size $(m_{max} + 2) \times (m_{max} +1)$. And the scalar quantities would just not make use of that last degree, and its entries would be simply zero. We will, however, for the following sections ignore this and only discuss it again in Meridional derivative.
Another consequence of the symmetry mentioned above is that the zonal harmonics, meaning $a_{l,m=0}$ have no imaginary component. Because these harmonics are zonally constant, a non-zero imaginary component would rotate them around the Earth's axis, which, well, doesn't actually change a real-valued field.
Following the notation of [Willmert2020] we can therefore write the truncated synthesis as
The $(2-\delta_{m0})$ factor using the Kronecker $\delta$ is used here because of the symmetry we have to count both the $m,-m$ order pairs (hence the $2$) except for the zonal harmonics which do not have a pair.
Another symmetry arises from the fact that the spherical harmonics are either symmetric or anti-symmetric around the Equator. There is an even/odd combination of degrees and orders so that the sign flips like a checkerboard
Note that this notation again uses colatitudes $\theta$, for latitudes the $\sin\theta$ becomes a $\cos\theta$ and the bounds have to be changed accordingly to $(-\frac{\pi}{2},\frac{\pi}{2})$. A discretization with $N$ grid points at location $(\phi_i,\theta_i)$, indexed by $i$ can be written as [Willmert2020]
The hat on $a$ just means that it is an approximation, or an estimate of the true $a_{lm} \approx \hat{a}_{lm}$. We can essentially make use of the same symmetries as already discussed in Synthesis. Splitting into the Fourier modes $e^{im\phi}$ and the Legendre polynomials $\lambda_l^m(\cos\theta)$ (which are defined over $[-1,1]$ so the $\cos\theta$ argument maps them to colatitudes) we have
So the term in brackets can be separated out as long as the latitude $\theta_j$ is constant, which motivates us to restrict the spectral transform to grids with iso-latitude rings, see Grids. Furthermore, this term can be written as a fast Fourier transform, if the $\phi_i$ are equally spaced on the latitude ring $j$. Note that the in-ring index $i$ can depend on the ring index $j$, so that one can have reduced grids, which have fewer grid points towards the poles, for example. Also the Legendre polynomials only have to be computed for the colatitudes $\theta_j$ (and in fact only one hemisphere, due to the north-south symmetry discussed in the Synthesis). It is therefore practical and efficient to design a spectral transform implementation for ring grids, but there is no need to hardcode a specific grid.
Spectral packing is the way how the coefficients $a_{lm}$ of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree $l$ and order $m$ as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)
Every row represents an order $l \geq 0$, starting from $l=0$ at the top. Every column represents an order $m \geq 0$, starting from $m=0$ on the left. The coefficients of these spherical harmonics are directly mapped into a matrix $a_{lm}$ as
$m$
$l$
$a_{00}$
$a_{10}$
$a_{11}$
$a_{20}$
$a_{12}$
$a_{22}$
$a_{30}$
$a_{13}$
$a_{23}$
$a_{33}$
which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal are zero. Note that internally vector fields include an additional degree, such that $l_{max} = m_{max} + 1$ (see Derivatives in spherical coordinates for more information). The harmonics with $a_{l0}$ (the first column) are also called zonal harmonics as they are constant with longitude $\phi$. The harmonics with $a_{ll}$ (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into $2l$ sectors in longitude $\phi$ without a zero-crossing in latitude.
For correctness it is mentioned here that SpeedyWeather.jl uses a LowerTriangularMatrix type to store the spherical harmonic coefficients. By doing so, the upper triangle is actually not explicitly stored and the data technically unravelled into a vector, but this is hidden as much as possible from the user. For more details see LowerTriangularMatrices.
Array indices
For a spectral field a note that due to Julia's 1-based indexing the coefficient $a_{lm}$ is obtained via a[l+1,m+1]. Alternatively, we may index over 1-based l,m but a comment is usually added for clarification.
Fortran SPEEDY does not use the same spectral packing as SpeedyWeather.jl. The alternative packing $l',m'$ therein uses $l'=m$ and $m'=l-m$ as summarized in the following table.
degree $l$
order $m$
$l'=m$
$m'=l-m$
0
0
0
0
1
0
0
1
1
1
1
0
2
0
0
2
2
1
1
1
2
2
2
0
3
0
0
3
...
...
...
...
This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices
$m'$
$l'$
$a_{00}$
$a_{10}$
$a_{20}$
$a_{30}$
$a_{11}$
$a_{21}$
$a_{31}$
$a_{22}$
$a_{32}$
$a_{33}$
This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran SPEEDY.
SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with $l \leq l_{max}$ and $|m| \leq m_{max}$ are explicitly represented. This is usually described as $Tm_{max}$, with $l_{max} = m_{max}$ (although in vector quantities require one more degree $l$ in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with $l_{max} = m_{max} = 31$. Note that the degree $l$ and order $m$ are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.
Technically, SpeedyWeather.jl supports arbitrarily chosen resolution parameter trunc when creating the SpectralGrid that refers to the highest non-zero degree $l_{max}$ that is resolved in spectral space. SpeedyWeather.jl will always try to choose an easily-Fourier transformable[FFT] size of the grid, but as we use FFTW.jl there is quite some flexibility without performance sacrifice. However, this has traditionally lead to typical resolutions that we also use for testing we therefore recommend to use. They are as follows with more details below
trunc
nlon
nlat
$\Delta x$
31 (default)
96
48
400 km
42
128
64
312 km
63
192
96
216 km
85
256
128
165 km
127
384
192
112 km
170
512
256
85 km
255
768
384
58 km
341
1024
512
43 km
511
1536
768
29 km
682
2048
1024
22 km
1024
3072
1536
14 km
1365
4092
2048
11 km
Some remarks on this table
This assumes the default quadratic truncation, you can always adapt the grid resolution via the dealiasing option, see Matching spectral and grid resolution
nlat refers to the total number of latitude rings, see Grids. With non-Gaussian grids, nlat will be one one less, e.g. 47 instead of 48 rings.
nlon is the number of longitude points on the Full Gaussian Grid, for other grids there will be at most these number of points around the Equator.
$\Delta x$ is the horizontal resolution. For a spectral model there are many ways of estimating this[9]. We use here the square root of the average area a grid cell covers, see Effective grid resolution
There are many ways to estimate the effective grid resolution of spectral models[9]. Some of them are based on the wavelength a given spectral resolution allows to represent, others on the total number of real variables per area. However, as many atmospheric models do represent a considerable amount of physics on the grid (see Parameterizations) there is also a good argument to include the actual grid resolution into this estimate and not just the spectral resolution. We therefore use the average grid cell area to estimate the resolution
\[\Delta x = \sqrt{\frac{4\pi R^2}{N}}\]
with $N$ number of grid points over a sphere with radius $R$. However, we have to acknowledge that this usually gives higher resolution compared to other methods of estimating the effective resolution, see [Randall2021] for a discussion. You may therefore need to be careful to make claims that, e.g. trunc=85 can resolve the atmospheric dynamics at a scale of 165km.
The radius of the sphere (i.e. Earth) is $R$. The zonal gradient scales with $1/\cos(\theta)$ as the longitudes converge towards the poles (note that $\theta$ describes latitudes here, definitions using colatitudes replace the $\cos$ with a $\sin$.)
Starting with a spectral field of vorticity $\zeta$ and divergence $\mathcal{D}$ one can obtain stream function $\Psi$ and velocity potential $\Phi$ by inverting the Laplace operator $\nabla^2$:
The velocities $u,v$ are then obtained from $(u,v) = \nabla^\bot\Psi + \nabla\Phi$ following the definition from above and $\nabla^\bot = (-R^{-1}\partial_\theta, (R\cos\theta)^{-1}\partial_\lambda)$
Alternatively, we can use the velocities $U = u\cos\theta, V = v\cos\theta$, which we do as the meridional gradient for spherical harmonics is easier implemented with a $\cos\theta$-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with $U,V$ and not $u,v$. From $u,v$ we can return to $\zeta, \mathcal{D}$ via
\[\begin{aligned}
+\zeta &= \frac{1}{R\cos\theta}\partial_\lambda v - \frac{1}{R\cos\theta}\partial_\theta (u \cos\theta) \\
+\mathcal{D} &= \frac{1}{R\cos\theta}\partial_\lambda u + \frac{1}{R\cos\theta}\partial_\theta (v \cos\theta).
+\end{aligned}\]
which is a more convenient formulation because of the way how the Meridional derivative is implemented with a recursion relation, actually computing $\cos\theta \partial_\theta$ rather than $\partial_\theta$ directly. The remaining cosine scalings in $(U,V)*\cos^{-2}\theta$ are done in grid-point space. If one wanted to get back to $\zeta, \mathcal{D}$ this is how it would be done, but it is often more convenient to unscale $U,V$ on the fly in the spectral transform to obtain $u,v$ and then divide again by $\cos\theta$ when any gradient (or divergence or curl) is taken. This is because other terms would need that single $\cos\theta$ unscaling too before a gradient is taken. How the operators $\nabla, \nabla \times, \nabla \cdot$ can be implemented with spherical harmonics is presented in the following sections.
Also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out in this last formulation too.
The zonal derivative of a scalar field $\Psi$ in spectral space is the zonal derivative of all its respective spherical harmonics $\Psi_{lm}(\phi,\theta)$ (now we use $\phi$ for longitudes to avoid confusion with the Legendre polynomials $\lambda_{lm}$)
So for every spectral harmonic, $\cos(\theta)v_{lm}$ is obtained from $\Psi_{lm}$ via a multiplication with $im/R$. Unscaling the $\cos(\theta)$-factor is done after transforming the spectral coefficients $v_{lm}$ into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as $\tilde{\Psi} = R^{-1}\Psi$ such that the division by radius $R$ in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order $im$. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number $m$ times imaginary $i$.
The meridional derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]
In the following we use the example of obtaining the zonal velocity $u$ from the stream function $\Psi$, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion
To obtain the coefficient of each spherical harmonic $l,m$ of the meridional gradient of a spectral field, two coefficients at $l-1,m$ and $l+1,m$ have to be combined. This means that the coefficient of a gradient $((\cos\theta) u)_{lm}$ is a linear combination of the coefficients of one higher and one lower degree $\Psi_{l+1,m},\Psi_{l-1,m}$. As the coefficient $\Psi_{lm}$ with $m<l$ are zero, the sectoral harmonics ($l=m$) of the gradients are obtained from the first off-diagonal only. However, the $l=l_{max}$ harmonics of the gradients require the $l_{max}-1$ as well as the $l_{max}+1$ harmonics. As a consequence vector quantities like velocity components $u,v$ require one more degree $l$ than scalar quantities like vorticity[Bourke72]. However, for easier compatibility all spectral fields in SpeedyWeather.jl use one more degree $l$, but scalar quantities should not make use of it. Equivalently, the last degree $l$ is set to zero before the time integration, which only advances scalar quantities.
In SpeedyWeather.jl vector quantities like $u,v$ use therefore one more meridional mode than scalar quantities such as vorticity $\zeta$ or stream function $\Psi$. The meridional derivative in SpeedyWeather.jl also omits the $1/R$-scaling as explained for the Zonal derivative and in Radius scaling.
The meridional gradient as described above can be applied to scalars, such as $\Psi$ and $\Phi$ in the conversion to velocities $(u,v) = \nabla^\bot\Psi + \nabla\Phi$, however, the operators curl $\nabla \times$ and divergence $\nabla \cdot$ in spherical coordinates involve a $\cos\theta$ scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].
Given that $R\zeta = \cos^{-1}\partial_\lambda v - \cos^{-1}\partial_\theta (u \cos\theta)$, we therefore have to evaluate a meridional integral of the form
Now we expand $(\tfrac{u}{\cos\theta})$ but only the $l,m$ harmonic will project onto$P_{l,m}$. Let $u^* = u\cos^{-1}\theta, v^* = v\cos^{-1}\theta$ we then have in total
And the divergence $D$ is similar, but $(u,v) \to (-v,u)$. We have moved the scaling with the radius $R$ directly into $\zeta,D$ as further described in Radius scaling.
The spectral Laplacian is easily applied to the coefficients $\Psi_{lm}$ of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator $\nabla^2$ in spherical coordinates with eigenvalues $-l(l+1)$ divided by the radius squared $R^2$, i.e. $\nabla^2 \Psi$ becomes $\tfrac{-l(l+1)}{R^2}\Psi_{lm}$ in spectral space. For example, vorticity $\zeta$ and streamfunction $\Psi$ are related by $\zeta = \nabla^2\Psi$ in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree $l$ and order $m$ to
\[\zeta_{l,m} = \frac{-l(l+1)}{R^2}\Psi_{l,m}\]
This can be easily inverted to obtain the stream function $\Psi$ from vorticity $\zeta$ instead. In order to avoid division by zero, we set $\Psi_{0,0}$ here, given that the stream function is only defined up to a constant anyway.
After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity $\zeta$ and divergence $D$ (which are prognostic variables) to $U=u\cos\theta, V=v\cos\theta$. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree $l$ (the meridional gradient). It is therefore computationally more efficient to compute $U,V$ directly from $\zeta,D$ instead of calculating stream function and velocity potential first. In total we have
Malardel2016Malardel S, Wedi N, Deconinck W, Diamantakis M, Kühnlein C, Mozdzynski G, Hamrud M, Smolarkiewicz P. A new grid for the IFS. ECMWF newsletter. 2016;146(23-28):321. doi: 10.21957/zwdu9u5i
Gorski2004Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976
FFTDepending on the implementation of the Fast Fourier Transform (Cooley-Tukey algorithm, or or the Bluestein algorithm) easily Fourier-transformable can mean different things: Vectors of the length $n$ that is a power of two, i.e. $n = 2^i$ is certainly easily Fourier-transformable, but for most FFT implementations so are $n = 2^i3^j5^k$ with $i,j,k$ some positive integers. In fact, FFTW uses $O(n \log n)$ algorithms even for prime sizes.
SpeedyTransforms is a submodule that has been developed for SpeedyWeather.jl which is technically independent (SpeedyWeather.jl however imports it) and can also be used without running simulations. It is just not put into its own respective repository.
S = SpectralTransform( alms::AbstractMatrix{Complex{NF}};
+ recompute_legendre::Bool=true,
+ Grid::Type{<:AbstractGrid}=DEFAULT_GRID)
Generator function for a SpectralTransform struct based on the size of the spectral coefficients alms and the grid Grid. Recomputes the Legendre polynomials by default.
Generator function for a SpectralTransform struct. With NF the number format, Grid the grid type <:AbstractGrid and spectral truncation lmax,mmax this function sets up necessary constants for the spetral transform. Also plans the Fourier transforms, retrieves the colatitudes, and preallocates the Legendre polynomials (if recompute_legendre == false) and quadrature weights.
S = SpectralTransform( map::AbstractGrid;
+ recompute_legendre::Bool=true)
Generator function for a SpectralTransform struct based on the size and grid type of gridded field map. Recomputes the Legendre polynomials by default.
Get U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.
Get U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.
Generic divergence function of vector u,v that writes into the output into div. Generic as it uses the kernel kernel such that curl, div, add or flipsign options are provided through kernel, but otherwise a single function is used.
Curl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.
Divergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.
get_recursion_factors( ::Type{NF}, # number format NF
+ lmax::Int, # max degree l of spherical harmonics (0-based here)
+ mmax::Int # max order m of spherical harmonics
+ ) where {NF<:AbstractFloat}
Returns a matrix of recursion factors ϵ up to degree lmax and order mmax of number format NF.
Spectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.
m = roundup_fft(n::Int;
+ small_primes::Vector{Int}=[2,3,5])
Returns an integer m >= n with only small prime factors 2, 3 (default, others can be specified with the keyword argument small_primes) to obtain an efficiently fourier-transformable number of longitudes, m = 2^i * 3^j * 5^k >= n, with i,j,k >=0.
Spectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Returns a spectral coefficient matrix alms_interp that is alms padded with zeros to interpolate in spectral space. If trunc is smaller or equal to the implicit truncation in alms obtained from its size than spectral_truncation is automatically called instead, returning alms_trunc, a coefficient matrix that is smaller than alms, implicitly setting higher degrees and orders to zero.
Smooth the spectral field A following A = (1-(1-c)∇²ⁿ) with power n of a normalised Laplacian so that the highest degree lmax is dampened by multiplication with c. Anti-diffusion for c>1.
Smooth the spectral field A following A_smooth = (1-c*∇²ⁿ)A with power n of a normalised Laplacian so that the highest degree lmax is dampened by multiplication with c. Anti-diffusion for c<0.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.
Truncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.
Truncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.
Returns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.
Recursion factors ϵ as a function of degree l and order m (0-based) of the spherical harmonics. ϵ(l,m) = sqrt((l^2-m^2)/(4*l^2-1)) with default number format Float64.
Recursion factors ϵ as a function of degree l and order m (0-based) of the spherical harmonics. ϵ(l,m) = sqrt((l^2-m^2)/(4*l^2-1)) and then converted to number format NF.
Laplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.
Keyword arguments
add=true adds the ∇²(alms) to the output
flipsign=true computes -∇²(alms) instead
inverse=true computes ∇⁻²(alms) instead
Default is add=false, flipsign=false, inverse=false. These options can be combined.
The prognostic variables in spectral space are called
vor # Vorticity of horizontal wind field
+ div # Divergence of horizontal wind field
+ temp # Absolute temperature [K]
+ pres_surf # Logarithm of surface pressure [log(Pa)]
+ humid # Specific humidity [g/kg]
their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are
We follow Julia's style guide and highlight here some important aspects of it.
Bang (!) convention. A function func does not change its input arguments, however, func! does.
Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.
Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup
but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.
Settings
This document was generated with Documenter.jl version 0.27.24 on Tuesday 23 May 2023. Using Julia version 1.8.5.
A mathematical and implementation-specific description of the dynamical core used in SpeedyWeather.jl. We start by describing the barotropic vorticity equations which is one set of equations that SpeedyWeather.jl can solve (see How to run SpeedyWeather.jl) as many details therein also apply to the Shallow water equations and Primitive equations explained thereafter.
The dynamical core presented here largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2], a shallow water model [3] and a primitive equation model[4].
The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity $\zeta$ with advection, Coriolis force and diffusion in a single global layer.
with time $t$, velocity vector $\mathbf{u} = (u, v)$, Coriolis parameter $f$, and hyperdiffusion $(-1)^{n+1} \nu \nabla^{2n} \zeta$ ($n$ is the hyperdiffusion order; see Horizontal diffusion). Starting with some relative vorticity $\zeta$, the Laplacian is inverted to obtain the stream function $\Psi$
\[\Psi = \nabla^{-2}\zeta\]
The zonal velocity $u$ and meridional velocity $v$ are then the (negative) meridional gradient and zonal gradient of $\Psi$
obtain meridional velocity $(\cos(\theta)v)_{lm}$ through a Zonal derivative
Transform zonal and meridional velocity $(\cos(\theta)u)_{lm}$, $(\cos(\theta)v)_{lm}$ to grid-point space and unscale the $\cos(\theta)$ factor to obtain $u,v$.
Multiply $u,v$ with $\zeta+f$ in grid-point space
Transform $u(\zeta + f)$ and $v(\zeta+f)$ to spectral space
Compute the divergence of $(\mathbf{u}(\zeta + f))_{lm}$ in spectral space through a Meridional derivative and Zonal derivative which will be the tendency of $\zeta_{lm}$
In SpeedyWeather.jl we use hyerdiffusion through an $n$-th power Laplacian $(-1)^{n+1}\nabla^{2n}$ (hyper when $n>1$) which can be implemented as a multiplication of the spectral coefficients $\Psi_{lm}$ with $(-l(l+1))^nR^{-2n}$ (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the $(-l(l+1))^nR^{-2n}$ can be precomputed. Note the sign change $(-1)^{n+1}$ here is such that the dissipative nature of the diffusion operator is retained for $n$ odd and even.
In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step $\Delta t$ of variable $\zeta$ to obtain from time steps $i-1$ and $i$, the next time step $i+1$
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t d\zeta,\]
with $d\zeta$ being some tendency evaluated from $\zeta_i$. Now we want to add a diffusion term $(-1)^{n+1}\nu \nabla^{2n}\zeta$ with viscosity $\nu$, wich however, is implicitly calculated from $\zeta_{i+1}$, then
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t (d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i+1})\]
As the application of $(-1)^{n+1}\nu\nabla^{2n}$ is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to
\[\zeta_{i+1} = \frac{\zeta_{i-1} + 2\Delta t d\zeta}{1 - 2\Delta (-1)^{n+1}\nu\nabla^{2n}},\]
and expand the numerator to
\[\zeta_{i+1} = \zeta_{i-1} + 2\Delta t \frac{d\zeta + (-1)^{n+1} \nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t (-1)^{n+1}\nu \nabla^{2n}},\]
Hence the diffusion can be applied implicitly by updating the tendency $d\zeta$ as
\[d\zeta \to \frac{d\zeta + (-1)^{n+1}\nu\nabla^{2n}\zeta_{i-1}}{1 - 2\Delta t \nu \nabla^{2n}}\]
which only depends on $\zeta_{i-1}$. Now let $D_\text{explicit} = (-1)^{n+1}\nu\nabla^{2n}$ be the explicit part and $D_\text{implicit} = 1 - (-1)^{n+1} 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are $D_\text{implicit} = 1 - 2\Delta t \nu\nabla^{2n}$ the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic $l,m$ we do
Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power $n$.
In physics, the Laplace operator $\nabla^2$ is often used to represent diffusion due to viscosity in a fluid. In that case, the viscosity coefficient is $\nu$ of units $\text{m}^2\text{s}^{-1}$ and the full operator reads as $\nu \nabla^2$ with units $(\text{m}^2\text{s}^{-1})(\text{m}^{-2}) = \text{s}^{-1}$. This motivates us to normalize the Laplace operator by a constant of units $\text{m}^{-2}$ and the viscosity coefficient by its inverse such that the viscosity coefficient becomes a damping timescale of unit $\text{s}^{-1}$. Given the application in spectral space we decide to normalize by the largest eigenvalue $-l_\text{max}(l_\text{max}+1)$ such that all entries in the discrete spectral Laplace operator are in $[0,1]$. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized viscosity coefficient $\nu^* = l_\text{max}(l_\text{max}+1)\nu$ (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have
In the barotropic voriticity equation model the inversion of the Laplcians in order to obtain $\Psi$ from $\zeta$ therefore becomes
\[\tilde{\zeta} = \tilde{\nabla}^2 \tilde{\Psi}\]
where the dimensionless gradients simply omit the scaling with $1/R$, $\tilde{\nabla} = R\nabla$. The Barotropic vorticity equation scaled with $R^2$ is
Similar to the scaled barotropic vorticity equations, the scaled shallow water equations scale the vorticity and the divergence equation with $R^2$, but the continuity equation with $R$
This document was generated with Documenter.jl version 0.27.24 on Tuesday 23 May 2023. Using Julia version 1.8.5.
diff --git a/v0.5.0/functions/index.html b/v0.5.0/functions/index.html
new file mode 100644
index 000000000..7f1621efd
--- /dev/null
+++ b/v0.5.0/functions/index.html
@@ -0,0 +1,103 @@
+
+Function and type index · SpeedyWeather.jl
P = Parameters{M<:ModelSetup}(kwargs...) <: AbstractParameters{M}
A struct to hold all model parameters that may be changed by the user. The struct uses keywords such that default values can be changed at creation. The default values of the keywords define the default model setup.
NF::DataType: number format
trunc::Int64: spectral truncation
Grid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid in use
mol_mass_dry_air::Any: molar mass of dry air [g/mol]
mol_mass_vapour::Any: molar mass of water vapour [g/mol]
cₚ::Float64: specific heat at constant pressure [J/K/kg]
R_gas::Float64: universal gas constant [J/K/mol]
R_dry::Float64: specific gas constant for dry air [J/kg/K]
R_vapour::Float64: specific gas constant for water vapour [J/kg/K]
alhc::Float64: latent heat of condensation [J/g] for consistency with specific humidity [g/Kg]
alhs::Float64: latent heat of sublimation [?]
sbc::Float64: stefan-Boltzmann constant [W/m²/K⁴]
lapse_rate::Float64: moist adiabatic temperature lapse rate $-dT/dz$ [K/km]
temp_ref::Float64: absolute temperature at surface $z=0$ [K]
temp_top::Float64: absolute temperature in stratosphere [K]
ΔT_stratosphere::Float64: for stratospheric lapse rate [K] after Jablonowski
scale_height::Float64: scale height for pressure [km]
pres_ref::Float64: surface pressure [hPa]
scale_height_humid::Float64: scale height for specific humidity [km]
relhumid_ref::Float64: relative humidity of near-surface air [1]
water_pres_ref::Float64: saturation water vapour pressure [Pa]
layer_thickness::Float64: layer thickness for the shallow water model [km]
GLcoefs::SpeedyWeather.Coefficients: vertical coordinates of the nlev vertical levels, defined by a generalised logistic function, interpolating ECMWF's L31 configuration
σ_tropopause::Float64: σ coordinate where the tropopause starts
σ_levels_half::Vector{Float64}: only used if set manually, otherwise empty
Spectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).
Spectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.
gridded!( diagn::DiagnosticVariables{NF}, # all diagnostic variables
+ progn::PrognosticVariables{NF}, # all prognostic variables
+ M::BarotropicModel, # everything that's constant
+ lf::Int=1 # leapfrog index
+ ) where NF
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.
gridded!( diagn::DiagnosticVariables{NF}, # all diagnostic variables
+ progn::PrognosticVariables{NF}, # all prognostic variables
+ lf::Int=1 # leapfrog index
+ M::ShallowWaterModel, # everything that's constant
+ ) where NF
Propagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities U,V (scaled by cos(lat)).
Spectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.
Returns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.
Truncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.
Truncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.
Truncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.
Laplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.
Keyword arguments
add=true adds the ∇²(alms) to the output
flipsign=true computes -∇²(alms) instead
inverse=true computes ∇⁻²(alms) instead
Default is add=false, flipsign=false, inverse=false. These options can be combined.
Divergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.
Curl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.
Get U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.
Get U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.
Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. NF is the number format used for the precomputed constants.
Vertical sigma coordinates defined by their nlev+1 half levels σ_levels_half. Sigma coordinates are fraction of surface pressure (p/p0) and are sorted from top (stratosphere) to bottom (surface). The first half level is at 0 the last at 1. Evaluate a generalised logistic function with coefficients in P for the distribution of values in between. Default coefficients follow the L31 configuration historically used at ECMWF.
Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF. See geometry.jl and function vertical_coordinate for more informaiton.
timestep!( progn::PrognosticVariables{NF}, # all prognostic variables
+ diagn::DiagnosticVariables{NF}, # all pre-allocated diagnostic variables
+ time::DateTime, # time at timestep
+ dt::Real, # time step (mostly =2Δt, but for init steps =Δt,Δt/2)
+ M::ShallowWaterModel, # everything that's constant at runtime
+ lf1::Int=2, # leapfrog index 1 (dis/enables Robert+William's filter)
+ lf2::Int=2 # leapfrog index 2 (time step used for tendencies)
+ ) where {NF<:AbstractFloat}
Calculate a single time step for the shallow water model of SpeedyWeather.jl
timestep!( progn::PrognosticVariables{NF}, # all prognostic variables
+ diagn::DiagnosticVariables{NF}, # all pre-allocated diagnostic variables
+ time::DateTime, # time at timestep
+ dt::Real, # time step (mostly =2Δt, but for init steps =Δt,Δt/2)
+ M::PrimitiveEquation, # everything that's constant at runtime
+ lf1::Int=2, # leapfrog index 1 (dis/enables Robert+William's filter)
+ lf2::Int=2 # leapfrog index 2 (time step used for tendencies)
+ ) where {NF<:AbstractFloat}
Calculate a single time step for the primitive equation model of SpeedyWeather.jl
first_timesteps!( progn::PrognosticVariables, # all prognostic variables
+ diagn::DiagnosticVariables, # all pre-allocated diagnostic variables
+ time::DateTime, # time at timestep
+ M::ModelSetup, # everything that is constant at runtime
+ feedback::AbstractFeedback # feedback struct
+ )
Performs the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.
leapfrog!( A_old::LowerTriangularMatrix{Complex{NF}}, # prognostic variable at t
+ A_new::LowerTriangularMatrix{Complex{NF}}, # prognostic variable at t+dt
+ tendency::LowerTriangularMatrix{Complex{NF}}, # tendency (dynamics+physics) of A
+ dt::Real, # time step (=2Δt, but for init steps =Δt,Δt/2)
+ lf::Int=2, # leapfrog index to dis/enable William's filter
+ C::DynamicsConstants{NF}, # struct with constants used at runtime
+ ) where {NF<:AbstractFloat} # number format NF
Performs one leapfrog time step with (lf=2) or without (lf=1) Robert+William's filter (see William (2009), Montly Weather Review, Eq. 7-9).
function shortwave_radiation!(
+ column::ColumnVariables{NF}, model::PrimitiveEquation
+)
Compute air temperature tendencies from shortwave radiation for an atmospheric column. For more details see http://users.ictp.it/~kucharsk/speedydescription/kmver41_appendixA.pdf
The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and how they can be used.
SpeedyWeather.jl's spectral transform currently only supports ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.
All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).
Currently the following full grids <: AbstractFullGrid are implemented
FullGaussianGrid, a full grid with Gaussian latitudes
FullClenshawGrid, a full grid with equi-angle latitudes
and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are
OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron
OctahedralClenshawGrid, similar but based on equi-angle latitudes
HEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces
OctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.
An overview of these grids is visualised here
Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.
All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half.
HEALPix grids do not use Nside as resolution parameter
The original formulation for HEALPix grids use $N_{side}$, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use $N_{side}$ for the documentation or within functions though.
A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation T with a grid resolution N (=nlat_half) such that T + 1 = N. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at $l_{max}=31$ in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid.
Let J be the total number of rings. Then we have
$T \approx J$ for linear truncation
$\frac{3}{2}T \approx J$ for quadratic truncation
$2T \approx J$ for cubic truncation
and in general $\frac{m+1}{2}T \approx J$ for m-th order truncation. So the higher the truncaction order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. In SpeedyWeather.jl the parameter dealiasing controls this option, = 1 would be linear, = 2 quadratic, = 3 cubic etc.
Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are $N_\varphi$ basepixels in zonal direction and $N_\theta$ basepixels in meridional direction. For $N_\varphi = 4$ and $N_\theta = 3$ we obtain the classical HEALPix grid with $N_\varphi N_\theta = 12$ basepixels shown above in Implemented Grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always $2N$, so 32 at $N=16$) and there are polar caps above and below the equatorial zone with the border at $\cos(\theta) = 2/3$ ($\theta$ in colatitudes).
Following Górski, 2004[1], the $z=cos(\theta)$ colatitude of the $j$-th ring in the north polar cap, $j=1,...,N_{side}$ with $2N_{side} = N$ is
\[z = 1 - \frac{j^2}{3N_{side}^2}\]
and on that ring, the longitude $\phi$ of the $i$-th point ($i$ is the in-ring-index) is at
\[\phi = \frac{\pi}{2j}(i-\tfrac{1}{2})\]
The in-ring index $i$ goes from $i=1,...,4$ for the first (i.e. northern-most) ring, $i=1,...,8$ for the second ring and $i = 1,...,4j$ for the $j$-th ring in the northern polar cap.
In the north equatorial belt $j=N_{side},...,2N_{side}$ this changes to
\[z = \frac{4}{3} - \frac{2j}{3N_{side}}\]
and the longitudes change to ($i$ is always $i = 1,...,4N_{side}$ in the equatorial belt meaning the number of longitude points is constant here)
The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.
The cell boundaries are obtained by setting $i = k + 1/2$ or $i = k + 1/2 + j$ (half indices) into the equations above, such that $z(\phi,k)$, a function for the cosine of colatitude $z$ of index $k$ and the longitude $\phi$ is obtained. These are then (northern polar cap)
While the classic HEALPix grid is based on a dodecahedron, other choices for $N_\varphi$ and $N_\theta$ in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With $N_\varphi = 4$ and $N_\theta = 1$ we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, $2\pi$ around the Equator versus $\pi$ between the poles. $N_\varphi = 6, N_\theta = 2$ or $N_\varphi = 8, N_\theta = 3$ are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visulations of these grids.
We call the $N_\varphi = 4, N_\theta = 1$ HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As $N_\theta = 1$ there is no equatorial belt which simplifies the grid. The latitude of the $j$-th isolatitude ring on the OctaHEALPixGrid is defined by
\[z = 1 - \frac{j^2}{N^2},\]
with $j=1,...,N$, and similarly for the southern hemisphere by symmetry. On this grid $N_{side} = N$ where $N$= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index $i = 1,...,4j$ are
\[\phi = \frac{\pi}{2j}(i - \tfrac{1}{2})\]
and again, the southern hemisphere grid points are obtained by symmetry.
The $3N_{side}^2$ in the denominator of the HEALPix grid came simply $N^2$ for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).
[1] Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976
Settings
This document was generated with Documenter.jl version 0.27.24 on Tuesday 23 May 2023. Using Julia version 1.8.5.
diff --git a/v0.5.0/how_to_run_speedy/index.html b/v0.5.0/how_to_run_speedy/index.html
new file mode 100644
index 000000000..7a27e3fbb
--- /dev/null
+++ b/v0.5.0/how_to_run_speedy/index.html
@@ -0,0 +1,7 @@
+
+How to run SpeedyWeather.jl · SpeedyWeather.jl
The simplest way to run SpeedyWeather.jl with default parameters is
using SpeedyWeather
+run_speedy()
Hooray, you have just simulated the Earth's atmosphere. Parameters, their meanings and defaults are documented in Parameters. For example, if you want to run the primitive equation dry core (no humidity) simulation in double precision (Float64), at higher resolution (trunc, the triangular spectral truncation), slow down the rotation of the Earth (rotation in $s^{-1}$), and create some netCDF ouput, do
If provided, the number format has to be the first argument, the model (Barotropic, ShallowWater, PrimitiveDryCore, PrimitiveWetCore are available) the second, and all other arguments are keyword arguments.
progn_vars = run_speedy(NF,Model;kwargs...) or
+progn_vars = run_speedy(NF;kwargs...) or
+progn_vars = run_speedy(Model;kwargs...)
Runs SpeedyWeather.jl with number format NF and the model Model and any additional parameters in the keyword arguments kwargs.... Any unspecified parameters will use the default values as defined in Parameters.
progn_vars, diagn_vars, model_setup = initialize_speedy(NF,Model;kwargs...) or
+progn_vars, diagn_vars, model_setup = initialize_speedy(NF,kwargs...) or
+progn_vars, diagn_vars, model_setup = initialize_speedy(Model,kwargs...)
Initialize the model by returning
progn_vars, the initial conditions of the prognostic variables
diagn_vars, the preallocated the diagnotic variables (initialised to zero)
model_setup, the collected pre-calculated structs that don't change throughout integration.
The keyword arguments kwargs are the same as for run_speedy. The model_setup contains fields that hold the parameters, constants, geometry, spectral transform, boundaries and diffusion.
Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.
SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to perform some calculations in spectral space (time integration, gradients, linear terms) and some in grid-point space (advection, non-linear terms, parameterizations). The prognostic variables used are vorticity, divergence, absolute temperature, logarithm of surface pressure and specific humidity. The time stepping uses a leapfrog scheme with additional filters and a semi-implicit formulation for gravity waves. The default resolution is T31 (96x48 grid points on a regular Gaussian grid, about 400km at the Equator) and 8 vertical levels.
Simple parameterizations are used to represent the physical processes convection, large-scale condensation, clouds, short-wave radiation, long-waves radiation, surface fluxes of momentum and energy, and vertical diffusion.
The focus of SpeedyWeather.jl is to develop a global atmospheric model of intermediate complexity, that can run at various levels of precision (16, 32 and 64-bit) on different architectures (x86 and ARM, GPUs in the future). Additionally, the model is written in an entirely number format-flexible way, such that any custom number format can be used and Julia will compile to the format automatically. Similarly, many model components are written in an abstract way to support modularity and extandability.
SpeedyWeather.jl is a Julia implementation of SPEEDY, which is written in Fortran 77. Sam Hatfield translated SPEEDY to Fortran 90 and started the project to port it to Julia. However, we are making an effort to overhaul the implementation of the mathematical model behind speedy completely and it is unlikely that a single line of code survived.
SpeedyWeather.jl is registered in the Julia Registry. Open Julia's package manager from the REPL with ] and add the github repository to install SpeedyWeather.jl and all dependencies
(@v1.8) pkg> add SpeedyWeather
which will automatically install the latest release. However, you may want to install directly from the main branch with
Contributors received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022.
Settings
This document was generated with Documenter.jl version 0.27.24 on Tuesday 23 May 2023. Using Julia version 1.8.5.
diff --git a/v0.5.0/new_model_setups/index.html b/v0.5.0/new_model_setups/index.html
new file mode 100644
index 000000000..ed0bcf6f2
--- /dev/null
+++ b/v0.5.0/new_model_setups/index.html
@@ -0,0 +1,2 @@
+
+New model setups · SpeedyWeather.jl
This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmopshere. Every section is followed by a brief description of implementation details.
This document was generated with Documenter.jl version 0.27.24 on Tuesday 23 May 2023. Using Julia version 1.8.5.
diff --git a/v0.5.0/search_index.js b/v0.5.0/search_index.js
new file mode 100644
index 000000000..fd74e242a
--- /dev/null
+++ b/v0.5.0/search_index.js
@@ -0,0 +1,3 @@
+var documenterSearchIndex = {"docs":
+[{"location":"development/#Development-notes","page":"Development notes","title":"Development notes","text":"","category":"section"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To run tests, from the path of your local clone of the repository do:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=. -e 'import Pkg; Pkg.test()'","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To install dependencies:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project -e 'import Pkg; Pkg.instantiate()`","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"then opening:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"you are able to using SpeedyWeather.","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"To generate the docs:","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"julia --project=docs -e 'import Pkg; Pkg.develop(path=\".\"); Pkg.instantiate()'\njulia --project=docs docs/make.jl","category":"page"},{"location":"development/","page":"Development notes","title":"Development notes","text":"If the docs are generated successfully, you view them by opening docs/build/index.html in your favorite browser.","category":"page"},{"location":"functions/#Function-and-type-index","page":"Function and type index","title":"Function and type index","text":"","category":"section"},{"location":"functions/#Parameters-and-constants","page":"Function and type index","title":"Parameters and constants","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Parameters\nSpeedyWeather.Constants","category":"page"},{"location":"functions/#SpeedyWeather.Parameters","page":"Function and type index","title":"SpeedyWeather.Parameters","text":"P = Parameters{M<:ModelSetup}(kwargs...) <: AbstractParameters{M}\n\nA struct to hold all model parameters that may be changed by the user. The struct uses keywords such that default values can be changed at creation. The default values of the keywords define the default model setup.\n\nNF::DataType: number format\ntrunc::Int64: spectral truncation\nGrid::Type{<:SpeedyWeather.RingGrids.AbstractGrid}: grid in use\ndealiasing::Float64: dealiasing factor, 1=linear, 2=quadratic, 3=cubic grid\nplanet::SpeedyWeather.Planet: planet\nmol_mass_dry_air::Any: molar mass of dry air [g/mol]\nmol_mass_vapour::Any: molar mass of water vapour [g/mol]\ncₚ::Float64: specific heat at constant pressure [J/K/kg]\nR_gas::Float64: universal gas constant [J/K/mol]\nR_dry::Float64: specific gas constant for dry air [J/kg/K]\nR_vapour::Float64: specific gas constant for water vapour [J/kg/K]\nalhc::Float64: latent heat of condensation [J/g] for consistency with specific humidity [g/Kg]\nalhs::Float64: latent heat of sublimation [?]\nsbc::Float64: stefan-Boltzmann constant [W/m²/K⁴]\nlapse_rate::Float64: moist adiabatic temperature lapse rate -dTdz [K/km]\ntemp_ref::Float64: absolute temperature at surface z=0 [K]\ntemp_top::Float64: absolute temperature in stratosphere [K]\nΔT_stratosphere::Float64: for stratospheric lapse rate [K] after Jablonowski\nscale_height::Float64: scale height for pressure [km]\npres_ref::Float64: surface pressure [hPa]\nscale_height_humid::Float64: scale height for specific humidity [km]\nrelhumid_ref::Float64: relative humidity of near-surface air [1]\nwater_pres_ref::Float64: saturation water vapour pressure [Pa]\nlayer_thickness::Float64: layer thickness for the shallow water model [km]\nGLcoefs::SpeedyWeather.Coefficients: vertical coordinates of the nlev vertical levels, defined by a generalised logistic function, interpolating ECMWF's L31 configuration\nσ_tropopause::Float64: σ coordinate where the tropopause starts\nσ_levels_half::Vector{Float64}: only used if set manually, otherwise empty\nnlev::Int64: number of vertical levels\ndiffusion::SpeedyWeather.DiffusionParameters: horizontal (hyper)-diffusion\nvertical_diffusion::SpeedyWeather.VerticalDiffusion: vertical diffusion\nstatic_energy_diffusion::SpeedyWeather.VerticalDiffusion: static energy diffusion\ninterface_relaxation::Bool: turn on interface relaxation for shallow water?\ninterface_relax_time::Float64: time scale [hrs] of interface relaxation\ninterface_relax_amplitude::Float64: Amplitude [m] of interface relaxation\nphysics::Bool: en/disables the physics parameterizations\nn_shortwave::Int64: Compute shortwave radiation every n steps\nsppt_on::Bool: Turn on SPPT?\nmagnus_coefs::SpeedyWeather.Coefficients: For computing saturation vapour pressure\nk_lsc::Int64: Index of atmospheric level at which large-scale condensation begins\nRH_thresh_pbl_lsc::Float64: Relative humidity threshold for boundary layer\nRH_thresh_range_lsc::Float64: Vertical range of relative humidity threshold\nRH_thresh_max_lsc::Float64: Maximum relative humidity threshold\nhumid_relax_time_lsc::Float64: Relaxation time for humidity (hours)\npres_thresh_cnv::Float64: Minimum (normalised) surface pressure for the occurrence of convection\nRH_thresh_pbl_cnv::Float64: Relative humidity threshold for convection in PBL\nRH_thresh_trop_cnv::Float64: Relative humidity threshold for convection in the troposphere\nhumid_relax_time_cnv::Float64: Relaxation time for PBL humidity (hours)\nmax_entrainment::Float64: Maximum entrainment as a fraction of cloud-base mass flux\nratio_secondary_mass_flux::Float64: Ratio between secondary and primary mass flux at cloud-base\nnband::Int64: Number of bands used to compute fband\nradiation_coefs::SpeedyWeather.Coefficients: radiation coefficients\nboundary_layer::SpeedyWeather.BoundaryLayer{Float64}: boundary layer drag\ntemperature_relaxation::SpeedyWeather.TemperatureRelaxation{Float64}: temperature relaxation\nstartdate::Dates.DateTime: time at which the integration starts\nn_days::Float64: number of days to integrate for\nΔt_at_T31::Float64: time step in minutes for T31, scale linearly to trunc\nrobert_filter::Float64: Robert (1966) time filter coefficeint to suppress comput. mode\nwilliams_filter::Float64: William's time filter (Amezcua 2011) coefficient for 3rd order acc\nimplicit_α::Float64: coefficient for semi-implicit computations to filter gravity waves\nrecalculate_implicit::Int64: recalculate implicit operators on temperature profile every n time steps\nrecompute_legendre::Bool: recomputation is slower but requires less memory\nlegendre_NF::DataType: which format to use to calculate the Legendre polynomials\nlegendre_shortcut::Symbol: :linear, :quadratic, :cubic, :lincub_coslat, :linquad_coslat²\nboundary_path::String: package location is default\norography::SpeedyWeather.AbstractOrography: orography\norography_scale::Float64: scale orography by a factor\norography_path::String: path of orography\norography_file::String: filename of orography\nseed::Int64: random seed for the global random number generator\ninitial_conditions::SpeedyWeather.InitialConditions: initial conditions\npressure_on_orography::Bool: calculate the initial surface pressure from orography\nverbose::Bool: print dialog for feedback\ndebug::Bool: print debug info, NaR detection\noutput::Bool: Store data in netCDF?\noutput_dt::Float64: output time step [hours]\noutput_path::String: path to output folder\nrun_id::Union{Int64, String}: name of the output folder, defaults to 4-digit number counting up from run-0001\noutput_filename::String: name of the output netcdf file\noutput_vars::Vector{Symbol}: variables to output: :u, :v, :vor, :div, :temp, :humid\ncompression_level::Int64: compression level; 1=low but fast, 9=high but slow\nkeepbits::NamedTuple: mantissa bits to keep for every variable\nversion::VersionNumber: SpeedyWeather.jl version number\noutput_NF::DataType: number format used for output\noutput_nlat_half::Int64: 0 = reuse nlat_half from dynamical core\noutput_Grid::Type{<:SpeedyWeather.RingGrids.AbstractFullGrid}: output grid\noutput_Interpolator::Type{<:SpeedyWeather.RingGrids.AbstractInterpolator}: output interpolator\noutput_matrix::Bool: if true sort gridpoints into a matrix\noutput_quadrant_rotation::NTuple{4, Int64}: rotation of output quadrant\noutput_matrix_quadrant::NTuple{4, Tuple{Int64, Int64}}: matrix of output quadrant\nmissing_value::Float64: missing value to be used in netcdf output\nwrite_restart::Bool: also write restart file if output==true?\nrestart_path::String: path for restart file\nrestart_id::Union{Int64, String}: run_id of restart file in run-????/restart.jld2\n\n\n\n\n\n","category":"type"},{"location":"functions/#Boundaries-and-boundary-conditions","page":"Function and type index","title":"Boundaries and boundary conditions","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Boundaries","category":"page"},{"location":"functions/#Spherical-harmonic-transform","page":"Function and type index","title":"Spherical harmonic transform","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.GeoSpectral\nSpeedyWeather.SpectralTransform\nSpeedyWeather.spectral\nSpeedyWeather.spectral!\nSpeedyWeather.gridded\nSpeedyWeather.gridded!\nSpeedyWeather.triangular_truncation\nSpeedyWeather.roundup_fft\nSpeedyWeather.spectral_truncation\nSpeedyWeather.spectral_truncation!\nSpeedyWeather.spectral_interpolation!\nSpeedyWeather.get_legendre_polynomials!\nSpeedyWeather.∇²!\nSpeedyWeather.∇²\nSpeedyWeather.∇⁻²!\nSpeedyWeather.∇⁻²\nSpeedyWeather.gradient_latitude!\nSpeedyWeather.gradient_latitude\nSpeedyWeather.gradient_longitude!\nSpeedyWeather.gradient_longitude\nSpeedyWeather.divergence!\nSpeedyWeather.curl!\nSpeedyWeather._divergence!\nSpeedyWeather.curl_div!\nSpeedyWeather.UV_from_vordiv!\nSpeedyWeather.UV_from_vor!\nSpeedyWeather.ϵlm\nSpeedyWeather.get_recursion_factors","category":"page"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.SpectralTransform","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.SpectralTransform","text":"S = SpectralTransform{NF<:AbstractFloat}(...)\n\nSpectralTransform struct that contains all parameters and preallocated arrays for the spectral transform.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral","text":"alms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\nalms = spectral( map::AbstractGrid;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nConverts map to Grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\nalms = spectral( map::AbstractMatrix;\n Grid::Type{<:AbstractGrid}=DEFAULT_GRID,\n kwargs...)\n\nSpectral transform (grid to spectral) map to grid(map) to execute spectral(map::AbstractGrid;kwargs...).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral!","text":"spectral!( alms::LowerTriangularMatrix,\n map::AbstractGrid,\n S::SpectralTransform)\n\nSpectral transform (grid to spectral space) from the gridded field map on a grid<:AbstractGrid to a LowerTriangularMatrix of spherical harmonic coefficients alms. Uses FFT in the zonal direction, and a Legendre Transform in the meridional direction exploiting symmetries. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded","text":"map = gridded( alms::AbstractMatrix;\n recompute_legendre::Bool=true,\n grid::Type{<:AbstractGrid}=DEFAULT_GRID)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map. Based on the size of alms the grid type grid, the spatial resolution is retrieved based on the truncation defined for grid. SpectralTransform struct S is allocated to execute gridded(alms,S).\n\n\n\n\n\nmap = gridded( alms::AbstractMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid space) from spherical coefficients alms to a newly allocated gridded field map with precalculated properties based on the SpectralTransform struct S. alms is converted to a LowerTriangularMatrix to execute the in-place gridded!.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.gridded!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.gridded!","text":"gridded!( diagn::DiagnosticVariables{NF}, # all diagnostic variables\n progn::PrognosticVariables{NF}, # all prognostic variables\n M::BarotropicModel, # everything that's constant\n lf::Int=1 # leapfrog index\n ) where NF\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the barotropic vorticity model. Updates grid vorticity, spectral stream function and spectral and grid velocities u,v.\n\n\n\n\n\ngridded!( diagn::DiagnosticVariables{NF}, # all diagnostic variables\n progn::PrognosticVariables{NF}, # all prognostic variables\n lf::Int=1 # leapfrog index\n M::ShallowWaterModel, # everything that's constant\n ) where NF\n\nPropagate the spectral state of the prognostic variables progn to the diagnostic variables in diagn for the shallow water model. Updates grid vorticity, grid divergence, grid interface displacement (pres_grid) and the velocities U,V (scaled by cos(lat)).\n\n\n\n\n\ngridded!( map::AbstractGrid,\n alms::LowerTriangularMatrix,\n S::SpectralTransform)\n\nSpectral transform (spectral to grid) of the spherical harmonic coefficients alms to a gridded field map. The spectral transform is number format-flexible as long as the parametric types of map, alms, S are identical. The spectral transform is grid-flexible as long as the typeof(map)<:AbstractGrid. Uses the precalculated arrays, FFT plans and other constants in the SpectralTransform struct S.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral_truncation","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation","text":"alms_trunc = spectral_truncation(alms,trunc)\n\nReturns a spectral coefficient matrix alms_trunc that is truncated from alms to the size (trunc+1)². alms_trunc only contains those coefficient of alms for which m,l ≤ trunc, and l ≥ m are zero anyway. If trunc is larger than the implicit truncation in alms obtained from its size than spectral_interpolation is automatically called instead, returning alms_interp, a coefficient matrix that is larger than alms with padded zero coefficients.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.spectral_truncation!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.spectral_truncation!","text":"spectral_truncation!(alms::AbstractMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc.\n\n\n\n\n\nspectral_truncation!(alms::LowerTriangularMatrix,ltrunc::Integer,mtrunc::Integer)\n\nTruncate spectral coefficients alms in-place by setting all coefficients for which the degree l is larger than the truncation ltrunc or order m larger than the truncaction mtrunc. Similar to spectral_truncation!(::AbstractMatrix, ...) but skips the upper triangle which is zero by design for LowerTriangularMatrix.\n\n\n\n\n\nspectral_truncation!(alms,trunc)\n\nTruncate spectral coefficients alms in-place by setting (a) the upper right triangle to zero and (b) all coefficients for which the degree l is larger than the truncation trunc.\n\n\n\n\n\nspectral_truncation!(alms)\n\nTruncate spectral coefficients alms in-place by setting the upper right triangle to zero. This is to enforce that all coefficients for which the degree l is larger than order m are zero.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.∇²!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.∇²!","text":"∇²!( ∇²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false,\n inverse::Bool=false)\n\nLaplace operator ∇² applied to the spectral coefficients alms in spherical coordinates. The radius R is omitted in the eigenvalues which are precomputed in S. ∇²! is the in-place version which directly stores the output in the first argument ∇²alms.\n\nKeyword arguments\n\nadd=true adds the ∇²(alms) to the output\nflipsign=true computes -∇²(alms) instead\ninverse=true computes ∇⁻²(alms) instead\n\nDefault is add=false, flipsign=false, inverse=false. These options can be combined.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.∇⁻²!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.∇⁻²!","text":"∇⁻²!( ∇⁻²alms::LowerTriangularMatrix,\n alms::LowerTriangularMatrix,\n S::SpectralTransform;\n add::Bool=false,\n flipsign::Bool=false)\n\nCalls ∇²!(∇⁻²alms, alms, S; add, flipsign, inverse=true).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.divergence!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.divergence!","text":"divergence!(div::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform{NF};\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nDivergence of a vector u,v written into div, div = ∇⋅(u,v). u,v are expected to have a 1/coslat-scaling included, then div is not scaled. flipsign option calculates -∇⋅(u,v) instead. add option calculates div += ∇⋅(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.curl!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.curl!","text":"curl!( curl::LowerTriangularMatrix,\n u::LowerTriangularMatrix,\n v::LowerTriangularMatrix,\n S::SpectralTransform;\n flipsign::Bool=false,\n add::Bool=false,\n )\n\nCurl of a vector u,v written into curl, curl = ∇×(u,v). u,v are expected to have a 1/coslat-scaling included, then curl is not scaled. flipsign option calculates -∇×(u,v) instead. add option calculates curl += ∇×(u,v) instead. flipsign and add can be combined. This functions only creates the kernel and calls the generic divergence function _divergence! subsequently with flipped u,v -> v,u for the curl.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.UV_from_vordiv!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.UV_from_vordiv!","text":"UV_from_vordiv!(U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n div::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ and divergence D in spectral space. Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity and velocity potential from divergence. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.SpeedyTransforms.UV_from_vor!","page":"Function and type index","title":"SpeedyWeather.SpeedyTransforms.UV_from_vor!","text":"UV_from_vor!( U::LowerTriangularMatrix,\n V::LowerTriangularMatrix,\n vor::LowerTriangularMatrix,\n S::SpectralTransform)\n\nGet U,V (=(u,v)*coslat) from vorticity ζ spectral space (divergence D=0) Two operations are combined into a single linear operation. First, invert the spherical Laplace ∇² operator to get stream function from vorticity. Then compute zonal and meridional gradients to get U,V.\n\n\n\n\n\n","category":"function"},{"location":"functions/#Dynamics","page":"Function and type index","title":"Dynamics","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.bernoulli_potential!\nSpeedyWeather.volume_flux_divergence!\nSpeedyWeather.vorticity_fluxes!\nSpeedyWeather.vorticity_flux_curl!\nSpeedyWeather.vorticity_flux_divergence!","category":"page"},{"location":"functions/#SpeedyWeather.bernoulli_potential!","page":"Function and type index","title":"SpeedyWeather.bernoulli_potential!","text":"bernoulli_potential!( diagn::DiagnosticVariablesLayer, \n G::Geometry,\n S::SpectralTransform)\n\nComputes the Laplace operator ∇² of the Bernoulli potential B in spectral space.\n\ncomputes the kinetic energy KE = ½(u²+v²) on the grid\ntransforms KE to spectral space\nadds geopotential for the Bernoulli potential in spectral space\ntakes the Laplace operator.\n\nThis version is used for both ShallowWater and PrimitiveEquation, only the geopotential calculation in geopotential! differs.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.volume_flux_divergence!","page":"Function and type index","title":"SpeedyWeather.volume_flux_divergence!","text":"volume_flux_divergence!(diagn::DiagnosticVariablesLayer,\n surface::SurfaceVariables,\n model::ShallowWater)\n\nComputes the (negative) divergence of the volume fluxes uh,vh for the continuity equation, -∇⋅(uh,vh).\n\n\n\n\n\n","category":"function"},{"location":"functions/#Geometry","page":"Function and type index","title":"Geometry","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.Geometry\nSpeedyWeather.vertical_coordinates\nSpeedyWeather.GenLogisticCoefs\nSpeedyWeather.generalised_logistic","category":"page"},{"location":"functions/#SpeedyWeather.Geometry","page":"Function and type index","title":"SpeedyWeather.Geometry","text":"Geometry{NF<:AbstractFloat} <: AbstractGeometry\n\nGeometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid and the vertical levels. NF is the number format used for the precomputed constants.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.vertical_coordinates","page":"Function and type index","title":"SpeedyWeather.vertical_coordinates","text":"σ_levels_half = vertical_coordinates(P::Parameters)\n\nVertical sigma coordinates defined by their nlev+1 half levels σ_levels_half. Sigma coordinates are fraction of surface pressure (p/p0) and are sorted from top (stratosphere) to bottom (surface). The first half level is at 0 the last at 1. Evaluate a generalised logistic function with coefficients in P for the distribution of values in between. Default coefficients follow the L31 configuration historically used at ECMWF.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.GenLogisticCoefs","page":"Function and type index","title":"SpeedyWeather.GenLogisticCoefs","text":"Coefficients of the generalised logistic function to describe the vertical coordinate. Default coefficients A,K,C,Q,B,M,ν are fitted to the old L31 configuration at ECMWF. See geometry.jl and function vertical_coordinate for more informaiton.\n\nFollowing the notation of https://en.wikipedia.org/wiki/Generalisedlogisticfunction (Dec 15 2021).\n\nChange default parameters for more/fewer levels in the stratosphere vs troposphere vs boundary layer.\n\n\n\n\n\n","category":"type"},{"location":"functions/#SpeedyWeather.generalised_logistic","page":"Function and type index","title":"SpeedyWeather.generalised_logistic","text":"Generalised logistic function based on the coefficients in coefs.\n\n\n\n\n\n","category":"function"},{"location":"functions/#Time-stepping","page":"Function and type index","title":"Time stepping","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.time_stepping!\nSpeedyWeather.timestep!\nSpeedyWeather.first_timesteps!\nSpeedyWeather.leapfrog!","category":"page"},{"location":"functions/#SpeedyWeather.time_stepping!","page":"Function and type index","title":"SpeedyWeather.time_stepping!","text":"time_stepping!( progn::PrognosticVariables, # all prognostic variables\n diagn::DiagnosticVariables, # all pre-allocated diagnostic variables\n model::ModelSetup) # all precalculated structs\n\nMain time loop that that initialises output and feedback, loops over all time steps and calls the output and feedback functions.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.timestep!","page":"Function and type index","title":"SpeedyWeather.timestep!","text":"timestep!( progn::PrognosticVariables, # all prognostic variables\n diagn::DiagnosticVariables, # all pre-allocated diagnostic variables\n time::DateTime, # time at timestep\n dt::Real, # time step (mostly =2Δt, but for init steps =Δt,Δt/2)\n lf1::Int=2, # leapfrog index 1 (dis/enables Robert+William's filter)\n lf2::Int=2, # leapfrog index 2 (time step used for tendencies)\n M::BarotropicModel, # everything that's constant at runtime\n )\n\nCalculate a single time step for the barotropic vorticity equation model of SpeedyWeather.jl \n\n\n\n\n\ntimestep!( progn::PrognosticVariables{NF}, # all prognostic variables\n diagn::DiagnosticVariables{NF}, # all pre-allocated diagnostic variables\n time::DateTime, # time at timestep\n dt::Real, # time step (mostly =2Δt, but for init steps =Δt,Δt/2)\n M::ShallowWaterModel, # everything that's constant at runtime\n lf1::Int=2, # leapfrog index 1 (dis/enables Robert+William's filter)\n lf2::Int=2 # leapfrog index 2 (time step used for tendencies)\n ) where {NF<:AbstractFloat}\n\nCalculate a single time step for the shallow water model of SpeedyWeather.jl \n\n\n\n\n\ntimestep!( progn::PrognosticVariables{NF}, # all prognostic variables\n diagn::DiagnosticVariables{NF}, # all pre-allocated diagnostic variables\n time::DateTime, # time at timestep\n dt::Real, # time step (mostly =2Δt, but for init steps =Δt,Δt/2)\n M::PrimitiveEquation, # everything that's constant at runtime\n lf1::Int=2, # leapfrog index 1 (dis/enables Robert+William's filter)\n lf2::Int=2 # leapfrog index 2 (time step used for tendencies)\n ) where {NF<:AbstractFloat}\n\nCalculate a single time step for the primitive equation model of SpeedyWeather.jl \n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.first_timesteps!","page":"Function and type index","title":"SpeedyWeather.first_timesteps!","text":"first_timesteps!( progn::PrognosticVariables, # all prognostic variables\n diagn::DiagnosticVariables, # all pre-allocated diagnostic variables\n time::DateTime, # time at timestep\n M::ModelSetup, # everything that is constant at runtime\n feedback::AbstractFeedback # feedback struct\n )\n\nPerforms the first two initial time steps (Euler forward, unfiltered leapfrog) to populate the prognostic variables with two time steps (t=0,Δt) that can then be used in the normal leap frogging.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.leapfrog!","page":"Function and type index","title":"SpeedyWeather.leapfrog!","text":"leapfrog!( A_old::LowerTriangularMatrix{Complex{NF}}, # prognostic variable at t\n A_new::LowerTriangularMatrix{Complex{NF}}, # prognostic variable at t+dt\n tendency::LowerTriangularMatrix{Complex{NF}}, # tendency (dynamics+physics) of A\n dt::Real, # time step (=2Δt, but for init steps =Δt,Δt/2)\n lf::Int=2, # leapfrog index to dis/enable William's filter\n C::DynamicsConstants{NF}, # struct with constants used at runtime\n ) where {NF<:AbstractFloat} # number format NF\n\nPerforms one leapfrog time step with (lf=2) or without (lf=1) Robert+William's filter (see William (2009), Montly Weather Review, Eq. 7-9).\n\n\n\n\n\n","category":"function"},{"location":"functions/#Longwave-radiation","page":"Function and type index","title":"Longwave radiation","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.radset!\nSpeedyWeather.radlw_down!\nSpeedyWeather.compute_bbe!\nSpeedyWeather.radlw_up!","category":"page"},{"location":"functions/#SpeedyWeather.radset!","page":"Function and type index","title":"SpeedyWeather.radset!","text":"function radset!(model::PrimitiveEquation) where {NF<:AbstractFloat}\n\nCompute energy fractions in four longwave bands as a function of temperature.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.radlw_down!","page":"Function and type index","title":"SpeedyWeather.radlw_down!","text":"function radlw_down!(\n column::ColumnVariables{NF}, model::PrimitiveEquation\n) where {NF<:AbstractFloat}\n\nCompute the downward flux of longwave radiation. Inputs variables are temp,wvi,tau2. Output column varables arefsfcd,dfabs,flux,st4a`.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.compute_bbe!","page":"Function and type index","title":"SpeedyWeather.compute_bbe!","text":"function compute_bbe!(\n column::ColumnVariables{NF}, model::PrimitiveEquation\n) where {NF<:AbstractFloat}\n\nComputes black-body (or grey-body) emissions.\n\nInput and output variables are ts and fsfcu, respectively.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.radlw_up!","page":"Function and type index","title":"SpeedyWeather.radlw_up!","text":"function radlw_up!(\n column::ColumnVariables{NF}, model::PrimitiveEquation\n) where {NF<:AbstractFloat}\n\nComputes the upward flux of longwave radiation.\n\nInput variables are nlev, temp, fsfcu, fsfcd, flux, ts, tau2, st4a, dfabs, stratc, σ_levels_thick, n_stratosphere_levels. Output column variables are fsfc and ftop.\n\n\n\n\n\n","category":"function"},{"location":"functions/#Shortwave-radiation","page":"Function and type index","title":"Shortwave radiation","text":"","category":"section"},{"location":"functions/","page":"Function and type index","title":"Function and type index","text":"SpeedyWeather.shortwave_radiation!\nSpeedyWeather.solar!\nSpeedyWeather.sol_oz!\nSpeedyWeather.cloud!\nSpeedyWeather.radsw!","category":"page"},{"location":"functions/#SpeedyWeather.shortwave_radiation!","page":"Function and type index","title":"SpeedyWeather.shortwave_radiation!","text":"function shortwave_radiation!(\n column::ColumnVariables{NF}, model::PrimitiveEquation\n)\n\nCompute air temperature tendencies from shortwave radiation for an atmospheric column. For more details see http://users.ictp.it/~kucharsk/speedydescription/kmver41_appendixA.pdf\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.solar!","page":"Function and type index","title":"SpeedyWeather.solar!","text":"function solar!(column::ColumnVariables{NF})\n\nCompute average daily flux of solar radiation for an atmospheric column, from Hartmann (1994).\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.sol_oz!","page":"Function and type index","title":"SpeedyWeather.sol_oz!","text":"function sol_oz!(\n column::ColumnVariables{NF}, model::PrimitiveEquation\n)\n\nCompute solar radiation parametres for an atmospheric column.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.cloud!","page":"Function and type index","title":"SpeedyWeather.cloud!","text":"function cloud!(\n column::ColumnVariables{NF}, model::PrimitiveEquation\n)\n\nCompute shortwave radiation cloud contibutions for an atmospheric column.\n\n\n\n\n\n","category":"function"},{"location":"functions/#SpeedyWeather.radsw!","page":"Function and type index","title":"SpeedyWeather.radsw!","text":"function radsw!(\n column::ColumnVariables{NF}, model::PrimitiveEquation\n)\n\nCompute shortwave radiation fluxes for an atmospheric column.\n\n\n\n\n\n","category":"function"},{"location":"parametrizations/#Parameterizations","page":"Parameterizations","title":"Parameterizations","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"This page describes the mathematical formulation of the parameterizations used in SpeedyWeather.jl to represent physical processes in the atmopshere. Every section is followed by a brief description of implementation details.","category":"page"},{"location":"parametrizations/#Convection","page":"Parameterizations","title":"Convection","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Large-scale-condensation","page":"Parameterizations","title":"Large-scale condensation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Clouds","page":"Parameterizations","title":"Clouds","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Short-wave-radiation","page":"Parameterizations","title":"Short-wave radiation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Long-wave-radiation","page":"Parameterizations","title":"Long-wave radiation","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Surface-fluxes-of-momentum-and-energy","page":"Parameterizations","title":"Surface fluxes of momentum and energy","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"parametrizations/#Vertical-diffusion","page":"Parameterizations","title":"Vertical diffusion","text":"","category":"section"},{"location":"parametrizations/","page":"Parameterizations","title":"Parameterizations","text":"more to come ...","category":"page"},{"location":"how_to_run_speedy/#How-to-run-SpeedyWeather.jl","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"The simplest way to run SpeedyWeather.jl with default parameters is","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"using SpeedyWeather\nrun_speedy()","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"Hooray, you have just simulated the Earth's atmosphere. Parameters, their meanings and defaults are documented in Parameters. For example, if you want to run the primitive equation dry core (no humidity) simulation in double precision (Float64), at higher resolution (trunc, the triangular spectral truncation), slow down the rotation of the Earth (rotation in s^-1), and create some netCDF ouput, do","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"run_speedy(Float64,PrimitiveDryCore,trunc=42,planet=Earth(rotation=1e-5),output=true)","category":"page"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"If provided, the number format has to be the first argument, the model (Barotropic, ShallowWater, PrimitiveDryCore, PrimitiveWetCore are available) the second, and all other arguments are keyword arguments.","category":"page"},{"location":"how_to_run_speedy/#The-run_speedy-interface","page":"How to run SpeedyWeather.jl","title":"The run_speedy interface","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"run_speedy","category":"page"},{"location":"how_to_run_speedy/#SpeedyWeather.run_speedy","page":"How to run SpeedyWeather.jl","title":"SpeedyWeather.run_speedy","text":"progn_vars = run_speedy(NF,Model;kwargs...) or\nprogn_vars = run_speedy(NF;kwargs...) or\nprogn_vars = run_speedy(Model;kwargs...)\n\nRuns SpeedyWeather.jl with number format NF and the model Model and any additional parameters in the keyword arguments kwargs.... Any unspecified parameters will use the default values as defined in Parameters.\n\n\n\n\n\n","category":"function"},{"location":"how_to_run_speedy/#The-initialize_speedy-interface","page":"How to run SpeedyWeather.jl","title":"The initialize_speedy interface","text":"","category":"section"},{"location":"how_to_run_speedy/","page":"How to run SpeedyWeather.jl","title":"How to run SpeedyWeather.jl","text":"initialize_speedy","category":"page"},{"location":"how_to_run_speedy/#SpeedyWeather.initialize_speedy","page":"How to run SpeedyWeather.jl","title":"SpeedyWeather.initialize_speedy","text":"progn_vars, diagn_vars, model_setup = initialize_speedy(NF,Model;kwargs...) or\nprogn_vars, diagn_vars, model_setup = initialize_speedy(NF,kwargs...) or\nprogn_vars, diagn_vars, model_setup = initialize_speedy(Model,kwargs...)\n\nInitialize the model by returning\n\nprogn_vars, the initial conditions of the prognostic variables\ndiagn_vars, the preallocated the diagnotic variables (initialised to zero)\nmodel_setup, the collected pre-calculated structs that don't change throughout integration.\n\nThe keyword arguments kwargs are the same as for run_speedy. The model_setup contains fields that hold the parameters, constants, geometry, spectral transform, boundaries and diffusion.\n\n\n\n\n\n","category":"function"},{"location":"grids/#Grids","page":"Grids","title":"Grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The spectral transform (the Spherical Harmonic Transform) in SpeedyWeather.jl supports any ring-based equi-longitude grid. Several grids are already implemented but other can be added. The following pages will describe an overview of these grids and how they can be used.","category":"page"},{"location":"grids/#Ring-based-equi-longitude-grids","page":"Grids","title":"Ring-based equi-longitude grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"SpeedyWeather.jl's spectral transform currently only supports ring-based equi-longitude grids. These grids have their grid points located on rings with constant latitude and on rings the points are equi-spaced in longitude. There is technically no constrain on the spacing of the latitude rings, but the Legendre transform requires a quadrature to map those to spectral space and back. Common choices for latitudes are the Gaussian latitudes which use the Gaussian quadrature, or equi-angle latitudes (i.e. just regular latitudes but excluding the poles) that use the Clenshaw-Curtis quadrature. The longitudes have to be equi-spaced on every ring, which is necessary for the fast Fourier transform, as one would otherwise need to use a non-uniform Fourier transform. In SpeedyWeather.jl the first grid point on any ring can have a longitudinal offset though, for example by spacing 4 points around the globe at 45˚E, 135˚E, 225˚E, and 315˚E. In this case the offset is 45˚E as the first point is not at 0˚E.","category":"page"},{"location":"grids/#Implemented-grids","page":"Grids","title":"Implemented grids","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids in SpeedyWeather.jl are a subtype of AbstractGrid, i.e. <: AbstractGrid. We further distinguish between full, and reduced grids. Full grids have the same number of longitude points on every latitude ring (i.e. points converge towards the poles) and reduced grids reduce the number of points towards the poles to have them more evenly spread out across the globe. More evenly does not necessarily mean that a grid is equal-area, meaning that every grid cell covers exactly the same area (although the shape changes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Currently the following full grids <: AbstractFullGrid are implemented","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"FullGaussianGrid, a full grid with Gaussian latitudes\nFullClenshawGrid, a full grid with equi-angle latitudes","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and additionally we have FullHEALPixGrid and FullOctaHEALPixGrid which are the full grid equivalents to the HEALPix grid and the OctaHEALPix grid discussed below. Full grid equivalent means that they have the same latitude rings, but no reduction in the number of points per ring towards the poles and no longitude offset. Other implemented reduced grids are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"OctahedralGaussianGrid, a reduced grid with Gaussian latitudes based on an octahedron\nOctahedralClenshawGrid, similar but based on equi-angle latitudes\nHEALPixGrid, an equal-area grid based on a dodecahedron with 12 faces\nOctaHEALPixGrid, an equal-area grid from the class of HEALPix grids but based on an octahedron.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"An overview of these grids is visualised here","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"(Image: Overview of implemented grids in SpeedyWeather.jl)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Visualised are each grid's grid points (white dots) and grid faces (white lines). All grids shown have 16 latitude rings on one hemisphere, Equator included. The total number of grid points is denoted in the top left of every subplot. The sphere is shaded with grey, orange and turquoise regions to denote the hemispheres in a and b, the 8 octahedral faces c, d,f and the 12 dodecahedral faces (or base pixels) in e. Coastlines are added for orientation.","category":"page"},{"location":"grids/#Resolution","page":"Grids","title":"Resolution","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"All grids use the same resolution parameter nlat_half, i.e. the number of rings on one hemisphere, Equator included. The Gaussian grids (full and reduced) do not have a ring on the equator, so their total number of rings nlat is always even and twice nlat_half. Clenshaw-Curtis grids and the HEALPix grids have a ring on the equator such their total number of rings is always odd and one less than the Gaussian grids at the same nlat_half. ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"info: HEALPix grids do not use Nside as resolution parameter\nThe original formulation for HEALPix grids use N_side, the number of grid points along the edges of each basepixel (8 in the figure above), SpeedyWeather.jl uses nlat_half, the number of rings on one hemisphere, Equator included, for all grids. This is done for consistency across grids. We may use N_side for the documentation or within functions though.","category":"page"},{"location":"grids/#Truncation","page":"Grids","title":"Truncation","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"A given spectral resolution can be matched to a variety of grid resolutions. A cubic grid, for example, combines a spectral truncation T with a grid resolution N (=nlat_half) such that T + 1 = N. Using T31 and an O32 is therefore often abbreviated as Tco31 meaning that the spherical harmonics are truncated at l_max=31 in combination with N=32, i.e. 64 latitude rings in total on an octahedral Gaussian grid.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Let J be the total number of rings. Then we have","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"T approx J for linear truncation\nfrac32T approx J for quadratic truncation\n2T approx J for cubic truncation","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and in general fracm+12T approx J for m-th order truncation. So the higher the truncaction order the more grid points are used in combination with the same spectral resolution. A higher truncation order therefore makes all grid-point calculations more expensive, but can represent products of terms on the grid (which will have higher wavenumber components) to a higher accuracy as more grid points are available within a given wavelength. Using a sufficiently high truncation is therefore one way to avoid aliasing. In SpeedyWeather.jl the parameter dealiasing controls this option, = 1 would be linear, = 2 quadratic, = 3 cubic etc.","category":"page"},{"location":"grids/#Full-Gaussian-grid","page":"Grids","title":"Full Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#Full-Clenshaw-Curtis-grid","page":"Grids","title":"Full Clenshaw-Curtis grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#Octahedral-Gaussian-grid","page":"Grids","title":"Octahedral Gaussian grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"...","category":"page"},{"location":"grids/#The-HEALPix-grid","page":"Grids","title":"The HEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Technically, HEALPix grids are a class of grids that tessalate the sphere into faces that are often called basepixels. For each member of this class there are N_varphi basepixels in zonal direction and N_theta basepixels in meridional direction. For N_varphi = 4 and N_theta = 3 we obtain the classical HEALPix grid with N_varphi N_theta = 12 basepixels shown above in Implemented Grids. Each basepixel has a quadratic number of grid points in them. There's an equatorial zone where the number of zonal grid points is constant (always 2N, so 32 at N=16) and there are polar caps above and below the equatorial zone with the border at cos(theta) = 23 (theta in colatitudes).","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"Following Górski, 2004[1], the z=cos(theta) colatitude of the j-th ring in the north polar cap, j=1N_side with 2N_side = N is ","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^23N_side^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and on that ring, the longitude phi of the i-th point (i is the in-ring-index) is at","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i-tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The in-ring index i goes from i=14 for the first (i.e. northern-most) ring, i=18 for the second ring and i = 14j for the j-th ring in the northern polar cap.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"In the north equatorial belt j=N_side2N_side this changes to","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac43 - frac2j3N_side","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and the longitudes change to (i is always i = 14N_side in the equatorial belt meaning the number of longitude points is constant here)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2N_side(i - fracs2) quad s = (j - N_side + 1) mod 2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The modulo function comes in as there is an alternating longitudinal offset from the prime meridian (see Implemented grids). For the southern hemisphere the grid point locations can be obtained by mirror symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"The cell boundaries are obtained by setting i = k + 12 or i = k + 12 + j (half indices) into the equations above, such that z(phik), a function for the cosine of colatitude z of index k and the longitude phi is obtained. These are then (northern polar cap)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^23N_side^2left(fracpi2phi_tright)^2 quad z = 1 - frack^23N_side^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with phi_t = phi mod tfracpi2 and in the equatorial belt","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = frac23-frac4k3N_side pm frac8phi3pi","category":"page"},{"location":"grids/#OctaHEALPix-grid","page":"Grids","title":"OctaHEALPix grid","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"While the classic HEALPix grid is based on a dodecahedron, other choices for N_varphi and N_theta in the class of HEALPix grids will change the number of faces there are in zonal/meridional direction. With N_varphi = 4 and N_theta = 1 we obtain a HEALPix grid that is based on an octahedron, which has the convenient property that there are twice as many longitude points around the equator than there are latitude rings between the poles. This is a desirable for truncation as this matches the distances too, 2pi around the Equator versus pi between the poles. N_varphi = 6 N_theta = 2 or N_varphi = 8 N_theta = 3 are other possible choices for this, but also more complicated. See Górski, 2004[1] for further examples and visulations of these grids.","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"We call the N_varphi = 4 N_theta = 1 HEALPix grid the OctaHEALPix grid, which combines the equal-area property of the HEALPix grids with the octahedron that's also used in the OctahedralGaussianGrid or the OctahedralClenshawGrid. As N_theta = 1 there is no equatorial belt which simplifies the grid. The latitude of the j-th isolatitude ring on the OctaHEALPixGrid is defined by","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - fracj^2N^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"with j=1N, and similarly for the southern hemisphere by symmetry. On this grid N_side = N where N= nlat_half, the number of latitude rings on one hemisphere, Equator included, because each of the 4 basepixels spans from pole to pole and covers a quarter of the sphere. The longitudes with in-ring- index i = 14j are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"phi = fracpi2j(i - tfrac12)","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"and again, the southern hemisphere grid points are obtained by symmetry.","category":"page"},{"location":"grids/#Grid-cell-boundaries-2","page":"Grids","title":"Grid cell boundaries","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"Similar to the grid cell boundaries for the HEALPix grid, the OctaHEALPix grid's boundaries are","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"z = 1 - frack^2N^2left(fracpi2phi_tright)^2 quad z = 1 - frack^2N^2left(fracpi2phi_t - piright)^2","category":"page"},{"location":"grids/","page":"Grids","title":"Grids","text":"The 3N_side^2 in the denominator of the HEALPix grid came simply N^2 for the OctaHEALPix grid and there's no separate equation for the equatorial belt (which doesn't exist in the OctaHEALPix grid).","category":"page"},{"location":"grids/#References","page":"Grids","title":"References","text":"","category":"section"},{"location":"grids/","page":"Grids","title":"Grids","text":"[1] Górski, Hivon, Banday, Wandelt, Hansen, Reinecke, Bartelmann, 2004. HEALPix: A FRAMEWORK FOR HIGH-RESOLUTION DISCRETIZATION AND FAST ANALYSIS OF DATA DISTRIBUTED ON THE SPHERE, The Astrophysical Journal. doi:10.1086/427976","category":"page"},{"location":"boundary_conditions/#Boundary-conditions","page":"Boundary conditions","title":"Boundary conditions","text":"","category":"section"},{"location":"boundary_conditions/","page":"Boundary conditions","title":"Boundary conditions","text":"This page describes the formulation of boundary conditions and their implementation.","category":"page"},{"location":"conventions/#Style-and-convention-guide","page":"Style and convention guide","title":"Style and convention guide","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"In SpeedyWeather.jl we've been following the several conventions that are documented here.","category":"page"},{"location":"conventions/#Variable-naming","page":"Style and convention guide","title":"Variable naming","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The prognostic variables in spectral space are called","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" vor # Vorticity of horizontal wind field\n div # Divergence of horizontal wind field\n temp # Absolute temperature [K]\n pres_surf # Logarithm of surface pressure [log(Pa)]\n humid # Specific humidity [g/kg]","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"their transforms into grid-point space get a _grid suffix, their tendencies a _tend suffix. Further derived diagnostic dynamic variables are","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" u\n v\n geopot\n ...","category":"page"},{"location":"conventions/#Preallocation","page":"Style and convention guide","title":"Preallocation","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"All arrays representing variables are preallocated and grouped into two structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" Prog::PrognosticVariables\n Diag::DiagnosticVariables","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"The Diag struct contains further structs which represent the grid-point transformations of the prognostic variables and their tendencies.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":" gridvars::GridVariables\n tendencies::Tendencies\n ...","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Constant arrays are grouped into several structs","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Boundaries","category":"page"},{"location":"conventions/#Julian-conventions","page":"Style and convention guide","title":"Julian conventions","text":"","category":"section"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"We follow Julia's style guide and highlight here some important aspects of it.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Bang (!) convention. A function func does not change its input arguments, however, func! does.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Hence, func! is often the in-place version of func, avoiding as much memory allocation as possible and often changing its first argument, e.g. func!(out,in) so that argument in is used to calculate out which has been preallocated before function call.","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"Number format flexibility. Numeric literals such as 2.0 or 1/3 are only used in the model setup","category":"page"},{"location":"conventions/","page":"Style and convention guide","title":"Style and convention guide","text":"but avoided throughout the code to obtain a fully number format-flexible package using the number format NF as a compile-time variable throughout the code. This often leads to overly specific code whereas a Real would generally suffice. However, this is done to avoid any implicit type conversions.","category":"page"},{"location":"new_model_setups/#New-model-setups","page":"New model setups","title":"New model setups","text":"","category":"section"},{"location":"new_model_setups/","page":"New model setups","title":"New model setups","text":"more to come...","category":"page"},{"location":"dynamical_core/#Dynamical-core","page":"Dynamical core","title":"Dynamical core","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"A mathematical and implementation-specific description of the dynamical core used in SpeedyWeather.jl. We start by describing the barotropic vorticity equations which is one set of equations that SpeedyWeather.jl can solve (see How to run SpeedyWeather.jl) as many details therein also apply to the Shallow water equations and Primitive equations explained thereafter.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The dynamical core presented here largely follows the idealized models with spectral dynamics developed at the Geophysical Fluid Dynamics Laboratory[1]: A barotropic vorticity model[2], a shallow water model [3] and a primitive equation model[4]. ","category":"page"},{"location":"dynamical_core/#Barotropic-vorticity-equation","page":"Dynamical core","title":"Barotropic vorticity equation","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The barotropic vorticity equation is the prognostic equation that describes the time evolution of relative vorticity zeta with advection, Coriolis force and diffusion in a single global layer.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"fracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) = (-1)^n+1nunabla^2nzeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with time t, velocity vector mathbfu = (u v), Coriolis parameter f, and hyperdiffusion (-1)^n+1 nu nabla^2n zeta (n is the hyperdiffusion order; see Horizontal diffusion). Starting with some relative vorticity zeta, the Laplacian is inverted to obtain the stream function Psi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Psi = nabla^-2zeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The zonal velocity u and meridional velocity v are then the (negative) meridional gradient and zonal gradient of Psi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nu = -frac1R fracpartial Psipartial theta \nv = frac1Rcos(theta) fracpartial Psipartial phi \nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"which is described in Derivatives in spherical coordinates.","category":"page"},{"location":"dynamical_core/#Algorithm","page":"Dynamical core","title":"Algorithm","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"We briefly outline the algorithm that SpeedyWeather.jl uses in order to integrate the barotropic vorticity equation","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Start with initial conditions of zeta_lm in spectral space\nUse zeta_lm to\nInvert the Laplacian to obtain the stream function Psi_lm in spectral space\nTransform zeta_lm to zeta in grid-point space\nUse Psi_lm to\nobtain zonal velocity (cos(theta)u)_lm through a Meridional derivative\nobtain meridional velocity (cos(theta)v)_lm through a Zonal derivative\nTransform zonal and meridional velocity (cos(theta)u)_lm, (cos(theta)v)_lm to grid-point space and unscale the cos(theta) factor to obtain uv.\nMultiply uv with zeta+f in grid-point space\nTransform u(zeta + f) and v(zeta+f) to spectral space\nCompute the divergence of (mathbfu(zeta + f))_lm in spectral space through a Meridional derivative and Zonal derivative which will be the tendency of zeta_lm\nCompute the Horizontal diffusion based on that tendency\nCompute a leapfrog time step as described in Time integration\nRepeat from 1.","category":"page"},{"location":"dynamical_core/#Shallow-water-equations","page":"Dynamical core","title":"Shallow water equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nfracpartial zetapartial t + nabla cdot (mathbfu(zeta + f)) = (-1)^n+1nunabla^2nzeta \nfracpartial mathcalDpartial t - nabla times (mathbfu(zeta + f)) = -nabla^2(tfrac12(u^2 + v^2) + geta) + (-1)^n+1nunabla^2nmathcalD \nfracpartial etapartial t + nabla cdot (mathbfuh) = 0\nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"more to come","category":"page"},{"location":"dynamical_core/#Primitive-equations","page":"Dynamical core","title":"Primitive equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"The primitive equations solved by SpeedyWeather.jl are","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\npartial_t u = \npartial_t v = \npartial_t T = \npartial_t Q = \nendaligned","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"more to come","category":"page"},{"location":"dynamical_core/#Horizontal-diffusion","page":"Dynamical core","title":"Horizontal diffusion","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In SpeedyWeather.jl we use hyerdiffusion through an n-th power Laplacian (-1)^n+1nabla^2n (hyper when n1) which can be implemented as a multiplication of the spectral coefficients Psi_lm with (-l(l+1))^nR^-2n (see spectral Laplacian) It is therefore computationally not more expensive to apply hyperdiffusion over diffusion as the (-l(l+1))^nR^-2n can be precomputed. Note the sign change (-1)^n+1 here is such that the dissipative nature of the diffusion operator is retained for n odd and even.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In SpeedyWeather.jl the diffusion is applied implicitly. For that, consider a leapfrog scheme with time step Delta t of variable zeta to obtain from time steps i-1 and i, the next time step i+1","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t dzeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with dzeta being some tendency evaluated from zeta_i. Now we want to add a diffusion term (-1)^n+1nu nabla^2nzeta with viscosity nu, wich however, is implicitly calculated from zeta_i+1, then","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t (dzeta + (-1)^n+1 nunabla^2nzeta_i+1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"As the application of (-1)^n+1nunabla^2n is, for every spectral mode, equivalent to a multiplication of a constant, we can rewrite this to","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = fraczeta_i-1 + 2Delta t dzeta1 - 2Delta (-1)^n+1nunabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and expand the numerator to","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"zeta_i+1 = zeta_i-1 + 2Delta t fracdzeta + (-1)^n+1 nunabla^2nzeta_i-11 - 2Delta t (-1)^n+1nu nabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Hence the diffusion can be applied implicitly by updating the tendency dzeta as","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"dzeta to fracdzeta + (-1)^n+1nunabla^2nzeta_i-11 - 2Delta t nu nabla^2n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"which only depends on zeta_i-1. Now let D_textexplicit = (-1)^n+1nunabla^2n be the explicit part and D_textimplicit = 1 - (-1)^n+1 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are D_textimplicit = 1 - 2Delta t nunabla^2n the implicit part. Both parts can be precomputed and are only an element-wise multiplication in spectral space. For every spectral harmonic lm we do","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"dzeta to D_textimplicit^-1(dzeta + D_textexplicitzeta_i-1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Hence 2 multiplications and 1 subtraction with precomputed constants. However, we will normalize the (hyper-)Laplacians as described in the following. This also will take care of the alternating sign such that the diffusion operation is dissipative regardless the power n.","category":"page"},{"location":"dynamical_core/#Normalization-of-diffusion","page":"Dynamical core","title":"Normalization of diffusion","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In physics, the Laplace operator nabla^2 is often used to represent diffusion due to viscosity in a fluid. In that case, the viscosity coefficient is nu of units textm^2texts^-1 and the full operator reads as nu nabla^2 with units (textm^2texts^-1)(textm^-2) = texts^-1. This motivates us to normalize the Laplace operator by a constant of units textm^-2 and the viscosity coefficient by its inverse such that the viscosity coefficient becomes a damping timescale of unit texts^-1. Given the application in spectral space we decide to normalize by the largest eigenvalue -l_textmax(l_textmax+1) such that all entries in the discrete spectral Laplace operator are in 01. This also has the effect that the alternating sign drops out, such that higher wavenumbers are always dampened and not amplified. The normalized viscosity coefficient nu^* = l_textmax(l_textmax+1)nu (always positive) is therefore reinterpreted as the (inverse) time scale at which the highest wavenumber is dampened to zero due to diffusion. Together we have ","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"D^textexplicit_lm = -nu^* fracl(l+1)l_textmax(l_textmax+1)","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and the hyper-Laplacian of power n follows as","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"D^textexplicitn_lm = -nu^* left(fracl(l+1)l_textmax(l_textmax+1)right)^n","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"and the implicit part is accordingly D^textimplicitn_lm = 1 - 2Delta t D^textexplicitn_lm.","category":"page"},{"location":"dynamical_core/#Radius-scaling","page":"Dynamical core","title":"Radius scaling","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"SpeedyWeather.jl uses a scaling for vorticity zeta and stream function Psi that is","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildezeta = zeta R tildePsi = Psi R^-1","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"In the barotropic voriticity equation model the inversion of the Laplcians in order to obtain Psi from zeta therefore becomes","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildezeta = tildenabla^2 tildePsi","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"where the dimensionless gradients simply omit the scaling with 1R, tildenabla = Rnabla. The Barotropic vorticity equation scaled with R^2 is","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"partial_tildettildezeta + tildenabla cdot (mathbfu(tildezeta + tildef)) = tildenutildenabla^2ntildezeta","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"with","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"tildet = tR^-1, the scaled time t\nmathbfu = (uv), the velocity vector (no scaling applied)\ntildef = fR, the scaled Coriolis parameter f\ntildenu = nu^* R, the scaled viscosity nu^*, which itself is normalized to a damping time scale, see Normalization of diffusion.","category":"page"},{"location":"dynamical_core/#Scaled-shallow-water-equations","page":"Dynamical core","title":"Scaled shallow water equations","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"Similar to the scaled barotropic vorticity equations, the scaled shallow water equations scale the vorticity and the divergence equation with R^2, but the continuity equation with R","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"beginaligned\nfracpartial tildezetapartial tildet + tildenabla cdot (mathbfu(tildezeta + tildef)) =\ntildenutildenabla^2ntildezeta \nfracpartial tildemathcalDpartial tildet - tildenabla times (mathbfu(tildezeta + tildef)) =\n-tildenabla^2left(tfrac12(u^2 + v^2) + geta right) + tildenutildenabla^2ntildemathcalD \nfracpartial etapartial tildet + tildenabla cdot (mathbfuh) = 0\nendaligned","category":"page"},{"location":"dynamical_core/#Time-integration","page":"Dynamical core","title":"Time integration","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"SpeedyWeather.jl uses a leapfrog time scheme with a Robert's and William's filter to dampen the computational mode and achieve 3rd order accuracy.","category":"page"},{"location":"dynamical_core/#Oscillation-equation","page":"Dynamical core","title":"Oscillation equation","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"fracdFdt = iomega F","category":"page"},{"location":"dynamical_core/#References","page":"Dynamical core","title":"References","text":"","category":"section"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[1]: Geophysical Fluid Dynamics Laboratory, Idealized models with spectral dynamics","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[2]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[3]: Geophysical Fluid Dynamics Laboratory, The Shallow Water Equations.","category":"page"},{"location":"dynamical_core/","page":"Dynamical core","title":"Dynamical core","text":"[4]: Geophysical Fluid Dynamics Laboratory, The Spectral Dynamical Core","category":"page"},{"location":"spectral_transform/#Spherical-Harmonic-Transform","page":"Spherical harmonic transform","title":"Spherical Harmonic Transform","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space on a longitude-latitude regular Gaussian grid.","category":"page"},{"location":"spectral_transform/#Inspiration","page":"Spherical harmonic transform","title":"Inspiration","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform implemented by SpeedyWeather.jl follows largely Justin Willmert's CMB.jl package and makes use of AssociatedLegendrePolynomials.jl and FFTW.jl (for Float32/64) or GenericFFT.jl (for generic) for the Fourier transform. Justin described his work in a Blog series [1][2][3][4][5][6][7][8].","category":"page"},{"location":"spectral_transform/#Spherical-harmonics","page":"Spherical harmonic transform","title":"Spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spherical harmonics Y_lm of degree l and order m over the longitude phi = (02pi) and colatitudes theta = (-pi2pi2), are","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Y_lm(phi theta) = lambda_l^m(sintheta) e^imphi","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with lambda_l^m being the pre-normalized associated Legendre polynomials, and e^imphi are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Latitudes versus colatitudes\nThe implementations of the spherical transforms in SpeedyWeather.jl use colatitudes theta = (0pi) (0 at the north pole) but the dynamical core uses latitudes theta = (-pi2pi2) (pi2 at the north pole). However, all arrays are always sorted north to south such that [i,1] will access the northern-most grid points. Note: We may also use latitudes in the spherical harmonic transform in the future for consistency. ","category":"page"},{"location":"spectral_transform/#Synthesis-(spectral-to-grid)","page":"Spherical harmonic transform","title":"Synthesis (spectral to grid)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The synthesis (or inverse transform) takes the spectral coefficients a_lm and transforms them to grid-point values f(phitheta) (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics Y_lm with non-zero coefficients.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"f(phitheta) = sum_l=0^infty sum_m=-l^l a_lm Y_lm(phitheta)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We obtain an approximation with a finite set of a_lm by truncating the series after l = l_max.","category":"page"},{"location":"spectral_transform/#Analysis-(grid-to-spectral)","page":"Spherical harmonic transform","title":"Analysis (grid to spectral)","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting in grid-point space we can transform a field f(lambdatheta) into the spectral space of the spherical harmonics by","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"a_lm = int_0^2pi int_-tfracpi2^tfracpi2 f(lambdatheta) Y_lm(lambdatheta) cos theta dtheta dlambda","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This integral has to be discretized to when grid-point values f(lambda_itheta_i) are used. For more details, see [7],[8].","category":"page"},{"location":"spectral_transform/#Spectral-packing","page":"Spherical harmonic transform","title":"Spectral packing","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Spectral packing is the way how the coefficients a_lm of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree l and order m as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Every row represents an order l geq 0, starting from l=0 at the top. Every column represents an order m geq 0, starting from m=0 on the left. The coefficients of these spherical harmonics are directly mapped into a matrix a_lm as ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 \n a_10 a_11 \n a_20 a_12 a_22 \n a_30 a_13 a_23 a_33","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal explicitly stores zeros. Note that internally vector fields include an additional degree, such that l_max = m_max + 1 (see Gradients in spherical coordinates for more information). The harmonics with a_l0 (the first column) are also called zonal harmonics as they are constant with longitude phi. The harmonics with a_ll (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into 2l sectors in longitude phi without a zero-crossing in latitude.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"info: Array indices\nFor a spectral field alms note that due to Julia's 1-based indexing the coefficient a_lm is obtained via alms[l+1,m+1].","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Fortran speedy does not use the same spectral packing as SpeedyWeather.jl. The alternative packing lm therein uses l=m and m=l-m as summarized in the following table.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"degree l order m l=m m=l-m\n0 0 0 0\n1 0 0 1\n1 1 1 0\n2 0 0 2\n2 1 1 1\n2 2 2 0\n3 0 0 3\n... ... ... ...","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":" m \nl a_00 a_10 a_20 a_30\n a_11 a_21 a_31 \n a_22 a_32 \n a_33 ","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran-speedy.","category":"page"},{"location":"spectral_transform/#Example-transforms","page":"Spherical harmonic transform","title":"Example transforms","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"julia> using SpeedyWeather\njulia> alms = zeros(ComplexF64,3,3) # spectral coefficients\njulia> alms[2,2] = 1 # only l=1,m=1 harmonic\njulia> map = gridded(alms) # convert to grid space\n8×4 Matrix{Float64}:\n -0.324541 -0.600363 -0.600363 -0.324541\n -0.134429 -0.248678 -0.248678 -0.134429\n 0.134429 0.248678 0.248678 0.134429\n 0.324541 0.600363 0.600363 0.324541\n 0.324541 0.600363 0.600363 0.324541\n 0.134429 0.248678 0.248678 0.134429\n -0.134429 -0.248678 -0.248678 -0.134429\n -0.324541 -0.600363 -0.600363 -0.324541\n \njulia> spectral(map) # back to spectral space\n3×3 Matrix{ComplexF64}:\n 0.0+0.0im 0.0+0.0im 0.0+0.0im\n 0.0+0.0im 1.0+3.60727e-17im 0.0+0.0im\n 0.0+0.0im 0.0+0.0im 0.0+0.0im","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"and we have successfully reobtained the l=m=1 spherical harmonic.","category":"page"},{"location":"spectral_transform/#Available-horizontal-resolutions","page":"Spherical harmonic transform","title":"Available horizontal resolutions","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with l leq l_max and m leq m_max are explicitly represented. This is usually described as Tm_max, with l_max = m_max (although in vector quantities require one more degree l in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with l_max = m_max = 31. Note that the degree l and order m are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Using triangular truncation[9], there are constraints on the corresponding grid resolution. Let nlon, nlat be the number of longitudes, latitudes on a regular Gaussian grid. Then spectral and grid resolution have to be chosen such that","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nlon geq 3l_max+1\nnlat geq (3l_max+1)2","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In general, we choose nlon = 2nlat, and ideally nlon is easily Fourier-transformable, e.g. nlon = 2^i3^j5^k with some integers ijk geq 0. SpeedyWeather.jl is tested at the following horizontal resolutions, with Delta x = tfrac2pi Rnlon as the approximate grid spacing at the Equator","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"l_max nlon nlat Delta x\n31 (default) 96 48 400 km\n42 128 64 300 km\n85 256 128 160 km\n170 512 256 80 km\n341 1024 512 40 km\n682 2048 1024 20 km","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Choosing trunc as argument in run_speedy will automatically choose nlon,nlat as presented in the table. Other common choices are T63 (192x96), T127 (384x192), T255 (768x384), T511 (1536x768), among others.","category":"page"},{"location":"spectral_transform/#Derivatives-in-spherical-coordinates","page":"Spherical harmonic transform","title":"Derivatives in spherical coordinates","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Horizontal gradients in spherical coordinates are defined for a scalar field A and the latitudes theta and longitudes lambda as","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla A = left(frac1Rcosthetafracpartial Apartial lambda frac1Rfracpartial Apartial theta right)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"However, the divergence of a vector field mathbfu = (uv) includes additional cos(theta) scalings","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla cdot mathbfu = frac1Rcosthetafracpartial upartial lambda +\nfrac1Rcosthetafracpartial (v costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"and similar for the curl","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"nabla times mathbfu = frac1Rcosthetafracpartial vpartial lambda -\nfrac1Rcosthetafracpartial (u costheta)partial theta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The radius of the sphere (i.e. Earth) is R. The zonal gradient scales with 1cos(theta) as the longitudes converge towards the poles (note that theta describes latitudes here, defintions using colatitudes replace the cos with a sin.)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Starting with a spectral field of vorticity zeta and divergence mathcalD one can obtain stream function Psi and velocity potential Phi by inverting the Laplace operator nabla^2:","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi = nabla^-2zeta quad Phi = nabla^-2mathcalD","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The velocities uv are then obtained from (uv) = nabla^botPsi + nablaPhi following the defintion from above and nabla^bot = (-R^-1partial_theta (Rcostheta)^-1partial_lambda)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nu = -frac1Rpartial_thetaPsi + frac1Rcosthetapartial_lambdaPhi \nv = +frac1Rpartial_thetaPhi + frac1Rcosthetapartial_lambdaPsi\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Alternatively, we can use the velocities U = ucostheta V = vcostheta, which we do as the meridional gradient for spherical harmonics is easier implemented with a costheta-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with UV and not uv. From uv we can return to zeta mathcalD via","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nzeta = frac1Rcosthetapartial_lambda v - frac1Rcosthetapartial_theta (u costheta) \nmathcalD = frac1Rcosthetapartial_lambda u + frac1Rcosthetapartial_theta (v costheta)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Equivalently, we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU = -fraccosthetaRpartial_thetaPsi + frac1Rpartial_lambdaPhi \nV = +fraccosthetaRpartial_thetaPhi + frac1Rpartial_lambdaPsi \nzeta = frac1Rpartial_lambda left( fracVcos^2theta right) -\nfraccosthetaRpartial_theta left( fracUcos^2theta right) \nmathcalD = frac1Rpartial_lambda left( fracUcos^2theta right) +\nfraccosthetaRpartial_theta left( fracVcos^2theta right)\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which is a more convenient formulation as required costheta scalings are reduced to a minimum. The remaining (UV)*cos^-2theta are done in grid-point space and usually in combination with other operations like the computation of the vorticity flux. But also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out too. As described in Meridional derivative, it is more convenient to implement costheta partial_theta via a recursion relation for the Legendre polynomials than partial_theta directly. How the operators nabla nabla times nabla cdot can be implemented with spherical harmonics is presented in the following sections.","category":"page"},{"location":"spectral_transform/#Zonal-derivative","page":"Spherical harmonic transform","title":"Zonal derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The zonal derivative of a scalar field Psi in spectral space is the zonal derivative of all its respective spherical harmonics Psi_lm(phitheta) (now we use phi for longitudes to avoid confusion with the Legendre polynomials lambda_lm)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"v_lm = frac1R cos(theta) fracpartialpartial phi left( lambda_l^m(costheta) e^imphi right) =\nfracimR cos(theta) lambda_l^m(costheta) e^imphi = fracimR cos(theta) Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"So for every spectral harmonic, cos(theta)v_lm is obtained from Psi_lm via a multiplication with imR. Unscaling the cos(theta)-factor is done after transforming the spectral coefficients v_lm into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as tildePsi = R^-1Psi such that the division by radius R in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order im. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number m times imaginary i.","category":"page"},{"location":"spectral_transform/#Meridional-derivative","page":"Spherical harmonic transform","title":"Meridional derivative","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridioinal derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costheta fracdP_lmdtheta = -lepsilon_l+1mP_l+1m + (l+1)epsilon_lmP_l-1m","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"with recursion factors","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"epsilon_lm = sqrtfracl^2-m^24l^2-1","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"In the following we use the example of obtaining the zonal velocity u from the stream function Psi, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Psi(lambdatheta) = sum_lmPsi_lmP_lm(sintheta)e^imlambda","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"we multiply with -R^-1costhetapartial_theta to obtain","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"costhetaleft(-frac1Rpartial_thetaPsi right) = -frac1Rsum_lmPsi_lme^imlambdacosthetapartial_theta P_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"at which point the recursion from above can be applied. Collecting terms proportional to P_lm then yields","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"(cos(theta)u)_lm = -frac1R(-(l-1)epsilon_lmPsi_l-1m + (l+2)epsilon_l+1mPsi_l+1m)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"To obtain the coefficient of each spherical harmonic lm of the meridional gradient of a spectral field, two coefficients at l-1m and l+1m have to be combined. This means that the coefficient of a gradient ((costheta) u)_lm is a linear combination of the coefficients of one higher and one lower degree Psi_l+1mPsi_l-1m. As the coefficient Psi_lm with ml are zero, the sectoral harmonics (l=m) of the gradients are obtained from the first off-diagonal only. However, the l=l_max harmonics of the gradients require the l_max-1 as well as the l_max+1 harmonics. In SpeedyWeather.jl vector quantitie like uv use therefore one more meridional mode than scalar quantities such as vorticity zeta or stream function Psi. The meridional derivative in SpeedyWeather.jl also omits the 1R-scaling as explained for the Zonal derivative and in Radius scaling.","category":"page"},{"location":"spectral_transform/#Divergence-and-curl-in-spherical-harmonics","page":"Spherical harmonic transform","title":"Divergence and curl in spherical harmonics","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The meridional gradient as described above can be applied to scalars, such as Psi and Phi in the conversion to velocities (uv) = nabla^botPsi + nablaPhi, however, the operators curl nabla times and divergence nabla cdot in spherical coordinates involve a costheta scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral transform of vorticity zeta is","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac12piint_-tfracpi2^tfracpi2int_0^2pi zeta(lambdatheta) P_lm(sintheta) e^imlambda dlambda costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Given that Rzeta = cos^-1partial_lambda v - cos^-1partial_theta (u costheta), we therefore have to evaluate a meridional integral of the form","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"int P_lm frac1cos theta partial_theta(u costheta)) cos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"which can be solved through integration by parts. As ucostheta = 0 at theta = pm tfracpi2 only the integral","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int partial_theta P_lm (u costheta) dtheta = -int costheta partial_theta P_lm (fracucostheta) costheta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"remains. Inserting the recurrence relation from the Meridional derivative turns this into","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"= -int left(-l epsilon_l+1mP_l+1m + (l+1)epsilon_lm P_l-1m right) (fracucostheta) cos theta dtheta","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"Now we expand (tfracucostheta) but only the lm harmonic will project ontoP_lm. Let u^* = ucos^-1theta v^* = vcos^-1theta we then have in total","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nRzeta_lm = imv^*_lm + (l+1)epsilon_lmu^*_l-1m - lepsilon_l+1mu^*_l+1m \nRD_lm = imu^*_lm - (l+1)epsilon_lmv^*_l-1m + lepsilon_l+1mv^*_l+1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"And the divergence D is similar, but (uv) to (-vu). We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#Laplacian","page":"Spherical harmonic transform","title":"Laplacian","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"The spectral Laplacian is easily applied to the coefficients Psi_lm of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator nabla^2 in spherical coordinates with eigenvalues -l(l+1) divided by the radius squared R^2, i.e. nabla^2 Psi becomes tfrac-l(l+1)R^2Psi_lm in spectral space. For example, vorticity zeta and streamfunction Psi are related by zeta = nabla^2Psi in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree l and order m to","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"zeta_lm = frac-l(l+1)R^2Psi_lm","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"This can be easily inverted to obtain the stream function Psi from vorticity zeta instead. In order to avoid division by zero, we set Psi_00 here, given that the stream function is only defined up to a constant anyway.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nPsi_lm = fracR^2-l(l+1)zeta_lm quad foralllm 0\nPsi_00 = 0\nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"See also Horizontal diffusion and Normalization of diffusion.","category":"page"},{"location":"spectral_transform/#U,V-from-vorticity-and-divergence","page":"Spherical harmonic transform","title":"U,V from vorticity and divergence","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity zeta and divergence D (which are prognostic variables) to U=ucostheta V=vcostheta. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree l (the meridional gradient). It is therefore computationally more efficient to compute UV directly from zetaD instead of calculating stream function and velocity potential first. In total we have","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"beginaligned\nU_lm = -fraciml(l+1)(RD)_lm + fracepsilon_l+1ml+1(Rzeta)_l+1m - fracepsilon_lml(Rzeta)_l-1m \nV_lm = -fraciml(l+1)(Rzeta)_lm - fracepsilon_l+1ml+1(RD)_l+1m + fracepsilon_lml(RD)_l-1m \nendaligned","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"We have moved the scaling with the radius R directly into zetaD as further described in Radius scaling.","category":"page"},{"location":"spectral_transform/#References","page":"Spherical harmonic transform","title":"References","text":"","category":"section"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[1]: Justin Willmert, 2020. Introduction to Associated Legendre Polynomials (Legendre.jl Series, Part I)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[2]: Justin Willmert, 2020. Calculating Legendre Polynomials (Legendre.jl Series, Part II)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[3]: Justin Willmert, 2020. Pre-normalizing Legendre Polynomials (Legendre.jl Series, Part III)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[4]: Justin Willmert, 2020. Maintaining numerical accuracy in the Legendre recurrences (Legendre.jl Series, Part IV)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[5]: Justin Willmert, 2020. Introducing Legendre.jl (Legendre.jl Series, Part V)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[6]: Justin Willmert, 2020. Numerical Accuracy of the Spherical Harmonic Recurrence Coefficient (Legendre.jl Series Addendum)","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[7]: Justin Willmert, 2020. Notes on Calculating the Spherical Harmonics","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[8]: Justin Willmert, 2022. More Notes on Calculating the Spherical Harmonics: Analysis of maps to harmonic coefficients","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[9]: David Randall, 2021. An Introduction to Numerical Modeling of the Atmosphere, Chapter 22.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[10]: Dale Durran, 2010. Numerical Methods for Fluid Dynamics, Springer. In particular section 6.2, 6.4.","category":"page"},{"location":"spectral_transform/","page":"Spherical harmonic transform","title":"Spherical harmonic transform","text":"[11]: Geophysical Fluid Dynamics Laboratory, The barotropic vorticity equation.","category":"page"},{"location":"#SpeedyWeather.jl-documentation","page":"Home","title":"SpeedyWeather.jl documentation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Welcome to the documentation for SpeedyWeather.jl a global atmospheric circulation model with simple parametrizations to represent physical processes such as clouds, precipitation and radiation.","category":"page"},{"location":"#Overview","page":"Home","title":"Overview","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl is a global spectral model that uses a spherical harmonic transform to perform some calculations in spectral space (time integration, gradients, linear terms) and some in grid-point space (advection, non-linear terms, parameterizations). The prognostic variables used are vorticity, divergence, absolute temperature, logarithm of surface pressure and specific humidity. The time stepping uses a leapfrog scheme with additional filters and a semi-implicit formulation for gravity waves. The default resolution is T31 (96x48 grid points on a regular Gaussian grid, about 400km at the Equator) and 8 vertical levels.","category":"page"},{"location":"","page":"Home","title":"Home","text":"Simple parameterizations are used to represent the physical processes convection, large-scale condensation, clouds, short-wave radiation, long-waves radiation, surface fluxes of momentum and energy, and vertical diffusion.","category":"page"},{"location":"#Manual-outline","page":"Home","title":"Manual outline","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"See the following pages of the documentation for more details","category":"page"},{"location":"","page":"Home","title":"Home","text":"How to run SpeedyWeather.jl\nSpherical harmonic transform\nGrids\nDynamical core\nParametrizations\nNew model setups\nFunction and type index","category":"page"},{"location":"","page":"Home","title":"Home","text":"and the original documentation by Molteni and Kucharski.","category":"page"},{"location":"#Scope","page":"Home","title":"Scope","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The focus of SpeedyWeather.jl is to develop a global atmospheric model of intermediate complexity, that can run at various levels of precision (16, 32 and 64-bit) on different architectures (x86 and ARM, GPUs in the future). Additionally, the model is written in an entirely number format-flexible way, such that any custom number format can be used and Julia will compile to the format automatically. Similarly, many model components are written in an abstract way to support modularity and extandability.","category":"page"},{"location":"#History","page":"Home","title":"History","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl is a Julia implementation of SPEEDY, which is written in Fortran 77. Sam Hatfield translated SPEEDY to Fortran 90 and started the project to port it to Julia. However, we are making an effort to overhaul the implementation of the mathematical model behind speedy completely and it is unlikely that a single line of code survived.","category":"page"},{"location":"#Installation","page":"Home","title":"Installation","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"SpeedyWeather.jl is registered in the Julia Registry. Open Julia's package manager from the REPL with ] and add the github repository to install SpeedyWeather.jl and all dependencies","category":"page"},{"location":"","page":"Home","title":"Home","text":"(@v1.8) pkg> add SpeedyWeather","category":"page"},{"location":"","page":"Home","title":"Home","text":"which will automatically install the latest release. However, you may want to install directly from the main branch with","category":"page"},{"location":"","page":"Home","title":"Home","text":"(@v1.8) pkg> add https://github.com/SpeedyWeather/SpeedyWeather.jl#main","category":"page"},{"location":"","page":"Home","title":"Home","text":"other branches than #main can be installed by adding #branch_name instead.","category":"page"},{"location":"#Developers","page":"Home","title":"Developers","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"The development of SpeedyWeather.jl is lead by Milan Klöwer and current and past contributors include","category":"page"},{"location":"","page":"Home","title":"Home","text":"Tom Kimpson\nAlistair White\nMaximilian Gelbrecht\nDavid Meyer\nDaisuke Hotta","category":"page"},{"location":"","page":"Home","title":"Home","text":"Any contributions are always welcome!","category":"page"},{"location":"#Funding","page":"Home","title":"Funding","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"Contributors received funding by the European Research Council under Horizon 2020 within the ITHACA project, grant agreement number 741112 from 2021-2022.","category":"page"},{"location":"time_integration/#Time-integration","page":"Time integration","title":"Time integration","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"SpeedyWeather.jl uses a leapfrog time scheme with a Robert's and William's filter to dampen the computational mode and achieve 3rd order accuracy.","category":"page"},{"location":"time_integration/#Oscillation-equation","page":"Time integration","title":"Oscillation equation","text":"","category":"section"},{"location":"time_integration/","page":"Time integration","title":"Time integration","text":"fracdFdt = iomega F","category":"page"},{"location":"time_integration/#Implementation-details","page":"Time integration","title":"Implementation details","text":"","category":"section"}]
+}
diff --git a/v0.5.0/siteinfo.js b/v0.5.0/siteinfo.js
new file mode 100644
index 000000000..06e1faf7a
--- /dev/null
+++ b/v0.5.0/siteinfo.js
@@ -0,0 +1 @@
+var DOCUMENTER_CURRENT_VERSION = "v0.5.0";
diff --git a/v0.5.0/spectral_transform/index.html b/v0.5.0/spectral_transform/index.html
new file mode 100644
index 000000000..c3acc853f
--- /dev/null
+++ b/v0.5.0/spectral_transform/index.html
@@ -0,0 +1,45 @@
+
+Spherical harmonic transform · SpeedyWeather.jl
The following sections outline the implementation of the spherical harmonic transform (in short spectral transform) between the coefficients of the spherical harmonics (the spectral space) and the grid space on a longitude-latitude regular Gaussian grid.
with $\lambda_l^m$ being the pre-normalized associated Legendre polynomials, and $e^{im\phi}$ are the complex exponentials (the Fourier modes). Together they form a set of orthogonal basis functions on the sphere. For an interactive visualisation of the spherical harmonics, see here.
Latitudes versus colatitudes
The implementations of the spherical transforms in SpeedyWeather.jl use colatitudes $\theta = (0,\pi)$ (0 at the north pole) but the dynamical core uses latitudes $\theta = (-\pi/2,\pi/2)$ ($\pi/2$ at the north pole). However, all arrays are always sorted north to south such that [i,1] will access the northern-most grid points. Note: We may also use latitudes in the spherical harmonic transform in the future for consistency.
The synthesis (or inverse transform) takes the spectral coefficients $a_{lm}$ and transforms them to grid-point values $f(\phi,\theta)$ (for the sake of simplicity first regarded as continuous). The synthesis is a linear combination of the spherical harmonics $Y_{lm}$ with non-zero coefficients.
Spectral packing is the way how the coefficients $a_{lm}$ of the spherical harmonics of a given spectral field are stored in an array. SpeedyWeather.jl uses the conventional spectral packing of degree $l$ and order $m$ as illustrated in the following image (Cyp, CC BY-SA 3.0, via Wikimedia Commons)
Every row represents an order $l \geq 0$, starting from $l=0$ at the top. Every column represents an order $m \geq 0$, starting from $m=0$ on the left. The coefficients of these spherical harmonics are directly mapped into a matrix $a_{lm}$ as
$m$
$l$
$a_{00}$
$a_{10}$
$a_{11}$
$a_{20}$
$a_{12}$
$a_{22}$
$a_{30}$
$a_{13}$
$a_{23}$
$a_{33}$
which is consistently extended for higher degrees and orders. Consequently, all spectral fields are lower-triangular matrices with complex entries. The upper triangle excluding the diagonal explicitly stores zeros. Note that internally vector fields include an additional degree, such that $l_{max} = m_{max} + 1$ (see Gradients in spherical coordinates for more information). The harmonics with $a_{l0}$ (the first column) are also called zonal harmonics as they are constant with longitude $\phi$. The harmonics with $a_{ll}$ (the main diagonal) are also called sectoral harmonics as they essentially split the sphere into $2l$ sectors in longitude $\phi$ without a zero-crossing in latitude.
Array indices
For a spectral field alms note that due to Julia's 1-based indexing the coefficient $a_{lm}$ is obtained via alms[l+1,m+1].
Fortran speedy does not use the same spectral packing as SpeedyWeather.jl. The alternative packing $l',m'$ therein uses $l'=m$ and $m'=l-m$ as summarized in the following table.
degree $l$
order $m$
$l'=m$
$m'=l-m$
0
0
0
0
1
0
0
1
1
1
1
0
2
0
0
2
2
1
1
1
2
2
2
0
3
0
0
3
...
...
...
...
This alternative packing uses the top-left triangle of a coefficient matrix, and the degrees and orders from above are stored at the following indices
$m'$
$l'$
$a_{00}$
$a_{10}$
$a_{20}$
$a_{30}$
$a_{11}$
$a_{21}$
$a_{31}$
$a_{22}$
$a_{32}$
$a_{33}$
This spectral packing is not used in SpeedyWeather.jl but illustrated here for completeness and comparison with Fortran-speedy.
SpeedyWeather.jl uses triangular truncation such that only spherical harmonics with $l \leq l_{max}$ and $|m| \leq m_{max}$ are explicitly represented. This is usually described as $Tm_{max}$, with $l_{max} = m_{max}$ (although in vector quantities require one more degree $l$ in the recursion relation of meridional gradients). For example, T31 is the spectral resolution with $l_{max} = m_{max} = 31$. Note that the degree $l$ and order $m$ are mathematically 0-based, such that the corresponding coefficient matrix is of size 32x32.
Using triangular truncation[9], there are constraints on the corresponding grid resolution. Let nlon, nlat be the number of longitudes, latitudes on a regular Gaussian grid. Then spectral and grid resolution have to be chosen such that
$nlon \geq 3l_{max}+1$
$nlat \geq (3l_{max}+1)/2$
In general, we choose $nlon = 2nlat$, and ideally $nlon$ is easily Fourier-transformable, e.g. $nlon = 2^i3^j5^k$ with some integers $i,j,k \geq 0$. SpeedyWeather.jl is tested at the following horizontal resolutions, with $\Delta x = \tfrac{2\pi R}{nlon}$ as the approximate grid spacing at the Equator
$l_{max}$
nlon
nlat
$\Delta x$
31 (default)
96
48
400 km
42
128
64
300 km
85
256
128
160 km
170
512
256
80 km
341
1024
512
40 km
682
2048
1024
20 km
Choosing trunc as argument in run_speedy will automatically choose nlon,nlat as presented in the table. Other common choices are T63 (192x96), T127 (384x192), T255 (768x384), T511 (1536x768), among others.
The radius of the sphere (i.e. Earth) is $R$. The zonal gradient scales with $1/\cos(\theta)$ as the longitudes converge towards the poles (note that $\theta$ describes latitudes here, defintions using colatitudes replace the $\cos$ with a $\sin$.)
Starting with a spectral field of vorticity $\zeta$ and divergence $\mathcal{D}$ one can obtain stream function $\Psi$ and velocity potential $\Phi$ by inverting the Laplace operator $\nabla^2$:
The velocities $u,v$ are then obtained from $(u,v) = \nabla^\bot\Psi + \nabla\Phi$ following the defintion from above and $\nabla^\bot = (-R^{-1}\partial_\theta, (R\cos\theta)^{-1}\partial_\lambda)$
Alternatively, we can use the velocities $U = u\cos\theta, V = v\cos\theta$, which we do as the meridional gradient for spherical harmonics is easier implemented with a $\cos\theta$-scaling included, and because the divergence and curl in spherical coordinates evaluates the meridional gradient with $U,V$ and not $u,v$. From $u,v$ we can return to $\zeta, \mathcal{D}$ via
\[\begin{aligned}
+\zeta &= \frac{1}{R\cos\theta}\partial_\lambda v - \frac{1}{R\cos\theta}\partial_\theta (u \cos\theta) \\
+\mathcal{D} &= \frac{1}{R\cos\theta}\partial_\lambda u + \frac{1}{R\cos\theta}\partial_\theta (v \cos\theta).
+\end{aligned}\]
which is a more convenient formulation as required $\cos\theta$ scalings are reduced to a minimum. The remaining $(U,V)*\cos^{-2}\theta$ are done in grid-point space and usually in combination with other operations like the computation of the vorticity flux. But also note that SpeedyWeather.jl scales the equations with the radius R (see Radius scaling) such that the divisions by R drop out too. As described in Meridional derivative, it is more convenient to implement $\cos\theta \partial_\theta$ via a recursion relation for the Legendre polynomials than $\partial_\theta$ directly. How the operators $\nabla, \nabla \times, \nabla \cdot$ can be implemented with spherical harmonics is presented in the following sections.
The zonal derivative of a scalar field $\Psi$ in spectral space is the zonal derivative of all its respective spherical harmonics $\Psi_{lm}(\phi,\theta)$ (now we use $\phi$ for longitudes to avoid confusion with the Legendre polynomials $\lambda_{lm}$)
So for every spectral harmonic, $\cos(\theta)v_{lm}$ is obtained from $\Psi_{lm}$ via a multiplication with $im/R$. Unscaling the $\cos(\theta)$-factor is done after transforming the spectral coefficients $v_{lm}$ into grid-point space. As discussed in Radius scaling, SpeedyWeather.jl scales the stream function as $\tilde{\Psi} = R^{-1}\Psi$ such that the division by radius $R$ in the gradients can be omitted. The zonal derivative becomes therefore effectively for each spherical harmonic a scaling with its (imaginary) order $im$. The spherical harmonics are essentially just a Fourier transform in zonal direction and the derivative a multiplication with the respective wave number $m$ times imaginary $i$.
The meridioinal derivative of the spherical harmonics is a derivative of the Legendre polynomials for which the following recursion relation applies[10],[11]
In the following we use the example of obtaining the zonal velocity $u$ from the stream function $\Psi$, which is through the negative meridional gradient. For the meridional derivative itself the leading minus sign has to be omitted. Starting with the spectral expansion
To obtain the coefficient of each spherical harmonic $l,m$ of the meridional gradient of a spectral field, two coefficients at $l-1,m$ and $l+1,m$ have to be combined. This means that the coefficient of a gradient $((\cos\theta) u)_{lm}$ is a linear combination of the coefficients of one higher and one lower degree $\Psi_{l+1,m},\Psi_{l-1,m}$. As the coefficient $\Psi_{lm}$ with $m<l$ are zero, the sectoral harmonics ($l=m$) of the gradients are obtained from the first off-diagonal only. However, the $l=l_{max}$ harmonics of the gradients require the $l_{max}-1$ as well as the $l_{max}+1$ harmonics. In SpeedyWeather.jl vector quantitie like $u,v$ use therefore one more meridional mode than scalar quantities such as vorticity $\zeta$ or stream function $\Psi$. The meridional derivative in SpeedyWeather.jl also omits the $1/R$-scaling as explained for the Zonal derivative and in Radius scaling.
The meridional gradient as described above can be applied to scalars, such as $\Psi$ and $\Phi$ in the conversion to velocities $(u,v) = \nabla^\bot\Psi + \nabla\Phi$, however, the operators curl $\nabla \times$ and divergence $\nabla \cdot$ in spherical coordinates involve a $\cos\theta$ scaling before the meridional gradient is applied. How to translate this to spectral coefficients has to be derived separately[10],[11].
Given that $R\zeta = \cos^{-1}\partial_\lambda v - \cos^{-1}\partial_\theta (u \cos\theta)$, we therefore have to evaluate a meridional integral of the form
Now we expand $(\tfrac{u}{\cos\theta})$ but only the $l,m$ harmonic will project onto$P_{l,m}$. Let $u^* = u\cos^{-1}\theta, v^* = v\cos^{-1}\theta$ we then have in total
And the divergence $D$ is similar, but $(u,v) \to (-v,u)$. We have moved the scaling with the radius $R$ directly into $\zeta,D$ as further described in Radius scaling.
The spectral Laplacian is easily applied to the coefficients $\Psi_{lm}$ of a spectral field as the spherical harmonics are eigenfunctions of the Laplace operator $\nabla^2$ in spherical coordinates with eigenvalues $-l(l+1)$ divided by the radius squared $R^2$, i.e. $\nabla^2 \Psi$ becomes $\tfrac{-l(l+1)}{R^2}\Psi_{lm}$ in spectral space. For example, vorticity $\zeta$ and streamfunction $\Psi$ are related by $\zeta = \nabla^2\Psi$ in the barotropic vorticity model. Hence, in spectral space this is equivalent for every spectral mode of degree $l$ and order $m$ to
\[\zeta_{l,m} = \frac{-l(l+1)}{R^2}\Psi_{l,m}\]
This can be easily inverted to obtain the stream function $\Psi$ from vorticity $\zeta$ instead. In order to avoid division by zero, we set $\Psi_{0,0}$ here, given that the stream function is only defined up to a constant anyway.
After having discussed the zonal and meridional derivatives with spherical harmonics as well as the Laplace operator, we can derive the conversion from vorticity $\zeta$ and divergence $D$ (which are prognostic variables) to $U=u\cos\theta, V=v\cos\theta$. Both are linear operations that act either solely on a given harmonic (the zonal gradient and the Laplace operator) or are linear combinations between one lower and one higher degree $l$ (the meridional gradient). It is therefore computationally more efficient to compute $U,V$ directly from $\zeta,D$ instead of calculating stream function and velocity potential first. In total we have