pybhpt 0.9.10__cp313-cp313-musllinux_1_2_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cybhpt_full.cpython-313-x86_64-linux-musl.so +0 -0
- pybhpt/__init__.py +0 -0
- pybhpt/flux.py +164 -0
- pybhpt/geo.py +910 -0
- pybhpt/hertz.py +402 -0
- pybhpt/metric.py +326 -0
- pybhpt/radial.py +444 -0
- pybhpt/redshift.py +19 -0
- pybhpt/swsh.py +659 -0
- pybhpt/teuk.py +492 -0
- pybhpt-0.9.10.dist-info/METADATA +151 -0
- pybhpt-0.9.10.dist-info/RECORD +18 -0
- pybhpt-0.9.10.dist-info/WHEEL +5 -0
- pybhpt-0.9.10.dist-info/licenses/LICENSE +674 -0
- pybhpt.libs/libgcc_s-0cd532bd.so.1 +0 -0
- pybhpt.libs/libgsl-6bd0117f.so.28.0.0 +0 -0
- pybhpt.libs/libgslcblas-55a77d35.so.0.0.0 +0 -0
- pybhpt.libs/libstdc++-5d72f927.so.6.0.33 +0 -0
pybhpt/swsh.py
ADDED
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
import scipy.sparse
|
|
2
|
+
import scipy.sparse.linalg
|
|
3
|
+
# from scipy.special import sph_harm_y
|
|
4
|
+
# from scipy.special import binom
|
|
5
|
+
# from scipy.special import factorial
|
|
6
|
+
import numpy as np
|
|
7
|
+
from cybhpt_full import _YslmCy, _YslmCy_derivative, _YslmCy_derivative2, _clebschCy, _w3jCy
|
|
8
|
+
|
|
9
|
+
"""
|
|
10
|
+
Wigner 3j-symbol and Clebsch-Gordon coefficients
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def fac(n):
|
|
14
|
+
"""
|
|
15
|
+
Computes the factorial of a non-negative integer n.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
n : int
|
|
20
|
+
A non-negative integer.
|
|
21
|
+
|
|
22
|
+
Returns
|
|
23
|
+
-------
|
|
24
|
+
float
|
|
25
|
+
The factorial of n.
|
|
26
|
+
"""
|
|
27
|
+
if n < 0:
|
|
28
|
+
return 0
|
|
29
|
+
return float(np.math.factorial(n))
|
|
30
|
+
|
|
31
|
+
def Yslm(s, l, m, th, ph = None):
|
|
32
|
+
"""
|
|
33
|
+
Evaluate the spin-weighted spherical harmonic $Y_{s}^{lm}$ at a given angle theta.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
s : int
|
|
38
|
+
The spin weight of the harmonic.
|
|
39
|
+
l : int
|
|
40
|
+
The angular number of the spherical harmonic.
|
|
41
|
+
m : int
|
|
42
|
+
The azimuthal number of the spherical harmonic.
|
|
43
|
+
th : array_like
|
|
44
|
+
The polar angle(s) at which to evaluate the spherical harmonic.
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
array_like
|
|
49
|
+
The values of the spherical harmonic at the specified angles.
|
|
50
|
+
"""
|
|
51
|
+
assert isinstance(s, int) and isinstance(l, int) and isinstance(m, int), "s, l, and m must be integers"
|
|
52
|
+
if ph is not None:
|
|
53
|
+
return Yslm(s, l, m, th)*np.exp(1.j*m*ph)
|
|
54
|
+
if np.abs(s) > l:
|
|
55
|
+
return 0.*th
|
|
56
|
+
|
|
57
|
+
return _YslmBase(s, l, m, th)
|
|
58
|
+
|
|
59
|
+
def Yslm_derivative(s, l, m, th, ph = None):
|
|
60
|
+
"""
|
|
61
|
+
Evaluate the derivative of the spin-weighted spherical harmonic $Y_{s}^{lm}$ at a given angle theta.
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
s : int
|
|
66
|
+
The spin weight of the harmonic.
|
|
67
|
+
l : int
|
|
68
|
+
The angular number of the spherical harmonic.
|
|
69
|
+
m : int
|
|
70
|
+
The azimuthal number of the spherical harmonic.
|
|
71
|
+
th : array_like
|
|
72
|
+
The polar angle(s) at which to evaluate the spherical harmonic.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
array_like
|
|
77
|
+
The derivatives of the spherical harmonic at the specified angles.
|
|
78
|
+
"""
|
|
79
|
+
assert isinstance(s, int) and isinstance(l, int) and isinstance(m, int), "s, l, and m must be integers"
|
|
80
|
+
if ph is not None:
|
|
81
|
+
return Yslm_derivative(s, l, m, th)*np.exp(1.j*m*ph)
|
|
82
|
+
if np.abs(s) > l:
|
|
83
|
+
return 0.*th
|
|
84
|
+
return _YslmDerivBase(s, l, m, th)
|
|
85
|
+
|
|
86
|
+
def Yslm_derivative2(s, l, m, th, ph = None):
|
|
87
|
+
"""
|
|
88
|
+
Evaluate the second derivative of the spin-weighted spherical harmonic $Y_{s}^{lm}$ at a given angle theta.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
s : int
|
|
93
|
+
The spin weight of the harmonic.
|
|
94
|
+
l : int
|
|
95
|
+
The angular number of the spherical harmonic.
|
|
96
|
+
m : int
|
|
97
|
+
The azimuthal number of the spherical harmonic.
|
|
98
|
+
th : array_like
|
|
99
|
+
The polar angle(s) at which to evaluate the spherical harmonic.
|
|
100
|
+
|
|
101
|
+
Returns
|
|
102
|
+
-------
|
|
103
|
+
array_like
|
|
104
|
+
The second derivatives of the spherical harmonic at the specified angles.
|
|
105
|
+
"""
|
|
106
|
+
assert isinstance(s, int) and isinstance(l, int) and isinstance(m, int), "s, l, and m must be integers"
|
|
107
|
+
if ph is not None:
|
|
108
|
+
return Yslm_derivative2(s, l, m, th)*np.exp(1.j*m*ph)
|
|
109
|
+
if np.abs(s) > l:
|
|
110
|
+
return 0.*th
|
|
111
|
+
return _YslmDeriv2Base(s, l, m, th)
|
|
112
|
+
|
|
113
|
+
def _YslmBase(s, l, m, th):
|
|
114
|
+
assert isinstance(s, int) and isinstance(l, int) and isinstance(m, int), "s, l, and m must be integers"
|
|
115
|
+
if not isinstance(th, (int, float)):
|
|
116
|
+
b = np.broadcast(th)
|
|
117
|
+
out = np.empty(b.shape)
|
|
118
|
+
out.flat = [_YslmCy(s, l, m, thi) for (thi,) in b]
|
|
119
|
+
else:
|
|
120
|
+
out = _YslmCy(s, l, m, th)
|
|
121
|
+
return out
|
|
122
|
+
|
|
123
|
+
def _YslmDerivBase(s, l, m, th):
|
|
124
|
+
assert isinstance(s, int) and isinstance(l, int) and isinstance(m, int), "s, l, and m must be integers"
|
|
125
|
+
if not isinstance(th, (int, float)):
|
|
126
|
+
b = np.broadcast(th)
|
|
127
|
+
out = np.empty(b.shape)
|
|
128
|
+
out.flat = [_YslmCy_derivative(s, l, m, thi) for (thi,) in b]
|
|
129
|
+
else:
|
|
130
|
+
out = _YslmCy_derivative(s, l, m, th)
|
|
131
|
+
return out
|
|
132
|
+
|
|
133
|
+
def _YslmDeriv2Base(s, l, m, th):
|
|
134
|
+
assert isinstance(s, int) and isinstance(l, int) and isinstance(m, int), "s, l, and m must be integers"
|
|
135
|
+
if not isinstance(th, (int, float)):
|
|
136
|
+
b = np.broadcast(th)
|
|
137
|
+
out = np.empty(b.shape)
|
|
138
|
+
out.flat = [_YslmCy_derivative2(s, l, m, thi) for (thi,) in b]
|
|
139
|
+
else:
|
|
140
|
+
out = _YslmCy_derivative2(s, l, m, th)
|
|
141
|
+
return out
|
|
142
|
+
|
|
143
|
+
def Yslm_eigenvalue(s, l, *args):
|
|
144
|
+
return l*(l + 1.) - s*(s + 1.)
|
|
145
|
+
|
|
146
|
+
def clebsch(l1, l2, l3, m1, m2, m3):
|
|
147
|
+
"""
|
|
148
|
+
Compute the Clebsch-Gordon coefficient <l1,m1,l2,m2|l3,m3>.
|
|
149
|
+
|
|
150
|
+
Parameters
|
|
151
|
+
----------
|
|
152
|
+
l1 : int
|
|
153
|
+
The angular number of the first state.
|
|
154
|
+
l2 : int
|
|
155
|
+
The angular number of the second state.
|
|
156
|
+
l3 : int
|
|
157
|
+
The angular number of the combined state.
|
|
158
|
+
m1 : int
|
|
159
|
+
The azimuthal number of the first state.
|
|
160
|
+
m2 : int
|
|
161
|
+
The azimuthal number of the second state.
|
|
162
|
+
m3 : int
|
|
163
|
+
The azimuthal number of the combined state.
|
|
164
|
+
|
|
165
|
+
Returns
|
|
166
|
+
-------
|
|
167
|
+
float
|
|
168
|
+
The Clebsch-Gordon coefficient <l1,m1,l2,m2|l3,m3>.
|
|
169
|
+
"""
|
|
170
|
+
assert isinstance(l1, int) and isinstance(l2, int) and isinstance(l3, int), "l1, l2, and l3 must be integers"
|
|
171
|
+
assert isinstance(m1, int) and isinstance(m2, int) and isinstance(m3, int), "m1, m2, and m3 must be integers"
|
|
172
|
+
return _clebschCy(l1, l2, l3, m1, m2, m3)
|
|
173
|
+
|
|
174
|
+
def w3j(l1, l2, l3, m1, m2, m3):
|
|
175
|
+
"""
|
|
176
|
+
Compute the Wigner 3j-symbol
|
|
177
|
+
| l1 l2 l3 |
|
|
178
|
+
| m1 m2 m3 |
|
|
179
|
+
|
|
180
|
+
Parameters
|
|
181
|
+
----------
|
|
182
|
+
l1 : int
|
|
183
|
+
The angular number of the first state.
|
|
184
|
+
l2 : int
|
|
185
|
+
The angular number of the second state.
|
|
186
|
+
l3 : int
|
|
187
|
+
The angular number of the combined state.
|
|
188
|
+
m1 : int
|
|
189
|
+
The azimuthal number of the first state.
|
|
190
|
+
m2 : int
|
|
191
|
+
The azimuthal number of the second state.
|
|
192
|
+
m3 : int
|
|
193
|
+
The azimuthal number of the combined state.
|
|
194
|
+
|
|
195
|
+
Returns
|
|
196
|
+
-------
|
|
197
|
+
float
|
|
198
|
+
The Wigner 3j-symbol $ \begin{pmatrix} l1 & l2 & l3 \\ m1 & m2 & m3 \end{pmatrix} $
|
|
199
|
+
"""
|
|
200
|
+
assert isinstance(l1, int) and isinstance(l2, int) and isinstance(l3, int), "l1, l2, and l3 must be integers"
|
|
201
|
+
assert isinstance(m1, int) and isinstance(m2, int) and isinstance(m3, int), "m1, m2, and m3 must be integers"
|
|
202
|
+
return _w3jCy(l1, l2, l3, m1, m2, m3)
|
|
203
|
+
|
|
204
|
+
"""
|
|
205
|
+
SWSH Eigenvalue Functions
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
def k1(s, l, j, m):
|
|
209
|
+
return np.sqrt((2*l + 1)/(2*j + 1))*clebsch(l, 1, j, m, 0, m)*clebsch(l, 1, j, -s, 0, -s);
|
|
210
|
+
|
|
211
|
+
def k2(s, l, j, m):
|
|
212
|
+
ktemp = 2./3.*np.sqrt((2*l + 1)/(2*j + 1))*clebsch(l, 2, j, m, 0, m)*clebsch(l, 2, j, -s, 0, -s);
|
|
213
|
+
if j == l:
|
|
214
|
+
ktemp += 1/3.
|
|
215
|
+
return ktemp
|
|
216
|
+
|
|
217
|
+
def k2m2(s, l, m):
|
|
218
|
+
temp = (l - m - 1.)/(l - 1.)
|
|
219
|
+
temp *= (l + m - 1.)/(l - 1.)
|
|
220
|
+
temp *= np.float64(l - m)/l
|
|
221
|
+
temp *= np.float64(l + m)/l
|
|
222
|
+
temp *= (l - s)/(2.*l - 1.)
|
|
223
|
+
temp *= (l + s)/(2.*l - 1.)
|
|
224
|
+
temp *= (l - s - 1.)/(2.*l + 1.)
|
|
225
|
+
temp *= (l + s - 1.)/(2.*l - 3.)
|
|
226
|
+
return np.sqrt(temp)
|
|
227
|
+
|
|
228
|
+
def k2m1(s, l, m):
|
|
229
|
+
temp = np.float64(l - m)*np.float64(l + m)/(2.*l - 1.)
|
|
230
|
+
temp *= np.float64(l - s)*np.float64(l + s)/(2.*l + 1.)
|
|
231
|
+
return -2.*m*s*np.sqrt(temp)/l/(l - 1.)/(l + 1.)
|
|
232
|
+
|
|
233
|
+
def k2p0(s, l, m):
|
|
234
|
+
temp = np.float64(l*(l + 1.) - 3.*m*m)/(2.*l - 1.)/l
|
|
235
|
+
temp *= np.float64(l*(l + 1.) - 3.*s*s)/(2.*l + 3.)/(l + 1.)
|
|
236
|
+
return 1./3.*(1. + 2.*temp)
|
|
237
|
+
|
|
238
|
+
def k2p1(s, l, m):
|
|
239
|
+
temp = np.float64(l - m + 1.)*np.float64(l + m + 1.)/(2.*l + 1.)
|
|
240
|
+
temp *= np.float64(l - s + 1.)*np.float64(l + s + 1.)/(2.*l + 3.)
|
|
241
|
+
return -2.*m*s*np.sqrt(temp)/l/(l + 1.)/(l + 2.)
|
|
242
|
+
|
|
243
|
+
def k2p2(s, l, m):
|
|
244
|
+
temp = (l - m + 1.)/(l + 1.)
|
|
245
|
+
temp *= (l + m + 1.)/(l + 1.)
|
|
246
|
+
temp *= (l - m + 2.)/(l + 2.)
|
|
247
|
+
temp *= (l + m + 2.)/(l + 2.)
|
|
248
|
+
temp *= (l - s + 2.)/(2.*l + 3.)
|
|
249
|
+
temp *= (l + s + 2.)/(2.*l + 3.)
|
|
250
|
+
temp *= (l - s + 1.)/(2.*l + 1.)
|
|
251
|
+
temp *= (l + s + 1.)/(2.*l + 5.)
|
|
252
|
+
return np.sqrt(temp)
|
|
253
|
+
|
|
254
|
+
def k1m1(s, l, m):
|
|
255
|
+
temp = np.float64(l - m)*np.float64(l + m)/(2.*l - 1.)
|
|
256
|
+
temp *= np.float64(l - s)*np.float64(l + s)/(2.*l + 1.)
|
|
257
|
+
return np.sqrt(temp)/l
|
|
258
|
+
|
|
259
|
+
def k1p0(s, l, m):
|
|
260
|
+
return -np.float64(m*s)/l/(l + 1.)
|
|
261
|
+
|
|
262
|
+
def k1p1(s, l, m):
|
|
263
|
+
temp = np.float64(l - m + 1.)*np.float64(l + m + 1.)/(2.*l + 3.)
|
|
264
|
+
temp *= np.float64(l - s + 1.)*np.float64(l + s + 1.)/(2.*l + 1.)
|
|
265
|
+
return np.sqrt(temp)/(l + 1.)
|
|
266
|
+
|
|
267
|
+
def akm2(s, l, m, g):
|
|
268
|
+
return -g*g*k2m2(s, l, m)
|
|
269
|
+
|
|
270
|
+
def akm1(s, l, m, g):
|
|
271
|
+
return -g*g*k2m1(s, l, m) + 2.*s*g*k1m1(s, l, m)
|
|
272
|
+
|
|
273
|
+
def akp0(s, l, m, g):
|
|
274
|
+
return -g*g*k2p0(s, l, m) + 2.*s*g*k1p0(s, l, m) + l*(l + 1.) - s*(s + 1.) - 2.*m*g + g*g
|
|
275
|
+
|
|
276
|
+
def akp1(s, l, m, g):
|
|
277
|
+
return -g*g*k2p1(s, l, m) + 2.*s*g*k1p1(s, l, m)
|
|
278
|
+
|
|
279
|
+
def akp2(s, l, m, g):
|
|
280
|
+
return -g*g*k2p2(s, l, m)
|
|
281
|
+
|
|
282
|
+
def spectral_sparse_matrix(s, m, g, nmax):
|
|
283
|
+
lmin = max(abs(s), abs(m))
|
|
284
|
+
larray = np.arange(lmin, lmin + nmax)
|
|
285
|
+
return scipy.sparse.diags([akm2(s, larray[2:], m, g), akm1(s, larray[1:], m, g), akp0(s, larray, m, g), akp1(s, larray[:-1], m, g), akp2(s, larray[:-2], m, g)], [-2, -1, 0, 1, 2])
|
|
286
|
+
|
|
287
|
+
def swsh_eigs(s, l, m, g, nmax=None, return_eigenvectors=True):
|
|
288
|
+
lmin = max(abs(s), abs(m))
|
|
289
|
+
kval = l - lmin
|
|
290
|
+
|
|
291
|
+
if nmax is None:
|
|
292
|
+
buffer = round(20 + 2*np.abs(g))
|
|
293
|
+
Nmax = kval + buffer + 2
|
|
294
|
+
else:
|
|
295
|
+
if nmax < kval:
|
|
296
|
+
Nmax = kval + 5
|
|
297
|
+
else:
|
|
298
|
+
Nmax = nmax
|
|
299
|
+
|
|
300
|
+
mat = spectral_sparse_matrix(s, m, g, Nmax)
|
|
301
|
+
out = scipy.sparse.linalg.eigs(mat, k=Nmax-2, which='SM', return_eigenvectors=return_eigenvectors)
|
|
302
|
+
|
|
303
|
+
return out
|
|
304
|
+
|
|
305
|
+
def swsh_coeffs(s, l, m, g, th):
|
|
306
|
+
if g == 0.:
|
|
307
|
+
return Yslm(s, l, m, th)
|
|
308
|
+
|
|
309
|
+
_, eig = swsh_eigs(s, l, m, g, nmax=None, return_eigenvectors=True)
|
|
310
|
+
if g.imag == 0.:
|
|
311
|
+
coeffs = np.real(eig[l - max(abs(s), abs(m))])
|
|
312
|
+
else:
|
|
313
|
+
coeffs = eig[l - max(abs(s), abs(m))]
|
|
314
|
+
return coeffs
|
|
315
|
+
|
|
316
|
+
def swsh_eigenvalue(s, l, m, g, nmax=None):
|
|
317
|
+
"""
|
|
318
|
+
Compute the eigenvalue of the spin-weighted spheroidal harmonic.
|
|
319
|
+
|
|
320
|
+
Parameters
|
|
321
|
+
----------
|
|
322
|
+
s : int
|
|
323
|
+
The spin weight of the harmonic.
|
|
324
|
+
l : int
|
|
325
|
+
The angular number of the harmonic.
|
|
326
|
+
m : int
|
|
327
|
+
The azimuthal number of the harmonic.
|
|
328
|
+
g : float or complex
|
|
329
|
+
The spheroidicity parameter.
|
|
330
|
+
nmax : int, optional
|
|
331
|
+
The maximum number of basis functions to use in the computation. If None, a default value is chosen.
|
|
332
|
+
|
|
333
|
+
Returns
|
|
334
|
+
-------
|
|
335
|
+
float or complex
|
|
336
|
+
The eigenvalue of the spin-weighted spheroidal harmonic.
|
|
337
|
+
"""
|
|
338
|
+
if g == 0.:
|
|
339
|
+
return Yslm_eigenvalue(s, l)
|
|
340
|
+
|
|
341
|
+
las = swsh_eigs(s, l, m, g, nmax=nmax, return_eigenvectors=False)
|
|
342
|
+
|
|
343
|
+
idx = l - max(abs(s), abs(m))
|
|
344
|
+
sorted_indices = np.argsort(np.real(las))
|
|
345
|
+
if idx < 0 or idx >= len(sorted_indices):
|
|
346
|
+
raise IndexError(f"Index {idx} out of bounds for eigenvalue array of length {len(sorted_indices)}")
|
|
347
|
+
if g.imag == 0.:
|
|
348
|
+
eigen = np.real(las)[sorted_indices[idx]]
|
|
349
|
+
else:
|
|
350
|
+
eigen = las[sorted_indices[idx]]
|
|
351
|
+
return eigen
|
|
352
|
+
class SWSHBase:
|
|
353
|
+
def __init__(self, *args):
|
|
354
|
+
arg_num = np.array(args).shape[0]
|
|
355
|
+
if arg_num < 3:
|
|
356
|
+
print('Error. Not enough arguments to create class')
|
|
357
|
+
pass
|
|
358
|
+
|
|
359
|
+
self.s = args[0]
|
|
360
|
+
self.j = args[1]
|
|
361
|
+
self.m = args[2]
|
|
362
|
+
self.lmin = max(abs(self.s), abs(self.m))
|
|
363
|
+
self.jmin = max(abs(self.s), abs(self.m))
|
|
364
|
+
|
|
365
|
+
if arg_num > 3:
|
|
366
|
+
self.spheroidicity = args[3]
|
|
367
|
+
if self.spheroidicity.imag == 0:
|
|
368
|
+
self.spheroidicity = np.real(self.spheroidicity)
|
|
369
|
+
|
|
370
|
+
class SWSHSeriesBase(SWSHBase):
|
|
371
|
+
def __init__(self, s, j, m, g):
|
|
372
|
+
SWSHBase.__init__(self, s, j, m, g)
|
|
373
|
+
|
|
374
|
+
def sparse_matrix(self, nmax):
|
|
375
|
+
return spectral_sparse_matrix(self.s, self.m, self.spheroidicity, nmax)
|
|
376
|
+
|
|
377
|
+
def eigs(self, nmax = None, **kwargs):
|
|
378
|
+
kval = self.j - self.jmin
|
|
379
|
+
|
|
380
|
+
if nmax is None:
|
|
381
|
+
buffer = round(20 + np.abs(2*self.spheroidicity))
|
|
382
|
+
Nmax = kval + buffer + 2
|
|
383
|
+
else:
|
|
384
|
+
if nmax < kval:
|
|
385
|
+
Nmax = kval + 5
|
|
386
|
+
else:
|
|
387
|
+
Nmax = nmax
|
|
388
|
+
|
|
389
|
+
if "k" not in kwargs.keys():
|
|
390
|
+
kwargs["k"] = Nmax - 2
|
|
391
|
+
|
|
392
|
+
if "which" not in kwargs.keys():
|
|
393
|
+
kwargs["which"] = 'SM'
|
|
394
|
+
|
|
395
|
+
if "return_eigenvectors" not in kwargs.keys():
|
|
396
|
+
kwargs["return_eigenvectors"] = True
|
|
397
|
+
|
|
398
|
+
mat = self.sparse_matrix(Nmax)
|
|
399
|
+
return scipy.sparse.linalg.eigs(mat, **kwargs)
|
|
400
|
+
|
|
401
|
+
def generate_eigenvalue(self):
|
|
402
|
+
if self.spheroidicity.imag == 0.:
|
|
403
|
+
las = np.real(self.eigs(return_eigenvectors=False))
|
|
404
|
+
else:
|
|
405
|
+
las = self.eigs(return_eigenvectors=False)
|
|
406
|
+
pos = np.argsort(np.real(las))[self.j - self.jmin]
|
|
407
|
+
return las[pos]
|
|
408
|
+
|
|
409
|
+
def generate_eigs(self):
|
|
410
|
+
las, eigs = self.eigs()
|
|
411
|
+
pos_vec = np.argsort(np.real(las))
|
|
412
|
+
pos = pos_vec[self.j - self.jmin]
|
|
413
|
+
if self.spheroidicity.imag == 0.:
|
|
414
|
+
eigs_temp = np.real(eigs[:, pos])
|
|
415
|
+
eigs_return = np.sign(eigs_temp[self.j - self.jmin])*eigs_temp
|
|
416
|
+
eig = np.real(las[pos])
|
|
417
|
+
else:
|
|
418
|
+
eigs_temp = eigs[:, pos]
|
|
419
|
+
ref = eigs_temp[self.j - self.jmin]
|
|
420
|
+
eigs_temp = eigs_temp/ref
|
|
421
|
+
eigs_norm = np.linalg.norm(eigs_temp)
|
|
422
|
+
eigs_return = eigs_temp/eigs_norm
|
|
423
|
+
eig = las[pos]
|
|
424
|
+
return (eig, eigs_return)
|
|
425
|
+
class SpinWeightedSpheroidalHarmonic(SWSHSeriesBase):
|
|
426
|
+
"""
|
|
427
|
+
A class for generating a spin-weighted spheroidal harmonic.
|
|
428
|
+
|
|
429
|
+
Parameters
|
|
430
|
+
----------
|
|
431
|
+
s : int
|
|
432
|
+
The spin weight of the harmonic.
|
|
433
|
+
j : int
|
|
434
|
+
The angular number of the harmonic.
|
|
435
|
+
m : int
|
|
436
|
+
The azimuthal number of the harmonic.
|
|
437
|
+
g : float or complex
|
|
438
|
+
The spheroidicity parameter.
|
|
439
|
+
|
|
440
|
+
Attributes
|
|
441
|
+
----------
|
|
442
|
+
couplingcoefficients : array_like
|
|
443
|
+
The coupling coefficients between the spin-weighted spheroidal harmonic and the spin-weighted spherical
|
|
444
|
+
|
|
445
|
+
"""
|
|
446
|
+
def __init__(self, s, j, m, g):
|
|
447
|
+
SWSHSeriesBase.__init__(self, s, j, m, g)
|
|
448
|
+
if self.spheroidicity == 0.:
|
|
449
|
+
self.eval = self.Yslm
|
|
450
|
+
self.deriv = self.Yslm_derivative
|
|
451
|
+
self.deriv2 = self.Yslm_derivative2
|
|
452
|
+
self.eigenvalue = Yslm_eigenvalue(self.s, self.j)
|
|
453
|
+
self.coeffs = np.zeros(self.j - self.jmin + 1)
|
|
454
|
+
self.coeffs[-1] = 1.
|
|
455
|
+
self.mincouplingmode = self.j
|
|
456
|
+
self.maxcouplingmode = self.j
|
|
457
|
+
else:
|
|
458
|
+
self.eval = self.Sslm
|
|
459
|
+
self.deriv = self.Sslm_derivative
|
|
460
|
+
self.deriv2 = self.Sslm_derivative2
|
|
461
|
+
self.eigenvalue, self.coeffs = self.generate_eigs()
|
|
462
|
+
self.mincouplingmode = self.lmin
|
|
463
|
+
self.maxcouplingmode = self.lmin + self.coeffs.shape[0] - 1
|
|
464
|
+
|
|
465
|
+
@property
|
|
466
|
+
def couplingcoefficients(self):
|
|
467
|
+
return self.coeffs
|
|
468
|
+
|
|
469
|
+
def couplingcoefficient(self, l):
|
|
470
|
+
return self.coeffs[l - self.lmin]
|
|
471
|
+
|
|
472
|
+
def Yslm(self, l, th):
|
|
473
|
+
"""
|
|
474
|
+
Evaluate the spin-weighted spherical harmonic $Y_{s}^{lm}(theta)$ at a given angle theta.
|
|
475
|
+
|
|
476
|
+
Parameters
|
|
477
|
+
----------
|
|
478
|
+
l : int
|
|
479
|
+
The angular number of the spherical harmonic.
|
|
480
|
+
th : array_like
|
|
481
|
+
The polar angle(s) at which to evaluate the spherical harmonic.
|
|
482
|
+
|
|
483
|
+
Returns
|
|
484
|
+
-------
|
|
485
|
+
array_like
|
|
486
|
+
The values of the spherical harmonic at the specified angles.
|
|
487
|
+
"""
|
|
488
|
+
return Yslm(self.s, l, self.m, th)
|
|
489
|
+
|
|
490
|
+
def Yslm_derivative(self, l, th):
|
|
491
|
+
"""
|
|
492
|
+
Evaluate the derivative of the spin-weighted spherical harmonic $Y_{s}^{lm}(theta)$ at a given angle theta.
|
|
493
|
+
|
|
494
|
+
Parameters
|
|
495
|
+
----------
|
|
496
|
+
l : int
|
|
497
|
+
The angular number of the spherical harmonic.
|
|
498
|
+
th : array_like
|
|
499
|
+
The polar angle(s) at which to evaluate the derivative of the spherical harmonic.
|
|
500
|
+
|
|
501
|
+
Returns
|
|
502
|
+
-------
|
|
503
|
+
array_like
|
|
504
|
+
The values of the derivative of the spherical harmonic at the specified angles.
|
|
505
|
+
"""
|
|
506
|
+
return Yslm_derivative(self.s, l, self.m, th)
|
|
507
|
+
|
|
508
|
+
def Yslm_derivative2(self, l, th):
|
|
509
|
+
"""
|
|
510
|
+
Evaluate the second derivative of the spin-weighted spherical harmonic $Y_{s}^{lm}(theta)$ at a given angle theta.
|
|
511
|
+
|
|
512
|
+
Parameters
|
|
513
|
+
----------
|
|
514
|
+
l : int
|
|
515
|
+
The angular number of the spherical harmonic.
|
|
516
|
+
th : array_like
|
|
517
|
+
The polar angle(s) at which to evaluate the second derivative of the spherical harmonic.
|
|
518
|
+
|
|
519
|
+
Returns
|
|
520
|
+
-------
|
|
521
|
+
array_like
|
|
522
|
+
The values of the second derivative of the spherical harmonic at the specified angles.
|
|
523
|
+
"""
|
|
524
|
+
return Yslm_derivative2(self.s, l, self.m, th)
|
|
525
|
+
|
|
526
|
+
def Sslm(self, *args):
|
|
527
|
+
"""
|
|
528
|
+
Evaluate the spin-weighted spheroidal harmonic $S_{s}^{lm}(theta)$ at a given angle theta.
|
|
529
|
+
|
|
530
|
+
Parameters
|
|
531
|
+
----------
|
|
532
|
+
th : array_like
|
|
533
|
+
The polar angle(s) at which to evaluate the spheroidal harmonic.
|
|
534
|
+
|
|
535
|
+
Returns
|
|
536
|
+
-------
|
|
537
|
+
array_like
|
|
538
|
+
The values of the spheroidal harmonic at the specified angles.
|
|
539
|
+
"""
|
|
540
|
+
th = args[-1]
|
|
541
|
+
term_num = self.coeffs.shape[0]
|
|
542
|
+
if isinstance(th, (int, float)):
|
|
543
|
+
Yslm_array = np.empty(term_num)
|
|
544
|
+
else:
|
|
545
|
+
pts_num = th.shape[0]
|
|
546
|
+
Yslm_array = np.empty((term_num, pts_num))
|
|
547
|
+
for i in range(term_num):
|
|
548
|
+
Yslm_array[i] = self.Yslm(self.lmin + i, th)
|
|
549
|
+
|
|
550
|
+
return np.dot(self.coeffs, Yslm_array)
|
|
551
|
+
|
|
552
|
+
def Sslm_derivative(self, *args):
|
|
553
|
+
"""
|
|
554
|
+
Evaluate the derivative of the spin-weighted spheroidal harmonic $S_{s}^{lm}(theta)$ at a given angle theta.
|
|
555
|
+
|
|
556
|
+
Parameters
|
|
557
|
+
----------
|
|
558
|
+
th : array_like
|
|
559
|
+
The polar angle(s) at which to evaluate the spheroidal harmonic.
|
|
560
|
+
|
|
561
|
+
Returns
|
|
562
|
+
-------
|
|
563
|
+
array_like
|
|
564
|
+
The derivatives of the spheroidal harmonic at the specified angles.
|
|
565
|
+
"""
|
|
566
|
+
th = args[-1]
|
|
567
|
+
term_num = self.coeffs.shape[0]
|
|
568
|
+
if isinstance(th, (int, float)):
|
|
569
|
+
dYslm_array = np.empty(term_num)
|
|
570
|
+
else:
|
|
571
|
+
pts_num = th.shape[0]
|
|
572
|
+
dYslm_array = np.empty((term_num, pts_num))
|
|
573
|
+
for i in range(term_num):
|
|
574
|
+
dYslm_array[i] = self.Yslm_derivative(self.lmin + i, th)
|
|
575
|
+
|
|
576
|
+
return np.dot(self.coeffs, dYslm_array)
|
|
577
|
+
|
|
578
|
+
def Sslm_derivative2(self, *args):
|
|
579
|
+
"""
|
|
580
|
+
Evaluate the second derivative of the spin-weighted spheroidal harmonic $S_{s}^{lm}(theta)$ at a given angle theta.
|
|
581
|
+
|
|
582
|
+
Parameters
|
|
583
|
+
----------
|
|
584
|
+
th : array_like
|
|
585
|
+
The polar angle(s) at which to evaluate the spheroidal harmonic.
|
|
586
|
+
|
|
587
|
+
Returns
|
|
588
|
+
-------
|
|
589
|
+
array_like
|
|
590
|
+
The second derivatives of the spheroidal harmonic at the specified angles.
|
|
591
|
+
"""
|
|
592
|
+
th = args[-1]
|
|
593
|
+
term_num = self.coeffs.shape[0]
|
|
594
|
+
if isinstance(th, (int, float)):
|
|
595
|
+
dY2slm_array = np.empty(term_num)
|
|
596
|
+
else:
|
|
597
|
+
pts_num = th.shape[0]
|
|
598
|
+
dY2slm_array = np.empty((term_num, pts_num))
|
|
599
|
+
for i in range(term_num):
|
|
600
|
+
dY2slm_array[i] = self.Yslm_derivative2(self.lmin + i, th)
|
|
601
|
+
|
|
602
|
+
return np.dot(self.coeffs, dY2slm_array)
|
|
603
|
+
|
|
604
|
+
def __call__(self, th, ph = None, deriv = None):
|
|
605
|
+
if deriv is None or deriv == 0:
|
|
606
|
+
out = self.eval(self.j, th)
|
|
607
|
+
elif deriv == 1:
|
|
608
|
+
out = self.deriv(self.j, th)
|
|
609
|
+
elif deriv == 2:
|
|
610
|
+
out = self.deriv2(self.j, th)
|
|
611
|
+
else:
|
|
612
|
+
raise ValueError(f"Derivative order = {deriv} not supported")
|
|
613
|
+
|
|
614
|
+
if ph is not None:
|
|
615
|
+
out = out*np.exp(1.j*self.m*ph)
|
|
616
|
+
return out
|
|
617
|
+
|
|
618
|
+
def muCoupling(s, l):
|
|
619
|
+
"""
|
|
620
|
+
Eigenvalue for the spin-weighted spherical harmonic lowering operator
|
|
621
|
+
Setting s -> -s gives the negative of the eigenvalue for the raising operator
|
|
622
|
+
"""
|
|
623
|
+
if l + s < 0 or l - s + 1. < 0:
|
|
624
|
+
return 0
|
|
625
|
+
return np.sqrt((l - s + 1.)*(l + s))
|
|
626
|
+
|
|
627
|
+
def Asjlm(s, j, l, m):
|
|
628
|
+
"""
|
|
629
|
+
Coupling coefficient between scalar and spin-weighted spherical harmonics
|
|
630
|
+
|
|
631
|
+
Parameters
|
|
632
|
+
----------
|
|
633
|
+
s : int
|
|
634
|
+
The spin weight of the harmonic.
|
|
635
|
+
j : int
|
|
636
|
+
The angular number of the scalar harmonic.
|
|
637
|
+
l : int
|
|
638
|
+
The angular number of the spin-weighted harmonic.
|
|
639
|
+
m : int
|
|
640
|
+
The azimuthal number of the harmonics.
|
|
641
|
+
|
|
642
|
+
Returns
|
|
643
|
+
-------
|
|
644
|
+
float
|
|
645
|
+
The coupling coefficient $A_{s}^{jlm}$
|
|
646
|
+
"""
|
|
647
|
+
if s >= 0:
|
|
648
|
+
return (-1.)**(m + s)*np.sqrt(4**s*fac(s)**2*(2*l + 1)*(2*j + 1)/fac(2*s))*w3j(s, l, j, 0, m, -m)*w3j(s, l, j, s, -s, 0)
|
|
649
|
+
else:
|
|
650
|
+
return (-1.)**(m)*np.sqrt(4**(-s)*fac(-s)**2*(2*l + 1)*(2*j + 1)/fac(-2*s))*w3j(-s, l, j, 0, m, -m)*w3j(-s, l, j, s, -s, 0)
|
|
651
|
+
|
|
652
|
+
def spin_operator_normalization(s, ns, l):
|
|
653
|
+
s_sgn = np.sign(s)
|
|
654
|
+
nmax1 = np.abs(s) + 1
|
|
655
|
+
Jterm = 1.
|
|
656
|
+
for ni in range(1, ns + 1):
|
|
657
|
+
Jterm *= -s_sgn*muCoupling((nmax1-ni), l)
|
|
658
|
+
return Jterm
|
|
659
|
+
|