pybhpt 0.9.4__cp310-cp310-musllinux_1_2_x86_64.whl → 0.9.5__cp310-cp310-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.

Potentially problematic release.


This version of pybhpt might be problematic. Click here for more details.

Binary file
pybhpt/geo.py CHANGED
@@ -4,21 +4,147 @@ from cybhpt_full import kerr_mino_frequencies_wrapper, kerr_orbital_constants_wr
4
4
  import numpy as np
5
5
 
6
6
  def kerrgeo_Vt_radial(a, En, Lz, Q, r):
7
+ """
8
+ The radial part of the potential Vt for the geodesic evolution of $t_p$.
9
+
10
+ Parameters
11
+ ----------
12
+ a : float
13
+ The black hole spin parameter.
14
+ En : float
15
+ The orbital energy of the particle.
16
+ Lz : float
17
+ The z-component of the orbital angular momentum of the particle.
18
+ Q : float
19
+ The Carter constant of the particle.
20
+ r : float
21
+ The radial coordinate.
22
+
23
+ Returns
24
+ -------
25
+ float
26
+ The value of the radial potential Vt at the given parameters.
27
+ """
7
28
  return kerr_geo_V01(a, En, Lz, Q, r)
8
29
 
9
30
  def kerrgeo_Vt_polar(a, En, Lz, Q, theta):
31
+ """
32
+ The polar part of the potential Vt for the geodesic evolution of $t_p$.
33
+
34
+ Parameters
35
+ ----------
36
+ a : float
37
+ The black hole spin parameter.
38
+ En : float
39
+ The orbital energy of the particle.
40
+ Lz : float
41
+ The z-component of the orbital angular momentum of the particle.
42
+ Q : float
43
+ The Carter constant of the particle.
44
+ theta : float
45
+ The polar angle coordinate.
46
+
47
+ Returns
48
+ -------
49
+ float
50
+ The value of the polar potential Vt at the given parameters.
51
+ """
10
52
  return kerr_geo_V02(a, En, Lz, Q, theta)
11
53
 
12
54
  def kerrgeo_Vr(a, En, Lz, Q, r):
55
+ """
56
+ The (squared) radial potential Vr for the geodesic evolution of $r_p$.
57
+
58
+ Parameters
59
+ ----------
60
+ a : float
61
+ The black hole spin parameter.
62
+ En : float
63
+ The orbital energy of the particle.
64
+ Lz : float
65
+ The z-component of the orbital angular momentum of the particle.
66
+ Q : float
67
+ The Carter constant of the particle.
68
+ r : float
69
+ The radial coordinate.
70
+
71
+ Returns
72
+ -------
73
+ float
74
+ The value of the radial potential Vr at the given parameters.
75
+ """
13
76
  return kerr_geo_V11(a, En, Lz, Q, r)
14
77
 
15
78
  def kerrgeo_Vtheta(a, En, Lz, Q, theta):
79
+ """
80
+ The (squared) polar potential Vtheta for the geodesic evolution of $\theta_p$.
81
+
82
+ Parameters
83
+ ----------
84
+ a : float
85
+ The black hole spin parameter.
86
+ En : float
87
+ The orbital energy of the particle.
88
+ Lz : float
89
+ The z-component of the orbital angular momentum of the particle.
90
+ Q : float
91
+ The Carter constant of the particle.
92
+ theta : float
93
+ The polar angle coordinate.
94
+
95
+ Returns
96
+ -------
97
+ float
98
+ The value of the polar potential Vtheta at the given parameters.
99
+ """
16
100
  return kerr_geo_V22(a, En, Lz, Q, theta)
17
101
 
18
102
  def kerrgeo_Vphi_radial(a, En, Lz, Q, r):
103
+ """
104
+ The (squared) radial potential Vphi for the geodesic evolution of $r_p$.
105
+
106
+ Parameters
107
+ ----------
108
+ a : float
109
+ The black hole spin parameter.
110
+ En : float
111
+ The orbital energy of the particle.
112
+ Lz : float
113
+ The z-component of the orbital angular momentum of the particle.
114
+ Q : float
115
+ The Carter constant of the particle.
116
+ r : float
117
+ The radial coordinate.
118
+
119
+ Returns
120
+ -------
121
+ float
122
+ The value of the radial potential Vphi at the given parameters.
123
+ """
19
124
  return kerr_geo_V31(a, En, Lz, Q, r)
20
125
 
21
126
  def kerrgeo_Vphi_polar(a, En, Lz, Q, theta):
127
+ """
128
+ The (squared) polar potential Vphi for the geodesic evolution of $r_p$.
129
+
130
+ Parameters
131
+ ----------
132
+ a : float
133
+ The black hole spin parameter.
134
+ En : float
135
+ The orbital energy of the particle.
136
+ Lz : float
137
+ The z-component of the orbital angular momentum of the particle.
138
+ Q : float
139
+ The Carter constant of the particle.
140
+ theta : float
141
+ The polar angle coordinate.
142
+
143
+ Returns
144
+ -------
145
+ float
146
+ The value of the polar potential Vphi at the given parameters.
147
+ """
22
148
  return kerr_geo_V32(a, En, Lz, Q, theta)
23
149
 
24
150
  def kerr_mino_frequencies(a, p, e, x):
pybhpt/radial.py CHANGED
@@ -11,14 +11,55 @@ def available_methods():
11
11
  """
12
12
  return available_methods_cython()
13
13
 
14
- def renormalized_angular_momentum(s, l, m, a, omega):
15
- return nu_cython(s, l, m, a, omega)
14
+ def renormalized_angular_momentum(s, j, m, a, omega):
15
+ """
16
+ Computes the renormalized angular momentum for the given parameters.
16
17
 
17
- def renormalized_angular_momentum_monodromy(s, l, m, a, omega, la):
18
- return nu_2_cython(s, l, m, a, omega, la)
18
+ Parameters
19
+ ----------
20
+ s : int
21
+ The spin weight of the field.
22
+ j : int
23
+ The spheroidal harmonic mode number.
24
+ m : int
25
+ The azimuthal harmonic mode number.
26
+ a : float
27
+ The black hole spin parameter.
28
+ omega : float
29
+ The frequency of the mode.
19
30
 
20
- def hypergeo_2F1(a, b, c, x):
21
- return hypergeo_2F1_cython(a, b, c, x)
31
+ Returns
32
+ -------
33
+ complex
34
+ The renormalized angular momentum.
35
+ """
36
+ return nu_cython(s, j, m, a, omega)
37
+
38
+ def renormalized_angular_momentum_monodromy(s, j, m, a, omega, la):
39
+ """
40
+ Computes the renormalized angular momentum using the monodromy method for the given parameters.
41
+
42
+ Parameters
43
+ ----------
44
+ s : int
45
+ The spin weight of the field.
46
+ j : int
47
+ The spheroidal harmonic mode number.
48
+ m : int
49
+ The azimuthal harmonic mode number.
50
+ a : float
51
+ The black hole spin parameter.
52
+ omega : complex
53
+ The frequency of the mode.
54
+ la : complex
55
+ The spheroidal eigenvalue.
56
+
57
+ Returns
58
+ -------
59
+ complex
60
+ The renormalized angular momentum.
61
+ """
62
+ return nu_2_cython(s, j, m, a, omega, la)
22
63
 
23
64
  class RadialTeukolsky:
24
65
  """A class for solving the homogeneous radial Teukolsky equation.
@@ -378,4 +419,26 @@ class RadialTeukolsky:
378
419
  elif deriv == 2:
379
420
  return self.radialderivatives2(bc)
380
421
  else:
381
- raise ValueError("RadialTeukolsky only solves up to the second derivative")
422
+ raise ValueError("RadialTeukolsky only solves up to the second derivative")
423
+
424
+ def hypergeo_2F1(a, b, c, x):
425
+ """
426
+ Gauss hypergeometric function 2F1(a, b; c; x). Note that this function is not very stable across the complex domain.
427
+
428
+ Parameters
429
+ ----------
430
+ a : complex
431
+ The first parameter of the hypergeometric function.
432
+ b : complex
433
+ The second parameter of the hypergeometric function.
434
+ c : complex
435
+ The third parameter of the hypergeometric function.
436
+ x : complex
437
+ The argument of the hypergeometric function.
438
+
439
+ Returns
440
+ -------
441
+ complex
442
+ The value of the hypergeometric function 2F1(a, b; c; x).
443
+ """
444
+ return hypergeo_2F1_cython(a, b, c, x)
pybhpt/swsh.py CHANGED
@@ -4,114 +4,130 @@ from scipy.special import sph_harm
4
4
  from scipy.special import binom
5
5
  from scipy.special import factorial
6
6
  import numpy as np
7
+ from cybhpt_full import YslmCy, clebschCy, w3jCy
7
8
 
8
9
  """
9
10
  Wigner 3j-symbol and Clebsch-Gordon coefficients
10
11
  """
11
12
 
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
+ """
13
27
  if n < 0:
14
28
  return 0
15
29
  return float(np.math.factorial(n))
16
30
 
17
- def Yslm(s, l, m, th):
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
+ """
18
51
  if np.abs(s) > l:
19
52
  return 0.*th
20
53
  if s == 0:
21
54
  return np.real(sph_harm(m, l, 0., th))
22
55
  elif s + m < 0:
23
- return (-1.)**(s+m)*YslmBase(-s, l, -m, np.cos(th))
24
- # elif th > np.pi/2.:
25
- # return (-1.)**(l + m)*YslmBase(-s, l, m, -np.cos(th))
56
+ return (-1.)**(s+m)*YslmBase(-s, l, -m, th)
57
+ else:
58
+ return YslmBase(s, l, m, th)
59
+
60
+ def YslmBase(s, l, m, th):
61
+ assert isinstance(s, int) and isinstance(l, int) and isinstance(m, int), "s, l, and m must be integers"
62
+ if not isinstance(th, (int, float)):
63
+ b = np.broadcast(th)
64
+ out = np.empty(b.shape)
65
+ out.flat = [YslmCy(s, l, m, thi) for (thi,) in b]
26
66
  else:
27
- return YslmBase(s, l, m, np.cos(th))
67
+ out = YslmCy(s, l, m, th)
68
+ return out
28
69
 
29
- def YslmBase(s, l, m, z):
30
- rmax = l - s
31
- pref = (0.5)**(l)*(-1.)**m*np.sqrt(factorial(l+m)/factorial(l+s)*factorial(l-m)/factorial(l-s)*(2*l+1)/(4.*np.pi))*np.sqrt(1. - z)**(s + m)*np.sqrt(1. + z)**(s - m)
32
-
33
- yslm = 0.*pref
34
- for r in range(0, rmax + 1):
35
- yslm += binom(l - s, r)*binom(l + s, r + s - m)*(z - 1.)**(rmax - r)*(z + 1.)**(r)
36
-
37
- return pref*yslm
70
+ def Yslm_eigenvalue(s, l, *args):
71
+ return l*(l + 1.) - s*(s + 1.)
38
72
 
39
73
  def clebsch(l1, l2, l3, m1, m2, m3):
40
- return (-1)**(l1 - l2 + m3)*np.sqrt(2*l3 + 1)*w3j(l1, l2, l3, m1, m2, -m3);
74
+ """
75
+ Compute the Clebsch-Gordon coefficient <l1,m1,l2,m2|l3,m3>.
76
+
77
+ Parameters
78
+ ----------
79
+ l1 : int
80
+ The angular number of the first state.
81
+ l2 : int
82
+ The angular number of the second state.
83
+ l3 : int
84
+ The angular number of the combined state.
85
+ m1 : int
86
+ The azimuthal number of the first state.
87
+ m2 : int
88
+ The azimuthal number of the second state.
89
+ m3 : int
90
+ The azimuthal number of the combined state.
91
+
92
+ Returns
93
+ -------
94
+ float
95
+ The Clebsch-Gordon coefficient <l1,m1,l2,m2|l3,m3>.
96
+ """
97
+ assert isinstance(l1, int) and isinstance(l2, int) and isinstance(l3, int), "l1, l2, and l3 must be integers"
98
+ assert isinstance(m1, int) and isinstance(m2, int) and isinstance(m3, int), "m1, m2, and m3 must be integers"
99
+ return clebschCy(l1, l2, l3, m1, m2, m3)
41
100
 
42
101
  def w3j(l1, l2, l3, m1, m2, m3):
43
- if m1 + m2 + m3 != 0:
44
- return 0
45
- elif abs(l1 - l2) > l3:
46
- return 0
47
- elif l1 + l2 < l3:
48
- return 0
49
-
50
- if abs(m1) > l1:
51
- return 0
52
- elif abs(m2) > l2:
53
- return 0
54
- elif abs(m3) > l3:
55
- return 0
56
-
57
- sumTerm = w3j_tsum(l1, l2, l3, m1, m2, m3)
58
- if sumTerm == 0:
59
- return 0
60
- sumSign = np.sign(sumTerm)
61
- tempLog = 0.5*(np.log(fac(l1 + m1)) + np.log(fac(l2 + m2)) + np.log(fac(l3 + m3)))
62
- tempLog += 0.5*(np.log(fac(l1 - m1)) + np.log(fac(l2 - m2)) + np.log(fac(l3 - m3)))
63
- tempLog += np.log(triangle_coeff(l1, l2, l3))
64
- tempLog += np.log(abs(sumTerm))
65
-
66
- temp = sumSign*np.exp(tempLog)
67
- temp *= (-1)**(l1-l2-m3)
68
-
69
- return temp
70
-
71
- def w3j_tsum(l1, l2, l3, m1, m2, m3):
72
- t_min_num = w3j_t_min(l1, l2, l3, m1, m2, m3)
73
- t_max_num = w3j_t_max(l1, l2, l3, m1, m2, m3)
74
- x = 0
75
- if t_max_num < t_min_num:
76
- t_max_num = t_min_num
77
-
78
- for t in range(t_min_num - 1, t_max_num + 2):
79
- term = (fac(t)*fac(l3 - l2 + m1 + t)*fac(l3 - l1 - m2 + t)
80
- *fac(l1 + l2 - l3 - t)*fac(l1 - t - m1)*fac(l2 - t + m2))
81
- if term > 0:
82
- x += (-1)**t/term
83
-
84
- return x
85
-
86
- def w3j_t_min(l1, l2, l3, m1, m2, m3):
87
- temp = 0
102
+ """
103
+ Compute the Wigner 3j-symbol
104
+ | l1 l2 l3 |
105
+ | m1 m2 m3 |
106
+
107
+ Parameters
108
+ ----------
109
+ l1 : int
110
+ The angular number of the first state.
111
+ l2 : int
112
+ The angular number of the second state.
113
+ l3 : int
114
+ The angular number of the combined state.
115
+ m1 : int
116
+ The azimuthal number of the first state.
117
+ m2 : int
118
+ The azimuthal number of the second state.
119
+ m3 : int
120
+ The azimuthal number of the combined state.
121
+
122
+ Returns
123
+ -------
124
+ float
125
+ The Wigner 3j-symbol $ \begin{pmatrix} l1 & l2 & l3 \\ m1 & m2 & m3 \end{pmatrix} $
126
+ """
127
+ assert isinstance(l1, int) and isinstance(l2, int) and isinstance(l3, int), "l1, l2, and l3 must be integers"
128
+ assert isinstance(m1, int) and isinstance(m2, int) and isinstance(m3, int), "m1, m2, and m3 must be integers"
129
+ return w3jCy(l1, l2, l3, m1, m2, m3)
88
130
 
89
- comp = l3 - l2 + m1
90
- if temp + comp < 0:
91
- temp = -comp
92
- comp = l3 - l1 - m2
93
- if temp + comp < 0:
94
- temp = -comp
95
-
96
- return temp
97
-
98
- def w3j_t_max(l1, l2, l3, m1, m2, m3):
99
- temp = 1
100
- comp = l1 + l2 - l3
101
- if comp - temp > 0:
102
- temp = comp
103
- comp = l1 - m1
104
- if comp - temp > 0:
105
- temp = comp
106
- comp = l2 + m2
107
- if comp - temp > 0:
108
- temp = comp
109
-
110
- return temp;
111
-
112
- def triangle_coeff(l1, l2, l3):
113
- return np.sqrt(fac(l1 + l2 - l3)*fac(l3 + l1 - l2)*fac(l2 + l3 - l1)/fac(l1 + l2 + l3 + 1))
114
-
115
131
  """
116
132
  SWSH Eigenvalue Functions
117
133
  """
@@ -200,7 +216,7 @@ def swsh_eigs(s, l, m, g, nmax=None, return_eigenvectors=True):
200
216
  kval = l - lmin
201
217
 
202
218
  if nmax is None:
203
- buffer = round(20 + 2*g)
219
+ buffer = round(20 + 2*np.abs(g))
204
220
  Nmax = kval + buffer + 2
205
221
  else:
206
222
  if nmax < kval:
@@ -213,26 +229,53 @@ def swsh_eigs(s, l, m, g, nmax=None, return_eigenvectors=True):
213
229
 
214
230
  return out
215
231
 
216
- def Yslm_eigenvalue(s, l, *args):
217
- return l*(l + 1.) - s*(s + 1.)
232
+ def swsh_coeffs(s, l, m, g, th):
233
+ if g == 0.:
234
+ return Yslm(s, l, m, th)
235
+
236
+ _, eig = swsh_eigs(s, l, m, g, nmax=None, return_eigenvectors=True)
237
+ if g.imag == 0.:
238
+ coeffs = np.real(eig[l - max(abs(s), abs(m))])
239
+ else:
240
+ coeffs = eig[l - max(abs(s), abs(m))]
241
+ return coeffs
218
242
 
219
243
  def swsh_eigenvalue(s, l, m, g, nmax=None):
244
+ """
245
+ Compute the eigenvalue of the spin-weighted spheroidal harmonic.
246
+
247
+ Parameters
248
+ ----------
249
+ s : int
250
+ The spin weight of the harmonic.
251
+ l : int
252
+ The angular number of the harmonic.
253
+ m : int
254
+ The azimuthal number of the harmonic.
255
+ g : float or complex
256
+ The spheroidicity parameter.
257
+ nmax : int, optional
258
+ The maximum number of basis functions to use in the computation. If None, a default value is chosen.
259
+
260
+ Returns
261
+ -------
262
+ float or complex
263
+ The eigenvalue of the spin-weighted spheroidal harmonic.
264
+ """
220
265
  if g == 0.:
221
266
  return Yslm_eigenvalue(s, l)
222
267
 
223
268
  las = swsh_eigs(s, l, m, g, nmax=nmax, return_eigenvectors=False)
224
269
 
225
- return np.real(las[::-1][l - max(abs(s), abs(m))])
226
-
227
- def swsh_coeffs(s, l, m, g, th):
228
- if g == 0.:
229
- return Yslm(s, l, m, th)
230
-
231
- _, eig = swsh_eigs(s, l, m, g, nmax=None, return_eigenvectors=True)
232
- coeffs = np.real(eig[l - max(abs(s), abs(m))])
233
-
234
- return np.real(eig[l - max(abs(s), abs(m))])
235
-
270
+ idx = l - max(abs(s), abs(m))
271
+ sorted_indices = np.argsort(np.real(las))
272
+ if idx < 0 or idx >= len(sorted_indices):
273
+ raise IndexError(f"Index {idx} out of bounds for eigenvalue array of length {len(sorted_indices)}")
274
+ if g.imag == 0.:
275
+ eigen = np.real(las)[sorted_indices[idx]]
276
+ else:
277
+ eigen = las[sorted_indices[idx]]
278
+ return eigen
236
279
  class SWSHBase:
237
280
  def __init__(self, *args):
238
281
  arg_num = np.array(args).shape[0]
@@ -247,7 +290,9 @@ class SWSHBase:
247
290
 
248
291
  if arg_num > 3:
249
292
  self.spheroidicity = args[3]
250
-
293
+ if self.spheroidicity.imag == 0:
294
+ self.spheroidicity = np.real(self.spheroidicity)
295
+
251
296
  class SWSHSeriesBase(SWSHBase):
252
297
  def __init__(self, s, l, m, g):
253
298
  SWSHBase.__init__(self, s, l, m, g)
@@ -259,7 +304,7 @@ class SWSHSeriesBase(SWSHBase):
259
304
  kval = self.l - self.lmin
260
305
 
261
306
  if nmax is None:
262
- buffer = round(20 + 2*self.spheroidicity)
307
+ buffer = round(20 + np.abs(2*self.spheroidicity))
263
308
  Nmax = kval + buffer + 2
264
309
  else:
265
310
  if nmax < kval:
@@ -283,18 +328,50 @@ class SWSHSeriesBase(SWSHBase):
283
328
  return scipy.sparse.linalg.eigs(mat, **kwargs)
284
329
 
285
330
  def generate_eigenvalue(self):
286
- las = np.real(self.eigs(return_eigenvectors=False))
287
- pos = np.argsort(las)[self.l - self.lmin]
331
+ if self.spheroidicity.imag == 0.:
332
+ las = np.real(self.eigs(return_eigenvectors=False))
333
+ else:
334
+ las = self.eigs(return_eigenvectors=False)
335
+ pos = np.argsort(np.real(las))[self.l - self.lmin]
288
336
  return las[pos]
289
-
337
+
290
338
  def generate_eigs(self):
291
339
  las, eigs = self.eigs()
292
- pos = np.argsort(np.real(las))[self.l - self.lmin]
293
- eigs_temp = np.real(eigs[:, pos])
294
- eigs_return = np.sign(eigs_temp[self.l - self.lmin])*eigs_temp
295
- return (np.real(las[pos]), eigs_return)
340
+ pos_vec = np.argsort(np.real(las))
341
+ pos = pos_vec[self.l - self.lmin]
342
+ if self.spheroidicity.imag == 0.:
343
+ eigs_temp = np.real(eigs[:, pos])
344
+ eigs_return = np.sign(eigs_temp[self.l - self.lmin])*eigs_temp
345
+ eig = np.real(las[pos])
346
+ else:
347
+ eigs_temp = eigs[:, pos]
348
+ ref = eigs_temp[self.l - self.lmin]
349
+ eigs_temp = eigs_temp/ref
350
+ eigs_norm = np.linalg.norm(eigs_temp)
351
+ eigs_return = eigs_temp/eigs_norm
352
+ eig = las[pos]
353
+ return (eig, eigs_return)
354
+ class SpinWeightedSpheroidalHarmonic(SWSHSeriesBase):
355
+ """
356
+ A class for generating a spin-weighted spheroidal harmonic.
357
+
358
+ Parameters
359
+ ----------
360
+ s : int
361
+ The spin weight of the harmonic.
362
+ l : int
363
+ The angular number of the harmonic.
364
+ m : int
365
+ The azimuthal number of the harmonic.
366
+ g : float or complex
367
+ The spheroidicity parameter.
368
+
369
+ Attributes
370
+ ----------
371
+ couplingcoefficients : array_like
372
+ The coupling coefficients between the spin-weighted spheroidal harmonic and the spin-weighted spherical
296
373
 
297
- class SWSH(SWSHSeriesBase):
374
+ """
298
375
  def __init__(self, s, l, m, g):
299
376
  SWSHSeriesBase.__init__(self, s, l, m, g)
300
377
  if self.spheroidicity == 0.:
@@ -305,23 +382,61 @@ class SWSH(SWSHSeriesBase):
305
382
  else:
306
383
  self.eval = self.Sslm
307
384
  self.eigenvalue, self.coeffs = self.generate_eigs()
308
-
385
+
386
+ @property
387
+ def couplingcoefficients(self):
388
+ return self.coeffs
389
+
309
390
  def Yslm(self, l, th):
391
+ """
392
+ Evaluate the spin-weighted spherical harmonic $Y_{s}^{lm}(theta)$ at a given angle theta.
393
+
394
+ Parameters
395
+ ----------
396
+ l : int
397
+ The angular number of the spherical harmonic.
398
+ th : array_like
399
+ The polar angle(s) at which to evaluate the spherical harmonic.
400
+
401
+ Returns
402
+ -------
403
+ array_like
404
+ The values of the spherical harmonic at the specified angles.
405
+ """
310
406
  return Yslm(self.s, l, self.m, th)
311
407
 
312
408
  def Sslm(self, *args):
409
+ """
410
+ Evaluate the spin-weighted spheroidal harmonic $S_{s}^{lm}(theta)$ at a given angle theta.
411
+
412
+ Parameters
413
+ ----------
414
+ th : array_like
415
+ The polar angle(s) at which to evaluate the spheroidal harmonic.
416
+
417
+ Returns
418
+ -------
419
+ array_like
420
+ The values of the spheroidal harmonic at the specified angles.
421
+ """
313
422
  th = args[-1]
314
423
  term_num = self.coeffs.shape[0]
315
- pts_num = th.shape[0]
316
- Yslm_array = np.empty((term_num, pts_num))
424
+ if isinstance(th, (int, float)):
425
+ Yslm_array = np.empty(term_num)
426
+ else:
427
+ pts_num = th.shape[0]
428
+ Yslm_array = np.empty((term_num, pts_num))
317
429
  for i in range(term_num):
318
430
  Yslm_array[i] = self.Yslm(self.lmin + i, th)
319
431
 
320
432
  return np.dot(self.coeffs, Yslm_array)
321
433
 
322
- def __call__(self, th):
323
- return self.eval(self.l, th)
324
-
434
+ def __call__(self, th, ph = None):
435
+ out = self.eval(self.l, th)
436
+ if ph is not None:
437
+ out = out*np.exp(1.j*self.m*ph)
438
+ return out
439
+
325
440
  def muCoupling(s, l):
326
441
  """
327
442
  Eigenvalue for the spin-weighted spherical harmonic lowering operator
@@ -332,6 +447,25 @@ def muCoupling(s, l):
332
447
  return np.sqrt((l - s + 1.)*(l + s))
333
448
 
334
449
  def Asjlm(s, j, l, m):
450
+ """
451
+ Coupling coefficient between scalar and spin-weighted spherical harmonics
452
+
453
+ Parameters
454
+ ----------
455
+ s : int
456
+ The spin weight of the harmonic.
457
+ j : int
458
+ The angular number of the scalar harmonic.
459
+ l : int
460
+ The angular number of the spin-weighted harmonic.
461
+ m : int
462
+ The azimuthal number of the harmonics.
463
+
464
+ Returns
465
+ -------
466
+ float
467
+ The coupling coefficient $A_{s}^{jlm}$
468
+ """
335
469
  if s >= 0:
336
470
  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)
337
471
  else:
pybhpt/teuk.py CHANGED
@@ -194,52 +194,243 @@ class TeukolskyMode:
194
194
  Flips the spin-weight and frequency of the Teukolsky solutions from :math:`s \rightarrow -s` and :math:`\omega \rightarrow -\omega`
195
195
  """
196
196
  def flipspinweightandfrequency(self):
197
+ """
198
+ Flips the spin-weight and frequency of the Teukolsky solutions from :math:`s \rightarrow -s` and :math:`\omega \rightarrow -\omega`
199
+ """
197
200
  self.base.flip_spinweight_frequency()
198
201
 
199
- """
200
- Spherical-spheroidal mixing coefficient between a spherical harmonic :math:`l` mode with a spheroidal :math:`j` mode
201
-
202
- :param l: spherical harmonic mode
203
- :type l: int
204
- """
205
202
  def couplingcoefficient(self, l):
203
+ """
204
+ Spherical-spheroidal mixing coefficient between a spherical harmonic $l$ mode with a spheroidal $j$ mode.
205
+
206
+ Parameters
207
+ ----------
208
+ l : int
209
+ Spherical harmonic mode.
210
+
211
+ Returns
212
+ -------
213
+ float
214
+ The coupling coefficient between the spherical harmonic mode `l` and the spheroidal harmonic mode `j`.
215
+ """
206
216
  return self.base.couplingcoefficient(l)
207
217
 
208
218
  def radialpoint(self, pos):
219
+ """
220
+ The radial point for the given position `pos`.
221
+
222
+ Parameters
223
+ ----------
224
+ pos : int
225
+ The radial position.
226
+
227
+ Returns
228
+ -------
229
+ float
230
+ The radial point at the given position `pos`.
231
+ """
209
232
  return self.base.radialpoint(pos)
210
233
 
211
234
  def radialsolution(self, bc, pos):
235
+ """
236
+ The extended homogeneous radial solution for the given boundary condition `bc` and position `pos`.
237
+
238
+ Parameters
239
+ ----------
240
+ bc : str
241
+ The boundary condition, either "In" for ingoing or "Up" for upgoing.
242
+ pos : int
243
+ The radial position.
244
+
245
+ Returns
246
+ -------
247
+ complex
248
+ The radial solution at the given boundary condition `bc` and position `pos`.
249
+ """
212
250
  return self.base.radialsolution(bc, pos)
213
251
 
214
252
  def radialderivative(self, bc, pos):
253
+ """
254
+ The derivative of the extended homogeneous radial solution for the given boundary condition `bc` and position `pos`.
255
+
256
+ Parameters
257
+ ----------
258
+ bc : str
259
+ The boundary condition, either "In" for ingoing or "Up" for upgoing.
260
+ pos : int
261
+ The radial position.
262
+
263
+ Returns
264
+ -------
265
+ complex
266
+ The radial derivative at the given boundary condition `bc` and position `pos`.
267
+ """
215
268
  return self.base.radialderivative(bc, pos)
216
269
 
217
270
  def radialderivative2(self, bc, pos):
271
+ """
272
+ The second derivative of the extended homogeneous radial solution for the given boundary condition `bc` and position `pos`.
273
+
274
+ Parameters
275
+ ----------
276
+ bc : str
277
+ The boundary condition, either "In" for ingoing or "Up" for upgoing.
278
+ pos : int
279
+ The radial position.
280
+
281
+ Returns
282
+ -------
283
+ complex
284
+ The radial second derivative at the given boundary condition `bc` and position `pos`.
285
+ """
218
286
  return self.base.radialderivative2(bc, pos)
219
287
 
220
288
  def homogeneousradialsolution(self, bc, pos):
289
+ """
290
+ The homogeneous radial solution for the given boundary condition `bc` and position `pos`.
291
+
292
+ Parameters
293
+ ----------
294
+ bc : str
295
+ The boundary condition, either "In" for ingoing or "Up" for upgoing.
296
+ pos : int
297
+ The radial position.
298
+
299
+ Returns
300
+ -------
301
+ complex
302
+ The radial solution at the given boundary condition `bc` and position `pos`.
303
+ """
221
304
  return self.base.homogeneousradialsolution(bc, pos)
222
305
 
223
306
  def homogeneousradialderivative(self, bc, pos):
307
+ """
308
+ The radial derivative of the homogeneous radial solution for the given boundary condition `bc` and position `pos`.
309
+
310
+ Parameters
311
+ ----------
312
+ bc : str
313
+ The boundary condition, either "In" for ingoing or "Up" for upgoing.
314
+ pos : int
315
+ The radial position.
316
+
317
+ Returns
318
+ -------
319
+ complex
320
+ The radial derivative at the given boundary condition `bc` and position `pos`.
321
+ """
224
322
  return self.base.homogeneousradialderivative(bc, pos)
225
323
 
226
324
  def homogeneousradialderivative2(self, bc, pos):
325
+ """
326
+ The second radial derivative of the homogeneous radial solution for the given boundary condition `bc` and position `pos`.
327
+
328
+ Parameters
329
+ ----------
330
+ bc : str
331
+ The boundary condition, either "In" for ingoing or "Up" for upgoing.
332
+ pos : int
333
+ The radial position.
334
+
335
+ Returns
336
+ -------
337
+ complex
338
+ The radial second derivative at the given boundary condition `bc` and position `pos`.
339
+ """
227
340
  return self.base.homogeneousradialderivative2(bc, pos)
228
341
 
229
342
  def polarpoint(self, pos):
343
+ """
344
+ The polar point for the given position `pos`.
345
+
346
+ Parameters
347
+ ----------
348
+ pos : int
349
+ The polar position.
350
+
351
+ Returns
352
+ -------
353
+ float
354
+ The polar point at the given position `pos`.
355
+ """
230
356
  return self.base.polarpoint(pos)
231
357
 
232
358
  def polarsolution(self, pos):
359
+ """
360
+ The polar solution for the given position `pos`.
361
+
362
+ Parameters
363
+ ----------
364
+ pos : int
365
+ The polar position.
366
+
367
+ Returns
368
+ -------
369
+ float
370
+ The polar solution at the given position `pos`.
371
+ """
233
372
  return self.base.polarsolution(pos)
234
373
 
235
374
  def polarderivative(self, pos):
375
+ """
376
+ The derivative of the polar solution for the given position `pos`.
377
+
378
+ Parameters
379
+ ----------
380
+ pos : int
381
+ The polar position.
382
+
383
+ Returns
384
+ -------
385
+ float
386
+ The polar derivative at the given position `pos`.
387
+ """
236
388
  return self.base.polarderivative(pos)
237
389
 
238
390
  def polarderivative2(self, pos):
391
+ """
392
+ The second derivative of the polar solution for the given position `pos`.
393
+
394
+ Parameters
395
+ ----------
396
+ pos : int
397
+ The polar position.
398
+
399
+ Returns
400
+ -------
401
+ float
402
+ The polar second derivative at the given position `pos`.
403
+ """
239
404
  return self.base.polarderivative2(pos)
240
405
 
241
406
  def amplitude(self, bc):
407
+ """
408
+ The Teukolsky amplitude for the given boundary condition `bc`.
409
+
410
+ Parameters
411
+ ----------
412
+ bc : str
413
+ The boundary condition, either "In" for ingoing or "Up" for upgoing.
414
+
415
+ Returns
416
+ -------
417
+ complex
418
+ The Teukolsky amplitude at the given boundary condition `bc`.
419
+ """
242
420
  return self.base.teukolsky_amplitude(bc)
243
421
 
244
422
  def precision(self, bc):
423
+ """
424
+ The precision of the Teukolsky amplitude for the given boundary condition `bc`.
425
+
426
+ Parameters
427
+ ----------
428
+ bc : str
429
+ The boundary condition, either "In" for ingoing or "Up" for upgoing.
430
+
431
+ Returns
432
+ -------
433
+ float
434
+ The precision of the Teukolsky amplitude at the given boundary condition `bc`.
435
+ """
245
436
  return self.base.teukolsky_amplitude_precision(bc)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: pybhpt
3
- Version: 0.9.4
3
+ Version: 0.9.5
4
4
  Summary: Black Hole Perturbation Theory and Self-Force Algorithms in Python
5
5
  Author-Email: Zach Nasipak <znasipak@gmail.com>
6
6
  License: GPL
@@ -30,11 +30,11 @@ Description-Content-Type: text/markdown
30
30
 
31
31
  # pybhpt
32
32
 
33
- A python package for solving problems in black hole perturbation theory
33
+ A python package for solving problems in black hole perturbation theory.
34
34
 
35
35
  `pybhpt` is a collection of numerical tools for analyzing perturbations of Kerr spacetime, particularly the self-forces and metric-perturbations experienced by small bodies moving in a Kerr background. Subpackages include:
36
36
 
37
- - `pybhpt.geodesic`: a module that generates bound timelike geodesics in Kerr spacetime
37
+ - `pybhpt.geodesic`: a module that generates bound periodic timelike geodesics in Kerr spacetime
38
38
  - `pybhpt.radial`: a module that calculates homogeneous solutions of the radial Teukolsky equation
39
39
  - `pybhpt.swsh`: a module that constructs the spin-weighted spheroidal harmonics
40
40
  - `pybhpt.teuk`: a module that evaluates the inhomogeneous solutions (Teukolsky amplitudes) of the radial Teukolsky equation due to a point-particle on a bound timelike Kerr geodesic
@@ -43,9 +43,11 @@ A python package for solving problems in black hole perturbation theory
43
43
  - `pybhpt.metric`: a module that produces the coefficients needed to reconstruct the metric from the Hertz potentials
44
44
  - `pybhpt.redshift`: a module that computes the generalized Detweiler redshift invariant in a variety of gauges
45
45
 
46
+ See the [Documentation](https://pybhpt.readthedocs.io/en/latest/) pages for more information about the package, including User Guides and API. References and author information can be found at the bottom of the README.
47
+
46
48
  ## Quick Installation
47
49
 
48
- Tagged releases of `pybhpt` are available as wheel packages for macOS and 64-bit Linux on [PyPI](https://pypi.org/project/matplotlib/). Install using `pip`:
50
+ Tagged releases of `pybhpt` are available as wheel packages for macOS and 64-bit Linux on [PyPI](https://pypi.org/project/pybhpt). Install using `pip`:
49
51
  ```
50
52
  python3 -m pip install pybhpt
51
53
  ```
@@ -130,6 +132,14 @@ To include the necessary compiler on Linux:
130
132
  conda install gcc_linux-64 gxx_linux-64
131
133
  ```
132
134
 
135
+ ## References
136
+
137
+ Theoretical background for the code and explanations of the numerical methods used within are summarized in the references below:
138
+
139
+ - Z. Nasipak, *Metric reconstruction and the Hamiltonian for eccentric, precessing binaries in the small-mass-ratio limit* (2025) [arXiv:2507.07746](https://arxiv.org/abs/2507.07746)
140
+ - Z. Nasipak, *An adiabatic gravitational waveform model for compact objects undergoing quasi-circular inspirals into rotating massive black holes*, Phys. Rev. D 109, 044020 (2024) [arXiv:2310.19706](https://arxiv.org/abs/2310.19706)
141
+ - Z. Nasipak, *Adiabatic evolution due to the conservative scalar self-force during orbital resonances*, Phys. Rev. D 106, 064042 (2022) [arXiv:2207.02224](https://arxiv.org/abs/2207.02224)
142
+
133
143
  ## Authors
134
144
 
135
145
  Zachary Nasipak
@@ -1,18 +1,18 @@
1
- cybhpt_full.cpython-310-x86_64-linux-gnu.so,sha256=pu4ft712Co-CnBcVW7p2OSSy8NrWA_5LQX_v0RUg-Oo,2357689
1
+ cybhpt_full.cpython-310-x86_64-linux-gnu.so,sha256=5x2uMU0C3C4Nl67uAn2Q3zbNqItrx2LBTRD_z_HV5EQ,2365833
2
2
  pybhpt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  pybhpt/flux.py,sha256=2ahStjbsswsWMNPfDiOlQIrzulJZrhaqoDENC0bCLE4,4299
4
- pybhpt/geo.py,sha256=QyiwlXF0cIYSpkV7wC9-ITd2bqIumvXiIt2_uNlhJMc,12237
4
+ pybhpt/geo.py,sha256=H-rTM6zy-9EvLaMaeyMfwbdwgAS53d_Z6dmBCp3eya4,15473
5
5
  pybhpt/hertz.py,sha256=9atjscxbR7eszsY6BuJvcy0PqlgMFwYI23kUIn4mUwc,13565
6
6
  pybhpt/metric.py,sha256=y8ip24rFto7m7rZb0-fuYMrYUmbCzInaWQVYJRI3m3o,11971
7
- pybhpt/radial.py,sha256=2H3rH4XczAVCbKveHmPO2wt3w8ApPZYWCAwkwnxPnps,13995
7
+ pybhpt/radial.py,sha256=t9Lx6Cyuprxqk2iowbvHMvsoL-6UO-YnZmO1G1wdyMU,15597
8
8
  pybhpt/redshift.py,sha256=ozDDbkv34FQ_Op3FotmHJ-J5Vfy2oF4_Ri1HRujnzAY,791
9
- pybhpt/swsh.py,sha256=WjtpHNZ6c7RVxuml1I6_cEu8D6cb4hD6aiHZVwD7AeA,10462
10
- pybhpt/teuk.py,sha256=w3tcidkaOGeywZPqjggacYhxaFxgsxXdMlJ2C7TwdMQ,8220
9
+ pybhpt/swsh.py,sha256=w3XQoadHY-aiZZYQo5sUjweimM3HJ44AWuFcA2uuffs,14641
10
+ pybhpt/teuk.py,sha256=8rZ-2fyzAfTbt9T4-lkUL8wUmPpORv0VkJCfkpEBssc,13616
11
11
  pybhpt.libs/libgcc_s-0cd532bd.so.1,sha256=yPk0-VjyKzucjnkP3mvC0vVaua6Ln17qZUJbICcXgtA,181737
12
12
  pybhpt.libs/libgsl-6bd0117f.so.28.0.0,sha256=tKEgaCuN1tjAk3WXpuinEQBNpBkNRP9w4eOLsa37Nww,12220441
13
13
  pybhpt.libs/libgslcblas-55a77d35.so.0.0.0,sha256=jq8fRo8SahPHscTDEeTZCRwTYI1tbGBWhmY_lVbKk1Y,990449
14
14
  pybhpt.libs/libstdc++-5d72f927.so.6.0.33,sha256=fogxHsmB1_D6C-a_-uHh8Ei_6Qh52a8vLlicJRM3ehk,3562401
15
- pybhpt-0.9.4.dist-info/METADATA,sha256=EMPVuX-dje_Xt0axnmQHVeuax7rV66-dPzXO0_92ehc,5386
16
- pybhpt-0.9.4.dist-info/WHEEL,sha256=eKPTQgephAF7ayzKBeb3xjIMHocbTq0nFrBjlb4BHjc,117
17
- pybhpt-0.9.4.dist-info/RECORD,,
18
- pybhpt-0.9.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
15
+ pybhpt-0.9.5.dist-info/METADATA,sha256=OXj1Mg_Dtv15cYn28Uo0FffhH9LkR9Xp8NcIUWBxMK0,6367
16
+ pybhpt-0.9.5.dist-info/WHEEL,sha256=CYpLaz3rW_bfd-VpqD3YW_mevtuSEo6tT6wWD7un-xA,117
17
+ pybhpt-0.9.5.dist-info/RECORD,,
18
+ pybhpt-0.9.5.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: scikit-build-core 0.11.5
2
+ Generator: scikit-build-core 0.11.6
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp310-cp310-musllinux_1_2_x86_64
5
5