Spherical sampling

FlowGeometries.SphericalSampling.AbstractRingSampling — Type
AbstractRingSampling <: AbstractSphericalSampling

Iso-latitude rings whose longitude count varies by ring, so the layout is not a tensor product. A reduced Gaussian grid is the canonical case: latitudes are the Gaussian ones, but each ring carries only as many longitudes as its circumference warrants.

source
FlowGeometries.SphericalSampling.ClenshawCurtisSampling — Type
ClenshawCurtisSampling <: AbstractClenshawCurtisSampling

Open equiangular Clenshaw–Curtis grid: $θ_i = π(i−1/2)/N_θ$ (no poles), $N_λ = 2N_θ − 1$, $l_{\max} = N_θ − 1$.

This is not the same as Driscoll–Healy: different $N_θ(l_{\max})$, open vs polar-cap nodes, and a different quadrature.

source
FlowGeometries.SphericalSampling.FibonacciSampling — Type
FibonacciSampling(n)

n points on the spherical Fibonacci (golden-spiral) lattice: z_k = (2k+1)/n - 1 with λ_k = 2πk/φ mod 2π, φ the golden ratio.

z advances in equal steps, so the points are spread one per equal-area band, and the golden angle in longitude — the most irrational rotation there is — keeps them from lining up into visible spokes at any n. Quasi-uniform with no polar clustering and no panel seams.

source
FlowGeometries.SphericalSampling.GaussLegendreSampling — Type
GaussLegendreSampling <: AbstractGaussLegendreSampling

Gauss–Legendre (Gauss–Neumann) latitudes: $μ = \cosθ$ at the $N_θ$ roots of $P_{N_θ}$, with $N_λ = 2N_θ − 1$ equispaced longitudes. Exact for band-limit $l_{\max} = N_θ − 1$.

source
FlowGeometries.SphericalSampling.HEALPixSampling — Type
HEALPixSampling <: AbstractHEALPixSampling

Hierarchical Equal Area isoLatitude Pixelisation (Górski et al.). Equal-area pixels on $4 N_{\mathrm{side}} − 1$ iso-latitude rings; not a tensor-product $N_λ × N_φ$ array ($n_λ$ varies by ring).

source
FlowGeometries.SphericalSampling.McEwenWiauxSampling — Type
McEwenWiauxSampling <: AbstractMcEwenWiauxSampling

McEwen–Wiaux (2011) equiangular sampling: $θ_t = π(2t+1)/(2L−1)$ for $t = 0,…,L−1$, $λ_p = 2π p/(2L−1)$ for $p = 0,…,2L−2$, with $L = l_{\max}+1$. Requires asymptotically $∼ 2 L^2$ samples (about half of classical DH).

source
FlowGeometries.SphericalSampling.Nested — Type
Nested()

Pixels numbered so that each is subdivided into four contiguous children — a quadtree per base face. The ordering that makes a neighbourhood contiguous. Requires nside to be a power of two.

source
FlowGeometries.SphericalSampling.OctahedralGaussianSampling — Type
OctahedralGaussianSampling(N)

Octahedral reduced Gaussian grid with N latitude rings between pole and equator (2N rings in all).

Ring i counted from either pole carries 4i + 16 longitudes — 20 on the ring nearest the pole and four more on each successive ring — which totals 4N(N+9) points. Latitudes are the 2N Gaussian latitudes, so the latitude quadrature is the Gauss–Legendre one.

source
FlowGeometries.SphericalSampling.OpenNodes — Type
OpenNodes(), ClosedNodes()

The two equiangular colatitude families: open θᵢ = π(i−½)/N (Clenshaw–Curtis) and closed θᵢ = π(i−1)/N (Driscoll–Healy). They are types, so the sum below dispatches on which one it has.

source
FlowGeometries.SphericalSampling.Ring — Type
Ring()

Pixels numbered along iso-latitude rings, north to south and east within a ring. The ordering that makes a ring contiguous, so a longitude transform per ring is possible.

source
FlowGeometries.SphericalSampling.Transform — Type
Transform()

One length-nlat backward transform: O(nlat·log nlat). Implemented by the AbstractFFTs extension, and raises without one — a planner has to exist for this to mean anything.

source
FlowGeometries.SphericalSampling._cubed_cell_edges — Method
_cubed_cell_edges(::Type{T}, n, i, j) -> (X1, X2, Y1, Y2)

Tangents of cell (i, j)'s panel-local boundary angles, -π/4 + (i-1)·Δ and -π/4 + i·Δ. These are the gnomonic-plane coordinates its area is the solid angle of.

source
FlowGeometries.SphericalSampling._equiangular_weights! — Method
_equiangular_weights!(w, family, nlat[, algorithm]) -> w

Latitude quadrature weights for an equiangular node set, from the sine-series expansion of the sinθ Jacobian:

wᵢ = (4/N)·sinθᵢ·Σ_{k=0}^{⌊N/2⌋} sin((2k+1)θᵢ)/(2k+1)

family is OpenNodes or ClosedNodes, so one construction serves both. Weights sum to ∫₀^π sinθ dθ = 2 — see latitude_weights for why every sampling here uses that normalization.

algorithm selects which construction computes the sums; omitted, the element type's default does.

source
FlowGeometries.SphericalSampling._gauss_legendre_newton! — Method
_gauss_legendre_newton!(T, TW, μ, w, n, m)

Nodes and weights by Newton on Pₙ, evaluated by the Bonnet recurrence from a Tricomi start.

O(n) per root, so O(n²) overall — but it is exact arithmetic converging to eps(TW), which the asymptotic expansion's fixed Float64 coefficient set cannot do. That makes it the right method for wide element types at any n, and for small n, where the expansion is inaccurate and the quadratic cost is microseconds.

source
FlowGeometries.SphericalSampling._gauss_legendre_μ! — Method
_gauss_legendre_μ!(μ, w) -> NamedTuple{(:μ,:w)}

Write n = length(μ)-point Gauss–Legendre nodes/weights on $μ ∈ (-1, 1)$ into the provided buffers, ascending in μ (length(μ) == length(w)).

Newton's method on $Pₙ$, evaluated by the Bonnet recurrence, from a Tricomi starting estimate accurate to $O(n⁻³)$ — so 2–4 iterations reach machine precision. Roots come in $±$ pairs, so only the upper half is solved.

Needs $O(1)$ scratch. Time is $O(n²)$: each of the $n/2$ roots costs an $O(n)$ recurrence. The Bogaert-style asymptotic expansions below are the $O(n)$ path.

source
FlowGeometries.SphericalSampling._healpix_nested_cloud! — Method
_healpix_nested_cloud!(λ, φ, nside) -> (; λ, φ)

The whole HEALPix cloud in NESTED pixel order.

The ring quantities are tabulated once, 4·nside − 1 of them, so the acos and the latitude conversion run per ring here too. A nested pixel's ring and position along it come from its face coordinates (_hp_xyf2ringj), and the coordinates are then the ring walk's own expressions, so the two orderings hold the same numbers to the bit. Both writes are sequential.

source
FlowGeometries.SphericalSampling._hp_ang2xyf — Method
_hp_ang2xyf(nside, θ, ϕ) -> (ix, iy, face)

Face-local coordinates of the pixel containing colatitude θ, longitude ϕ, by the HEALPix projection (Górski et al. 2005). Division and remainder, so it holds for any nside, including one that is not a power of two.

source
FlowGeometries.SphericalSampling._ico_decode — Method
_ico_decode(id, ν) -> (kind, a, b)

Which entity owns vertex id: kind = 1 a corner (a the corner, b unused), 2 a macro-edge interior (a the edge, b its position from the edge's low corner), 3 a face interior (a the face, b its position in the face's own lattice walk).

source
FlowGeometries.SphericalSampling._ico_face_ij — Method
_ico_face_ij(k, ν) -> (i, j)

The face-interior lattice node whose position in the walk for i in 1:(ν-1), j in 1:(ν-1-i) is k.

Row i of the walk holds ν-1-i nodes, so the nodes before it number S(i-1) = (i-1)(ν-1) - (i-1)i/2. Then m = i-1 is the largest value with S(m) < k, and j = k - S(m). S(m) < k rearranges to m² - m(2ν-3) + 2k > 0, so the smaller root of that quadratic brackets m; its floor is taken and then stepped in either direction, so the result does not depend on the square root's last bit.

source
FlowGeometries.SphericalSampling._ico_fold_incident_triangles — Method
_ico_fold_incident_triangles(f, acc, i, j, face, ν) -> acc

Thread acc = f(acc, other1, other2) over the triangles of one face that contain lattice node (i, j), other1 and other2 being the other two vertices' ids.

A triangle belongs to exactly one face, so folding over each of a vertex's occurrences visits each incident triangle once. The two lattice orientations give up to three triangles each.

source
FlowGeometries.SphericalSampling._ico_node_id — Method
_ico_node_id(f, face, i, j, ν, edge_index, nint, face_base) -> Int

Global vertex id of barycentric lattice node (i, j) (with i + j ≤ ν, weights ν-i-j, i, j on the face's corners A, B, C) of face f. A node on a corner or a macro-edge resolves to that shared entity's id, so the two faces meeting at an edge agree with no lookup table.

source
FlowGeometries.SphericalSampling._ico_occurrences — Method
_ico_occurrences(id, ν) -> (NTuple{5,NTuple{3,Int}}, n)

Every (face, i, j) lattice position vertex id occupies, in the first n entries. A face interior has one, a macro-edge interior two, a corner five; the twelve corners are the geodesic sphere's twelve pentagons.

source
FlowGeometries.SphericalSampling._icosahedron_base — Method
_icosahedron_base(T) -> NTuple{12,NTuple{3,T}}

The 12 unit vertices of the base icosahedron, in the order _ICOSAHEDRON_FACES indexes them. A tuple, so it costs no allocation. Every raw vertex is a permutation of (0, ±1, ±φ) and so shares the norm √(1+φ²), formed once.

source
FlowGeometries.SphericalSampling._yin_yang_rotate — Method
_yin_yang_rotate(λ, φ) -> (λ_global, φ_global)

The Kageyama–Sato rotation carrying a panel-frame (λ, φ) onto yang's position on the sphere. Yang is yin rigidly rotated, so this is the whole difference between the two panels.

source
FlowGeometries.SphericalSampling.admits_exact_bandlimited_quadrature — Method
admits_exact_bandlimited_quadrature(sampling) -> Bool

Whether this sampling's latitude_weights integrate the products that spectral analysis actually forms — two degree-lmax functions, hence degree 2·lmax — exactly at the sampling's own bandlimit.

That is a stronger statement than "the quadrature integrates a single P_l up to lmax", and the distinction decides the answer here. Measured exactness of the weights in this package:

samplingexact for a single P_l up tobandlimitneeds 2·lmaxexact?
Gauss–Legendre2N−1N−12N−2yes
Driscoll–HealyN−1N/2−1N−2yes
Clenshaw–CurtisN−1N−12N−2no

Clenshaw–Curtis's band limit describes what its grid can represent; its quadrature supports quadrature-based analysis only to lmax ≈ (N−1)/2. Use GaussLegendreSampling when analysis must be exact at the stated band limit.

source
FlowGeometries.SphericalSampling.ang2pix — Method
ang2pix(nside, θ, ϕ; scheme = Ring()) -> Int

The 0-based index of the pixel containing colatitude θ ∈ [0, π] and longitude ϕ.

θ is a colatitude, matching the HEALPix convention throughout this section; use colatitude to convert a geographic latitude.

source
FlowGeometries.SphericalSampling.cubed_sphere_points! — Method
cubed_sphere_points!(λ, φ, panel, n; backend=nothing) -> NamedTuple{(:λ,:φ,:panel)}

Gnomonic cubed-sphere cell centres into caller-owned buffers of length 6n², plus each point's panel index. Pass panel = nothing when the panel id is not wanted, and it is not computed.

See cubed_sphere_points for the allocating form.

source
FlowGeometries.SphericalSampling.icosahedral_mesh — Function
icosahedral_mesh([T = Float64], frequency) -> (; λ, φ, edges, triangles, verts)

Geodesic vertices at frequency ν as both lon/lat (λ, φ) and unit vectors (verts), plus the 10ν²+2 mesh's undirected edges (i,j) with i < j and its 20ν² triangles (i,j,k) (1-based). Vertex numbering is topological — corners, then macro-edge interiors, then face interiors — so it is deterministic and every vertex is generated exactly once.

source
FlowGeometries.SphericalSampling.icosahedral_vertices! — Method
icosahedral_vertices!(λ, φ, frequency = 1) -> NamedTuple{(:λ,:φ)}

Write the 10ν²+2 geodesic vertices' longitude/latitude into the caller's buffers, allocating nothing. The numbering is icosahedral_mesh's topological one — corners, then macro-edge interiors, then face interiors — so each vertex is emitted at its own index with no intermediate array and no lookup.

source
FlowGeometries.SphericalSampling.latitude_weights! — Method
latitude_weights!(w, ::AbstractClenshawCurtisSampling, nlat)

Weights for the open nodes θᵢ = π(i−½)/N, from the same sine-series rule as the closed equiangular families.

These integrate a single P_l exactly for l ≤ N−1, which is weaker than bandlimit(ClenshawCurtisSampling(), N) = N−1 suggests: spectral analysis integrates products of two degree-lmax functions, so a quadrature exact to l ≤ N−1 supports quadrature-based analysis only up to lmax ≈ (N−1)/2. The reported band limit describes what the grid represents; this quadrature integrates to half of it. Use GaussLegendreSampling (exact to 2N−1) where analysis must be exact at the stated band limit.

source
FlowGeometries.SphericalSampling.latitude_weights — Method
latitude_weights([T = Float64], sampling::AbstractReducedGaussianSampling) -> Vector{T}

Gauss–Legendre weights for the grid's rings, north to south, normalized as everywhere else in this module so that Σw = 2. A full-sphere integral is then Σⱼ wⱼ (2π/nlonⱼ) Σᵢ f, the longitude factor varying by ring because the ring populations do.

source
FlowGeometries.SphericalSampling.latitude_weights — Method
latitude_weights([T = Float64], s, nlat) -> Vector{T}
latitude_weights!(w, s, nlat) -> w

Latitude quadrature weights wⱼ for sampling s, normalized so that

Σⱼ wⱼ = ∫₀^π sinθ dθ = 2

for every sampling that provides them. The weights therefore carry the sinθ Jacobian and nothing else; the longitude factor is the caller's, so a full-sphere integral is always

∫ f dΩ ≈ (2π/nlon) · Σⱼ wⱼ Σᵢ f(λᵢ, φⱼ)

regardless of which sampling produced the weights. Not every sampling has them — LatLonSampling has no spectral quadrature at all, and McEwenWiauxSampling's is a different construction.

The equiangular families — Driscoll–Healy and Clenshaw–Curtis — additionally take algorithm::AbstractEquiangularAlgorithm, which pins the construction of their sine sums. The two constructions agree only to round-off, so naming one fixes the result across machines that differ in whether an FFT backend is installed. Gauss–Legendre's weights come from a root solve, so it takes no algorithm.

source
FlowGeometries.SphericalSampling.nlon_in_ring — Function
nlon_in_ring(sampling, ring) -> Int
nlon_in_ring(sampling, nlat, ring) -> Int

Longitudes on a single iso-latitude ring, counted from the north pole, in O(1) and allocating nothing.

The per-ring form of nlon_per_ring, and the one a ring-by-ring loop wants: the table costs an allocation and an O(nrings) build per call, which a loop over rings pays again on every iteration if it asks for it there.

source
FlowGeometries.SphericalSampling.nlon_per_ring — Method
nlon_per_ring(sampling) -> Vector{Int}
nlon_per_ring(sampling, nlat; nlon=nothing) -> Vector{Int}

Longitudes on each iso-latitude ring, north to south.

Defined for every sampling laid out in rings, so a caller walking a map ring by ring — a per-ring longitude transform, a zonal reduction, a row-wise sweep — writes one loop for all of them. A sampling that carries its own size (HEALPixSampling, the reduced Gaussians) answers from itself; a tensor-product one takes the nlat that fixes its shape, as npoints does.

source
FlowGeometries.SphericalSampling.ring2nest — Method
ring2nest(nside, pix) -> Int
nest2ring(nside, pix) -> Int

Convert a 0-based pixel index between the two orderings. Both need nside to be a power of two, which is the condition for the nested quadtree to exist.

source
FlowGeometries.SphericalSampling.ring_info — Method
ring_info([T = Float64], nside, ring) -> NamedTuple

What HEALPix ring ring ∈ 1:(4·nside-1) contains, counted from the north pole: startpix (the 0-based RING index of its first pixel, matching ang2pix), ringpix (how many pixels it holds), colatitude, latitude, and shifted — whether its pixel centres are offset half a pixel in ϕ.

Ring width grows 4, 8, … through the polar cap, is 4·nside across the equatorial belt, and shrinks again symmetrically, so this is how to walk a HEALPix map ring by ring without decoding every pixel.

source
FlowGeometries.SphericalSampling.ring_range — Function
ring_range(sampling, ring) -> UnitRange{Int}
ring_range(sampling, nlat, ring) -> UnitRange{Int}

The 1-based slice of the flattened point vector holding one iso-latitude ring, north to south — the indices spherical_points writes that ring into.

O(1) for every sampling, so a ring-by-ring pass is a loop over slices carrying no running offset. The octahedral rule's offset is a closed form in the ring index; the tabulated reduced grid carries cumulative counts, built once with the sampling.

source
FlowGeometries.SphericalSampling.spherical_axes! — Function
spherical_axes!(λ, φ, sampling, nlat; nlon=…) -> NamedTuple{(:λ,:φ)}

Fill preallocated longitude / geographic-latitude axes. Requires length(λ) == sz.nlon and length(φ) == sz.nlat for sz = axes_lengths(...).

source
FlowGeometries.SphericalSampling.spherical_points! — Method
spherical_points!(λ_out, φ_out, ::AbstractScatteredSphericalSampling, λ, φ) -> NamedTuple

Copy a scattered point set into caller-owned buffers.

The source arrays are required, a scattered sampling carrying no rule to generate points from.

source
FlowGeometries.SphericalSampling.spherical_points! — Method
spherical_points!(λ, φ, sampling::AbstractReducedGaussianSampling; scratch = nothing) -> NamedTuple

Ring-by-ring points of a reduced Gaussian grid, north to south, longitudes equispaced within each ring starting at zero. Buffer length is npoints.

The Gaussian latitudes need one value per ring, which cannot overlap the output here (see the note in the body), so scratch — any vector of at least nrings(sampling) elements — makes the fill allocation-free for a caller filling many grids.

source
FlowGeometries.SphericalSampling.spherical_points — Method
spherical_points(::AbstractScatteredSphericalSampling, λ, φ) -> NamedTuple{(:λ,:φ)}

A scattered sampling's points are the caller's arrays, so this hands them back. It exists so a scattered set can be driven through the same entry point as a generated one.

source
FlowGeometries.SphericalSampling.spherical_quadrature! — Function
spherical_quadrature!(λ, φ, w, sampling, nlat; nlon=…) -> NamedTuple{(:λ,:φ,:w)}

Fill preallocated axes and latitude weights together. Buffer lengths are as for spherical_axes!, with length(w) == sz.nlat.

This is the entry point to use whenever both are needed. Gauss–Legendre nodes and weights fall out of a single root solve, but spherical_axes! keeps only the nodes and latitude_weights! only the weights — so calling them in sequence pays the $O(n²)$ solve twice. Every other sampling has independent closed forms for the two, and gets the generic method.

source
FlowGeometries.SphericalSampling.yin_yang_panels! — Method
yin_yang_panels!(λyin, φyin, λyang, φyang, nlon, nlat) -> (; yin, yang)

The two Kageyama–Sato panels. yin is a pair of axes (nlon and nlat long): in its own frame the panel is a separable lat–lon patch. yang is that panel rotated onto the sphere, which is separable in neither global longitude nor latitude, so it is a pair of nlon × nlat fields, one (λ, φ) per cell.

The argument types carry that difference.

source