$π$ 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:
- Explicitly from its components as
Rotor(w, x, y, z), or from aQuaternionasRotor(q). - From Euler angles.
- From spherical coordinates.
- From a rotation matrix.
- From an angle
ΞΈand unit vectorvasexp(ΞΈ*QuatVec(v)/2), which is the right-handed rotation byΞΈaboutv.
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 for | sY[β] 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 β -β:βendAn 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 β -β:βendA 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 β -β:βendWhat 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).
$π$ 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.
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 β -β:βendBeginning 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 endendHowever, 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 β -β:βendThe 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 β -β:βendAn 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 storedendThis 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 rotorsThe 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β²$.
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.
Base.:* β Method
π * w
calc * wRigidly 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 propertySee also ModeWeights and HarmonicValues.
Base.:* β Method
Y * w
calc * wEvaluate 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)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.
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).
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 for | sY[β] 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.
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.
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.
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 β -β:βendFor 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!.
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.
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:
ITis the index type,IntorHalfOddInteger.RTis the real type the calculator works in.STis 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β²|endUnlike 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.
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 β -β:βendFor 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.
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.
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.
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.
ITis the index type,IntorHalfOddInteger.RTis the real type the calculator works in.NTis the number type of the harmonics,Complex{RT}orRT.STis the storage type of the $H$ wedge.Sis the type of the spin weights served: the index type for one spin weight, or aUnitRangeof it for a range of them.Bistrueexactly when the calculator was built from a vector of rotor data, and is whatisbatchedreads.FTis the real type in which the recurrence runs, which isRTitself unlessRTholds derivatives, as a dual number does.LisNothing, 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.
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 β -β:βendWhat comes back depends on the calculator, and in every case it is indexed by the natural ranges rather than from 1:
| calculator | block |
|---|---|
DCalculator, dCalculator | WignerMatrix, [mβ², m] |
sYlmCalculator for one spin weight | DegreeBlock, [m] |
sYlmCalculator for a range of them | SpinMatrix, [s, m] |
HCalculator | HWedge, [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, :].
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.
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 # ... endendSphericalFunctions.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.
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(ΞΈ, Ο).
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.
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
- For a
ModeWeightsor the result ofsYlmit 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 arrayAn 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.
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.
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.
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].
ITis the index type,IntorHalfOddInteger, which is the type of $β$ and of every natural index.NTis the number type, such asComplexF64for a block of $π$ orFloat64for one of $d$.STis 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, with1:Nα΅£for the rotor axis of a batch, andsize(w),length(w)andndims(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βββandsβα΅’β, for the axes a block has, andisbatchedsays whether it has a rotor axis; checkbounds(Bool, w, inds...)tests natural indices;Array(w)andcollect(w)copy the block into an ordinary 1-based array, as doMatrix(w)for a two-axis block andVector(v)for aDegreeBlock;copy(w)andsimilar(w)keep the labels, and iteration visits the elements in the order ofArray(w);==,isequal,hashandβ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.
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).
ITis the index type,IntorHalfOddInteger.NTis the number type of the elements.STis the type of the storage, anAbstractVector{NT}(seeAbstractBlock).
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.
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.
ITis the index type,IntorHalfOddInteger.NTis the number type of the elements.STis the type of the storage, anAbstractVector{NT}(seeAbstractBlock).
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.
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.
ITis the index type,IntorHalfOddInteger.NTis the number type of the elements.STis the type of the storage, anAbstractVector{NT}(seeAbstractBlock).
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.
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.
ITis the index type,IntorHalfOddInteger.NTis the number type of the elements.STis the type of the storage, anAbstractVector{NT}, or anAbstractMatrix{NT}for the sliceb[:, s, :]of aSpinMatrixBatch(seeAbstractBlock).
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.
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 $β$.
ITis the index type,IntorHalfOddInteger.NTis the number type of the elements.STis the type of the storage, anAbstractVector{NT}(seeAbstractBlock).
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.
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].
ITis the index type,IntorHalfOddInteger.NTis the number type of the elements.STis the type of the storage, anAbstractVector{NT}(seeAbstractBlock).
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.
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.
ITis the index type,IntorHalfOddInteger.
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.
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.
ITis the index type,IntorHalfOddInteger.VTis 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.
SphericalFunctions.HarmonicValues β Type
HarmonicValuesThe values of the spin-weighted spherical harmonics ${}_sY_{β,m}$ at one or more rotors, indexed first by $β$ and then naturally within the block:
| built for | Y[β] 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:
Tis the number type.ITis the index type,IntorHalfOddInteger.Sis the type of the spin weights:ITfor one, or a range ofITfor several.Ais the type of the storage, an array ofT.
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.
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:
ITis the index type,IntorHalfOddInteger.RTis the real type of the rotor data, and of the elements.NTis the number type of the matrix elements,Complex{RT}orRT.STis the storage type of the $H$ wedge.Bistrueexactly when the calculator was built from a vector of rotor data, of any length, and is whatisbatchedreads.FTis the real type in which the recurrence runs, which isRTitself unlessRTholds derivatives, as a dual number does.LisNothing, 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.
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.
ITis the index type,IntorHalfOddInteger.RTis the real type of the elements.STis the type of the flat storage, a vector ofRT.
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.
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.
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].
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.
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, :].
b[iα΅£]The SpinMatrix of rotor iα΅£ in a SpinMatrixBatch, as a view; index it naturally as b[iα΅£][s, m].
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.
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.
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.
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.
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.
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.
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.
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.
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].
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(x)The value of $β$ that x currently holds: the degree of a block (WignerMatrix, DegreeBlock, HWedge, β¦), or of the block most recently computed by a calculator. For a calculator on which recurrence! has not been called since it was built, or since its data were last replaced (by set_R!, set_Ξ²!, set_ΞΈ! or fill!), this is ββα΅’β(x) - 1.
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.
SphericalFunctions.ββββ β Function
ββββ(x)
ell_max(x)The largest $β$ that x can hold β the value its storage was sized for.
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.
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.
SphericalFunctions.mβββ β Function
mβββ(x)
m_max(x)The largest $m$ in the block x holds or returns.
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) β€ β.
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.
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.
SphericalFunctions.sβα΅’β β Function
sβα΅’β(x)
s_min(x)The smallest $s$ in the block x holds; see sβββ.
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))1See also modes, ModeWeights, SSHT, and spins.
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.
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).
SphericalFunctions.nmodes β Function
nmodes(π―)Number of mode weights the transform π― works with, Ysize(abs(s), ββββ).
SphericalFunctions.npixels β Function
npixels(π―)Number of sample points (function values) the transform π― works with.
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.
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.