Postprocessing

A non-exhaustive collection of spectral to spatial discretization and postprocessing routines.

Spatial discretization

In the Limace.Discretization submodule, some methods to discretize BasisElement and eigenvectors are provided.

A fully manual discretization routine is given by

Limace.Discretization.discretizeFunction
discretize(b, r, θ, ϕ, V::Volume=Sphere())

Discretize a basis element (or a vector of BasisElement's), b at the given coordinates (r, θ, ϕ) in the volume V.

Example usage

b = BasisElement(Basis{Inviscid,Sphere}, Poloidal, (2,0,1))
Limace.Discretization.discretize(b, 0.9, π/4, 3π/2)
source
discretize(αs::Vector{T}, u::TU, rs, θ, ϕ) where {T<:Number,TU<:Basis}

Discretize an eigenvector αs, for a basis u at the given radii rs, latitudes θ, and longitudes ϕ.

source

To evaluate one BasisElement at a given point $(r,\theta,\phi)$, we can do

using Limace.Discretization: discretize
u = Inviscid(10)
b = BasisElement(u, Poloidal, (2,0,1))
r,θ,ϕ = 0.9, π/4, 3π/2
discretize(b, r,θ,ϕ)
3-element StaticArraysCore.SVector{3, ComplexF64} with indices SOneTo(3):
  0.05686000202511553 + 0.0im
 -0.21626856102305547 + 5.634722292314655e-33im
                  0.0 + 0.0im

The same also works for a vector of BasisElements.

bs = [BasisElement(u, Poloidal, (2,0,1)), BasisElement(u, Poloidal, (2,0,2))]
discretize(bs, r,θ,ϕ)
3-element StaticArraysCore.SVector{3, ComplexF64} with indices SOneTo(3):
  0.0648005036787584 + 0.0im
 -0.9251770367151191 + 2.410482433718393e-32im
                 0.0 + 0.0im

To discretize an eigenvector using Limace.Discretization.discretize, consider the following example of the Malkus modes

N = 6
Le = 1e-1

u = Inviscid(N)
b = PerfectlyConducting(N)

bases = [u,b]

B0 = BasisElement(b, Toroidal, (1,0,0), 2sqrt(2pi/15))
forcings = [Limace.Inertial(u), Limace.Inertial(b), Limace.Coriolis(u, 1/Le), Limace.Lorentz(u, b, B0), Limace.InductionB0(b,u,B0)]

problem = LimaceProblem(bases, forcings)
Limace.assemble!(problem)
Limace.solve!(problem)

We can chose a single eigenvector x and define some arbitrary spatial grid for r, θ and ϕ:

x = problem.sol.vectors[:,1]
nr,nθ,nϕ = 20,30,40
r = range(0.01,0.99, length=nr)
θ = range(0.01,π-0.01,length=nθ)
ϕ = range(0,2π, length=nϕ)
ur,uθ,uϕ, br, bθ, bϕ = discretize(x, u, b, r, π/2 .- θ, ϕ)
size(ur)
(20, 30, 40)

For more performant transforms to spatial space, it is recommended to use Limace.Discretization.spectospat, which relies on fast vector spherical harmonic transforms provided through SHTns.jl.

Limace.Discretization.spectospatFunction
spectospat(coeffs::Vector{T}, u::TU, nr::Int, nθ::Int, nϕ::Int; kwargs...) where {TU<:Basis, T<:ComplexF64}

Transform eigenvector containing spectral coefficients to three-dimensional vector field at nr x nθ x nϕ grid points. Returns ur,uθ,uϕ, r,θ,ϕ.

source
spectospat(coeffs::Vector{T}, u::TU, r::Float64, nθ::Int, nϕ::Int; kwargs...) where {TU<:Basis, T<:ComplexF64}

Transform eigenvector containing spectral coefficients to two-dimensional vector field at 1 x nθ x nϕ grid points at radius r. Returns ur,uθ,uϕ, θ,ϕ.

source
spectospat(coeffs::Vector{T}, u::TU, b::TB, nr::Int, nθ::Int, nϕ::Int; kwargs...) where {TU<:Basis, TB<:Basis, T<:ComplexF64}

Transform eigenvector containing spectral coefficients to three-dimensional vector fields at nr x nθ x nϕ grid points. Returns ur,uθ,uϕ, br,bθ,bϕ, r,θ,ϕ.

source
spectospat(coeffs::Vector{T}, u::TU, b::TB, r::Tr, nθ::Int, nϕ::Int; kwargs...) where {TU<:Basis, TB<:Basis, T<:ComplexF64, Tr<:Union{AbstractVector{Float64},Float64}}

Transform eigenvector containing spectral coefficients to two-dimensional vector fields at 1 x nθ x nϕ grid points at radius (or radii) r. Returns ur,uθ,uϕ, br,bθ,bϕ, θ,ϕ.

source

To discretize the eigenvector x on a Gauss-Legendre grid in r and θ, as well as equidistant grid in ϕ, we can call spectospat:

using Limace.Discretization: spectospat
ur,uθ,uϕ, br,bθ,bϕ, r,θ,ϕ = spectospat(x, u, b, nr, nθ, nϕ)

Filtering of spectrum

When the background state couples different spectral degrees (may be in radius, spherical harmonic degree or order), the solutions are only truncated approximations of the true solution. This is not the case, for example, for the Inviscid inertial modes or the Malkus modes, which are fully resolved at one truncation degree and are therefore perfectly converged. In all other cases, the computed eigen spectrum will contain some unconverged solutions that need to be filtered out before further analysis.

To check convergence of the solutions, Limace.jl provides the following filters

Limace.Processing.eigenvalue_filterFunction
eigenvalue_filter(evals1, evals2; λtol)

Find all indices of evals1 and evals2 for which findall(y->any(x->isapprox(x,y; rtol=λtol), evals2),evals1). Multithreaded.

source
Limace.Processing.eigenvector_filterFunction
eigenvector_filter(evecs, u, b; thresh)

Find all indices of eigenvectors evecs for which the ratio of peak energy to energy at truncation degree (max between two last Cartesian degrees) in toroidal/poloidal kinetic/magnetic energy is below thresh.

source
Limace.Processing.numerical_filterFunction
numerical_filter(
    evecs1,
    evecs2,
    evals1,
    evals2,
    u1,
    u2,
    b1,
    b2;
    threshc,
    λtol,
    threads,
    epeakratiothresh,
    kwargs...
)

Find numerically converged eigensolutions between two resolutions.

source

After numerical convergence is validated, one can further filter the spectrum using for example a filter that determines the observability of a mode. In the geomagnetic context, observability of a mode may be determined by the peak spherical harmonic degree of the poloidal magnetic field of a mode, which should lie below the maximum resolvable degree of the considered geomagnetic model (e.g. $l\leq 17$ for the secular variation in the CHAOS-7 model).

Limace.Processing.observability_filterFunction
observability_filter(
    evals,
    evecs,
    u,
    b;
    ωlow,
    ωhigh,
    Qlow,
    lthresh
)

Find observationally relevant solutions, based on the poloidal magnetic field at the surface. The function takes the arguments evals, evecs, u, and b which are the eigenvalues, eigenvectors, velocity basis, and magnetic field basis, respectively. Keyword arguments ωlow, ωhigh, Qlow, and lthresh are used to filter the eigenvalues based on their frequency, quality factor, and the maximum allowed peak degree in energy.

source

Energy and spectra

To compute the (kinetic and magnetic) energies of the modes, one can use

Limace.Processing.energiesFunction
energies(problem::LimaceProblem)

Compute the energies for all bases problem.bases of all eigenvectors problem.sol.vectors if problem.solved==true.

source

See the example usage of energies in the Malkus mode example, which shows how to plot a frequency vs. energy-ratio spectrum.

Limace.jl also provides routines to compute the spectrum of an eigenvector as a function of spherical harmonic degree, spherical harmonic order, radial degree, or Cartesian degree.

Limace.Processing.spectrumFunction
spectrum(evecs, u, b; lmn)

Calculate spectrum for all eigenvectors evecs comprised of velocity basis u and magnetic field basis b. Use keyword lmn=1,2,3 to select l=1, m=2 or n=3.

source
spectrum(problem; lmn)

Calculate spectrum for all eigenvectors problem.sol.vectors, comprised of velocity basis u (problem.bases[1]!) and magnetic field basis b (problem.bases[2]!). Use keyword lmn=1,2,3 to compute spectra in $l$ (lmn=1), $m$ (lmn=2) or in $n$ (lmn=3).

source
Limace.Processing.spectrum_cartesianFunction
spectrum_cartesian(evecs, u, b)

Compute poloidal/toroidal kinetic/magnetic spectra of evecs as a function of max. Cartesian monomial degree. evecs are the eigenvectors computed using the velocity basis u and magnetic field basis b.

source

For example, we can compute the spectra of the modes and plot them for one solution like this:

ls, p_up, p_ut, p_bp, p_bt = Limace.Processing.spectrum(problem, lmn=1)

using CairoMakie

let
	f = Figure()
	ax = Axis(f[1,1], xlabel=L"l", ylabel=L"p(l)", yscale=log10)
	for (spec,label) in zip((p_up, p_ut, p_bp, p_bt),(L"u_p", L"u_t", L"b_p", L"b_t"))
		scatterlines!(ax, ls, spec[:,1].+eps(); label)
	end
	axislegend(ax)
	f
end
Example block output

and to do the same as a function of Cartesian polynomial degree

p_up, p_ut, p_bp, p_bt = Limace.Processing.spectrum_cartesian(problem.sol.vectors, problem.bases...)
ns = 1:problem.bases[1].N

let
	f = Figure()
	ax = Axis(f[1,1], xlabel=L"l", ylabel=L"p(l)", yscale=log10)
	for (spec,label) in zip((p_up, p_ut, p_bp, p_bt),(L"u_p", L"u_t", L"b_p", L"b_t"))
		scatterlines!(ax, ns, spec[:,1].+eps(); label)
	end
	axislegend(ax)
	f
end
Example block output

Misc functions

Some convenient functions that are used for other postprocessing routines.

Limace.Processing.epeak_etrunc_cartesianFunction
epeak_etrunc_cartesian(evecs, u, b)

Compute the ratio of peak energy to energy at truncation degree (max between two last Cartesian degrees) in toroidal/poloidal kinetic/magnetic energy. evecs are the eigenvectors computed using the velocity basis u and magnetic field basis b.

source
Limace.Processing.lmn_nFunction
lmn_n(u, b)

Get all (l,m,n) that correspond to the poloidal and toroidal component of u and b basis at each Cartesian degree ñ ∈ 1:N.

source