$s$-SHT Transformations

One important capability of this package is the transformation between the two representations of a spin-weighted spherical function:

  1. Values f of the function evaluated on a set of points or "pixels" in the domain of the function.
  2. Values f̃ of the mode weights (coefficients) of an expansion in the standard spin-weighted spherical-harmonic basis.

In the literature, the transformation f ↦ fΜƒ is usually called "analysis" or map2salm, while the inverse transformation f ↦ fΜƒ is called "synthesis" or salm2map. These are both referred to as spin-spherical-harmonic transforms, or $s$-SHTs.

To describe the values of a spin-$s$ function up to some maximum angular resolution $\ell_\mathrm{max}$, we need $(\ell_\mathrm{max}+1)^2 - s^2$ mode weights. We assume throughout that the values f̃ are stored as

fΜƒ = [mode_weight(β„“, m) for β„“ ∈ abs(s):β„“β‚˜β‚β‚“ for m ∈ -β„“:β„“]

(Here, mode_weight is a made-up function intended to provide a schematic.) In particular, the $m$ index varies most rapidly, and the $\ell$ index varies most slowly. Correspondingly, there must be at least $(\ell_\mathrm{max}+1)^2 - s^2$ function values f. However, some $s$-SHT algorithms require more function values β€” usually by a factor of 2 or 4 β€” trading off between speed and memory usage.

The SSHT object implements these transformations, storing pre-computed constants and pre-allocated workspace for the transformations. The interface is designed to be similar to that of FFTW.jl, whereby an SSHT object 𝒯 can be used to perform the transformation as either

f = 𝒯 * fΜƒ

or

fΜƒ = 𝒯 \ f

Currently, there are three algorithms implemented, each having different advantages and disadvantages:

  1. The "Direct" algorithm (introduced here for the first time), which is the default, but should only be used up to $\ell_\mathrm{max} \lesssim 50$ because its intermediate storage requirements scale as $\ell_\mathrm{max}^4$. This algorithm is the fastest for small $\ell_\mathrm{max}$, it can be used with arbitrary (non-degenerate) pixelizations, and achieves optimal dimensionality.
  2. The "Minimal" algorithm due to Elahi et al. [2], with some minor improvements. This algorithm is fast and β€” as the name implies β€” also achieves optimal dimensionality, and its storage scales as $\ell_\mathrm{max}^3$. However, its pixelization is restricted, and its accuracy at very high $\ell_\mathrm{max}$ is not as good as the "RS" algorithm. The algorithm itself is not actually fully specified by Elahi et al., and leaves out some relatively simple improvements, so I have had to take some liberties with my interpretation.
  3. The "RS" algorithm due to Reinecke and Seljebotn [3]. This forms the basis for the libsharp and ducc.sht packages. It requires pixelizations on "iso-latitude rings", and does not achieve optimal dimensionality. However, it is very fast, and its accuracy is excellent at extremely high $\ell_\mathrm{max}$.

SSHT objects

SphericalFunctions.SSHT β€” Method
SSHT(s, β„“β‚˜β‚β‚“; [method="Direct"], [T=Float64], [kwargs...])

Construct an SSHT object to transform between spin-weighted spherical-harmonic mode weights and function values β€” performing an $s$-SHT.

This object behaves similarly to an AbstractFFTs.Plan object β€” specifically in the ability to use the semantics of algebra to perform transforms. For example, if the function values are stored as a vector f, the mode weights as fΜƒ, and the SSHT as 𝒯, then we can compute the function values from the mode weights as

f = 𝒯 * fΜƒ

or solve for the mode weights from the function values as

fΜƒ = 𝒯 \ f

The first dimensions of fΜƒ must index the mode weights (as usual, for β„“βˆˆabs(s):β„“β‚˜β‚β‚“ and m∈-β„“:β„“) and the first index of f must index the locations at which the function is evaluated. Any following dimensions will be broadcast over. Note that certain types will broadcast using Julia threads, while others will broadcast using BLAS threads. The relevant number of threads must be set appropriately.

Certain SSHT types (currently, only Minimal and Direct) also have an option to always act in place β€” meaning that they simply re-use the input storage, even when used in an expression like 𝒯 \ f. The option must be passed as the inplace argument to the constructors, and part of the type of the resulting object. Regardless of the value of that option, for those types where the option exists, it is also possible to use mul! and ldiv! from the LinearAlgebra package to force operation in place.

Note that different algorithms require different "pixelizations", or sets of Rotors on which to evaluate the function. These can be obtained from the SSHT object using the pixels and rotors functions.

source
SphericalFunctions.pixels β€” Function
pixels(𝒯)

Return the spherical coordinates (ΞΈ, Ο•) on which the spin-weighted spherical harmonics are evaluated. See also rotors, which provides the actual Rotors on which they are evaluated.

source
SphericalFunctions.rotors β€” Function
rotors(𝒯)

Return the Rotors on which the spin-weighted spherical harmonics are evaluated. See also pixels, which provides the corresponding spherical coordinates.

source
SphericalFunctions.SSHTDirect β€” Type
SSHTDirect(s, β„“β‚˜β‚β‚“; decomposition=LinearAlgebra.qr, T=Float64, RΞΈΟ•=golden_ratio_spiral_rotors(s, β„“β‚˜β‚β‚“, T), inplace=true)

Construct an $s$-SHT object that uses the "Direct" method; see β‚›π˜ for details about the method and optional arguments. Also see SSHT for general information about how to use these objects.

By default, this uses precisely optimal sampling β€” meaning that the number of points on which the function is evaluated, represented by RΞΈΟ•, is equal to the number of modes. However, it is equally possible to evaluate on more points than there are modes. This can be useful, for example, when processing multiple fields with different spin weights; the function could be evaluated on points appropriate for the lowest value of $|s|$, and therefore could also be used to solve for fields of all other spin weights.

Note that in-place operation is possible for this type when the length of the input RΞΈΟ• is equal to the number of modes given s and β„“β‚˜β‚β‚“ β€” and is the default behavior when possible. See SSHT for description of in-place operation.

This method is typically better than other current implementations for $β„“β‚˜β‚β‚“ ≲ 24$, both in terms of speed and accuracy. However, this advantage quickly falls away. A warning will be issued if β„“β‚˜β‚β‚“ is greater than about 64, because this method is not likely to be the most efficient or most accurate choice.

source
SphericalFunctions.SSHTMinimal β€” Method
SSHTMinimal(s, β„“β‚˜β‚β‚“; kwargs...)

Construct a SSHTMinimal object directly. This may also be achieved by calling the main SSHT function with the same keywords, along with method="Minimal".

This object uses the algorithm described by Elahi et al.

The basic floating-point number type may be adjusted with the keyword argument T, which defaults to Float64.

The SSHs are evaluated on a series of "rings" at constant colatitude. Their locations are specified by the ΞΈ keyword argument, which defaults to sorted_rings(s, β„“β‚˜β‚β‚“, T). The first element of ΞΈ is the colatitude of the smallest ring (containing $2s+1$ elements), and so on to the last element of ΞΈ, which is the colatitude of the largest ring (containing $2β„“β‚˜β‚β‚“+1$ elements).

Whenever T is either Float64 or Float32, the keyword arguments plan_fft_flags and plan_fft_timelimit may also be useful for obtaining more efficient FFTs. They default to FFTW.ESTIMATE and Inf, respectively. They are passed to AbstractFFTs.plan_fft.

Note that, because this algorithm achieves optimal dimensionality, the transformation will be performed in place by default. If this is not desired, pass the keyword argument inplace=false. This will cause the algorithm to copy the input and perform in-place transformation on that copy.

source
SphericalFunctions.SSHTRS β€” Method
SSHTRS(s, β„“β‚˜β‚β‚“; kwargs...)

Construct a SSHTRS object directly. This may also be achieved by calling the main SSHT function with the same keywords, along with method="RS".

This object uses the algorithm described by Reinecke and Seljebotn.

The basic floating-point number type may be adjusted with the keyword argument T, which defaults to Float64.

The SSHs are evaluated on a series of "rings" at constant colatitude. Their locations are specified by the ΞΈ keyword argument, which defaults to fejer1_rings(2β„“β‚˜β‚β‚“+1, T). If this is changed, the user should also provide the corresponding quadrature_weights argument β€” the default being fejer1(length(ΞΈ), T).

On each of these rings, an FFT is performed. To reach the band limit of $m = Β± β„“β‚˜β‚β‚“$, the number of points along each ring must therefore be at least $2β„“β‚˜β‚β‚“+1$, but may be greater. For example, if $2β„“β‚˜β‚β‚“+1$ does not factorize neatly into a product of small primes, it may be preferable to use $2β„“β‚˜β‚β‚“+2$ points along each ring. (In that case, whenever β„“β‚˜β‚β‚“ is 1 less than a power of 2, the number of points will be exactly a power of 2, which is usually particularly efficient.) The number of points on each ring can be modified independently, if given as a vector with the same length as ΞΈ, or as a single number which is assumed to be the same for all rings.

Whenever T is either Float64 or Float32, the keyword arguments plan_fft_flags and plan_fft_timelimit may also be useful for obtaining more efficient FFTs. They default to FFTW.ESTIMATE and Inf, respectively. They are passed to AbstractFFTs.plan_fft.

source

Pixelizations

The algorithms implemented here require pixelizations. While the "Direct" algorithm can be used with arbitrary pixelizations, the "Minimal" and "RS" algorithms require more specific choices, as noted in their docstrings.

Typically, "pixelization" refers exclusively to a choice of points on the sphere π•ŠΒ² at which to compute function values. Of course, as mentioned elsewhere, it is not technically possible to define spin-weighted functions as functions of a point on π•ŠΒ² alone; we also need some sense of reference direction in the tangent space. Quite generally, we can define spin-weighted functions on the group π’πŽ(3) or 𝐒𝐩𝐒𝐧(3), so we will also refer to a choice of a set of points in 𝐒𝐩𝐒𝐧(3) (which is essentially the group of unit quaternions) as a "pixelization". However, assuming spherical coordinates, a choice of coordinates on the sphere almost everywhere induces a choice of the reference direction in the tangent space, so it is almost possible to define pixelizations just in terms of points on π•ŠΒ². But using spherical coordinates is actually enough to fully specify the pixelization, because the degeneracies at the poles also allow us to define the reference direction.

In principle, we could be concerned about the choice of reference direction in the tangent space. That is, we might expect to care about pixelizations over π•ŠΒ³. However, we are dealing with spin-weighted functions, which are eigenfunctions of a final rotation about the reference direction. This means that once we choose any reference direction at each point, we know the function values for any other reference direction at those points. In particular, an important property of a pixelization is the condition number of the transformation matrix between the function values and the mode weights. If we rotate the reference direction at a single point, this is equivalent to multiplying the matrix by a diagonal matrix with entries of 1 everywhere except the entry corresponding to that point, where the entry is some complex phase. This does not change the condition number of the matrix, so we can ignore the choice of reference direction at every point. For other situations, where we might care about the choice of reference direction, it might be interesting to consider this work by Marc Alexa, and references therein.

Interesting discussions of various pixelizations and metrics can be found in Saff and Kuijlaars (1997) and Brauchart and Grabner (2015), as well as blog posts here and here. Note that the "equal-area" pixelizations of Healpix are very restrictiveβ€”only being available for very specific numbers of pointsβ€”and do not provide any obvious advantages over the more flexible pixelizations available here.

The various pixelizations may be computed as follows:

SphericalFunctions.clenshaw_curtis_rings β€” Method
clenshaw_curtis_rings(N, [T=Float64])

Values of the colatitude coordinate ($ΞΈ$) appropriate for quadrature by the Clenshaw-Curtis rule, using weights provided by clenshaw_curtis.

Note that the first argument to this function is N, rather than the β„“β‚˜β‚β‚“ used in some other functions. For spin-weighted spherical harmonics, you may want to use N=2β„“β‚˜β‚β‚“+1.

source
SphericalFunctions.fejer1_rings β€” Method
fejer1_rings(N, [T=Float64])

Values of the colatitude coordinate ($ΞΈ$) appropriate for quadrature by FejΓ©r's first rule, using weights provided by fejer1.

Note that the first argument to this function is N, rather than the β„“β‚˜β‚β‚“ used in some other functions. For spin-weighted spherical harmonics, you may want to use N=2β„“β‚˜β‚β‚“+1.

source
SphericalFunctions.fejer2_rings β€” Method
fejer2_rings(N, [T=Float64])

Values of the colatitude coordinate ($ΞΈ$) appropriate for quadrature by FejΓ©r's second rule, using weights provided by fejer2.

Note that the first argument to this function is N, rather than the β„“β‚˜β‚β‚“ used in some other functions. For spin-weighted spherical harmonics, you may want to use N=2β„“β‚˜β‚β‚“+1.

source
SphericalFunctions.golden_ratio_spiral_pixels β€” Method
golden_ratio_spiral_pixels(s, β„“β‚˜β‚β‚“, [T=Float64])

Cover the sphere π•ŠΒ² with pixels generated by the golden-ratio spiral. Successive pixels are separated by the azimuthal angle $Δϕ = 2Ο€(2-Ο†)$, and are uniformly distributed in $\cos ΞΈ$.

This is also known as the "Fibonacci sphere" or "Fibonacci lattice".

Visually, this is a very reasonable-looking pixelization, with fairly uniform distance between neighbors, and approximate isotropy. No two pixels will share the same values of either $ΞΈ$ or $Ο•$. Also note that no point is present on either the North or South poles.

The returned quantity is a vector of 2-SVectors providing the spherical coordinates of each pixel. See also golden_ratio_spiral_rotors for the corresponding Rotors.

source
SphericalFunctions.sorted_ring_pixels β€” Method
sorted_ring_pixels(s, β„“β‚˜β‚β‚“, [T=Float64])

Cover the sphere π•ŠΒ² with $(β„“β‚˜β‚β‚“+1)Β²-sΒ²$ pixels distributed in rings provided by sorted_rings; see that function's documentation for more description.

The returned quantity is a vector of 2-SVectors containing the spherical coordinates of each pixel. See also sorted_ring_rotors for the corresponding Rotors.

source
SphericalFunctions.sorted_ring_rotors β€” Method
sorted_ring_rotors(s, β„“β‚˜β‚β‚“, [T=Float64])

Cover the sphere π•ŠΒ² with $(β„“β‚˜β‚β‚“+1)Β²-sΒ²$ pixels distributed in rings provided by sorted_rings; see that function's documentation for more description.

The returned quantity is a vector of Rotors. See also sorted_ring_rotors for the corresponding spherical coordinates.

source
SphericalFunctions.sorted_rings β€” Method
sorted_rings(s, β„“β‚˜β‚β‚“, [T=Float64])

Compute locations of a series of rings labelled by $j ∈ |s|:β„“β‚˜β‚β‚“$ (analogous to $β„“$), where each ring will contain $k = 2j+1$ (analogous to $m$) pixels distributed evenly around the ring. These rings are then sorted, so that the ring with the most pixels ($j = β„“β‚˜β‚β‚“$) is closest to the equator, and the next-largest ring is placed just above or below the equator (depending on the sign of $s$), the next just below or above, and so on. This is generally a fairly good first guess when minimizing the condition number of matrices used to solve for mode weights from function values. In particular, I use this to initialize the Minimal algorithm, which is then fed into an optimizer to fine-tune the positions of the rings.

This function does not provide the individual pixels; it just provides the colatitude values of the rings on which the pixels will be placed. The pixels themselves are provided by sorted_ring_pixels.

source

Quadrature weights

The "RS" algorithm requires quadrature weights corresponding to the input pixelization. Though there is a working default choice, it is possible to use others. There are several that are currently implemented, along with their corresponding pixelizations:

SphericalFunctions.clenshaw_curtis β€” Method
clenshaw_curtis(n, [T])

Compute n weights for the Clenshaw-Curtis rule, corresponding to n evenly spaced nodes from 0 to Ο€ inclusive. That is, the nodes are located at

\[\theta_k = k \frac{\pi}{n-1} \quad k=0, \ldots, n-1.\]

This function uses Waldvogel's method.

The type T may be any AbstractFloat, but defaults to Float64.

source
SphericalFunctions.fejer1 β€” Method
fejer1(n, [T])

Compute n weights for FejΓ©r's first rule, corresponding to n evenly spaced nodes from 0 to Ο€ inclusive. That is, the nodes are located at

\[\theta_k = k \frac{\pi}{n-1} \quad k=0, \ldots, n-1.\]

This function uses Waldvogel's method.

The type T may be any AbstractFloat, but defaults to Float64.

source
SphericalFunctions.fejer2 β€” Method
fejer2(n, [T])

Compute n weights for FejΓ©r's second rule, corresponding to n evenly spaced nodes between 0 and Ο€ exclusive. That is, the nodes are located at

\[\theta_k = k \frac{\pi}{n+1} \quad k=1, \ldots, n.\]

This function uses Waldvogel's method. However, contrary to Waldvogel's notation, this routine does not include the weight corresponding to the Ο‘=0 or Ο€ nodes, which both have weight 0.

The type T may be any AbstractFloat, but defaults to Float64.

source