diffpes 2026.3.1__py3-none-any.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.
Files changed (55) hide show
  1. diffpes/__init__.py +65 -0
  2. diffpes/inout/__init__.py +88 -0
  3. diffpes/inout/chgcar.py +459 -0
  4. diffpes/inout/doscar.py +248 -0
  5. diffpes/inout/eigenval.py +258 -0
  6. diffpes/inout/hdf5.py +818 -0
  7. diffpes/inout/helpers.py +295 -0
  8. diffpes/inout/kpoints.py +433 -0
  9. diffpes/inout/plotting.py +757 -0
  10. diffpes/inout/poscar.py +174 -0
  11. diffpes/inout/procar.py +331 -0
  12. diffpes/inout/py.typed +0 -0
  13. diffpes/maths/__init__.py +68 -0
  14. diffpes/maths/dipole.py +368 -0
  15. diffpes/maths/gaunt.py +496 -0
  16. diffpes/maths/spherical_harmonics.py +336 -0
  17. diffpes/py.typed +0 -0
  18. diffpes/radial/__init__.py +47 -0
  19. diffpes/radial/bessel.py +198 -0
  20. diffpes/radial/integrate.py +128 -0
  21. diffpes/radial/wavefunctions.py +335 -0
  22. diffpes/simul/__init__.py +146 -0
  23. diffpes/simul/broadening.py +258 -0
  24. diffpes/simul/crosssections.py +196 -0
  25. diffpes/simul/expanded.py +1021 -0
  26. diffpes/simul/forward.py +586 -0
  27. diffpes/simul/oam.py +112 -0
  28. diffpes/simul/polarization.py +443 -0
  29. diffpes/simul/py.typed +0 -0
  30. diffpes/simul/resolution.py +122 -0
  31. diffpes/simul/self_energy.py +142 -0
  32. diffpes/simul/spectrum.py +1116 -0
  33. diffpes/simul/workflow.py +424 -0
  34. diffpes/tightb/__init__.py +68 -0
  35. diffpes/tightb/diagonalize.py +340 -0
  36. diffpes/tightb/hamiltonian.py +286 -0
  37. diffpes/tightb/projections.py +134 -0
  38. diffpes/types/__init__.py +162 -0
  39. diffpes/types/aliases.py +52 -0
  40. diffpes/types/bands.py +1064 -0
  41. diffpes/types/dos.py +510 -0
  42. diffpes/types/geometry.py +306 -0
  43. diffpes/types/kpath.py +365 -0
  44. diffpes/types/params.py +496 -0
  45. diffpes/types/py.typed +0 -0
  46. diffpes/types/radial_params.py +482 -0
  47. diffpes/types/self_energy.py +271 -0
  48. diffpes/types/tb_model.py +531 -0
  49. diffpes/types/volumetric.py +608 -0
  50. diffpes/utils/__init__.py +30 -0
  51. diffpes/utils/math.py +255 -0
  52. diffpes/utils/py.typed +0 -0
  53. diffpes-2026.3.1.dist-info/METADATA +176 -0
  54. diffpes-2026.3.1.dist-info/RECORD +55 -0
  55. diffpes-2026.3.1.dist-info/WHEEL +4 -0
diffpes/maths/gaunt.py ADDED
@@ -0,0 +1,496 @@
1
+ r"""Gaunt coefficient table for dipole transitions.
2
+
3
+ Extended Summary
4
+ ----------------
5
+ Precomputes real-valued Gaunt integrals for electric dipole
6
+ transitions in real spherical harmonics:
7
+
8
+ .. math::
9
+
10
+ G(l, m, l', m') = \int Y_l^m(\hat{r})\, r_q\, Y_{l'}^{m'}(\hat{r})\,d\Omega
11
+
12
+ where :math:`r_q` is one of the three components of the position
13
+ operator expressed in the real spherical harmonic basis (q = -1, 0, +1).
14
+
15
+ Selection rules: :math:`l' = l \pm 1` and :math:`|m' - m| \leq 1`.
16
+
17
+ The table is computed once at module load time using pure Python
18
+ (not JAX-traced) and stored as a JAX array for O(1) lookup.
19
+
20
+ Routine Listings
21
+ ----------------
22
+ :data:`GAUNT_TABLE`
23
+ Module-level precomputed Gaunt coefficient table for l_max=4.
24
+ :data:`L_MAX`
25
+ Maximum angular momentum supported by the precomputed table.
26
+ :func:`build_gaunt_table`
27
+ Build the dipole Gaunt coefficient lookup table.
28
+ :func:`gaunt_lookup`
29
+ Look up a single Gaunt coefficient from the precomputed table.
30
+ """
31
+
32
+ import math
33
+ from functools import cache
34
+
35
+ import numpy as np
36
+ import jax.numpy as jnp
37
+ from jaxtyping import Array, Float
38
+
39
+
40
+ def _wigner3j(j1: int, j2: int, j3: int, m1: int, m2: int, m3: int) -> float:
41
+ r"""Evaluate Wigner 3-j symbol using the Racah formula.
42
+
43
+ Extended Summary
44
+ ----------------
45
+ Computes the Wigner 3-j symbol
46
+
47
+ .. math::
48
+
49
+ \begin{pmatrix} j_1 & j_2 & j_3 \\ m_1 & m_2 & m_3 \end{pmatrix}
50
+
51
+ using the explicit Racah formula, which expresses the 3-j symbol as a
52
+ finite sum over factorials. The formula is:
53
+
54
+ .. math::
55
+
56
+ \begin{pmatrix} j_1 & j_2 & j_3 \\ m_1 & m_2 & m_3 \end{pmatrix}
57
+ = (-1)^{j_1 - j_2 - m_3} \Delta(j_1, j_2, j_3) \cdot P \cdot
58
+ \sum_t \frac{(-1)^t}{t! \cdot D(t)}
59
+
60
+ where :math:`\Delta(j_1, j_2, j_3)` is the triangle coefficient
61
+
62
+ .. math::
63
+
64
+ \Delta = \sqrt{
65
+ \frac{(j_1+j_2-j_3)!(j_1-j_2+j_3)!(-j_1+j_2+j_3)!}
66
+ {(j_1+j_2+j_3+1)!}
67
+ }
68
+
69
+ and :math:`P` is the product of square roots of magnetic-number
70
+ factorials:
71
+
72
+ .. math::
73
+
74
+ P = \sqrt{(j_1+m_1)!(j_1-m_1)!(j_2+m_2)!(j_2-m_2)!(j_3+m_3)!(j_3-m_3)!}
75
+
76
+ The summation index *t* runs over all integers for which all
77
+ factorial arguments are non-negative, determined by ``t_min`` and
78
+ ``t_max``.
79
+
80
+ **Selection rules** enforced before computation:
81
+
82
+ - :math:`m_1 + m_2 + m_3 = 0` (magnetic quantum number conservation)
83
+ - :math:`|m_i| \le j_i` for each i (projection bounds)
84
+ - :math:`|j_1 - j_2| \le j_3 \le j_1 + j_2` (triangle inequality)
85
+
86
+ If any selection rule is violated, the function returns 0.0 immediately
87
+ without computing the sum.
88
+
89
+ Only needed for small angular momenta (l_max <= 5), so the
90
+ factorial-based formula is efficient and exact (no floating-point
91
+ cancellation issues at this scale).
92
+
93
+ Parameters
94
+ ----------
95
+ j1, j2, j3 : int
96
+ Angular momentum quantum numbers (non-negative integers).
97
+ m1, m2, m3 : int
98
+ Magnetic quantum numbers satisfying :math:`|m_i| \le j_i`.
99
+
100
+ Returns
101
+ -------
102
+ value : float
103
+ The Wigner 3-j symbol value.
104
+ """
105
+ if m1 + m2 + m3 != 0:
106
+ return 0.0
107
+ if abs(m1) > j1 or abs(m2) > j2 or abs(m3) > j3:
108
+ return 0.0
109
+ if j3 < abs(j1 - j2) or j3 > j1 + j2:
110
+ return 0.0
111
+
112
+ t_min: int = max(0, j2 - j3 - m1, j1 - j3 + m2)
113
+ t_max: int = min(j1 + j2 - j3, j1 - m1, j2 + m2)
114
+
115
+ prefactor: float = math.sqrt(
116
+ math.factorial(j1 + m1)
117
+ * math.factorial(j1 - m1)
118
+ * math.factorial(j2 + m2)
119
+ * math.factorial(j2 - m2)
120
+ * math.factorial(j3 + m3)
121
+ * math.factorial(j3 - m3)
122
+ )
123
+ triangle: float = math.sqrt(
124
+ math.factorial(j1 + j2 - j3)
125
+ * math.factorial(j1 - j2 + j3)
126
+ * math.factorial(-j1 + j2 + j3)
127
+ / math.factorial(j1 + j2 + j3 + 1)
128
+ )
129
+
130
+ total: float = 0.0
131
+ for t in range(t_min, t_max + 1):
132
+ sign: int = (-1) ** t
133
+ denom: int = (
134
+ math.factorial(t)
135
+ * math.factorial(j1 + j2 - j3 - t)
136
+ * math.factorial(j1 - m1 - t)
137
+ * math.factorial(j2 + m2 - t)
138
+ * math.factorial(j3 - j2 + m1 + t)
139
+ * math.factorial(j3 - j1 - m2 + t)
140
+ )
141
+ total += sign / denom
142
+
143
+ value: float = float((-1) ** (j1 - j2 - m3) * prefactor * triangle * total)
144
+ return value
145
+
146
+
147
+ @cache
148
+ def _complex_gaunt(
149
+ l1: int, m1: int, l2: int, m2: int, l3: int, m3: int
150
+ ) -> float:
151
+ r"""Complex Gaunt integral for three complex spherical harmonics.
152
+
153
+ Extended Summary
154
+ ----------------
155
+ Computes the Gaunt integral (three-Y integral) defined as:
156
+
157
+ .. math::
158
+
159
+ G(l_1,m_1; l_2,m_2; l_3,m_3)
160
+ = \int Y_{l_1}^{m_1} Y_{l_2}^{m_2} Y_{l_3}^{m_3} \, d\Omega
161
+
162
+ Using the standard decomposition into Wigner 3-j symbols:
163
+
164
+ .. math::
165
+
166
+ G = (-1)^{m_3}
167
+ \sqrt{\frac{(2l_1+1)(2l_2+1)(2l_3+1)}{4\pi}}
168
+ \begin{pmatrix} l_1 & l_2 & l_3 \\ 0 & 0 & 0 \end{pmatrix}
169
+ \begin{pmatrix} l_1 & l_2 & l_3 \\ m_1 & m_2 & -m_3 \end{pmatrix}
170
+
171
+ The first 3-j symbol enforces the parity selection rule
172
+ :math:`l_1 + l_2 + l_3 = \text{even}`, and is evaluated first as
173
+ an early-exit optimization. The result is cached via
174
+ ``functools.cache`` since the same (l, m) combinations appear
175
+ repeatedly during Gaunt table construction.
176
+
177
+ Parameters
178
+ ----------
179
+ l1, l2, l3 : int
180
+ Angular momentum quantum numbers.
181
+ m1, m2, m3 : int
182
+ Magnetic quantum numbers.
183
+
184
+ Returns
185
+ -------
186
+ value : float
187
+ The complex Gaunt integral.
188
+ """
189
+ w3j_000: float = _wigner3j(l1, l2, l3, 0, 0, 0)
190
+ if w3j_000 == 0.0:
191
+ return 0.0
192
+ w3j_mmm: float = _wigner3j(l1, l2, l3, m1, m2, -m3)
193
+ if w3j_mmm == 0.0:
194
+ return 0.0
195
+
196
+ prefactor: float = (-1) ** m3 * math.sqrt(
197
+ (2 * l1 + 1) * (2 * l2 + 1) * (2 * l3 + 1) / (4.0 * math.pi)
198
+ )
199
+ value: float = prefactor * w3j_000 * w3j_mmm
200
+ return value
201
+
202
+
203
+ def _real_gaunt_dipole(l: int, m: int, lp: int, mp: int, q: int) -> float:
204
+ r"""Gaunt coefficient for real spherical harmonics with dipole operator.
205
+
206
+ Extended Summary
207
+ ----------------
208
+ Computes the integral of :math:`Y_l^m(\text{real}) \cdot r_q \cdot
209
+ Y_{l'}^{m'}(\text{real})` over the unit sphere, where
210
+ :math:`r_q` (q = -1, 0, +1) is a dipole operator component.
211
+
212
+ Because the Gaunt integral is natively defined for complex spherical
213
+ harmonics, this function performs a basis transformation: each real
214
+ harmonic is expanded in the complex basis using unitary coefficients,
215
+ and the resulting triple sum of complex Gaunt integrals is accumulated.
216
+
217
+ **Real-to-complex transformation:**
218
+
219
+ .. math::
220
+
221
+ Y_l^m(\text{real}) = \frac{1}{\sqrt{2}}
222
+ \bigl(Y_l^m + (-1)^m Y_l^{-m}\bigr)
223
+ \quad (m > 0)
224
+
225
+ Y_l^0(\text{real}) = Y_l^0 \quad (m = 0)
226
+
227
+ Y_l^m(\text{real}) = \frac{-i}{\sqrt{2}}
228
+ \bigl((-1)^{|m|} Y_l^{|m|} - Y_l^{-|m|}\bigr)
229
+ \quad (m < 0)
230
+
231
+ Each real harmonic thus becomes a sum of at most two complex harmonics
232
+ with known coefficients (computed by the nested helper
233
+ ``_real_to_complex_coeffs``).
234
+
235
+ **Dipole operator in complex basis:**
236
+
237
+ The position operator component :math:`r_q` corresponds to the l=1
238
+ real spherical harmonic :math:`Y_1^q(\text{real})`, which is itself
239
+ expanded in the complex basis using the same transformation.
240
+
241
+ **Assembly:**
242
+
243
+ The full real Gaunt coefficient is:
244
+
245
+ .. math::
246
+
247
+ G_{\text{real}} = \sum_{\mu, \nu, \rho}
248
+ \overline{U_{\text{final}}(m', \rho)} \cdot
249
+ U_{\text{dip}}(q, \nu) \cdot
250
+ U_{\text{init}}(m, \mu) \cdot
251
+ (-1)^{\rho} \cdot
252
+ G_{\text{complex}}(l', -\rho; 1, \nu; l, \mu)
253
+
254
+ where :math:`U` matrices are the real-to-complex transformation
255
+ coefficients. The conjugation on the final-state coefficients arises
256
+ because the complex Gaunt integral involves :math:`Y_{l'}^{\rho *}`,
257
+ and :math:`Y_l^{m *} = (-1)^m Y_l^{-m}`.
258
+
259
+ The result is guaranteed to be real; an imaginary part exceeding
260
+ :math:`10^{-12}` triggers a ``ValueError`` as a consistency check.
261
+
262
+ Parameters
263
+ ----------
264
+ l : int
265
+ Initial state angular momentum.
266
+ m : int
267
+ Initial state magnetic quantum number.
268
+ lp : int
269
+ Final state angular momentum.
270
+ mp : int
271
+ Final state magnetic quantum number.
272
+ q : int
273
+ Dipole component index (-1, 0, or +1).
274
+
275
+ Returns
276
+ -------
277
+ value : float
278
+ The real Gaunt coefficient for the specified quantum numbers.
279
+ """
280
+ sqrt2: float = math.sqrt(2.0)
281
+
282
+ # Build transformation coefficients for Y_l^m(real)
283
+ # in terms of complex Y_l^mu: Y_l^m(real) = sum_mu U_{m,mu} Y_l^mu
284
+ def _real_to_complex_coeffs(ll: int, mm: int) -> list[tuple[complex, int]]:
285
+ r"""Return real-to-complex expansion coefficients.
286
+
287
+ Computes the unitary transformation coefficients :math:`U_{m,\mu}`
288
+ such that :math:`Y_l^m(\text{real}) = \sum_\mu U_{m,\mu} Y_l^\mu`.
289
+ For m > 0, two terms with :math:`\mu = \pm m` contribute with
290
+ coefficients :math:`1/\sqrt{2}` and :math:`(-1)^m/\sqrt{2}`.
291
+ For m = 0, a single term with coefficient 1. For m < 0, two
292
+ terms with imaginary coefficients that isolate the sine component.
293
+
294
+ Parameters
295
+ ----------
296
+ ll : int
297
+ Angular momentum (unused in the formula but kept for clarity).
298
+ mm : int
299
+ Magnetic quantum number of the real harmonic.
300
+
301
+ Returns
302
+ -------
303
+ coeffs : list of (complex, int)
304
+ List of ``(coefficient, mu)`` pairs.
305
+ """
306
+ if mm > 0:
307
+ return [
308
+ (1.0 / sqrt2, mm),
309
+ ((-1) ** mm / sqrt2, -mm),
310
+ ]
311
+ if mm == 0:
312
+ return [(1.0, 0)]
313
+ am: int = abs(mm)
314
+ return [
315
+ (-1j * (-1) ** am / sqrt2, am),
316
+ (1j / sqrt2, -am),
317
+ ]
318
+
319
+ # The dipole operator r_q in terms of complex Y_1^mu:
320
+ # r_q is proportional to Y_1^q(complex)
321
+ # We need the complex m-value for the dipole component
322
+ # Convention: q=-1 -> m_dip=+1 (y), q=0 -> m_dip=0 (z), q=+1 -> m_dip=-1 (x)
323
+ # Actually, using the standard convention:
324
+ # x = sqrt(4pi/3) * Y_1^{-1}(real)
325
+ # y = sqrt(4pi/3) * Y_1^{+1}(real) ... but let's use complex:
326
+ # r_{-1} = sqrt(2pi/3) (x - iy) / sqrt(2) ~ Y_1^{-1}(complex)
327
+ # r_0 = sqrt(4pi/3) z ~ Y_1^0(complex)
328
+ # r_{+1} = -sqrt(2pi/3) (x + iy) / sqrt(2) ~ Y_1^{+1}(complex)
329
+ #
330
+ # For the real dipole operator r_q (q index for the 3 Cartesian components
331
+ # mapped to real spherical harmonics of l=1):
332
+ # r_q(real) corresponds to Y_1^q(real)
333
+ # Transform Y_1^q(real) to complex basis:
334
+ dip_coeffs: list[tuple[complex, int]] = _real_to_complex_coeffs(1, q)
335
+
336
+ # Coefficients for initial state Y_l^m(real)
337
+ init_coeffs: list[tuple[complex, int]] = _real_to_complex_coeffs(l, m)
338
+ # Coefficients for final state Y_{l'}^{m'}(real)
339
+ final_coeffs: list[tuple[complex, int]] = _real_to_complex_coeffs(lp, mp)
340
+
341
+ # The real Gaunt integral is:
342
+ # G_real = sum_{mu, nu, rho} conj(U_final(mp,rho)) * U_dip(q,nu) * U_init(m,mu)
343
+ # * integral Y_{lp}^{rho*} Y_1^nu Y_l^mu d Omega
344
+ # where the integral is the complex Gaunt coefficient.
345
+ total: complex = 0.0 + 0.0j
346
+ for c_init, mu in init_coeffs:
347
+ for c_dip, nu in dip_coeffs:
348
+ for c_final, rho in final_coeffs:
349
+ # integral of Y_{lp}^{rho*} * Y_1^nu * Y_l^mu
350
+ # = (-1)^rho * integral Y_{lp}^{-rho} Y_1^nu Y_l^mu
351
+ # Using our _complex_gaunt which computes
352
+ # integral Y_{l1}^{m1} Y_{l2}^{m2} Y_{l3}^{m3}:
353
+ cg: float = _complex_gaunt(lp, -rho, 1, nu, l, mu)
354
+ coeff: complex = (
355
+ complex(c_final).conjugate()
356
+ * complex(c_dip)
357
+ * complex(c_init)
358
+ )
359
+ total += coeff * (-1) ** rho * cg
360
+
361
+ result: float = total.real
362
+ if abs(total.imag) > 1e-12:
363
+ msg: str = f"Imaginary part {total.imag} in real Gaunt coefficient"
364
+ raise ValueError(msg)
365
+ return result
366
+
367
+
368
+ def build_gaunt_table(
369
+ l_max: int = 4,
370
+ ) -> Float[Array, "L_src M_src 3 L_dst M_dst"]:
371
+ r"""Build the dipole Gaunt coefficient lookup table.
372
+
373
+ Extended Summary
374
+ ----------------
375
+ Precomputes every non-zero real Gaunt coefficient for electric
376
+ dipole transitions up to angular momentum ``l_max``. The table
377
+ is stored as a dense 5-D NumPy array and then converted to a
378
+ JAX array for O(1) lookup during forward-model evaluation.
379
+
380
+ The five axes correspond to:
381
+
382
+ - **axis 0** (``l``): initial angular momentum, size ``l_max + 1``.
383
+ - **axis 1** (``m + l_max``): offset initial magnetic quantum number,
384
+ size ``2 * l_max + 1``, so that m = -l_max maps to index 0.
385
+ - **axis 2** (``q + 1``): dipole component index, size 3, for
386
+ q in {-1, 0, +1}.
387
+ - **axis 3** (``lp``): final angular momentum, size ``l_max + 2``
388
+ (because the dipole operator can promote l to l + 1).
389
+ - **axis 4** (``mp + l_max``): offset final magnetic quantum number,
390
+ size ``2 * (l_max + 1) + 1``.
391
+
392
+ The construction loops over all valid quantum number combinations
393
+ respecting the dipole selection rules :math:`l' = l \pm 1` and
394
+ calls `_real_gaunt_dipole` for each. Zero entries (forbidden
395
+ transitions) are left at their initialized value.
396
+
397
+ The table is indexed as ``GAUNT_TABLE[l, m + l_max, q + 1, lp, mp + l_max]``
398
+ where q in {-1, 0, +1} indexes the three dipole components.
399
+
400
+ Parameters
401
+ ----------
402
+ l_max : int
403
+ Maximum angular momentum. Default 4 (s through g).
404
+
405
+ Returns
406
+ -------
407
+ table : Float[Array, "..."]
408
+ Dense array of Gaunt coefficients.
409
+ Shape: ``(l_max+1, 2*l_max+1, 3, l_max+2, 2*(l_max+1)+1)``.
410
+
411
+ Notes
412
+ -----
413
+ This function uses pure Python / NumPy (not JAX-traced) because it
414
+ runs once at module import time. The result is frozen as a JAX
415
+ array constant, so it does not appear as a trainable parameter in
416
+ any gradient computation.
417
+ """
418
+ l_src_dim: int = l_max + 1
419
+ m_src_dim: int = 2 * l_max + 1
420
+ q_dim: int = 3
421
+ l_dst_dim: int = l_max + 2
422
+ m_dst_dim: int = 2 * (l_max + 1) + 1
423
+
424
+ table: np.ndarray = np.zeros(
425
+ (l_src_dim, m_src_dim, q_dim, l_dst_dim, m_dst_dim),
426
+ dtype=np.float64,
427
+ )
428
+
429
+ for l in range(l_src_dim):
430
+ for m in range(-l, l + 1):
431
+ for q in (-1, 0, 1):
432
+ for lp in (l - 1, l + 1):
433
+ if lp < 0 or lp >= l_dst_dim:
434
+ continue
435
+ for mp in range(-lp, lp + 1):
436
+ val: float = _real_gaunt_dipole(l, m, lp, mp, q)
437
+ table[l, m + l_max, q + 1, lp, mp + l_max] = val
438
+
439
+ return jnp.asarray(table, dtype=jnp.float64)
440
+
441
+
442
+ GAUNT_TABLE: Float[Array, "..."] = build_gaunt_table(l_max=4)
443
+ """Module-level precomputed Gaunt coefficient table for l_max=4."""
444
+
445
+ L_MAX: int = 4
446
+ """Maximum angular momentum supported by the precomputed table."""
447
+
448
+
449
+ def gaunt_lookup(l: int, m: int, q: int, lp: int, mp: int) -> float:
450
+ r"""Look up a single Gaunt coefficient from the precomputed table.
451
+
452
+ Extended Summary
453
+ ----------------
454
+ Provides a convenience accessor for the module-level ``GAUNT_TABLE``
455
+ array, converting the physical quantum numbers (l, m, q, l', m')
456
+ into the offset indices used by the dense storage layout. The
457
+ index mapping is:
458
+
459
+ - ``l`` indexes axis 0 directly.
460
+ - ``m + L_MAX`` offsets the magnetic quantum number to a
461
+ non-negative index on axis 1.
462
+ - ``q + 1`` maps q in {-1, 0, +1} to indices {0, 1, 2} on axis 2.
463
+ - ``lp`` indexes axis 3 directly.
464
+ - ``mp + L_MAX`` offsets m' on axis 4.
465
+
466
+ The returned value is cast to a Python float for use in
467
+ non-JAX contexts. For JAX-traced code, direct indexing into
468
+ ``GAUNT_TABLE`` is preferred to avoid Python-level overhead.
469
+
470
+ Parameters
471
+ ----------
472
+ l : int
473
+ Initial state angular momentum.
474
+ m : int
475
+ Initial state magnetic quantum number.
476
+ q : int
477
+ Dipole component (-1, 0, or +1).
478
+ lp : int
479
+ Final state angular momentum.
480
+ mp : int
481
+ Final state magnetic quantum number.
482
+
483
+ Returns
484
+ -------
485
+ coeff : float
486
+ The Gaunt coefficient.
487
+ """
488
+ return float(GAUNT_TABLE[l, m + L_MAX, q + 1, lp, mp + L_MAX])
489
+
490
+
491
+ __all__: list[str] = [
492
+ "GAUNT_TABLE",
493
+ "L_MAX",
494
+ "build_gaunt_table",
495
+ "gaunt_lookup",
496
+ ]