$𝔇$ and $d$ matrices, and ${}_{s}Y_{β„“,m}$ and $Y_{β„“,m}$ functions

Wigner's $𝔇$ matrices β€” and to a lesser extent, the related $d$ matrices β€” are extremely important in the theory of rotations. Each element is, itself, a special function of the rotation group: in particular, an eigenfunction of the left- and right-Lie derivatives, and thus a spin-weighted spherical function. See the "Background" section, and particularly this page, for details. Collectively, they describe how spin-weighted spherical functions transform under rotation. But their accurate and efficient computation is surprisingly subtle. This package implements the current state-of-the-art techniques for their fast and accurate computation, based on the $H$ recursion introduced by Gumerov and Duraiswami [7].

The convention used here is that

\[𝔇^{(β„“)}_{m',m}\left( e^{α𝐀/2} 𝐐 e^{γ𝐀/2} \right) = e^{-i m' Ξ±}\, 𝔇^{(β„“)}_{m',m}\left( 𝐐 \right)\, e^{-i m Ξ³}.\]

Note that this equation does not use Euler angles per se; $𝔇$ is not defined in terms of Euler angles in this package, and the parameters $Ξ±$ and $Ξ³$ are simply unrelated parameters in this expression. Nonetheless, we can relate our expression to the more common Euler-angle form: our convention agrees with

\[𝔇^{(β„“)}_{m',m}(Ξ±, Ξ², Ξ³) = e^{-i m' Ξ±}\, d^{(β„“)}_{m',m}(Ξ²)\, e^{-i m Ξ³}.\]

In either form, this is the complex conjugate of the convention used by versions of this package before 3.0, but is more standard in modern references. See the conventions summary for the full list of properties, and the comparison pages for how it relates to other sources.

The spin-weighted spherical harmonics are an important set of functions defined on that same group, particularly useful in describing the angular dependence of polarized fields, like the electromagnetic field and the gravitational-wave field. Originally introduced by Newman and Penrose [4], they are essentially single columns of Wigner's $𝔇$ matrices:

\[{}_{s}Y_{β„“,m}(𝐑) = (-1)^s \sqrt{\frac{2β„“+1}{4Ο€}} \, \overline{𝔇^{(β„“)}_{m, -s}(𝐑)}.\]

(See the conventions summary for this and the related definitions.) They are therefore computed by the same recursion, and because only the single column $m = -s$ is needed β€” the second index of $𝔇$ β€” both the storage and the work are much smaller than for the full matrices. The standard (scalar) spherical harmonics are the special case of spin weight $s = 0$,

\[Y_{β„“,m}(𝐑) = {}_{0}Y_{β„“,m}(𝐑),\]

with the usual spherical-coordinate form being defined as

\[Y_{β„“,m}(ΞΈ, Ο•) = {}_{0}Y_{β„“,m}\left( \exp\left(\frac{Ο•}{2}𝐀\right) \exp\left(\frac{ΞΈ}{2}𝐣\right) \right).\]

Everything below applies to all three sets of functions, and the few differences among them are noted where they arise.

Evaluating the functions

The simplest call takes a rotor and a maximum $β„“$:

using Quaternionicusing SphericalFunctionsR = randn(RotorF64)β„“β‚˜β‚β‚“ = 8𝔇 = D(R, β„“β‚˜β‚β‚“)

A rotation must be given as a Quaternionic.Rotor: that type is what specifies that a quaternion denotes a rotation. To make this easier, the Quaternionic package includes several easy ways to create a Rotor:

For the most common of these, the conversion is built in: D, DCalculator, sYlm, Ylm, sYlmCalculator and YlmCalculator also accept Euler angles or spherical coordinates in place of a single rotor. So D(Ξ±, Ξ², Ξ³, β„“β‚˜β‚β‚“) is D(from_euler_angles(Ξ±, Ξ², Ξ³), β„“β‚˜β‚β‚“), and sYlm(ΞΈ, Ο•, β„“β‚˜β‚β‚“, s) is sYlm(from_spherical_coordinates(ΞΈ, Ο•), β„“β‚˜β‚β‚“, s), with exactly the same values.

The result is indexed by $β„“$ and then by the two matrix indices, each with its natural range:

𝔇[β„“][mβ€², m]  # for β„“ ∈ β„“β‚˜α΅’β‚™:β„“β‚˜β‚β‚“ and mβ€²,m ∈ -β„“:β„“

There is no index arithmetic to get wrong: 𝔇[β„“] is a WignerMatrix whose axes are just -β„“:β„“, and the outer container is a WignerSeries. Note that β„“β‚˜α΅’β‚™=0 for integers but β„“β‚˜α΅’β‚™=1//2 for half-integers.

A block is deliberately not an AbstractMatrix, so linear algebra does not apply to it directly; array_view gives a 1-based StridedArray view of the same storage, which BLAS takes at full speed, and relabel puts the natural indices back on the result. Matrix(𝔇[β„“]) gives an independent copy. The reasons for that arrangement are set out under Containers below.

For the $d$ matrices the interface is the same, except that the argument is the angle $Ξ²$ rather than a rotor, and the values are real:

Ξ² = Ο€ * rand(Float64)𝔑 = d(Ξ², β„“β‚˜β‚β‚“)𝔑[β„“][mβ€², m]

d also accepts the complex phase $e^{iΞ²}$ directly, which is useful when that is what you have, and a rotor, from which it takes the equivalent of the $Ξ²$ angle.

Both D and d accept four keyword arguments β€” mβ€²β‚˜β‚β‚“, mβ€²β‚˜α΅’β‚™, mβ‚˜β‚β‚“, and mβ‚˜α΅’β‚™, which may also be passed as mp_max, mp_min, m_max and m_min β€” restricting the block of each matrix that is returned; each lower limit defaults to minus the corresponding upper one, so that D(R, β„“β‚˜β‚β‚“; mβ€²β‚˜β‚β‚“=2) has the rows $-2 ≀ m' ≀ 2$. Restricting either mβ€² (the rows) or m (the columns) to a narrow band makes the calculation cheaper as well as smaller, because the recurrence at each $β„“$ costs in proportion to the narrower of the two ranges. Each range must contain 0 β€” or both $Β±1/2$, for half-integer indices β€” because that is where the recurrence starts; a single row or column elsewhere, such as $m = 2$, is read from the block afterwards. (The spin-weighted spherical harmonics need a single column, and are computed most cheaply by sYlm, below.)

For those spin-weighted spherical harmonics, a more direct and efficient method is provided by sYlm, taking the spin weight as a third argument:

s = -2sY = sYlm(R, β„“β‚˜β‚β‚“, s)

The s value can also be a range of spin weights, which is more efficient than calling sYlm repeatedly for each spin weight:

sY = sYlm(R, β„“β‚˜β‚β‚“, -2:2)

The order of these arguments follows one rule throughout the package. A function evaluated at a rotor takes the rotor first (or the angles that stand in for it), then $β„“β‚˜β‚β‚“$, and then the spin weight when there is one: D(R, β„“β‚˜β‚β‚“), sYlm(R, β„“β‚˜β‚β‚“, s), sYlm_matrix(Rβƒ—, β„“β‚˜β‚β‚“, s), sYlmCalculator(R, β„“β‚˜β‚β‚“, s) and the rest of that family, with $β„“β‚˜α΅’β‚™$ as a keyword. An object labelled by a spin weight takes the spin weight first, then $β„“β‚˜α΅’β‚™$ where it may be given, and then $β„“β‚˜β‚β‚“$: ModeWeights{T}(undef, s, β„“β‚˜α΅’β‚™, β„“β‚˜β‚β‚“), SSHT(s, β„“β‚˜β‚β‚“), the differential operators op(s, β„“β‚˜α΅’β‚™, β„“β‚˜β‚β‚“), and the pixelizations such as leja_rotors(s, β„“β‚˜β‚β‚“). Exchanging the arguments of one family for those of the other asks for a spin weight larger than $β„“β‚˜β‚β‚“$, unless the two are equal, and is usually refused for that reason. The exception is a spin weight larger by exactly one, for which the operators and the ModeWeights constructors return an empty result, since the range $|s| ≀ β„“ ≀ β„“β‚˜β‚β‚“$ is then empty rather than invalid.

A harmonic has only one index besides $β„“$, so a block is a vector rather than a matrix, but the result is indexed the same way as $𝔇$: by $β„“$ first, then naturally. It is a HarmonicValues, and sY[β„“][m] is one value; sY[β„“, :] is equivalent to the block sY[β„“]. As for $𝔇$, iterating over the result gives β„“ => block pairs, while first, last and only give blocks. A whole collection of rotors may be given instead of one, and the spin weight may be a range, which between them give a block four possible shapes:

built forsY[β„“] is indexed
one rotor, one spin weight[m]
many rotors, one spin weight[iα΅£, m]
one rotor, a range of spin weights[s, m]
many rotors, a range of spin weights[iα΅£, s, m]

Underneath, the values are held in one array whose last axis is the modes in the canonical ordering described by Ysize, Yindex and Yrange, and whose leading axes are the rotors and spin weights. array_view hands that array back:

array_view(sY)[Yindex(β„“, m, abs(s))] == sY[β„“][m]

That flat form is what a product with the plain vector of mode weights takes, to synthesize a function at the rotors; sYlm_matrix is the direct name for it, for those who want the bare array. The weights themselves are held in a ModeWeights, which the labelled sY multiplies directly, as described below.

Modes with $β„“ < |s|$ do not exist (or are inherently zero), so by default $β„“$ starts at $|s|$. Pass β„“β‚˜α΅’β‚™=0 (or ell_min=0) to start at $β„“ = 0$ instead, with zeros in the nonexistent modes; this is the layout that some downstream packages use for every spin weight at once. For $s = 0$ these are the ordinary scalar spherical harmonics $Y_{β„“,m}$, which Ylm gives without the redundant argument: Ylm(R, β„“β‚˜β‚β‚“) is exactly sYlm(R, β„“β‚˜β‚β‚“, 0), and starts at $β„“ = 0$ because no modes are missing there.

Iterating over $β„“$ and reusing the storage

The functions above allocate a fresh workspace on every call, and copy the results for all $β„“$ values out of it. When the total size is too large, or the values are needed for many rotors, allocate the workspace once as a DCalculator (or a dCalculator) and iterate over the values of $β„“$:

calculator = DCalculator(R, β„“β‚˜β‚β‚“)for (β„“, 𝔇ˑ) ∈ calculator    # 𝔇ˑ[mβ€², m] is available for mβ€², m ∈ -β„“:β„“end

An sYlmCalculator is built and iterated in just the same way, with the spin weight given as a third argument:

calculator = sYlmCalculator(R, β„“β‚˜β‚β‚“, s)for (β„“, β‚›Yβ‚—) ∈ calculator    # β‚›Yβ‚—[m] is available for m ∈ -β„“:β„“end

A calculator always starts at $β„“ = 0$ (or $1/2$), and takes no β„“β‚˜α΅’β‚™ keyword; the blocks for $β„“ < |s|$, whose modes do not exist, come back full of zeros rather than being skipped.

The m keywords have their counterpart in the spin weight. Because ${}_{s}Y_{β„“,m}$ is the $m = -s$ column of $𝔇$, a range of spin weights is a range of columns of one recursion, and an sYlmCalculator will serve several of them at once, giving its blocks a spin axis indexed by the spin weight itself:

calculator = sYlmCalculator(R, β„“β‚˜β‚β‚“, -2:2)for (β„“, β‚›Yβ‚—) ∈ calculator    # β‚›Yβ‚—[s, m] for s ∈ -2:2, m ∈ -β„“:β„“end

What the recursion costs is governed by the largest $|s|$ asked for, so reading the rest of the range out of it is close to free; naming a single spin weight is a saving in storage and in the final assembly rather than in the recursion itself. A single spin weight of such a block is β‚›Yβ‚—[s, :], which is written the same way whichever kind of index the calculator has. spins reports the range a calculator serves, and spin the one value when there is only one.

Note that we begin with some R, which sets the underlying float type of the calculator. For example, an input Rotor{Float32} gives a Float32 calculator, which returns matrices of Complex{Float32}. All subsequent calls must use that same type of Rotor; if you need to use a different type, you have to create a new calculator. For example, when differentiating with ForwardDiff, the input rotor must be a dual number. A dCalculator is built the same way from $Ξ²$, $e^{iΞ²}$, or a rotor. The same four keywords are accepted by both functions. floattype(calculator) reports the type in use. Nothing but the input can set it β€” there is no type argument on any of these constructors β€” so computing in a wider type means building the rotor in that type, as in sYlmCalculator(Rotor{BigFloat}(R), β„“β‚˜β‚β‚“, s).

Derivatives at the poles

$𝔇$ and the harmonics may be differentiated with respect to the rotor by automatic differentiation everywhere, including at rotors with $Ξ² = 0$ or $Ξ² = Ο€$. The same is not true of $d$ and $H$ of a rotor: some of their elements, such as $d^{(1)}_{1,0}$, have no derivative at the poles as functions of the rotor, because $Ξ²$ itself has none, and those derivatives are returned as NaN. To differentiate $d$ at the poles, give it the angle $Ξ²$ or the phase $e^{iΞ²}$ instead. This note explains the details.

Danger

Each 𝔇ˑ block is a view into the storage kept in the calculator. The next step of the loop overwrites it, so you cannot keep a block between steps unless you copy it. The same applies to array_view of a block, which aliases that storage rather than copying it; Matrix, Array and collect are the forms that survive.

copy keeps the block's natural indices, while collect gives an ordinary 1-based array; collect applied to the calculator itself copies every block for you.

A calculator is not indexed, and there is no restricted form of the iteration. Both are the same for loop over recurrence!, which computes one $β„“$ and returns its block:

for β„“ ∈ 2:4    𝔇ˑ = recurrence!(calculator, β„“)    # 𝔇ˑ[mβ€², m] for mβ€², m ∈ -β„“:β„“end

Beginning above the calculator's own $β„“β‚˜α΅’β‚™$ costs nothing in accuracy: the recursion runs through the values below either way, and the result is bit-for-bit what a full sweep gives. Values of $β„“$ taken in decreasing order are a different matter β€” each one restarts the recursion from $β„“β‚˜α΅’β‚™$, so a loop that reads two neighboring $β„“$ together pays that restart at every step.

For the same reason, two loops over one calculator cannot be interleaved. Each step of either loop overwrites what the other is looking at. There is no way to warn about this behavior; the answers will simply be wrong if you try this. A simple way to get a second calculator of the same type is similar(calculator), or similar(calculator, R) to give it other rotors. The same holds for tasks: a calculator is a mutable workspace, so one calculator must never be used by two tasks at the same time, and each task that computes in parallel with the others needs a calculator of its own.

To reset the calculator to the beginning of the loop over $β„“$, and change the R value, call set_R! on a DCalculator or an sYlmCalculator, or set_Ξ²! (also available as set_beta!) on a dCalculator. For example, given a collection of rotors, you can iterate over them all like this:

calculator = DCalculator(first(Rβƒ—), β„“β‚˜β‚β‚“)for R ∈ Rβƒ—    set_R!(calculator, R)    for (β„“, 𝔇ˑ) ∈ calculator        # 𝔇ˑ[mβ€², m] is available for mβ€², m ∈ -β„“:β„“ with this value of R    endend

However, such a loop is not always the best way to handle many rotors. You can also give a whole collection of rotors to the constructor, and the calculator will use SIMD to compute the Wigner matrix elements for all of them at once β€” which can be significantly faster than looping over them one at a time. This would not be accessible to the user by external looping as above. For example,

calculator = DCalculator(Rβƒ—, β„“β‚˜β‚β‚“)for (β„“, 𝔇ˑ) ∈ calculator    # 𝔇ˑ[iα΅£, mβ€², m] is available for iα΅£ ∈ 1:length(Rβƒ—) and mβ€², m ∈ -β„“:β„“end

The type and number of rotors are fixed when the calculator is built, but you can still use set_R! and set_Ξ²! to adjust their values and restart the loop over $β„“$. Each block of an sYlmCalculator built this way gains the same leading rotor index, so that it is β‚›Yβ‚—[iα΅£, m], or β‚›Yβ‚—[iα΅£, s, m] for a range of spin weights. What decides this is that the rotors came as a vector, not how many there are: a calculator built from a vector of one rotor is still a batch, with a rotor index of length one, and isbatched says which kind a calculator is. The operations that need a single rotor β€” rotating a ModeWeights, for example β€” refuse a batch of one, rather than guessing which was meant.

The real harmonics ${}_sΞ»_{β„“,m}(ΞΈ)$

A calculator also accepts real angles $ΞΈ$ in place of rotors, either at construction or later through set_ΞΈ! (also available as set_theta!), and then evaluates the harmonics at $(ΞΈ, Ο•=0)$. That is the ${}_{s}Ξ»_{β„“,m}(ΞΈ)$ the ring-based transforms need β€” one ring of the sphere for each angle, which is why a whole vector of them is the natural input:

calculator = SphericalFunctions.sΞ»lmCalculator(ΞΈβƒ—, β„“β‚˜β‚β‚“, -2)  # a vector of angles, or one ΞΈfor (β„“, β‚›Ξ»β‚—) ∈ calculator    # β‚›Ξ»β‚—[iα΅£, m] for iα΅£ ∈ 1:length(ΞΈβƒ—), m ∈ -β„“:β„“end

An sΞ»lmCalculator stores its values as real numbers. The name is public but not exported, and has an ASCII alias, slambdalmCalculator. Everything else is as it is for the complex family: the same blocks, which are generic in the number type, the same iteration, and the same half-integer types.

The two flavors share one struct, HarmonicCalculator, exactly as DCalculator and dCalculator do β€” and for the same reason. The underlying $H$ recursion is real either way; it is only the factor $e^{-i(mΞ± - sΞ³)}$ that ever makes a result complex, and an angle sets $Ξ± = Ξ³ = 0$. So the real flavor runs precisely the same recursion, allocates no phase tables at all, and writes half as many numbers.

The definition is

\[{}_sΞ»_{β„“,m}(ΞΈ) = \begin{cases} {}_sY_{β„“,m}(ΞΈ, 0), & s ∈ β„€, \\ {}_sY_{β„“,m}(ΞΈ, 0) \big/ i^{2s}, & s ∈ β„€ + \tfrac{1}{2}, \end{cases}\]

which is real for both kinds of index. For an integer spin weight the prefactor $(-1)^s$ in the definition of ${}_sY_{β„“,m}$ is $\pm 1$, so ${}_sY_{β„“,m}(ΞΈ, 0)$ is already real, and ${}_sΞ»_{β„“,m}$ is simply the harmonic at $Ο• = 0$, as the literature writes it. For a half-odd spin weight that prefactor is $i^{2s} = \pm i$, so ${}_sY_{β„“,m}(ΞΈ, 0)$ is imaginary rather than real, and dividing that constant phase out is what leaves a real function behind. (Dividing by $i^{2s}$ in both cases would give the wrong sign for odd integer $s$, where $i^{2s} = -1$.)

A Rotor is refused, by the constructor and by set_ΞΈ! alike: it specifies the angles $Ξ±$ and $Ξ³$, whose phases a real calculator has nowhere to put. Use an sYlmCalculator for that. Conversely, set_R! takes only rotors, and refuses an angle with a message naming set_ΞΈ!. Angles fix the element type exactly as rotors do, and set_ΞΈ! requires the same agreement as set_R!, so a BigFloat calculator takes big(ΞΈ) rather than a bare literal.

The underlying $H$ recursion

All of these calculators are built on an HCalculator, which computes the real, symmetric $H$ wedge that the Gumerov–Duraiswami recursion produces before any phases are applied. It is available directly for the rare cases where the wedge itself is needed β€” probably as an optimization:

h = HCalculator(Ξ², β„“β‚˜β‚β‚“)for β„“ ∈ β„“β‚˜α΅’β‚™:β„“β‚˜β‚β‚“  # β„“β‚˜α΅’β‚™=0 for integers or 1//2 for half-integers    HΛ‘ = recurrence!(h, β„“)    # HΛ‘[iα΅£, mβ€², m] is available; iα΅£ is always present; only |mβ€²| ≀ m ≀ β„“ is storedend

This one is intentionally not iterable, and is the exception to everything said above about blocks: the wedge is a single mutable object, the calculator's own workspace, handed back by identity rather than as a view. The next step overwrites it, and its β„“ must not be reassigned by hand. Read the values out before stepping on, or keep copy(HΛ‘), which is an independent wedge holding the same numbers.

The wedge is stored as an HWedge (and, during the recursion, an HAxis, which is internal); the symmetries that relate the rest of the matrix to the stored wedge are described in the notes on the $H$ recursion, and wedge_value reads any element of the matrix through them. Most users should prefer the $𝔇$, $d$ and ${}_{s}Y_{β„“,m}$ calculators above, which apply the symmetries and the phases for you.

Half-integer indices

Everything on this page works for half-integer $β„“, m', m$ and spin weight $s$ as well. Ask for them by giving Rational{Int}s whose denominator is exactly 2, or HalfOddIntegers:

𝔇 = D(R, 7//2)                                # β„“ = 1//2, 3//2, 5//2, 7//2𝔑 = d(Ξ², 7//2)calculator = sYlmCalculator(R, 7//2, -3//2:3//2)

The containers that come back are the same ones as for integer indices. The index type behind them, and the handful of places where the half-integer case actually differs, are described on the half-integer page.

Rotating and evaluating mode weights

Two products tie the containers together. Multiplying a ModeWeights by the Wigner matrices of a rotor rotates it, and multiplying by the harmonics at some rotors evaluates it there:

wβ€² = D(R, β„“β‚˜β‚β‚“) * w              # the weights of fβ€²(𝐐) = f(𝐑⁻¹𝐐)f  = sYlm(R, β„“β‚˜β‚β‚“, s) * w         # the value of f at Rfβƒ—  = sYlm(Rβƒ—, β„“β‚˜β‚β‚“, s) * w        # ... and at each of many rotors

The calculator forms, DCalculator(R, β„“β‚˜β‚β‚“) * w and sYlmCalculator(R, β„“β‚˜β‚β‚“, s) * w, compute the same things one $β„“$ at a time rather than materializing every block.

Evaluation reads only the weights that belong to a function of spin weight $s$, those with $β„“ β‰₯ \max(β„“β‚˜α΅’β‚™(w), |s|)$, so the harmonics need to cover only that range, and weights that hold nothing at or above $|s|$ evaluate to zero. The flat sYlm_matrix is a plain matrix, which has no labels to check, so its product with a ModeWeights is refused rather than trusted. The same synthesis is written either with the labelled harmonics, as sYlm(Rβƒ—, β„“β‚˜β‚β‚“, s) * fΜƒ, which checks the spin weight and the range of $β„“$, or with the matrix and the raw numbers, as Y * array_view(fΜƒ), for weights stored from the matrix's own $β„“β‚˜α΅’β‚™$. Weights over some other range of $β„“$ are copied into the one wanted with ModeWeights(fΜƒ; β„“β‚˜α΅’β‚™, β„“β‚˜β‚β‚“), which fills the modes that fΜƒ lacks with zeros.

Rotation is an ordinary matrix–vector product on each $β„“$ block, with no complex conjugate β€” see Rotation of mode weights for the derivation. Because version 2 used the conjugate convention for $𝔇$, code ported from it must drop a conj rather than add one. The matrices must cover the weights' range of $β„“$, which is not the same as matching it, and their blocks must be whole: a $𝔇$ built with the mβ€²β‚˜β‚β‚“ or mβ‚˜β‚β‚“ restrictions cannot rotate anything, because every $m$ mixes into every $mβ€²$.

Warning

Evaluation is written * and not β‹…. β‹… is LinearAlgebra.dot, which conjugates its first argument; evaluation must not conjugate the harmonics. Calling dot on these types raises an error saying so, rather than quietly returning an answer with the wrong phase. For the conjugating inner product of two sets of mode weights, dot(w₁, wβ‚‚) is still what you want.

SphericalFunctions.ModeWeights β€” Method
w(R)
w(R⃗)

Evaluate the function with mode weights w at the rotor R, or at each of a vector of rotors Rβƒ—, $f(𝐑) = \sum_{β„“,m} f_{β„“,m}\, {}_sY_{β„“,m}(𝐑)$. For spherical coordinates use R = from_spherical_coordinates(ΞΈ, Ο•).

This computes the harmonics on every call. For repeated evaluation build an sYlmCalculator once and use calc * w; for many rotors at once see sYlm_matrix and the transforms in the "Transformations" section. As for Y * w, the harmonics are computed in the floating-point type of the rotor, so a rotor of lower precision than the weights limits the precision of the result.

The entries of w with $β„“ < |s|$ belong to no harmonic, and are not read. Mode weights that hold no $β„“ β‰₯ |s|$ at all, such as an empty ModeWeights or weights whose $|s|$ exceeds their $β„“β‚˜β‚β‚“$, describe the zero function, and evaluate to zero, although sYlm refuses such a spin weight; weights of that kind arise from Γ° applied to weights with $s = β„“β‚˜β‚β‚“$, for example. An empty vector of rotors gives an empty vector of values.

source
Base.:* β€” Method
𝔇 * w
calc * w

Rigidly rotate the mode weights w, giving the weights of $fβ€²(𝐐) = f(𝐑^{-1} 𝐐)$ β€” the function actively rotated by the rotor whose Wigner matrices 𝔇 (a WignerSeries from D) or calc (a DCalculator) hold. Mode by mode,

\[fβ€²_{β„“,mβ€²} = \sum_m 𝔇^{(β„“)}_{mβ€²,m}(𝐑)\, f_{β„“,m},\]

with no complex conjugate: version 2's $𝔇$ was the conjugate of this one, so code ported from it must drop a conj rather than add one. The derivation is in the conventions section, under Rotation of mode weights.

The result is a new ModeWeights with the same spin weight and the same range of $β„“$ as w β€” a rotation changes neither. 𝔇 must cover w's range, which is not the same as matching it: D has no β„“β‚˜α΅’β‚™ argument and always starts at $0$ (or $1/2$), while a ModeWeights usually starts at $|s|$. The blocks must be whole: a $𝔇$ built with any of the mβ€²β‚˜β‚β‚“, mβ€²β‚˜α΅’β‚™, mβ‚˜β‚β‚“ or mβ‚˜α΅’β‚™ restrictions cannot rotate anything, because every $m$ mixes into every $mβ€²$, and is refused.

Real matrices from d are accepted, and are exactly the rotation by from_euler_angles(0, Ξ², 0), for which $𝔇 = d$. The matrices are computed in the floating-point type of the rotor, as D says, and the result is of the promoted type of the matrices and the weights, so weights of higher precision than the rotor are rotated only to the rotor's precision; convert the rotor to the weights' type where their full precision is wanted.

The calculator form holds one $β„“$ at a time instead of materializing every block, so it allocates only the result; mul! into an existing container allocates nothing at all. (The destination may also be a bare vector at least as long as the result, which then comes back as a ModeWeights over it.) The calculator must have been built from a single Rotor: one built from a vector of rotors, even of length one, is batched, and is refused. A calculator holds workspace, so it must not be used by two tasks at once; similar(calc) gives each task one of its own.

wβ€² = D(R, β„“β‚˜β‚β‚“(w)) * wwβ€²(Q) β‰ˆ w(inv(R) * Q)          # the defining property

See also ModeWeights and HarmonicValues.

source
Base.:* β€” Method
Y * w
calc * w

Evaluate the function with mode weights w at the rotor (or rotors) whose harmonics Y holds, $f(𝐑) = \sum_{β„“,m} f_{β„“,m}\, {}_sY_{β„“,m}(𝐑)$. Y is a HarmonicValues from sYlm, or an sYlmCalculator. Real harmonics are refused, whether from an sΞ»lmCalculator or in a HarmonicValues of real numbers: they are functions of $ΞΈ$ alone, without the phase $i^{2s}$ of ${}_sY_{β„“,m}$, so they evaluate the function at no rotor.

The result is a scalar when the harmonics were computed for a single rotor, and a Vector of one value per rotor otherwise. Where Y holds a range of spin weights, the row of w's own spin weight is the one used, and the result is as above; weights of one spin weight paired against harmonics of another mean nothing, so a w whose spin weight is not among them is refused.

Y's range of $β„“$ must cover that of w, from the larger of β„“β‚˜α΅’β‚™(w) and $|s|$ up, which is not the same as matching it: sYlm takes an β„“β‚˜α΅’β‚™, and the two need not agree. The entries of w with $β„“ < |s|$ belong to no harmonic and are not read, so a non-finite value there does not reach the result; weights that hold no $β„“ β‰₯ |s|$ at all evaluate to zero.

The harmonics are computed in the floating-point type of the rotor, and the result is of the promoted type of the harmonics and the weights, so weights of higher precision than the rotor are evaluated only to the rotor's precision; convert the rotor to the weights' type where their full precision is wanted.

This product does not conjugate anything, which is why it is * and not ⋅: ⋅ is LinearAlgebra.dot, which conjugates its first argument. It is the labelled form of the product that sYlm_matrix's docstring writes as f = Y * array_view(f̃), and unlike that product of plain arrays it checks the spin weight and the range of $ℓ$ of w.

The calculator form holds one $β„“$ at a time rather than every harmonic at once. A calculator holds workspace, so it must not be used by two tasks at once; similar(calc) gives each task one of its own.

Y = sYlm(R, β„“β‚˜β‚β‚“(w), spin(w); β„“β‚˜α΅’β‚™=β„“β‚˜α΅’β‚™(w))Y * w == w(R)
source

Docstrings

SphericalFunctions.D β€” Function
D(R, β„“β‚˜β‚β‚“; mβ€²β‚˜β‚β‚“=β„“β‚˜β‚β‚“, mβ€²β‚˜α΅’β‚™=-mβ€²β‚˜β‚β‚“, mβ‚˜β‚β‚“=β„“β‚˜β‚β‚“, mβ‚˜α΅’β‚™=-mβ‚˜β‚β‚“)
D(Ξ±, Ξ², Ξ³, β„“β‚˜β‚β‚“; mβ€²β‚˜β‚β‚“=β„“β‚˜β‚β‚“, mβ€²β‚˜α΅’β‚™=-mβ€²β‚˜β‚β‚“, mβ‚˜β‚β‚“=β„“β‚˜β‚β‚“, mβ‚˜α΅’β‚™=-mβ‚˜β‚β‚“)

Wigner's $𝔇^{(β„“)}_{m',m}(R)$ matrices for all $β„“ ≀ β„“β‚˜β‚β‚“$, for the single rotor R, or for the rotor from_euler_angles(Ξ±, Ξ², Ξ³) of the Euler angles $(Ξ±, Ξ², Ξ³)$.

The result is a WignerSeries, indexed by $β„“$ and then naturally by $(m', m)$: D(R, β„“β‚˜β‚β‚“)[β„“][mβ€², m]. Each block is a WignerMatrix; Matrix (or collect) gives the ordinary $(2β„“+1)Γ—(2β„“+1)$ Matrix with rows and columns in order of increasing $m'$ and $m$. The keyword arguments restrict the block of each matrix that is computed, and may also be spelled mp_max, mp_min, m_max and m_min. The convention is $𝔇^{(β„“)}_{m',m}(𝐑_{Ξ±,Ξ²,Ξ³}) = e^{-im'Ξ±}\, d^{(β„“)}_{m',m}(Ξ²)\, e^{-imΞ³}$; see the "Conventions" section of the documentation.

β„“β‚˜β‚β‚“ may be a half-integer, as a Rational with denominator 2 or a HalfOddInteger, as may the keyword limits; then $β„“$ runs over $1/2, 3/2, …, β„“β‚˜β‚β‚“$ rather than $0, 1, …, β„“β‚˜β‚β‚“$. The container types are the same either way, and D(R, β„“β‚˜β‚β‚“)[β„“][mβ€², m] reads the same.

This function allocates all of its output on every call, and takes a single rotor. To evaluate the matrices for many rotors, or to avoid holding every $β„“$ at once, use a DCalculator instead, which allocates once, computes one $β„“$ at a time, and takes a vector of rotors as a batch.

See also d and sYlm.

source
SphericalFunctions.d β€” Function
d(Ξ², β„“β‚˜β‚β‚“; mβ€²β‚˜β‚β‚“=β„“β‚˜β‚β‚“, mβ€²β‚˜α΅’β‚™=-mβ€²β‚˜β‚β‚“, mβ‚˜β‚β‚“=β„“β‚˜β‚β‚“, mβ‚˜α΅’β‚™=-mβ‚˜β‚β‚“)

Wigner's real $d^{(β„“)}_{mβ€²,m}(Ξ²)$ matrices for all $β„“ ≀ β„“β‚˜β‚β‚“$, for the single angle Ξ², which may also be given as the phase $e^{iΞ²}$ or as a Rotor or other Quaternion. The result is indexed as d(Ξ², β„“β‚˜β‚β‚“)[β„“][mβ€², m]. See D for details, including the keyword arguments and their ASCII spellings; this function is the real, $Ξ²$-only analogue.

A Rotor contributes the $Ξ² ∈ [0, Ο€]$ of its canonical Euler decomposition, as described under dCalculator, so d(R, β„“β‚˜β‚β‚“) equals d(Ξ², β„“β‚˜β‚β‚“) for the angle $Ξ²$ used to build R only when $Ξ² ∈ [0, Ο€]$.

β„“β‚˜β‚β‚“ may be a half-integer, as a Rational with denominator 2 or a HalfOddInteger. Half-integer $d$ has period $4Ο€$ in $Ξ²$, so an angle or a Rotor determines it unambiguously, but a bare phase $e^{iΞ²}$ determines it only up to the sign $(-1)^{2β„“}$ (the branch $Ξ² ∈ (-Ο€, Ο€]$ is used).

source
SphericalFunctions.sYlm β€” Function
sYlm(R, β„“β‚˜β‚β‚“, s; β„“β‚˜α΅’β‚™=abs(s))
sYlm(ΞΈ, Ο•, β„“β‚˜β‚β‚“, s; β„“β‚˜α΅’β‚™=abs(s))
sYlm(Rβƒ—, β„“β‚˜β‚β‚“, s; β„“β‚˜α΅’β‚™=abs(s))

The spin-weighted spherical harmonics ${}_sY_{β„“,m}(R)$ for all $β„“β‚˜α΅’β‚™ ≀ β„“ ≀ β„“β‚˜β‚β‚“$ and $-β„“ ≀ m ≀ β„“$, as a HarmonicValues: indexed first by $β„“$ and then naturally, so that sYlm(R, β„“β‚˜β‚β‚“, s)[β„“][m] is one value. With β„“β‚˜α΅’β‚™ below abs(s) the entries for $β„“ < |s|$ are zero. The keyword may also be given as ell_min. The computation is done in the rotor's own floating-point type, and the result is Complex of it; to compute in another type, convert the rotor.

The first argument may be a single rotor or a vector of them, and s may be a single spin weight or an ascending unit range such as -2:2, which between them give a block four shapes:

built forsY[β„“] is indexed
one rotor, one spin weight[m]
many rotors, one spin weight[iα΅£, m]
one rotor, a range of spin weights[s, m]
many rotors, a range of spin weights[iα΅£, s, m]

A vector of rotors gives the batched shapes whatever its length, even one. For a range, β„“β‚˜α΅’β‚™ defaults to the smallest $|s|$ in it, which is 0 (or 1/2) whenever the range straddles zero, so rows below their own $|s|$ are zero.

Underneath, the values are held in one array whose last axis is the modes in the canonical ordering [β‚›Yβ‚—β‚˜ for β„“ ∈ β„“β‚˜α΅’β‚™:β„“β‚˜β‚β‚“ for m ∈ -β„“:β„“] (see Yindex), and whose leading axes are the rotors and spin weights. array_view hands that array back, which is the form a product with mode weights takes; sYlm_matrix is the direct name for it, for callers who want the bare array.

A single rotor may instead be given by the spherical coordinates of its point: sYlm(ΞΈ, Ο•, β„“β‚˜β‚β‚“, s) is sYlm(from_spherical_coordinates(ΞΈ, Ο•), β„“β‚˜β‚β‚“, s). For repeated evaluation use an sYlmCalculator, which allocates once, and give it each new rotor with set_R!. The convention is ${}_sY_{β„“,m} = (-1)^s \sqrt{(2β„“+1)/4Ο€}\, \overline{𝔇^{(β„“)}_{m,-s}}$; see the "Conventions" section of the documentation.

Half-integer indices

β„“β‚˜β‚β‚“ and s may be half-integers, passed as Rationals with denominator 2 or as HalfOddIntegers β€” as in sYlm(R, 7//2, 1//2), or sYlm(R, 7//2, -3//2:3//2) β€” in which case every $β„“$ and $m$ is a half-odd-integer, and so is β„“β‚˜α΅’β‚™, which may be as small as 1//2. The indices in one call must all be of one kind, integers or half-odd-integers; a call that mixes them, such as sYlm(R, 7//2, 1), is refused with an ArgumentError. For half-integer s the prefactor $(-1)^s$ is $i^{2s} = \pm i$, so the values include that phase and are not real multiples of $\overline{𝔇}$; the choice of branch is explained under sYlmCalculator.

source
SphericalFunctions.Ylm β€” Function
Ylm(R, β„“β‚˜β‚β‚“; β„“β‚˜α΅’β‚™=0)
Ylm(ΞΈ, Ο•, β„“β‚˜β‚β‚“; β„“β‚˜α΅’β‚™=0)
Ylm(Rβƒ—, β„“β‚˜β‚β‚“; β„“β‚˜α΅’β‚™=0)

The ordinary scalar spherical harmonics $Y_{β„“,m}(R)$ for all $β„“β‚˜α΅’β‚™ ≀ β„“ ≀ β„“β‚˜β‚β‚“$, as a HarmonicValues indexed by $β„“$ and then by $m$: Ylm(R, β„“β‚˜β‚β‚“)[β„“][m]. The keyword may also be given as ell_min.

These are the spin-weight-zero case of the spin-weighted harmonics; this function is exactly sYlm(R, β„“β‚˜β‚β‚“, 0; β„“β‚˜α΅’β‚™), and everything sYlm says applies here too β€” including that a vector of rotors gives blocks indexed [iα΅£, m], and that Ylm(ΞΈ, Ο•, β„“β‚˜β‚β‚“) is Ylm(from_spherical_coordinates(ΞΈ, Ο•), β„“β‚˜β‚β‚“). See YlmCalculator for repeated evaluation.

The $β„“$ arguments must be integers. Half-integer $β„“$ goes with half-integer spin weight, so there is no half-integer analogue of this function, and a half-integer index is refused with an ArgumentError that says so; see sYlm for half-integer spin weights.

source
SphericalFunctions.sYlm_matrix β€” Function
sYlm_matrix(Rβƒ—, β„“β‚˜β‚β‚“, s; β„“β‚˜α΅’β‚™=abs(s))

The dense matrix of spin-weighted spherical harmonics ${}_sY_{β„“,m}(R_i)$, with rows indexed by the rotors in Rβƒ— and columns by the modes $(β„“, m)$ in the canonical ordering [(β„“, m) for β„“ ∈ β„“β‚˜α΅’β‚™:β„“β‚˜β‚β‚“ for m ∈ -β„“:β„“] (see Yindex). Row i equals sYlm(Rβƒ—[i], β„“β‚˜β‚β‚“, s; β„“β‚˜α΅’β‚™). The keyword may also be given as ell_min. The computation is done in the rotors' own floating-point type; to compute in another type, convert them.

Multiplying this matrix by the array of mode weights synthesizes the corresponding spin-weighted function at the rotors, as Y * array_view(fΜƒ), provided the weights are stored from the same β„“β‚˜α΅’β‚™; the labelled sYlm(Rβƒ—, β„“β‚˜β‚β‚“, s) * fΜƒ does the same, and checks the labels. The (pseudo)inverse of the matrix performs the analysis. It is computed with all rotors batched, which is the efficient path; for $β„“β‚˜β‚β‚“ ≳ 64$ the matrix becomes large and the transforms in the "Transformations" section of the documentation should be preferred.

The argument s may also be a range of spin weights (ascending, and with step size 1). The result is then three-dimensional, indexed [rotor, spin, mode], and is a stack of the matrices above rather than one of them. The spin index is an ordinary 1-based position, and β„“β‚˜α΅’β‚™ defaults to the smallest $|s|$ in the range, which is 0 (or 1/2) for a range that straddles zero. Y[:, i, :] is the synthesis matrix of spin weight s[i] for weights stored from that same β„“β‚˜α΅’β‚™. Weights that start at their own $|s[i]|$, as those the transforms return do, need that β„“β‚˜α΅’β‚™ passed explicitly; the labelled sYlm(Rβƒ—, β„“β‚˜β‚β‚“, s) * w picks the right spin row and $β„“$ range itself.

The indices may be half-integers, passed as Rationals with denominator 2 or as HalfOddIntegers, on the terms described under sYlm: all of one kind, with β„“β‚˜α΅’β‚™ defaulting to the smallest $|s|$ and the values including the phase $i^{2s}$. The columns are then indexed by half-odd $(β„“, m)$ in the same canonical ordering, and Yindex locates them as before.

source
SphericalFunctions.DCalculator β€” Type
DCalculator(R, β„“β‚˜β‚β‚“; mβ€²β‚˜β‚β‚“=β„“β‚˜β‚β‚“, mβ€²β‚˜α΅’β‚™=-mβ€²β‚˜β‚β‚“, mβ‚˜β‚β‚“=β„“β‚˜β‚β‚“, mβ‚˜α΅’β‚™=-mβ‚˜β‚β‚“)
DCalculator(Ξ±, Ξ², Ξ³, β„“β‚˜β‚β‚“; mβ€²β‚˜β‚β‚“=β„“β‚˜β‚β‚“, mβ€²β‚˜α΅’β‚™=-mβ€²β‚˜β‚β‚“, mβ‚˜β‚β‚“=β„“β‚˜β‚β‚“, mβ‚˜α΅’β‚™=-mβ‚˜β‚β‚“)

Calculator for Wigner's $𝔇^{(β„“)}_{mβ€²,m}(R)$ matrices, for $β„“ ≀ β„“β‚˜β‚β‚“$, with elements of type Complex{RT}. The keyword arguments restrict the block of each matrix that is computed and returned; they may also be spelled mp_max, mp_min, m_max and m_min. Restricting either the rows $mβ€²$ or the columns $m$ to a narrow band also narrows the recurrence itself, which at each $β„“$ costs in proportion to the narrower of the two ranges.

The rotor is given at construction, so that the calculator is usable the moment it exists, and so that the element type is the rotor's own: a Rotor{Float32} gives a Float32 calculator, and floattype(calc) reports it. There is no argument to override that β€” to compute in another type, convert the rotor, which is the honest way to say that those are the values to be treated as exact. Give R as an AbstractVector of rotors to get a calculator that handles all Nα΅£ = length(R) of them at once β€” which is substantially faster per rotor, for the reason described under Reusing the workspace. Later rotors are supplied with set_R!. A single rotor may instead be given by its Euler angles: DCalculator(Ξ±, Ξ², Ξ³, β„“β‚˜β‚β‚“) is DCalculator(from_euler_angles(Ξ±, Ξ², Ξ³), β„“β‚˜β‚β‚“).

The calculator is iterable, yielding one $β„“$ at a time:

calc = DCalculator(R, β„“β‚˜β‚β‚“)for (β„“, 𝔇ˑ) ∈ calc    # 𝔇ˑ[mβ€², m] with mβ€², m ∈ -β„“:β„“end

For a calculator built from a vector of rotors β€” of any length, even one β€” each block is indexed as [iα΅£, mβ€², m] instead. The block is a view into the calculator's storage and is overwritten by the next step, so copy it if it must survive (the copy keeps the natural indices), or collect it to get an ordinary 1-based Array: a Matrix, or a three-dimensional Array for a block with a rotor index. collect(calc) copies every block, so it is safe. To sweep part of the range, or to take the values of $β„“$ in some other order, loop over recurrence! yourself.

A calculator is a mutable workspace, which every step and every setter overwrites, so one calculator must not be used by two tasks at once; similar(calc) gives each task a calculator of its own, holding the same rotor data.

The convention is $𝔇^{(β„“)}_{mβ€²,m}(𝐑_{Ξ±,Ξ²,Ξ³}) = e^{-imβ€²Ξ±}\, d^{(β„“)}_{mβ€²,m}(Ξ²)\, e^{-imΞ³}$; see the "Conventions" section of the documentation.

Half-integer indices

DCalculator(R, 7//2) β€” a Rational β„“β‚˜β‚β‚“ with denominator 2, or a HalfOddInteger β€” gives a calculator for half-integer $β„“, mβ€², m$. All four keyword limits must then be half-integers too, in either spelling; recurrence! accepts only half-integer β„“ and returns a WignerMatrix (or a WignerMatrixBatch when built from a vector) whose indices are half-odd-integers; it is indexed the same way. The double cover is respected exactly: $𝔇(-R) = -𝔇(R)$.

See also D for a simpler interface when the matrices for only one rotor are needed, dCalculator for the real $d$ matrices, and recurrence!.

source
SphericalFunctions.dCalculator β€” Type
dCalculator(Ξ², β„“β‚˜β‚β‚“; mβ€²β‚˜β‚β‚“=β„“β‚˜β‚β‚“, mβ€²β‚˜α΅’β‚™=-mβ€²β‚˜β‚β‚“, mβ‚˜β‚β‚“=β„“β‚˜β‚β‚“, mβ‚˜α΅’β‚™=-mβ‚˜β‚β‚“)

Calculator for Wigner's real $d^{(β„“)}_{mβ€²,m}(Ξ²)$ matrices, for $β„“ ≀ β„“β‚˜β‚β‚“$, with elements of the angle's own floating-point type. The first argument may be the angle $Ξ²$, the phase $e^{iΞ²}$, or a Rotor or other Quaternion, or an AbstractVector of Nα΅£ of any one of those; later values are supplied with set_Ξ²!. Otherwise this behaves exactly like DCalculator β€” including iteration, the ASCII spellings of the keywords, and half-integer $β„“$ for a Rational or HalfOddInteger β„“β‚˜β‚β‚“.

A Rotor contributes the $Ξ² ∈ [0, Ο€]$ of its canonical Euler decomposition. If the rotor was built from a $Ξ²$ outside that range, that $Ξ²$ is folded into its $Ξ±$ and $Ξ³$, which a $d$ matrix does not see, so d of such a rotor equals d of its angle only for $Ξ² ∈ [0, Ο€]$; this is consistent with D of the same rotor.

Half-integer $d$ has period $4Ο€$ in $Ξ²$, so an angle or a Rotor determines it unambiguously, while a bare phase $e^{iΞ²}$ determines it only up to the double-cover sign $(-1)^{2β„“}$; the branch $Ξ² ∈ (-Ο€, Ο€]$ is used in that case.

See also d.

source
SphericalFunctions.HCalculator β€” Type
HCalculator(Ξ², β„“β‚˜β‚β‚“; mβ€²β‚˜β‚β‚“=β„“β‚˜β‚β‚“)

Engine for the Gumerov–Duraiswami recurrences, computing the $H$ wedge (see HWedge) for one value of $β„“$ at a time, for Nα΅£ rotors simultaneously.

The parameters of the type HCalculator{IT, RT, ST} are as follows:

  • IT is the index type, Int or HalfOddInteger.
  • RT is the real type the calculator works in.
  • ST is the storage type of the $H$ wedge.

The $H$ matrix depends on the rotor only through $Ξ²$, so the calculator stores one phase $e^{iΞ²}$ per rotor. The first argument supplies them: an angle $Ξ²$, a phase $e^{iΞ²}$, a Rotor or Quaternion, or an AbstractVector of any one of those β€” and it is the vector case that makes the calculator handle Nα΅£ = length(Ξ²) rotors at once. A Rotor contributes the $Ξ² ∈ [0, Ο€]$ of its canonical Euler decomposition; if the rotor was built from a $Ξ²$ outside that range, that $Ξ²$ is folded into its $Ξ±$ and $Ξ³$, which this calculator does not see. Later values are supplied by set_Ξ²!. The wedge is stored for $|mβ€²| ≀ mβ€²β‚˜β‚β‚“$ and $m β‰₯ |mβ€²|$; every other element of the $H$ matrix is obtained by symmetry through wedge_value. The keyword may also be spelled mp_max.

The element type is the rotor data's own: an angle given as a Float32 gives a Float32 calculator, and there is no argument to override that. To compute in another type, convert the data β€” HCalculator(Double64(Ξ²), β„“β‚˜β‚β‚“) β€” which says what is meant, that these are the values to treat as exact. floattype(calc) reports the type in use.

This is the low-level calculator of $H$ shared by DCalculator, dCalculator, and the spin-weighted spherical harmonics; most users will want one of those instead.

Usage

calc = HCalculator(rotors, β„“β‚˜β‚β‚“)for β„“ ∈ 0:β„“β‚˜β‚β‚“    recurrence!(calc, β„“)            # advance to the next β„“ (cheap when sequential)    H = calc.HΛ‘                     # HWedge for the current β„“; H[iα΅£, mβ€², m] for m β‰₯ |mβ€²|end

Unlike DCalculator and the other calculators built on it, this one is not iterable: its wedge is one mutable object handed back by identity, rather than a view that copy can preserve. The wedge belongs to the calculator, so its β„“ must not be reassigned; copy(calc.HΛ‘) gives an independent wedge that survives the next step.

Requesting an $β„“$ smaller than the current one restarts the recurrence from $β„“_{min}$; jumping forward advances through the intermediate values. Nothing is allocated after construction.

A calculator is a mutable workspace, which every step and every setter overwrites, so one calculator must not be used by two tasks at once; similar(calc) gives each task a calculator of its own.

Half-integer indices

Passing a Rational β„“β‚˜β‚β‚“ with denominator 2 β€” HCalculator(Ξ², 7//2) β€” or a HalfOddInteger gives a calculator for half-integer $β„“, mβ€², m$. Then mβ€²β‚˜β‚β‚“ must also be a half-integer, in either spelling, and recurrence!(calc, β„“) accepts only half-integer β„“. The recurrence is the same one: the $m'=0$ axis is run at the integer order $j = β„“ - 1/2$, the rows $m' = Β±1/2$ are seeded from it by a Clebsch–Gordan step, and the $m'$ ladder then proceeds unchanged (see the notes on the $H$ recursion). Note that the $H$ matrix is then symmetric only up to the sign $Οƒ = \mathrm{sgn}(m)\,\mathrm{sgn}(m')$ (see transpose_sign), which wedge_value applies for you.

Because half-integer $d$ has period $4Ο€$ in $Ξ²$, a rotor or an angle $Ξ²$ determines it unambiguously, but a bare phase $e^{iΞ²}$ determines $Ξ²$ only modulo $2Ο€$ and hence $d$ only up to the double-cover sign $(-1)^{2β„“}$; the branch $Ξ² ∈ (-Ο€, Ο€]$ is used.

source
SphericalFunctions.sYlmCalculator β€” Type
sYlmCalculator(R, β„“β‚˜β‚β‚“, s)
sYlmCalculator(ΞΈ, Ο•, β„“β‚˜β‚β‚“, s)

Calculator for the spin-weighted spherical harmonics ${}_sY_{β„“,m}$ for all $β„“ ≀ β„“β‚˜β‚β‚“$, with elements of Complex of the rotor's own element type. The spin weight s is either a single value or an ascending unit range of them, such as -2:2; the calculator serves exactly those, and works out for itself how much of the underlying recurrence they need.

The rotor is given at construction, so that the calculator is usable the moment it exists, and so that the element type is the rotor's own: a Rotor{Float32} gives a Float32 calculator, and floattype(calc) reports it. There is no argument to override that β€” to compute in another type, convert the rotor. Give R as an AbstractVector of rotors to get a calculator that handles all Nα΅£ = length(R) of them at once. Later rotors are supplied with set_R!. A single rotor may instead be given by the spherical coordinates of its point: sYlmCalculator(ΞΈ, Ο•, β„“β‚˜β‚β‚“, s) is sYlmCalculator(from_spherical_coordinates(ΞΈ, Ο•), β„“β‚˜β‚β‚“, s).

Only one $β„“$ is held at a time, and iteration is how to step through them:

calc = sYlmCalculator(R, β„“β‚˜β‚β‚“, 2)for (β„“, β‚›Yβ‚—) ∈ calc    # β‚›Yβ‚—[m] is available for m ∈ -β„“:β„“endcalc = sYlmCalculator(R, β„“β‚˜β‚β‚“, -2:2)for (β„“, β‚›Yβ‚—) ∈ calc    # β‚›Yβ‚—[s, m] is available for s ∈ -2:2 and m ∈ -β„“:β„“end

For a calculator built from a vector of rotors or angles β€” of any length, even one β€” each block gains a leading rotor index, so it is read as β‚›Yβ‚—[iα΅£, m] or β‚›Yβ‚—[iα΅£, s, m]. The same block is what recurrence! returns when the calculator is stepped by hand, and β‚›Yβ‚—[s, :] picks out the row of one spin weight of a calculator built for several. Each block is a view into the calculator's storage, overwritten by the next step; copy it if it must survive (keeping the natural indices), or collect it to get an ordinary 1-based array. Wherever $β„“ < |s|$ the values are zero.

spins reports the range of spin weights served, and spin the single value when there is only one. Lengthening the range costs storage and arithmetic in proportion to its length, but it is the largest $|s|$ alone that decides the size of the underlying Wigner $H$ wedge: sYlmCalculator(R, β„“β‚˜β‚β‚“, 2) and sYlmCalculator(R, β„“β‚˜β‚β‚“, -2:2) run precisely the same recurrence, and differ only in how much of it is read out.

Instead of rotors, the angle $ΞΈ$ (or a vector of Nα΅£ angles) may be given, at construction or through set_ΞΈ!. This evaluates the harmonics at $(ΞΈ, Ο•=0)$, which is what the ring-based transforms need β€” though the values are still stored as complex numbers, and a calculator that will only ever be used that way is better built as an sΞ»lmCalculator, which stores them as reals.

A calculator is a mutable workspace, which every step and every setter overwrites, so one calculator must not be used by two tasks at once; similar(calc) gives each task a calculator of its own, holding the same rotor data.

The harmonics are defined by

\[{}_sY_{β„“,m}(𝐑) = (-1)^s \sqrt{\frac{2β„“+1}{4Ο€}}\, \overline{𝔇^{(β„“)}_{m,-s}(𝐑)},\]

so that they are functions on the rotation group, and the harmonics at the spherical coordinates $(ΞΈ, Ο•)$ are those at R = from_spherical_coordinates(ΞΈ, Ο•). See the "Conventions" section of the documentation.

Half-integer indices

β„“β‚˜β‚β‚“ and the spin weights may all be half-integers, passed as Rationals with denominator 2 or as HalfOddIntegers, in which case $β„“$, $m$ and $s$ are all half-integers. A range is written the same way, as -3//2:3//2. The prefactor $(-1)^s$ is then $\pm i$; the principal branch $(-1)^s ≑ e^{iΟ€s} = i^{2s}$ settled in the "Conventions" section is used, so the harmonics are not real multiples of $\overline{𝔇}$, and ${}_sY_{β„“,m}(ΞΈ, 0)$ is therefore not real (see sΞ»lmCalculator, which divides that constant phase out). The blocks are the same containers as for integer indices β€” a DegreeBlock or DegreeBlockBatch for one spin weight, and a SpinMatrix or SpinMatrixBatch for several β€” indexed exactly as described above. The flat interfaces sYlm and sYlm_matrix accept the same types, and lay the values out in the canonical mode-weight ordering of Yindex, which holds for half-odd indices exactly as it does for integers.

See also sYlm and sYlm_matrix for simpler interfaces, and DCalculator.

source
SphericalFunctions.YlmCalculator β€” Function
YlmCalculator(R, β„“β‚˜β‚β‚“)
YlmCalculator(ΞΈ, Ο•, β„“β‚˜β‚β‚“)

Calculator for the ordinary scalar spherical harmonics $Y_{β„“,m}$, for $β„“ ≀ β„“β‚˜β‚β‚“$.

This is exactly sYlmCalculator(R, β„“β‚˜β‚β‚“, 0), or sYlmCalculator(ΞΈ, Ο•, β„“β‚˜β‚β‚“, 0), and returns an sYlmCalculator rather than a type of its own β€” there is nothing about spin weight zero to specialize. Everything sYlmCalculator documents applies, so blocks are indexed Yβ‚—[m] (or Yβ‚—[iα΅£, m] for a collection of rotors), and it iterates as for (β„“, Yβ‚—) ∈ calc.

$β„“β‚˜β‚β‚“$ must be an integer: a half-integer $β„“$ goes with a half-integer spin weight, so spin weight zero has no half-integer analogue, and a half-integer β„“β‚˜β‚β‚“ is refused with an ArgumentError that says so. See sYlmCalculator for those.

source
SphericalFunctions.sΞ»lmCalculator β€” Type
sΞ»lmCalculator(ΞΈ, β„“β‚˜β‚β‚“, s)
slambdalmCalculator(ΞΈ, β„“β‚˜β‚β‚“, s)

Calculator for the real functions

\[{}_sΞ»_{β„“,m}(ΞΈ) = \begin{cases} {}_sY_{β„“,m}(ΞΈ, 0), & s ∈ β„€, \\ {}_sY_{β„“,m}(ΞΈ, 0) \big/ i^{2s}, & s ∈ β„€ + \tfrac{1}{2}, \end{cases}\]

for all $β„“ ≀ β„“β‚˜β‚β‚“$, with elements of the angle's own real type. The first argument is the angle $ΞΈ$, or an AbstractVector of Nα΅£ of them; later values are supplied with set_ΞΈ!. Otherwise this behaves exactly like sYlmCalculator β€” the same blocks, the same iteration, the same half-integer types β€” but stores real numbers rather than complex ones, and allocates no phase tables at all. slambdalmCalculator is an ASCII alias of the same constructor.

Both are real. For an integer spin weight the prefactor $(-1)^s$ in the definition of ${}_sY_{β„“,m}$ is $\pm 1$, so ${}_sY_{β„“,m}(ΞΈ, 0)$ is already real, and ${}_sΞ»_{β„“,m}$ is just the harmonic evaluated at $Ο• = 0$, exactly as the literature writes it. For a half-odd spin weight that prefactor is $i^{2s} = \pm i$, so ${}_sY_{β„“,m}(ΞΈ, 0)$ is imaginary rather than real, and dividing that constant phase out is what leaves a real function behind. (Dividing by $i^{2s}$ in both cases would give the wrong sign for odd integer $s$, where $i^{2s} = -1$.) The values are bit-for-bit those of an sYlmCalculator at the same angles: the real part for an integer spin weight, and $Β±$ the imaginary part for a half-odd one.

A Rotor is not accepted, here or through set_R!: a rotor specifies the angles $Ξ±$ and $Ξ³$, whose phases a real calculator cannot represent. Use an sYlmCalculator for that.

See also dCalculator, which stands in the same relation to DCalculator.

source
SphericalFunctions.HarmonicCalculator β€” Type
HarmonicCalculator{IT, RT, NT, ST, S, B, FT, L}

Calculator producing the spin-weighted spherical harmonics ${}_sY_{β„“,m}$ (when NT is Complex{RT}) or the real ${}_sΞ»_{β„“,m}$ (when NT is RT), for Nα΅£ points at a time, one $β„“$ at a time. Use the constructors sYlmCalculator and sΞ»lmCalculator.

  • IT is the index type, Int or HalfOddInteger.
  • RT is the real type the calculator works in.
  • NT is the number type of the harmonics, Complex{RT} or RT.
  • ST is the storage type of the $H$ wedge.
  • S is the type of the spin weights served: the index type for one spin weight, or a UnitRange of it for a range of them.
  • B is true exactly when the calculator was built from a vector of rotor data, and is what isbatched reads.
  • FT is the real type in which the recurrence runs, which is RT itself unless RT holds derivatives, as a dual number does.
  • L is Nothing, unless the calculator lifts the blocks of a calculator of the values of its rotors into blocks that hold derivatives, when it is the type of the data for that.

Internally this holds an HCalculator, which runs the recurrence that both subtypes use, and the tables Zβ‚Š and Zβ‚‹ of the powers of the rotors' phases, in the same form as the calculators of Wigner's matrices, plus a buffer backing the block for the current $β„“$. The tables are empty for the real subtype, which is the whole of the saving: the $H$ recurrence is real, and it is only the $e^{-i(mΞ± - sΞ³)}$ factor that ever made the result complex.

Because S and B decide the shape of the block, the type of the block is known at compile time, and a loop over the blocks is inferrable.

source
SphericalFunctions.recurrence! β€” Function
recurrence!(calc, R, β„“)
recurrence!(calc, β„“)

Compute the quantities for index $β„“$ in the calculator calc, and return the block holding them.

In the first form, the rotor data R is stored in the calculator first. For a calculator with Nα΅£ rotors, R is an AbstractVector of length Nα΅£; for Nα΅£ = 1 a single element is also accepted. The elements may be Rotors or Quaternions, or β€” for quantities that depend only on $Ξ²$, such as $d$ and $H$ β€” the angle $Ξ²$ or the phase $e^{iΞ²}$.

In the second form, the rotor data from the previous call is reused. Successive calls with $β„“, β„“+1, β„“+2, …$ are the cheap path: each costs $O(N_r β„“^2)$. Requesting a smaller $β„“$ than the current one restarts the recurrence from $β„“_{min}$, so a loop that reads two neighboring $β„“$ together pays that restart at every step.

This is the only way to read a calculator by hand β€” the calculators are not indexed β€” and it is what iterating one calls for each $β„“$:

for β„“ ∈ 2:β„“β‚˜β‚β‚“    𝔇ˑ = recurrence!(calc, β„“)    # 𝔇ˑ[mβ€², m] for mβ€², m ∈ -β„“:β„“end

What comes back depends on the calculator, and in every case it is indexed by the natural ranges rather than from 1:

calculatorblock
DCalculator, dCalculatorWignerMatrix, [mβ€², m]
sYlmCalculator for one spin weightDegreeBlock, [m]
sYlmCalculator for a range of themSpinMatrix, [s, m]
HCalculatorHWedge, [iα΅£, mβ€², m] for $m β‰₯ |mβ€²|$

For a calculator built from a vector of rotor data β€” of any length, even one β€” each of the others gains a leading rotor index, so that the first is [iα΅£, mβ€², m]; an HWedge has that index even for a single rotor, where it is 1. One spin weight of a block that holds several is β‚›Yβ‚—[s, :].

Warning

For every calculator but HCalculator the block is a view into storage that the next call overwrites, so copy it if it must outlive the step (the copy keeps the natural indices), or collect it for an ordinary 1-based array. An HCalculator is the exception, and the sharper case: its wedge is one mutable object handed back by identity, so every call returns the same object, laid out afresh for the new $β„“$. copy(HΛ‘) gives an independent wedge that survives the next step; the wedge itself belongs to the calculator, and its β„“ must not be reassigned.

β„“ must be an index of the calculator's own kind β€” an Int for a calculator built with integer indices, and a half-odd-integer, as a HalfOddInteger or a Rational{Int} with denominator 2, for one built with half-integer indices β€” and must lie between β„“β‚˜α΅’β‚™(calc) and β„“β‚˜β‚β‚“(calc); anything else is refused with an ArgumentError. An index that is not of the calculator's kind, including an integer of another type such as an Int8 and a floating-point number such as 2.0, is refused with a message that says how to write it, as it is by every function that takes an index.

The rotor data given this way must be of the calculator's own floating-point type, exactly as for set_R!, set_Ξ²! and set_ΞΈ!, and anything else is refused rather than converted. A phase given as a Complex number must have unit modulus (to within rounding), and is used as given.

source
SphericalFunctions.set_R! β€” Function
set_R!(calc, R)

Replace the rotor data of calc with the rotor R, and return calc. Any results the calculator was holding are discarded, so the next step of the recurrence starts from $β„“_{min}$ again.

R must be a Rotor, or an AbstractVector of exactly as many rotors as the calculator handles, which is required when the calculator was built from a vector of rotors; a vector of one rotor is accepted by a calculator built from a single one. A Quaternion may also stand in for a rotor, as the rotation of its normalization; a QuatVec is refused, as not_a_rotor explains. The reason we must accept Quaternions is that some automatic-differentiation packages require the tangent of a type to have the same type; the tangent to the space of Rotors is not a Rotor, but a Quaternion, so we must accept Quaternions to begin with. The number of rotors is fixed at construction and cannot be changed here; for a different number of rotors, build another calculator.

This applies to DCalculator and sYlmCalculator, both of which need the whole rotor. The real $d$ matrices and the $H$ wedge depend on $Ξ²$ alone, so their calculators take set_Ξ²! instead, and the $(ΞΈ, Ο•=0)$ evaluation of the harmonics takes set_ΞΈ!; an angle given to set_R! is refused with a message naming it.

The new rotor's floating-point type must be the one the calculator already works in, which was fixed by the rotor it was built from. A mismatch is an error rather than a silent conversion β€” narrowing a BigFloat rotor into a Float64 calculator would throw away precision that nobody chose to throw away β€” so convert whichever side you meant, or build a calculator from data of the type you want. floattype(calc) reports the type in use.

Everything is checked before anything is replaced, so data that are refused leave the calculator exactly as it was, including whatever it had computed.

calc = DCalculator(first(rotors), β„“β‚˜β‚β‚“)for R ∈ rotors    set_R!(calc, R)    for (β„“, 𝔇ˑ) ∈ calc        # ...    endend
source
SphericalFunctions.set_Ξ²! β€” Function
set_Ξ²!(calc, Ξ²)
set_beta!(calc, Ξ²)

Replace the rotor data of calc with the angle Ξ², and return calc. Any results the calculator was holding are discarded. set_beta! is an ASCII alias of the same function.

Ξ² may be given as the angle itself, as the phase $e^{iΞ²}$, or as a Rotor or other Quaternion, of which only the $Ξ²$ Euler angle is kept β€” the $d$ matrices and the $H$ wedge depend on nothing else. A Rotor contributes the $Ξ² ∈ [0, Ο€]$ of its canonical Euler decomposition; if the rotor was built from a $Ξ²$ outside that range, that $Ξ²$ is folded into its $Ξ±$ and $Ξ³$, which are discarded here. For a calculator built from a vector, pass an AbstractVector of exactly as many of any one of those forms; a vector of one is accepted by a calculator built from a single value.

This applies to dCalculator and HCalculator. Wigner's $𝔇$ matrices and the spin-weighted harmonics need the whole rotor, so their calculators take set_R! instead.

As for set_R!, the new value's floating-point type must match the calculator's, and data that are refused leave the calculator as it was. An angle that is infinite or NaN has no phase, and a phase whose modulus is not 1 is not one; both are refused with a DomainError.

Half-integer $d$ has period $4Ο€$ in $Ξ²$, so an angle or a rotor determines it unambiguously, while a bare phase determines it only up to the double-cover sign $(-1)^{2β„“}$; the branch $Ξ² ∈ (-Ο€, Ο€]$ is used in that case.

source
SphericalFunctions.set_ΞΈ! β€” Function
set_ΞΈ!(calc, ΞΈ)
set_theta!(calc, ΞΈ)

Replace the rotor data of the sYlmCalculator or sΞ»lmCalculator calc with the angle ΞΈ, meaning the point $(ΞΈ, Ο• = 0)$, and return calc. Any results the calculator was holding are discarded. set_theta! is an ASCII alias of the same function.

For an sYlmCalculator the values are then ${}_sY_{β„“,m}(ΞΈ, 0)$, stored as complex numbers. For an integer spin weight these are the real functions ${}_sΞ»_{β„“,m}(ΞΈ)$ that the ring-based transforms are built on, with zero imaginary part; for a half-odd one they are $i^{2s}$ times ${}_sΞ»_{β„“,m}(ΞΈ)$, and so imaginary. An sΞ»lmCalculator gives ${}_sΞ»_{β„“,m}(ΞΈ)$ directly, as real numbers. For a calculator built from a vector, pass an AbstractVector of exactly as many angles; a vector of one is accepted by a calculator built from a single angle.

As for set_R!, the angle's floating-point type must match the calculator's, and data that are refused leave the calculator as it was. An angle that is infinite or NaN is refused with a DomainError, and a rotor with an ArgumentError that names the setter for rotors.

For a general point on the sphere use set_R! with from_spherical_coordinates(ΞΈ, Ο•).

source

Containers

The types that D, d, sYlm and recurrence! return, for either kind of index, and their abstract supertypes: AbstractBlock for the blocks of one $β„“$, and AbstractDegreeSeries for the series of blocks indexed by $β„“$.

These containers are deliberately not AbstractArrays. Half-odd indices cannot satisfy that interface at all β€” axes must be integer ranges, and -3//2:3//2 is not one β€” but the reason they are not arrays on the integer path either is a sharper one. There the natural array would be an OffsetArray, and an OffsetArray with non-trivial offsets accepts * and mul! and returns silently wrong answers: a product of two blocks comes back as a 1-based Matrix of mostly zeros, and an adjoint product comes back holding uninitialized memory.

How to get results as `Array`s

The package provides array_view to get a 1-based StridedArray view of the storage, and relabel to put the natural indices back on the result. Matrix(𝔇[β„“]) gives an independent copy of the storage. The reason for that arrangement is discussed in the Containers section.

SphericalFunctions.array_view β€” Function
array_view(w)

The contents of w as an ordinary 1-based array, aliasing its storage (returning a view) rather than copying it.

The containers in this package are deliberately not AbstractArrays, because their natural indices may be half-odd-integers; this is the explicit route from one of them to the plain numbers, with the $β„“$, $mβ€²$, $m$ and $s$ labels dropped and every axis starting at

  1. For a ModeWeights or the result of sYlm it is the flat array in the

canonical mode ordering of Yindex, which is what the transforms take:

Y = sYlm(R, β„“β‚˜β‚β‚“, s)array_view(Y)[Yindex(β„“, m, β„“β‚˜α΅’β‚™)]      # one mode of the flat array

An ordinary 1-based array is returned unchanged, so a function can accept either a labelled container or a bare array without asking which it was given. An array with other axes, such as an OffsetArray, is refused with an ArgumentError, because the result is indexed from 1.

For a calculator's blocks, the blocks of D and d, and the results of copy, similar, and relabel, the result is a contiguous StridedArray, which is the second reason to want it: BLAS needs a unit stride down the first axis and a constant stride between columns β€” not contiguity β€” so a block can go straight to mul!, lu!, norm, and the rest with no copy at all:

𝔇₃ = array_view(𝔇₁[β„“]) * array_view(𝔇₂[β„“])

The array_view of a block that a calculator returns is moreover linearly indexed, so that sum, broadcasting, and copyto! run over it as fast as over an Array, and a batch can be reshaped to a matrix of Nα΅£ times as many rows for BLAS without a copy.

A single rotor's slice of a batched block has a leading stride of Nα΅£, so BLAS cannot take it; mul! then falls back to the generic implementation, which is slower but correct. The answer is never wrong, only sometimes slow.

Warning

The result aliases the storage of w, so writing through it writes into w. When w came from a calculator, that storage is overwritten by the next call to recurrence! β€” use Array(w) or collect(w) for a copy that survives, or Matrix(w) for a two-axis block.

See also relabel, which is the return leg.

source
SphericalFunctions.relabel β€” Function
relabel(w, A)

A container with the natural indices of w and the data of the plain array A, which must have the shape of w.

This is the return leg of a trip through linear algebra, so that a result comes back labelled rather than as a bare array:

𝔇₃ = relabel(𝔇₁[β„“], array_view(𝔇₁[β„“]) * array_view(𝔇₂[β„“]))

A is used as storage, not copied, exactly as the container constructors do.

The labels are those of w, whatever A holds. For a ModeWeights that includes the spin weight, so the result of a spin-changing operator matrix applied to array_view(w), such as Γ°(s, β„“β‚˜α΅’β‚™, β„“β‚˜β‚β‚“) * array_view(w), is labelled correctly only by ModeWeights(A, s + 1, β„“β‚˜α΅’β‚™, β„“β‚˜β‚β‚“); the operator itself, Γ° * w, gives the labelled result directly.

See also array_view.

source
SphericalFunctions.AbstractBlock β€” Type
AbstractBlock{IT, NT, ST}

Abstract supertype of the blocks, the containers that hold the values of one $β„“$, indexed by their natural indices $mβ€²$, $m$ and $s$ rather than by position. There are six: WignerMatrix, indexed w[mβ€², m], WignerMatrixBatch, w[iα΅£, mβ€², m], DegreeBlock, v[m], DegreeBlockBatch, v[iα΅£, m], SpinMatrix, b[s, m], and SpinMatrixBatch, b[iα΅£, s, m].

  • IT is the index type, Int or HalfOddInteger, which is the type of $β„“$ and of every natural index.
  • NT is the number type, such as ComplexF64 for a block of $𝔇$ or Float64 for one of $d$.
  • ST is the type of the storage, parent(w), which is one-dimensional and 1-based (see below).

A block's storage is one-dimensional, and element k of the block, in the order of Array(w) (the column-major order of its axes), is entry k of the storage; entries beyond length(w) are not read. The constructors take a 1-based array of any shape with at least length(w) entries and use it in its linear order: the storage is vec of that array, which shares its memory, and an array of exactly the block's shape is the block. A calculator's blocks are the leading entries of the calculator's one buffer, so parent(w) of such a block is that buffer. The blocks of D and d are consecutive parts of one vector, which holds them all, so parent(w) of each is a view of its own part. array_view(w) is the block's entries of the storage, reshaped to the block's shape, without a copy: contiguous for a calculator's block, for the blocks of D and d, and for the results of copy, similar, and relabel. A slice of a batch, w[iα΅£] or b[iα΅£], and the row b[s, :] of a SpinMatrix, are views of an arithmetic progression of the storage, with the step Nα΅£ or the number of spin weights. The one exception is the slice b[:, s, :] of a SpinMatrixBatch, whose elements are not one such progression: its storage is a strided matrix view of exactly its shape, and its array_view is that matrix.

These types are not AbstractArrays, because half-integer indices cannot satisfy that interface: axes must be integer ranges there, and -3//2:3//2 is not one. Consequently linear algebra (*, ', \, lu) does not apply to them; array_view gives the numbers as an ordinary 1-based array, and relabel puts the labels back.

Methods

Every block has parent(w), the storage; β„“(w), the degree; β„“β‚˜α΅’β‚™(w), the smallest degree of the index type, 0 or 1//2; eltype(w), the number type; ishalfinteger; and summary and show. Each block defines its own indexing, since the natural indices differ from one to the next, and all six share an array-like interface as well:

  • axes(w) are the ranges of the natural indices, one per axis, with 1:Nα΅£ for the rotor axis of a batch, and size(w), length(w) and ndims(w) describe the block, whose storage may be larger, as a calculator's buffer is;
  • the limits are read with mβ€²β‚˜β‚β‚“, mβ€²β‚˜α΅’β‚™, mβ‚˜β‚β‚“, mβ‚˜α΅’β‚™, sβ‚˜β‚β‚“ and sβ‚˜α΅’β‚™, for the axes a block has, and isbatched says whether it has a rotor axis;
  • checkbounds(Bool, w, inds...) tests natural indices;
  • Array(w) and collect(w) copy the block into an ordinary 1-based array, as do Matrix(w) for a two-axis block and Vector(v) for a DegreeBlock;
  • copy(w) and similar(w) keep the labels, and iteration visits the elements in the order of Array(w);
  • ==, isequal, hash and β‰ˆ compare the labels β€” the kind of block, $β„“$ and the axes β€” as well as the numbers, so the same numbers under different labels are not equal;
  • broadcasting gives an ordinary 1-based Array, and a broadcast that combines blocks, or writes into one with .=, requires their labels to agree, since it pairs the elements by position.

Note that there is deliberately no linear indexing (w[i]), for the reason given above.

source
SphericalFunctions.WignerMatrix β€” Type
WignerMatrix{IT, NT, ST} <: AbstractBlock{IT, NT, ST}
WignerMatrix(parent, β„“; mβ€²β‚˜β‚β‚“=β„“, mβ€²β‚˜α΅’β‚™=-mβ€²β‚˜β‚β‚“, mβ‚˜β‚β‚“=β„“, mβ‚˜α΅’β‚™=-mβ‚˜β‚β‚“)

General concrete subtype of AbstractBlock for Wigner rotation matrices, which can include D-matrices (when NT is complex) or d-matrices (when NT is real).

  • IT is the index type, Int or HalfOddInteger.
  • NT is the number type of the elements.
  • ST is the type of the storage, an AbstractVector{NT} (see AbstractBlock).

The block holds some or all of the Wigner matrix for a specific $β„“$, as a rectangular array whose first dimension corresponds to the mβ€² index and whose second corresponds to the m index; how it is stored is described under AbstractBlock. The allowed ranges of mβ€² and m are governed by the fields mβ€²β‚˜β‚β‚“, mβ€²β‚˜α΅’β‚™, mβ‚˜β‚β‚“, and mβ‚˜α΅’β‚™, which must satisfy

\[\begin{aligned} -β„“β‚˜β‚β‚“ &≀ mβ€²β‚˜α΅’β‚™ ≀ -β„“β‚˜α΅’β‚™ ≀ β„“β‚˜α΅’β‚™ ≀ mβ€²β‚˜β‚β‚“ ≀ β„“β‚˜β‚β‚“, \\ -β„“β‚˜β‚β‚“ &≀ mβ‚˜α΅’β‚™ ≀ -β„“β‚˜α΅’β‚™ ≀ β„“β‚˜α΅’β‚™ ≀ mβ‚˜β‚β‚“ ≀ β„“β‚˜β‚β‚“, \end{aligned}\]

where β„“β‚˜α΅’β‚™ is either 0 or 1//2 depending on whether IT is an integer or half-integer type. Both rows Β±β„“β‚˜α΅’β‚™ must be included because the recurrence seeds the half-integer ladder from the pair of rows mβ€² = Β±1/2; for integers this reduces to the familiar mβ€²β‚˜α΅’β‚™ ≀ 0 ≀ mβ€²β‚˜β‚β‚“.

The constructor wraps parent without copying it. parent is a 1-based array of any shape with at least (mβ€²β‚˜β‚β‚“-mβ€²β‚˜α΅’β‚™+1) Γ— (mβ‚˜β‚β‚“-mβ‚˜α΅’β‚™+1) entries, read in its linear order, so that a matrix of exactly that size is the block. β„“ and the limits must all be integers of type Int, or all half-odd-integers, each a HalfOddInteger or a Rational{Int} with denominator 2; the limits default to the whole block, and each lower limit defaults to minus the corresponding upper one. The keywords may also be spelled mp_max, mp_min, m_max, and m_min; where both spellings of one are given, the Unicode one is used.

source
SphericalFunctions.WignerMatrixBatch β€” Type
WignerMatrixBatch{IT, NT, ST} <: AbstractBlock{IT, NT, ST}
WignerMatrixBatch(parent, β„“; mβ€²β‚˜β‚β‚“=β„“, mβ€²β‚˜α΅’β‚™=-mβ€²β‚˜β‚β‚“, mβ‚˜β‚β‚“=β„“, mβ‚˜α΅’β‚™=-mβ‚˜β‚β‚“)

Nα΅£ Wigner matrices of one $β„“$, stored together and indexed as w[iα΅£, mβ€², m]. This is what recurrence! returns for a calculator built from a vector of rotor data, of any length, for either kind of index.

  • IT is the index type, Int or HalfOddInteger.
  • NT is the number type of the elements.
  • ST is the type of the storage, an AbstractVector{NT} (see AbstractBlock).

The elements are ordered [iα΅£, mβ€², m], exactly as in the calculator. The constructor takes a 1-based array of any shape, whose first extent is Nα΅£ and which has at least as many entries as the block, read in its linear order, so that an array of exactly the block's shape is the block; it takes the indices and the limits, with their ASCII spellings, exactly as WignerMatrix does, and under the same rules. w[iα΅£] gives the WignerMatrix view of one rotor's matrix, which is then indexed naturally as w[iα΅£][mβ€², m].

See also WignerMatrix and WignerSeries.

source
SphericalFunctions.DegreeBlock β€” Type
DegreeBlock{IT, NT, ST}

One harmonic degree's worth of values, indexed naturally by the order $m$: v[m] for $mβ‚˜α΅’β‚™ ≀ m ≀ mβ‚˜β‚β‚“$. This is the 1-dimensional sibling of WignerMatrix.

  • IT is the index type, Int or HalfOddInteger.
  • NT is the number type of the elements.
  • ST is the type of the storage, an AbstractVector{NT} (see AbstractBlock).

The name is deliberately neutral, because the same shape serves three things: a block of spin-weighted harmonics at one $β„“$, from sYlm or sYlmCalculator's β‚›Yβ‚—[s, :]; one $β„“$'s worth of mode weights, from ModeWeights's w[β„“, :]; and one spin weight's row of a SpinMatrix, from b[s, :]. All three are values at a fixed degree indexed by order, whatever they mean.

The elements are in order of increasing $m$. The constructor

DegreeBlock(parent, β„“; mβ‚˜β‚β‚“=β„“, mβ‚˜α΅’β‚™=-mβ‚˜β‚β‚“)

wraps parent without copying it. parent is a 1-based array of any shape with at least mβ‚˜β‚β‚“-mβ‚˜α΅’β‚™+1 entries, read in its linear order, so that a vector of exactly that length is the block. The limits must satisfy -β„“ ≀ mβ‚˜α΅’β‚™ ≀ mβ‚˜β‚β‚“ ≀ β„“; unlike the limits of a WignerMatrix, these need not bracket zero, because no recurrence fills the block. β„“ and the limits must all be integers of type Int, or all half-odd-integers, each a HalfOddInteger or a Rational{Int} with denominator 2. The keywords may also be spelled m_max and m_min.

See also DegreeBlockBatch.

source
SphericalFunctions.DegreeBlockBatch β€” Type
DegreeBlockBatch{IT, NT, ST}

Nα΅£ rows of values of a single $β„“$, stored together and indexed as v[iα΅£, m]. This is the 1-dimensional sibling of WignerMatrixBatch, and is what a batched sYlmCalculator built for one spin weight yields, for either kind of index. v[iα΅£] gives the DegreeBlock view of one rotor's row.

  • IT is the index type, Int or HalfOddInteger.
  • NT is the number type of the elements.
  • ST is the type of the storage, an AbstractVector{NT}, or an AbstractMatrix{NT} for the slice b[:, s, :] of a SpinMatrixBatch (see AbstractBlock).

The elements are ordered [iα΅£, m]. The constructor, DegreeBlockBatch(parent, β„“; mβ‚˜β‚β‚“=β„“, mβ‚˜α΅’β‚™=-mβ‚˜β‚β‚“), takes a 1-based array of any shape, whose first extent is Nα΅£ and which has at least as many entries as the block, read in its linear order, so that a matrix of exactly the block's shape is the block; it takes the indices and the limits exactly as DegreeBlock does, and under the same rules.

source
SphericalFunctions.SpinMatrix β€” Type
SpinMatrix{IT, NT, ST}

The values of a single $β„“$ for a range of spin weights, indexed naturally by $(s, m)$: b[s, m] for $sβ‚˜α΅’β‚™ ≀ s ≀ sβ‚˜β‚β‚“$ and $mβ‚˜α΅’β‚™ ≀ m ≀ mβ‚˜β‚β‚“$, and b[s, :] for one whole row as a DegreeBlock. This is what an sYlmCalculator built for a range of spin weights yields for each $β„“$.

  • IT is the index type, Int or HalfOddInteger.
  • NT is the number type of the elements.
  • ST is the type of the storage, an AbstractVector{NT} (see AbstractBlock).

The elements are ordered [s, m]. The constructor

SpinMatrix(parent, β„“; sβ‚˜β‚β‚“, sβ‚˜α΅’β‚™, mβ‚˜β‚β‚“=β„“, mβ‚˜α΅’β‚™=-mβ‚˜β‚β‚“)

wraps parent without copying it. parent is a 1-based array of any shape with at least as many entries as the block, read in its linear order, so that a matrix of exactly the block's shape is the block. The spin limits are required, and the keywords may also be spelled s_max, s_min, m_max, and m_min. β„“ and every limit must all be integers of type Int, or all half-odd-integers, each a HalfOddInteger or a Rational{Int} with denominator 2. The $m$ axis obeys the rule of DegreeBlock, -β„“ ≀ mβ‚˜α΅’β‚™ ≀ mβ‚˜β‚β‚“ ≀ β„“.

The spin axis, spins(b), is under none of the restrictions a WignerMatrix places on its $mβ€²$: it is whatever range of spin weights was asked for, so it may lie wholly on one side of zero, and it may reach beyond $β„“$ β€” the harmonics with $|s| > β„“$ simply vanish, and a calculator stores them as zeros. It must only be in order, sβ‚˜α΅’β‚™ ≀ sβ‚˜β‚β‚“.

See also SpinMatrixBatch and DegreeBlock.

source
SphericalFunctions.SpinMatrixBatch β€” Type
SpinMatrixBatch{IT, NT, ST}

Nα΅£ SpinMatrix blocks of one $β„“$, stored together and indexed as b[iα΅£, s, m]. This is what a batched sYlmCalculator yields when it was built for a range of spin weights. b[iα΅£] gives the SpinMatrix view of one rotor's block, which is then indexed naturally as b[iα΅£][s, m].

  • IT is the index type, Int or HalfOddInteger.
  • NT is the number type of the elements.
  • ST is the type of the storage, an AbstractVector{NT} (see AbstractBlock).

The elements are ordered [iα΅£, s, m], exactly as in the calculator. The constructor, SpinMatrixBatch(parent, β„“; sβ‚˜β‚β‚“, sβ‚˜α΅’β‚™, mβ‚˜β‚β‚“=β„“, mβ‚˜α΅’β‚™=-mβ‚˜β‚β‚“), takes a 1-based array of any shape, whose first extent is Nα΅£ and which has at least as many entries as the block, read in its linear order, so that an array of exactly the block's shape is the block; it takes the indices and the limits exactly as SpinMatrix does, and under the same rules.

See also SpinMatrix and DegreeBlockBatch.

source
SphericalFunctions.AbstractDegreeSeries β€” Type
AbstractDegreeSeries{IT}

Supertype of the series of blocks indexed by the degree $β„“$: WignerSeries, the blocks of a Wigner matrix, and HarmonicValues, the values of the spin-weighted spherical harmonics.

A series s holds one block for each $β„“$ from β„“β‚˜α΅’β‚™(s) to β„“β‚˜β‚β‚“(s), and s[β„“] is the block of degree $β„“$; for a half-integer series β„“ may be written as a HalfOddInteger or as a Rational{Int} with denominator 2, and for an integer series it is an Int. Like a calculator, a series iterates as β„“ => block pairs, so eltype(s) is that pair type; keys(s) is the range of $β„“$, and length(s) counts the blocks. first(s) and last(s) are the first and last blocks, as indexing gives them, and so are first(s, n) and last(s, n), which give vectors of the first or last n blocks, and only(s), the one block of a series that has only one. ishalfinteger says which kind of index the series takes.

A series is not an AbstractVector: $β„“$ may be a half-odd-integer, and the axes of an AbstractVector must be integer ranges.

source
SphericalFunctions.WignerSeries β€” Type
WignerSeries{IT, VT}
WignerSeries(blocks, β„“β‚˜α΅’β‚™, β„“β‚˜β‚β‚“)

The blocks of a Wigner matrix for every $β„“$ from β„“β‚˜α΅’β‚™ to β„“β‚˜β‚β‚“, indexed by $β„“$: s[β„“] is the block of degree β„“, and s[β„“][mβ€², m] an element of it. For a half-integer series β„“ may be written as a HalfOddInteger or as a Rational{Int} with denominator 2, and for an integer series it is an Int.

  • IT is the index type, Int or HalfOddInteger.
  • VT is the type of the vector of blocks.

This is what D and d return, for either kind of index. Like a calculator, it iterates as β„“ => block pairs, so that for (β„“, 𝔇ˑ) ∈ D(R, β„“β‚˜β‚β‚“) reads exactly as the same loop over a DCalculator; keys is the range of $β„“$, values gives the blocks alone, and length counts them. first and last give the first and last blocks, as indexing does, and so do first(s, n) and last(s, n), which give vectors of the first or last n blocks, and only(s), the one block of a series that has only one.

Two series are ==, isequal or β‰ˆ when they have the same range of $β„“$ and their blocks are, block by block; β‰ˆ applies its tolerances to each block separately.

The constructor takes a 1-based vector of blocks without copying it, and requires block i to have $β„“ = β„“β‚˜α΅’β‚™ + i - 1$, with the index type of the bounds. The bounds must both be integers of type Int, or both half-odd-integers, each a HalfOddInteger or a Rational{Int} with denominator 2.

See also WignerMatrix and WignerMatrixBatch.

source
SphericalFunctions.HarmonicValues β€” Type
HarmonicValues

The values of the spin-weighted spherical harmonics ${}_sY_{β„“,m}$ at one or more rotors, indexed first by $β„“$ and then naturally within the block:

built forY[β„“] is indexed
one rotor, one spin weight[m]
many rotors, one spin weight[iα΅£, m]
one rotor, a range of spin weights[s, m]
many rotors, a range of spin weights[iα΅£, s, m]

The parameters of HarmonicValues{T, IT, S, A} are as follows:

  • T is the number type.
  • IT is the index type, Int or HalfOddInteger.
  • S is the type of the spin weights: IT for one, or a range of IT for several.
  • A is the type of the storage, an array of T.

This is what sYlm returns. The blocks are DegreeBlock, DegreeBlockBatch, SpinMatrix and SpinMatrixBatch respectively, and are views into the storage rather than copies, so writing through one writes into Y.

array_view gives the flat storage: a Vector of modes, or an array whose last axis is the modes in the canonical ordering (see Yindex) and whose leading axes are the rotors and spin weights. That is the form a product with mode weights takes, and sYlm_matrix is the direct name for it.

spins(Y) is the range of spin weights served, spin(Y) the single value when there is only one, Nα΅£(Y) the number of rotors, isbatched(Y) whether there is a rotor axis, and β„“β‚˜α΅’β‚™(Y)/β„“β‚˜β‚β‚“(Y) the range of $β„“$.

Y[β„“] and Y[β„“, :] both give the block of degree $β„“$, the second as w[β„“, :] gives the block of a ModeWeights w; for a half-integer Y, β„“ may be written as a HalfOddInteger or as a Rational{Int} with denominator 2, and for an integer Y it is an Int. first(Y) and last(Y) are the first and last blocks, as indexing gives them, and so are first(Y, n), last(Y, n) and only(Y), as for a WignerSeries. Iterating gives β„“ => block pairs, as a calculator does, so eltype(Y) is that pair type; the number type is eltype(array_view(Y)). length(Y) counts the blocks, and keys(Y) is the range of $β„“$.

HarmonicValues(data, s, β„“β‚˜α΅’β‚™, β„“β‚˜β‚β‚“, Nα΅£) wraps existing storage of one of those four shapes without copying it. Its labels must describe the storage: the rank must be that of one of the shapes, the spin axis as long as the range s, the leading axis Nα΅£ long (Nα΅£ = 1 without it), and the mode axis Ysize(β„“β‚˜α΅’β‚™, β„“β‚˜β‚β‚“) long. The indices must all be integers of type Int, or all half-odd-integers, each a HalfOddInteger or a Rational{Int} with denominator 2.

copy, similar and relabel keep the labels. As for a ModeWeights, two HarmonicValues are equal, or approximately equal, only when their labels agree as well as their numbers, while ==, isequal and β‰ˆ against a plain array compare the numbers of array_view(Y); hash is therefore that of the numbers alone. Broadcasting reads the numbers of array_view(Y) and gives a plain array, and Y .= x writes into the storage; a broadcast that combines two HarmonicValues, or writes one into another, requires their labels to agree, since it pairs the numbers by position.

See also ModeWeights, which shares this layout but holds the weights of a function rather than the values of the harmonics.

source
SphericalFunctions.WignerCalculator β€” Type
WignerCalculator{IT, RT, NT, ST, B, FT, L}

Calculator producing Wigner's $𝔇$ matrices (when NT is Complex{RT}) or $d$ matrices (when NT is RT) for Nα΅£ rotors at a time, one $β„“$ at a time. Use the constructors DCalculator and dCalculator. The type parameters are as follows:

  • IT is the index type, Int or HalfOddInteger.
  • RT is the real type of the rotor data, and of the elements.
  • NT is the number type of the matrix elements, Complex{RT} or RT.
  • ST is the storage type of the $H$ wedge.
  • B is true exactly when the calculator was built from a vector of rotor data, of any length, and is what isbatched reads.
  • FT is the real type in which the recurrence runs, which is RT itself unless RT holds derivatives, as a dual number does.
  • L is Nothing, unless the calculator lifts the blocks of a calculator of the values of its rotors into blocks that hold derivatives, when it is the type of the data for that.

Internally this holds an HCalculator, which does the actual recurrence, and the tables of the powers of the rotors' phases, in the same form as the calculators of the harmonics, plus a buffer into which the requested block of the matrix is written for the current $β„“$; that block is what recurrence! returns, as a WignerMatrix indexed naturally by [mβ€², m], or a WignerMatrixBatch indexed by [iα΅£, mβ€², m] when the calculator was built from a vector of rotor data.

A calculator whose rotor data hold derivatives, such as dual numbers, runs the recurrence on the values of those data, in FT, and gives each block its derivatives from the angular-momentum operators (for $d$, from the one about the $y$ axis), as described in src/derivatives/kernels.jl; the recurrence itself is never differentiated.

Because B is a type parameter, the type of the block is known at compile time, and a loop over the blocks is inferrable.

source

Workspaces

The wedge that an HCalculator returns is an HWedge, and wedge_value reads any element of $H^β„“$ from it, applying the symmetries described in the notes on the $H$ recursion. The axis that seeds the wedge during the recursion is an internal type, described on the internal page.

SphericalFunctions.HWedge β€” Type
HWedge{IT, RT, ST}
HWedge(RT, Nα΅£, β„“β‚˜β‚β‚“, mβ€²β‚˜β‚β‚“=β„“β‚˜β‚β‚“)

A compact, real-valued workspace holding the $HΛ‘$ matrix of one $β„“$ for Nα΅£ rotors at once.

  • IT is the index type, Int or HalfOddInteger.
  • RT is the real type of the elements.
  • ST is the type of the flat storage, a vector of RT.

The $HΛ‘$ matrix is critical to efficient and stable computation of the Wigner $D$ and $d$ matrices β€” in fact, it essentially is the $d$ matrix with signs adjusted to avoid numerical problems with alternating signs. This gives it additional symmetries that reduce the amount of data that needs to be stored to about 1/4 of the total $d$ size.

The purpose of an HWedge is to provide a workspace for the Wigner recurrences that is efficient, both in terms of the size of memory used, and the implications for vectorization. Specifically, the data is stored as strictly Real values, in contiguous storage. Indexing is performed efficiently via precomputed row offsets. Once the full recurrence is done, the data can be used directly β€” computing phases and symmetry on the fly β€” or copied into a full explicit matrix with the appropriate phases.

The recurrences require $m$ in the full range from 0 (or 1/2) to $β„“$, but $m'$ only needs the rows $|m'| ≀ m'_{\mathrm{max}}$, which must include the axis $m'=0$ for integer indices and both rows $m' = Β±1/2$ for half-integer ones. The range of $m'$ is always symmetric, so mβ€²β‚˜α΅’β‚™(H) is -mβ€²β‚˜β‚β‚“(H), and only enough storage for those rows is allocated. Specifically, an HWedge stores elements in a vector as if they were components of the HΛ‘ matrix:

[    HΛ‘[mβ€², m]    for mβ€² ∈ max(-β„“, -mβ€²β‚˜β‚β‚“):min(β„“, mβ€²β‚˜β‚β‚“)    for m ∈ abs(mβ€²):β„“]

However, for further efficiency when vectorizing over multiple rotors, the data is stored as a 1-dimensional vector, though it can be indexed as if it were a three-dimensional array, with the first dimension indexing Nα΅£ different rotors, and the second dimension indexing mβ€², and the third dimension indexing m. Thus, this object can be indexed as HΛ‘[iα΅£, mβ€², m] to get the HΛ‘ value for rotor index iα΅£, and matrix element (mβ€², m). Accordingly ndims(HΛ‘) is 3 and axes(HΛ‘) and size(HΛ‘, d) describe those three indices, while length(HΛ‘) and size(HΛ‘) describe the flat storage, which is what linear indexing HΛ‘[i] runs over. Only the elements with $m β‰₯ |m'|$ are stored; the others are read through wedge_value, which applies the symmetries of $H$.

Because of this complicated layout, the constructor is fairly restrictive, but will do all the allocation needed. β„“β‚˜β‚β‚“ and mβ€²β‚˜β‚β‚“ may be integers, or half-integers given as Rationals with denominator 2 or as HalfOddIntegers, as for the other index arguments. To avoid multiple allocations, it is advisable to first construct an instance with the maximum β„“ value that will be needed, and then change the β„“ field as needed to compute different orders. That is, if H isa HWedge, then H.β„“ = new_ell can be used to change the current order being computed; this lays out the storage for the new order without computing anything. The constructor starts out with the smallest β„“ value possible (0 or 1/2), which is the natural choice for recurrence. The wedge an HCalculator returns is that calculator's own workspace, so its β„“ should not be reassigned; copy(HΛ‘) gives an independent wedge holding the same numbers, which survives the calculator's next step.

A single-owner workspace

An HWedge is a mutable workspace, and none of its operations may run on two tasks at once: in particular, changing the β„“ field rewrites the row offsets that every read uses. Give each task its own wedge, or its own calculator.

source
SphericalFunctions.wedge_value β€” Function
wedge_value(H::HWedge, iα΅£, mβ€², m)

Value of $H_{mβ€²,m}$ for rotor iα΅£, for any $|mβ€²|, |m| ≀ β„“$ (at least one of them $≀ mβ€²β‚˜β‚β‚“$), read from the stored wedge through wedge_source, which applies the symmetry sign $Οƒ$ of half-integer indices. This is the way to read an element of $H$ that the wedge does not store, and the only correct one for half-integer indices, where transposing the stored elements by hand gets the sign of half of them wrong.

mβ€² and m must be of the wedge's own kind: Ints for an integer wedge, and half-odd-integers β€” HalfOddIntegers, or Rational{Int}s with denominator 2 such as 1//2 β€” for a half-integer one. An index of the other kind is refused with an ArgumentError rather than floored onto a neighboring element, and so is an integer of another type, such as an Int8, as it is by every function that takes an index. An element that the wedge cannot supply is a BoundsError or, when both $|mβ€²|$ and $|m|$ exceed mβ€²β‚˜β‚β‚“(H), an ArgumentError.

source

Methods of Base functions

Indexing a calculator or a container, copying one, emptying one, gathering every $β„“$ of one, and converting one to an ordinary Array are all written with the usual Base functions. Every specialization the package defines is collected here, for the Wigner calculators and containers and for the sYlmCalculator alike.

Base.getindex β€” Function
w[iα΅£]

The WignerMatrix of rotor iα΅£ in a WignerMatrixBatch, as a view; index it naturally as w[iα΅£][mβ€², m].

source
b[s, :]

The row of one spin weight of a SpinMatrix, as a DegreeBlock view; it is then indexed naturally as b[s, :][m]. The notation follows ModeWeights's w[β„“, :], so that a loop over spin weights reads the same in both places.

source
b[:, s, :]

The rows of one spin weight of a SpinMatrixBatch, over every rotor, as a DegreeBlockBatch view; it is then indexed naturally as b[:, s, :][iα΅£, m]. The notation follows SpinMatrix's b[s, :].

source
b[iα΅£]

The SpinMatrix of rotor iα΅£ in a SpinMatrixBatch, as a view; index it naturally as b[iα΅£][s, m].

source
w[β„“, m]

The mode weight of $(β„“, m)$ in the ModeWeights w. For a w with half-integer indices, β„“ and m may be passed as Rational{Int}s with denominator 2 β€” w[3//2, 1//2] β€” or as HalfOddIntegers, either way for each; for a w with integer indices they must be Ints. An index of another kind or type is refused with an ArgumentError that says why, as the constructors refuse one.

source
w[β„“, :]

A view of the mode weights of the ModeWeights w for the given $β„“$, indexed by m ∈ -β„“:β„“. This is a DegreeBlock for either kind of index; where the indices are half-odd-integers they may be passed either as Rationals or as HalfOddIntegers. Writing through the view writes into w.

source
Base.similar β€” Function
similar(w::AbstractBlock, [T=eltype(w)])

A new block of the same kind as w, with the same β„“ and the same natural axes (and so, for a batch, the same Nα΅£), over uninitialized storage of element type T. The storage is a Vector of exactly length(w) entries, even when parent(w) is a calculator's buffer or a view.

source
similar(calc)
similar(calc, R)

A second calculator of exactly the type of calc β€” the same limits, number of rotors and element type β€” with storage of its own and nothing computed. With one argument it holds a copy of the rotor data of calc, so that stepping it gives the values calc would give; with two it holds the rotor data R instead, which must describe the same number of rotors, in the same floating-point type. This applies to every calculator: DCalculator, dCalculator, HCalculator, sYlmCalculator and sΞ»lmCalculator.

Unlike similar of an array, this does copy the rotor data, because a calculator without rotor data is not representable, and the rotor data cannot be recovered from what a calculator stores. The second argument is rotor data rather than an element type; to compute in another type, build a calculator from rotor data of that type.

A calculator is a mutable workspace, which every step and every setter overwrites, so one calculator must not be used by two tasks at once. similar is the way to give each task a calculator of its own; the two share nothing that either one changes.

source
Base.fill! β€” Function
fill!(w::HCalculator, v)

Fill every internal buffer of w (both axes, the wedge, and the table of recurrence coefficients) with the value v, and mark the axis data as invalid. The stored rotor data β€” the phases e^{iΞ²} and, on the half-integer path, the half angles β€” is deliberately left alone, so that recurrence!(w, β„“) still has everything it needs, and so is the constant table of square roots that the constructor builds, which the recurrence reads but never writes. Useful for testing that no uninitialized storage is ever read: everything the recurrence is responsible for writing is poisoned, while everything the rotor data consists of is preserved.

source
fill!(c::WignerCalculator, v)

Fill the axis, wedge and output buffers of c with the value v and mark the current results as invalid. The stored rotor data β€” e^{iΞ²}, the half angles, the phase powers Zβ‚Š and Zβ‚‹ β€” is deliberately not touched, so recurrence!(c, β„“) still has everything it needs, exactly as for HCalculator. Useful for testing that no uninitialized storage is ever read: everything the recurrence is responsible for writing is poisoned, while everything set_rotors! is responsible for writing is left alone.

source
fill!(c::sYlmCalculator, v)

Fill the axis, wedge and output buffers of c with the value v and mark the current results as invalid. The stored rotor data β€” e^{iΞ²}, the half angles, the phase powers Zβ‚Š, Zβ‚‹, and the flag recording whether rotors or bare angles were given β€” is deliberately not touched, so recurrence!(c, β„“) still has everything it needs, exactly as for HCalculator. Useful for testing that no uninitialized storage is ever read.

source
Base.collect β€” Function
collect(calc)

Every $β„“$ of a calculator, as an ordinary 1-based Vector of β„“ => block pairs, with each block copied.

The copies are the point: the blocks a calculator yields are views into storage that the next step overwrites, so a collect that did not copy would hand back length(calc) aliases of one buffer, all holding the last $β„“$. Each copy keeps its natural $(mβ€², m)$ indices, and generic functions that store what they are handed β€” map, first(calc, n), Iterators.take β€” do not copy, so use this or copy the block inside the loop.

Note that the result is indexed from 1, while D and d return containers indexed by $β„“$; collect(calc)[i] is the pair whose first element is the $β„“$.

values(calc) iterates over the blocks alone, as values of a WignerSeries does. Those blocks are the same views that iteration over calc yields, and collect(values(calc)) copies each of them for the same reason.

source
Base.Matrix β€” Type
Matrix(w::WignerMatrix)

Materialize the block of the Wigner matrix represented by w as an ordinary Matrix. The rows and columns are in order of increasing mβ€² and m, so that the element w[mβ€², m] is at [Int(mβ€²-mβ€²β‚˜α΅’β‚™)+1, Int(m-mβ‚˜α΅’β‚™)+1].

source

Accessors

The same handful of names reports the index ranges, the spin weights, the sizes, and the floating-point type of every container, calculator, and transform in the package. Those whose names are not ASCII have ASCII aliases, given in their docstrings, for use where the subscripted Unicode names are inconvenient: ell, ell_min, ell_max, mp_max, mp_min, m_max, m_min, s_max, s_min, and Nr. The keyword arguments of the same names are spelled the same way in ASCII, so that D(R, β„“β‚˜β‚β‚“; mp_max=2) is D(R, β„“β‚˜β‚β‚“; mβ€²β‚˜β‚β‚“=2) and sYlm(R, β„“β‚˜β‚β‚“, s; ell_min=0) is sYlm(R, β„“β‚˜β‚β‚“, s; β„“β‚˜α΅’β‚™=0). The functions whose names are not ASCII have aliases too β€” set_beta!, set_theta!, and slambdalmCalculator here, and those of the differential operators β€” which are public but not exported, and are mentioned in the docstrings of the functions they name.

SphericalFunctions.β„“β‚˜α΅’β‚™ β€” Function
β„“β‚˜α΅’β‚™(x)
ell_min(x)

The smallest $β„“$ that x holds or can hold.

For a block or a calculator, this is the smallest degree of the index type: 0 for integer indices and 1//2 for half-integer ones. For a ModeWeights, a HarmonicValues, a WignerSeries, or a transform, it is the smallest $β„“$ stored, which may be larger, such as the $|s|$ of the weights that an analysis returns.

source
SphericalFunctions.mβ€²β‚˜β‚β‚“ β€” Function
mβ€²β‚˜β‚β‚“(x)
mp_max(x)

The largest $m'$ in the block x holds or returns. For a calculator this is the keyword argument of the same name, which restricts the rows that are computed.

source
SphericalFunctions.mβ€²β‚˜α΅’β‚™ β€” Function
mβ€²β‚˜α΅’β‚™(x)
mp_min(x)

The smallest $m'$ in the block x holds or returns. Note that both $Β±β„“β‚˜α΅’β‚™$ must lie in mβ€²β‚˜α΅’β‚™(x):mβ€²β‚˜β‚β‚“(x), because the recurrence seeds the half-integer ladder from the pair of rows $m' = Β±1/2$; for integer indices that reduces to the familiar mβ€²β‚˜α΅’β‚™ ≀ 0 ≀ mβ€²β‚˜β‚β‚“.

The blocks with a single $m$ axis, DegreeBlock, SpinMatrix and their batches, have no $m'$, and this and mβ€²β‚˜β‚β‚“ refuse them with an explanation.

source
SphericalFunctions.mβ‚˜α΅’β‚™ β€” Function
mβ‚˜α΅’β‚™(x)
m_min(x)

The smallest $m$ in the block x holds or returns. For a WignerMatrix, a WignerMatrixBatch or a calculator, the range must bracket $Β±β„“β‚˜α΅’β‚™$, as for mβ€²β‚˜α΅’β‚™, because the recurrence computes those blocks. The blocks that no recurrence fills, DegreeBlock, SpinMatrix and their batches, require only -β„“ ≀ mβ‚˜α΅’β‚™(x) ≀ mβ‚˜β‚β‚“(x) ≀ β„“.

source
SphericalFunctions.spins β€” Function
spins(c)

The spin weights an sYlmCalculator serves, as a range. A calculator built for a single spin weight reports the one-element range containing it, so that this accessor answers in the same currency whichever way the calculator was built; spin gives the value itself, and has no method for a calculator built for several.

For a SpinMatrix or a SpinMatrixBatch, which is what such a calculator yields when it serves several spin weights, this is the range of spin weights the block holds, sβ‚˜α΅’β‚™(b):sβ‚˜β‚β‚“(b). For a HarmonicValues it is the range of spin weights the values were computed for, again a one-element range when there is only one.

source
SphericalFunctions.sβ‚˜β‚β‚“ β€” Function
sβ‚˜β‚β‚“(x)
s_max(x)

The largest $s$ in the block x holds. Unlike mβ€²β‚˜β‚β‚“, this is under no obligation to bracket zero or to stay within $Β±β„“$: a block of spin-weighted harmonics may hold any consecutive run of spin weights, and those with $|s| > β„“$ are zero.

source
SphericalFunctions.spin β€” Function
spin(w)

The spin weight of a ModeWeights vector, of an SSHT transform, or of an sYlmCalculator built for a single one. A calculator built for a range of spin weights has no single value to report, so it has no method here; ask it for spins instead, which answers for either kind.

A function of spin weight $s$ has $R_z f = s f$, and is expanded in the harmonics ${}_{s}Y_{β„“,m}$ with $β„“ β‰₯ |s|$. The spin weight is kept alongside the numbers because nothing about the numbers themselves reveals it.

julia> using SphericalFunctionsjulia> spin(ModeWeights(zeros(ComplexF64, 21), -2))-2julia> spin(SSHT(1, 4))1

See also modes, ModeWeights, SSHT, and spins.

source
SphericalFunctions.Nα΅£ β€” Function
Nα΅£(x)
Nr(x)

The number of rotors x handles at once. A calculator built from a vector of rotor data evaluates the whole batch in one pass, and its blocks have a leading rotor index, 𝔇ˑ[iα΅£, mβ€², m], even when the vector has only one element; isbatched says which kind a calculator is, since Nα΅£ == 1 does not.

source
SphericalFunctions.floattype β€” Function
floattype(x)
floattype(T)

The floating-point type in which x, or anything of type T, is computed. For a calculator or a transform, this is the type it works in, and its results are that type, or Complex of it. For rotor data β€” a Rotor or other Quaternion, an angle Ξ²::Real, a phase e^{iΞ²}::Complex, or an AbstractVector of any one of those β€” it is the type in which a calculator built from that data works: the float of the data's component type, so that a Rotor{Float32} gives a Float32 calculator, a Rotor{Double64} a Double64 one, and the angle 3 a Float64 one. As with eltype, the method for a value is that for its type.

The type of the rotor data is the only thing that decides the type in which a calculator works. There is no way to set it independently of the data: to compute in some other type, convert the data β€” which is also the honest way to say it, since the type of the data is the claim being made about the points. The methods that replace a calculator's data β€” set_R!, set_Ξ²!, set_ΞΈ! β€” require the new data to agree with what this reports. A Quaternion counts as a rotor here; we always divide out by the norm, and accept Quaternion for compatibility with various automatic-differentiation packages.

The rotor data must therefore commit to a type to go on: data whose component type is abstract, such as a Complex{Real} or a Vector{Rotor{Real}}, or a vector whose element type is abstract or a Union, is refused with an ArgumentError rather than guessed at, as is a QuatVec, which does not denote a rotation (see not_a_rotor).

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

Number of mode weights the transform 𝒯 works with, Ysize(abs(s), β„“β‚˜β‚β‚“).

source
SphericalFunctions.ishalfinteger β€” Function
ishalfinteger(w)

Whether the natural indices of w are half-odd-integers (true) or integers (false). This is a property of the index type β€” half-odd-integer indices are represented as HalfOddIntegers β€” so it is known at compile time.

source
SphericalFunctions.isbatched β€” Function
isbatched(c)

Whether the calculator c was built for a batch of rotors (true, when it was built from a vector of rotor data, of any length) or for a single one (false). Like ishalfinteger, this is a property of the type rather than of the data, so it is known at compile time β€” which is what lets the block that recurrence! returns have a single, inferrable type rather than a union of the batched and unbatched ones. For a HarmonicValues it says whether the storage has a rotor axis, which again it has exactly when the values were computed for a vector of rotors. For a block it says whether the block has a leading rotor axis: it is true for a WignerMatrixBatch, a DegreeBlockBatch and a SpinMatrixBatch, which is what a batched calculator returns, and false for the other blocks.

source