Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prognostic and DiagnosticVariables array-agnostic #525

Draft
wants to merge 54 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
322afb3
PrognosticVariables with LowerTriangularArray
milankl Apr 25, 2024
6ad2472
WIP: initial indexing changes
maximilian-gelbrecht May 16, 2024
d48246b
WIP: indexing not working yet
maximilian-gelbrecht May 17, 2024
624654a
WIP indexing (probably) working
maximilian-gelbrecht May 17, 2024
94a8337
WIP: zeros, ones, rand, ...
maximilian-gelbrecht May 17, 2024
37045fc
WIP: copyto!
maximilian-gelbrecht May 17, 2024
3249876
copyto! working
maximilian-gelbrecht May 18, 2024
2997c3e
LTA N-1 dim change tests passing
maximilian-gelbrecht May 18, 2024
4873de7
adjusting for size(alms)
maximilian-gelbrecht May 21, 2024
eb8725b
fast cartesian indexing + broadcasting
maximilian-gelbrecht May 22, 2024
a7183f9
clean up explicit arithmetics functions
maximilian-gelbrecht May 22, 2024
bc196fe
code cleanup broadcasting
maximilian-gelbrecht May 22, 2024
df15546
remove add! tests
maximilian-gelbrecht May 22, 2024
676c630
show LowerTriangularMatrix
milankl May 22, 2024
6fea1c5
fix LTA plot
maximilian-gelbrecht May 23, 2024
b884c56
PrognosticVariables new structure
milankl May 23, 2024
90100cb
DiagnosticVariables new structure
milankl May 23, 2024
8ab67c1
zeros for Vector{Particle}
milankl May 23, 2024
891987e
no set vars tests due to new structure
milankl May 23, 2024
ccdc992
SpectralGrid with Spectral/GridVariable2D/3D types
milankl May 23, 2024
0ff4f5f
progn/diagn prettier printing
milankl May 23, 2024
40358d3
fill!(::Tendencies, x) instead of zero_tendencies!
milankl May 24, 2024
8730dc8
prognostic variables as NTuple{2, LowerTriangularArray}
milankl May 24, 2024
ee42b31
BarotropicModel without layer structure
milankl May 24, 2024
e1cbf24
N-dimensional transform! spec->grid
milankl May 24, 2024
bfeb2db
Merge branch 'main' into mk/newvariables
milankl May 24, 2024
ac6af8b
n-dim transform! also grid -> spectral
milankl May 24, 2024
26ca59a
CartesianIndex{0} and broadcast tests
maximilian-gelbrecht May 30, 2024
e4dd023
LTA tests fixed
maximilian-gelbrecht May 30, 2024
ad6f527
SpectralTransform adjusted to ndim-1 LTA, still WIP
maximilian-gelbrecht May 30, 2024
b33fedb
Merge branch 'main' into mg/lta-ndim-1
maximilian-gelbrecht May 30, 2024
d146f36
n-dim leapfrogging
milankl May 30, 2024
575f72a
Merge branch 'mg/lta-ndim-1' into mk/newvariables
milankl May 30, 2024
8be0d59
n-dimensional UV_from_vor!
milankl May 30, 2024
d7075d2
BarotropicModel with n-dim formulation
milankl May 30, 2024
24b6339
define prognostic_variables(::ModelSetup)
milankl Jun 4, 2024
59c2c81
shallow water model with new array structure
milankl Jun 4, 2024
c181fc1
First working shallow water model with new variable structure
milankl Jun 4, 2024
b8737f7
Spectral gradients with better size checks
milankl Jun 5, 2024
88c7e2b
horizontal diffusion with nlayers
milankl Jun 10, 2024
d9e070a
implicit, land, ocean adapted to new structure
milankl Jun 10, 2024
9505328
First half of the primitive equation dynamical core restructured
milankl Jun 12, 2024
8829129
surface pressure tendency, vertical velocity and linear pres gradient…
milankl Jun 13, 2024
4d3de41
vertical advection to new structure
milankl Jun 21, 2024
bb2d710
vor, div, temp, humid tendencies, implicit and diffusion
milankl Jun 23, 2024
c88a4f2
physics_tendencies_only! renaming
milankl Jun 26, 2024
2b34e92
Merge branch 'main' into mk/newvariables
milankl Jul 5, 2024
e333f67
Further size calls for LTA corrected
milankl Jul 5, 2024
c9181d9
replace spectral/gridded functions with transform
milankl Jul 6, 2024
9627a5e
transform and gradient tests passing
milankl Jul 7, 2024
eaa96e8
Merge branch 'main' into mk/newvariables
milankl Jul 7, 2024
f9e3d06
more tests adapted
milankl Jul 8, 2024
45e378e
Merge branch 'mk/newvariables' of https://github.com/SpeedyWeather/Sp…
milankl Jul 8, 2024
1f9139e
extending tests adapted
milankl Jul 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions docs/src/analysis.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ function total_energy(u, v, η, model)
E = @. h/2*(u^2 + v^2) + g*h^2 # vertically-integrated mechanical energy

# transform to spectral, take l=m=0 mode at [1] and normalize for mean
return E_mean = real(spectral(E)[1]) / model.spectral_transform.norm_sphere
return E_mean = real(transform(E)[1]) / model.spectral_transform.norm_sphere
end
```

Expand Down Expand Up @@ -262,7 +262,7 @@ function total_angular_momentum(u, η, model)
Λ = @. (u*r + Ω*r^2) * h # vertically-integrated AAM

# transform to spectral, take l=m=0 mode at [1] and normalize for mean
return Λ_mean = real(spectral(Λ)[1]) / model.spectral_transform.norm_sphere
return Λ_mean = real(transform(Λ)[1]) / model.spectral_transform.norm_sphere
end
```

Expand Down Expand Up @@ -299,7 +299,7 @@ function total_circulation(ζ, model)
f = coriolis(ζ) # create f on the grid of ζ
C = ζ .+ f # absolute vorticity
# transform to spectral, take l=m=0 mode at [1] and normalize for mean
return C_mean = real(spectral(C)[1]) / model.spectral_transform.norm_sphere
return C_mean = real(transform(C)[1]) / model.spectral_transform.norm_sphere
end

total_circulation(ζ, model)
Expand Down Expand Up @@ -336,7 +336,7 @@ function total_enstrophy(ζ, η, model)
Q = @. q^2 / 2 # Potential enstrophy

# transform to spectral, take l=m=0 mode at [1] and normalize for mean
return Q_mean = real(spectral(Q)[1]) / model.spectral_transform.norm_sphere
return Q_mean = real(transform(Q)[1]) / model.spectral_transform.norm_sphere
end
```

Expand Down Expand Up @@ -373,14 +373,14 @@ to show how to global integral ``\iint dV`` can be written more efficiently
# define a global integral, reusing a precomputed SpectralTransform S
# times surface area of sphere omitted
function ∬dA(v, h, S::SpectralTransform)
return real(spectral(v .* h, S)[1]) / S.norm_sphere
return real(transform(v .* h, S)[1]) / S.norm_sphere
end

# use SpectralTransform from model
∬dA(v, h, model::ModelSetup) = ∬dA(v, h, model.spectral_transform)
```
By reusing `model.spectral_transform` we do not have to re-precompute
the spectral tranform on every call to `spectral`. Providing
the spectral tranform on every call to `transform`. Providing
the spectral transform from model as the 2nd argument simply reuses
a previously precomputed spectral transform which is much faster
and uses less memory.
Expand Down
12 changes: 6 additions & 6 deletions docs/src/gradients.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ using SpeedyWeather, CairoMakie
trunc = 64 # 1-based maximum degree of spherical harmonics
L = randn(LowerTriangularMatrix{ComplexF32}, trunc, trunc)
spectral_truncation!(L, 5) # remove higher wave numbers
G = gridded(L)
G = transform(L)
heatmap(G, title="Some fake data G") # requires `using CairoMakie`
save("gradient_data.png", ans) # hide
nothing # hide
Expand Down Expand Up @@ -136,8 +136,8 @@ RingGrids.scale_coslat⁻¹!(u)
RingGrids.scale_coslat⁻¹!(v)

S = SpectralTransform(u, one_more_degree=true)
us = spectral(u, S)
vs = spectral(v, S)
us = transform(u, S)
vs = transform(v, S)

vor = curl(us, vs, radius = spectral_grid.radius)
```
Expand Down Expand Up @@ -172,7 +172,7 @@ that we also used in `spectral_grid`. The Coriolis parameter for a grid like `vo
is obtained, and we do the following for ``f\zeta/g``.

```@example gradient
vor_grid = gridded(vor, Grid=spectral_grid.Grid)
vor_grid = transform(vor, Grid=spectral_grid.Grid)
f = coriolis(vor_grid) # create Coriolis parameter f on same grid with default rotation
g = model.planet.gravity
fζ_g = @. vor_grid * f / g # in-place and element-wise
Expand All @@ -181,11 +181,11 @@ nothing # hide
Now we need to apply the inverse Laplace operator to ``f\zeta/g`` which we do as follows

```@example gradient
fζ_g_spectral = spectral(fζ_g, one_more_degree=true)
fζ_g_spectral = transform(fζ_g, one_more_degree=true)

R = spectral_grid.radius
η = SpeedyTransforms.∇⁻²(fζ_g_spectral) * R^2
η_grid = gridded(η, Grid=spectral_grid.Grid)
η_grid = transform(η, Grid=spectral_grid.Grid)
nothing # hide
```
Note the manual scaling with the radius ``R^2`` here. We now compare the results
Expand Down
14 changes: 7 additions & 7 deletions docs/src/particles.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,9 @@ would not just convert from `Float64` to `Float32` but also
from an active to an inactive particle. In SpeedyWeather all particles can be
activated or deactivated at any time.

First, you create a [`SpectralGrid`](@ref) with the `n_particles` keyword
First, you create a [`SpectralGrid`](@ref) with the `nparticles` keyword
```@example particle
spectral_grid = SpectralGrid(n_particles = 3)
spectral_grid = SpectralGrid(nparticles = 3)
```
Then the particles live as `Vector{Particle}` inside the prognostic variables
```@example particle
Expand Down Expand Up @@ -246,7 +246,7 @@ the particle locations via netCDF. We can create it like

```@example particle_tracker
using SpeedyWeather
spectral_grid = SpectralGrid(n_particles = 100)
spectral_grid = SpectralGrid(nparticles = 100)
particle_tracker = ParticleTracker(spectral_grid, schedule=Schedule(every=Hour(3)))
```

Expand Down Expand Up @@ -285,18 +285,18 @@ ds["lat"]
```
where the last two lines are lazy loading a matrix with each row a particle and each column a time step.
You may do `ds["lon"][:,:]` to obtain the full `Matrix`. We had specified
`spectral_grid.n_particles` above and we will have time steps in this file
`spectral_grid.nparticles` above and we will have time steps in this file
depending on the `period` the simulation ran for and the `particle_tracker.Δt` output
frequency. We can visualise the particles' trajectories with

```@example particle_tracker
lon = ds["lon"][:,:]
lat = ds["lat"][:,:]
n_particles = size(lon,1)
nparticles = size(lon,1)

using CairoMakie
fig = lines(lon[1, :], lat[1, :]) # first particle only
[lines!(fig.axis, lon[i,:], lat[i,:]) for i in 2:n_particles] # add lines for other particles
[lines!(fig.axis, lon[i,:], lat[i,:]) for i in 2:nparticles] # add lines for other particles

# display updated figure
fig
Expand All @@ -319,7 +319,7 @@ using GeoMakie, CairoMakie

fig = Figure()
ga = GeoAxis(fig[1, 1]; dest = "+proj=ortho +lon_0=45 +lat_0=45")
[lines!(ga, lon[i,:], lat[i,:]) for i in 1:n_particles]
[lines!(ga, lon[i,:], lat[i,:]) for i in 1:nparticles]
fig
save("particles_geomakie.png", fig) # hide
nothing # hide
Expand Down
37 changes: 20 additions & 17 deletions docs/src/speedytransforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,23 @@ alms = zeros(LowerTriangularMatrix{ComplexF64}, 6, 6) # spectral coefficient
alms[2, 2] = 1 # only l=1, m=1 harmonic
alms
```
Now `gridded` is the function that takes spectral coefficients `alms` and converts
them a grid-point space `map`
Now `transform` is the function that takes spectral coefficients `alms` and converts
them a grid-point space `map` (or vice versa)

```@example speedytransforms
map = gridded(alms)
map = transform(alms)
```
By default, the `gridded` transforms onto a [`FullGaussianGrid`](@ref FullGaussianGrid) unravelled here
By default, the `transforms` transforms onto a [`FullGaussianGrid`](@ref FullGaussianGrid) unravelled here
into a vector west to east, starting at the prime meridian, then north to south, see [RingGrids](@ref).
We can visualize `map` quickly with a UnicodePlot via `plot` (see [Visualising RingGrid data](@ref))
```@example speedytransforms
import SpeedyWeather.RingGrids: plot # not necessary when `using SpeedyWeather`
plot(map)
```
Yay! This is the what the ``l=m=1`` spherical harmonic is supposed to look like!
Now let's go back to spectral space with `spectral`
Now let's go back to spectral space with `transform`
```@example speedytransforms
alms2 = spectral(map)
alms2 = transform(map)
```
Comparing with `alms` from above you can see that the transform is exact up to a typical rounding error
from `Float64`.
Expand All @@ -70,15 +70,15 @@ a transform error that is larger than the rounding error from floating-point ari
While the default grid for [SpeedyTransforms](@ref) is the [`FullGaussianGrid`](@ref FullGaussianGrid)
we can transform onto other grids by specifying `Grid` too
```@example speedytransforms
map = gridded(alms, Grid=HEALPixGrid)
map = transform(alms, Grid=HEALPixGrid)
plot(map)
```
which, if transformed back, however, can yield a larger transform error as discussed above
```@example speedytransforms
spectral(map)
transform(map)
```
On such a coarse grid the transform error (absolute and relative) is about ``10^{-2}``, this decreases
for higher resolution. The `gridded` and `spectral` functions will choose a corresponding
for higher resolution. The `transform` function will choose a corresponding
grid-spectral resolution (see [Matching spectral and grid resolution](@ref)) following quadratic
truncation, but you can always truncate/interpolate in spectral space with `spectral_truncation`,
`spectral_interpolation` which takes `trunc` = ``l_{max} = m_{max}`` as second argument
Expand All @@ -90,13 +90,16 @@ order 5 before.
If the second argument in `spectral_truncation` is larger than `alms` then it will automatically
call `spectral_interpolation` and vice versa. Also see [Interpolation on RingGrids](@ref)
to interpolate directly between grids. If you want to control directly the resolution of the
grid `gridded` is supposed to transform onto you have to provide a `SpectralTransform` instance.
grid you want to `transform` onto, use the keyword `dealiasing` (default: 2 for quadratic,
see [Matching spectral and grid resolution](@ref)).
But you can also provide a `SpectralTransform` instance to reuse a precomputed spectral transform.
More on that now.

## [The `SpectralTransform` struct](@id SpectralTransform)

Both `spectral` and `gridded` create an instance of `SpectralTransform` under the hood. This object
contains all precomputed information that is required for the transform, either way:
The function `transform` only with arguments as shown above,
will create an instance of `SpectralTransform` under the hood.
This object contains all precomputed information that is required for the transform, either way:
The Legendre polynomials, pre-planned Fourier transforms, precomputed gradient, divergence and
curl operators, the spherical harmonic eigenvalues among others. Maybe the most intuitive way to
create a `SpectralTransform` is to start with a `SpectralGrid`, which already defines
Expand All @@ -121,15 +124,15 @@ recomputation or pre-computation of the Legendre polynomials, fore more informat
Passing on `S` the `SpectralTransform` now allows us to transform directly on the grid
defined therein.
```@example speedytransforms
map = gridded(alms, S)
map = transform(alms, S)
plot(map)
```
Yay, this is again the ``l=m=1`` harmonic, but this time on a slightly higher resolution
`OctahedralGaussianGrid` as specified in the `SpectralTransform` `S`.
Note that also the number format was converted on the fly to Float32 because that is the number
format we specified in `S`! And from grid to spectral
```@example speedytransforms
alms2 = spectral(map, S)
alms2 = transform(map, S)
```
As you can see the rounding error is now more like ``10^{-8}`` as we are using Float32
(the [OctahedralGaussianGrid](@ref OctahedralGaussianGrid) is another _exact_ grid).
Expand Down Expand Up @@ -166,7 +169,7 @@ Say you have some global data in a matrix `m` that looks, for example, like
```@example speedytransforms
alms = randn(LowerTriangularMatrix{Complex{Float32}}, 32, 32) # hide
spectral_truncation!(alms, 10) # hide
map = gridded(alms, Grid=FullClenshawGrid) # hide
map = transform(alms, Grid=FullClenshawGrid) # hide
m = Matrix(map) # hide
m
```
Expand All @@ -189,7 +192,7 @@ nothing # hide

Now we transform into spectral space and call `power_spectrum(::LowerTriangularMatrix)`
```@example speedytransforms
alms = spectral(map)
alms = transform(map)
power = SpeedyTransforms.power_spectrum(alms)
nothing # hide
```
Expand Down Expand Up @@ -232,7 +235,7 @@ is correctly applied across dimensions of `A` and then convert to a
Awesome. For higher degrees and orders the amplitude clearly decreases!
Now to grid-point space and let us visualize the result
```@example speedytransforms
map = gridded(alms)
map = transform(alms)

using CairoMakie
heatmap(map, title="k⁻²-distributed noise")
Expand Down
1 change: 0 additions & 1 deletion src/LowerTriangularMatrices/LowerTriangularMatrices.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import LinearAlgebra: tril!

# VISUALISATION
import UnicodePlots
# export plot

export LowerTriangularMatrix, LowerTriangularArray
export eachharmonic, eachmatrix
Expand Down
21 changes: 16 additions & 5 deletions src/LowerTriangularMatrices/lower_triangular_matrix.jl
Original file line number Diff line number Diff line change
Expand Up @@ -475,16 +475,27 @@ function _copyto_core!(
L1
end

function Base.copyto!( L::LowerTriangularArray{T}, # copy to L
M::AbstractArray) where T # copy from M
@boundscheck size(L, as=Matrix) == size(M) || throw(BoundsError)
L.data .= convert.(T, M[lowertriangle_indices(M)])

function Base.copyto!(
L::LowerTriangularArray{T}, # copy to L
M::AbstractArray, # copy from M
) where T

# if matrix sizes agree copy over the non-zero elements
if size(L, as=Matrix) == size(M)
L.data .= convert.(T, M[lowertriangle_indices(M)])

# if vector sizes agree copy straight into underlying data array
elseif size(L) == size(M)
L.data .= convert.(T, M)
else # not matching sizes
throw(DimensionMismatch)
end
L
end

function Base.copyto!( M::AbstractArray{T}, # copy to M
L::LowerTriangularArray) where T # copy from L

@boundscheck size(L, as=Matrix) == size(M) || throw(BoundsError)

lower_triangle_indices = lowertriangle_indices(M)
Expand Down
Loading