Source code for diffpes.radial.integrate
r"""Evaluate radial integrals on fixed grids.
Extended Summary
----------------
The module provides fixed-grid quadrature for dipole radial integrals
.. math::
B^{l'}(k) = (i)^{l'} \int_0^\infty R(r) r^3 j_{l'}(k r) dr
The module uses JAX-traceable composite trapezoidal integration.
Routine Listings
----------------
:func:`radial_integral`
Evaluate dipole radial integral on a fixed radial grid.
"""
import jax.numpy as jnp
from beartype import beartype
from jaxtyping import Array, Complex, Float, jaxtyped
from .bessel import spherical_bessel_jl
[docs]
@jaxtyped(typechecker=beartype)
def radial_integral(
k: Float[Array, " ..."],
r: Float[Array, " R"],
radial_values: Float[Array, " R"],
l_prime: int,
) -> Complex[Array, " ..."]:
r"""Evaluate dipole radial integral on a fixed radial grid.
The function computes the dipole radial integral in the partial-wave
expansion of the photoemission matrix element:
.. math::
B^{l'}(k) = i^{l'} \int_0^\infty R(r) \, r^3 \, j_{l'}(k r) \, dr
The computation proceeds in five steps:
1. **Form the outer product**: Form the 2-D array :math:`kr` by
broadcasting ``k`` (shape ``...``) against ``r`` (shape ``R``) via
``jnp.expand_dims``. This produces shape ``(... , R)``.
2. **Evaluate the Bessel function**: Evaluate :math:`j_{l'}(kr)`
element-wise using `spherical_bessel_jl`. This function handles the
small-argument limit internally.
3. **Assemble the integrand**: Multiply the Bessel values by
:math:`R(r) \cdot r^3`. The :math:`r^3` factor combines the volume
element and the dipole factor.
4. **Apply trapezoidal quadrature**: Integrate over the radial grid
using ``jnp.trapezoid`` along the last axis. The grid need not
be uniform; ``jnp.trapezoid`` handles non-uniform spacing via
the ``x`` argument.
5. **Apply the phase factor**: Multiply the real integral by the complex
phase :math:`i^{l'}` to obtain the final complex-valued result.
The function supports batched evaluation. If ``k`` has shape ``(...)``, the
output retains its leading shape. The integration contracts the last axis.
:see: :class:`~.test_integrate.TestRadialIntegral`
Parameters
----------
k : Float[Array, " ..."]
Momentum magnitudes at which the function computes the integral.
r : Float[Array, " R"]
Monotonic radial grid.
radial_values : Float[Array, " R"]
Radial wavefunction values sampled on ``r``.
l_prime : int
Final-state angular momentum order.
Returns
-------
values : Complex[Array, " ..."]
Complex radial integral values with the same leading shape as ``k``.
Raises
------
ValueError
If ``l_prime`` is negative.
Notes
-----
The result accuracy depends on the density and extent of the radial grid.
Slater-type orbitals decay as :math:`e^{-\zeta r}`. A grid with 200--500
points usually suffices when it extends to :math:`r \sim 10/\zeta`.
The function uses the trapezoidal rule for its simplicity and JAX
compatibility. Higher-order quadrature can improve accuracy for smooth
integrands on coarse grids. Such quadrature requires a custom JAX-traceable
implementation.
"""
if l_prime < 0:
msg: str = "l_prime must be non-negative"
raise ValueError(msg)
k_arr: Float[Array, " ..."] = jnp.asarray(k, dtype=jnp.float64)
r_arr: Float[Array, " R"] = jnp.asarray(r, dtype=jnp.float64)
radial_arr: Float[Array, " R"] = jnp.asarray(
radial_values, dtype=jnp.float64
)
kr: Float[Array, " ... R"] = jnp.expand_dims(k_arr, axis=-1) * r_arr
bessel_vals: Float[Array, " ... R"] = spherical_bessel_jl(l_prime, kr)
radial_factor: Float[Array, " R"] = radial_arr * (r_arr**3)
integrand: Float[Array, " ... R"] = bessel_vals * radial_factor
real_integral: Float[Array, " ..."] = jnp.trapezoid(
integrand,
x=r_arr,
axis=-1,
)
phase: Complex[Array, " "] = jnp.asarray(
(1j) ** l_prime, dtype=jnp.complex128
)
values: Complex[Array, " ..."] = phase * real_integral.astype(
jnp.complex128
)
return values
__all__: list[str] = ["radial_integral"]