| import numpy as np |
| from warnings import warn |
| from ._basic import rfft, irfft |
| from ..special import loggamma, poch |
|
|
| from scipy._lib._array_api import array_namespace |
|
|
| __all__ = ['fht', 'ifht', 'fhtoffset'] |
|
|
| |
| LN_2 = np.log(2) |
|
|
|
|
| def fht(a, dln, mu, offset=0.0, bias=0.0): |
| xp = array_namespace(a) |
| a = xp.asarray(a) |
|
|
| |
| n = a.shape[-1] |
|
|
| |
| if bias != 0: |
| |
| j_c = (n-1)/2 |
| j = xp.arange(n, dtype=xp.float64) |
| a = a * xp.exp(-bias*(j - j_c)*dln) |
|
|
| |
| u = xp.asarray(fhtcoeff(n, dln, mu, offset=offset, bias=bias)) |
|
|
| |
| A = _fhtq(a, u, xp=xp) |
|
|
| |
| if bias != 0: |
| |
| A *= xp.exp(-bias*((j - j_c)*dln + offset)) |
|
|
| return A |
|
|
|
|
| def ifht(A, dln, mu, offset=0.0, bias=0.0): |
| xp = array_namespace(A) |
| A = xp.asarray(A) |
|
|
| |
| n = A.shape[-1] |
|
|
| |
| if bias != 0: |
| |
| j_c = (n-1)/2 |
| j = xp.arange(n, dtype=xp.float64) |
| A = A * xp.exp(bias*((j - j_c)*dln + offset)) |
|
|
| |
| u = xp.asarray(fhtcoeff(n, dln, mu, offset=offset, bias=bias, inverse=True)) |
|
|
| |
| a = _fhtq(A, u, inverse=True, xp=xp) |
|
|
| |
| if bias != 0: |
| |
| a /= xp.exp(-bias*(j - j_c)*dln) |
|
|
| return a |
|
|
|
|
| def fhtcoeff(n, dln, mu, offset=0.0, bias=0.0, inverse=False): |
| """Compute the coefficient array for a fast Hankel transform.""" |
| lnkr, q = offset, bias |
|
|
| |
| |
| |
| xp = (mu+1+q)/2 |
| xm = (mu+1-q)/2 |
| y = np.linspace(0, np.pi*(n//2)/(n*dln), n//2+1) |
| u = np.empty(n//2+1, dtype=complex) |
| v = np.empty(n//2+1, dtype=complex) |
| u.imag[:] = y |
| u.real[:] = xm |
| loggamma(u, out=v) |
| u.real[:] = xp |
| loggamma(u, out=u) |
| y *= 2*(LN_2 - lnkr) |
| u.real -= v.real |
| u.real += LN_2*q |
| u.imag += v.imag |
| u.imag += y |
| np.exp(u, out=u) |
|
|
| |
| if n % 2 == 0: |
| u.imag[-1] = 0 |
|
|
| |
| if not np.isfinite(u[0]): |
| |
| |
| u[0] = 2**q * poch(xm, xp-xm) |
| |
| |
|
|
| |
| if np.isinf(u[0]) and not inverse: |
| warn('singular transform; consider changing the bias', stacklevel=3) |
| |
| u = np.copy(u) |
| u[0] = 0 |
| elif u[0] == 0 and inverse: |
| warn('singular inverse transform; consider changing the bias', stacklevel=3) |
| |
| u = np.copy(u) |
| u[0] = np.inf |
|
|
| return u |
|
|
|
|
| def fhtoffset(dln, mu, initial=0.0, bias=0.0): |
| """Return optimal offset for a fast Hankel transform. |
| |
| Returns an offset close to `initial` that fulfils the low-ringing |
| condition of [1]_ for the fast Hankel transform `fht` with logarithmic |
| spacing `dln`, order `mu` and bias `bias`. |
| |
| Parameters |
| ---------- |
| dln : float |
| Uniform logarithmic spacing of the transform. |
| mu : float |
| Order of the Hankel transform, any positive or negative real number. |
| initial : float, optional |
| Initial value for the offset. Returns the closest value that fulfils |
| the low-ringing condition. |
| bias : float, optional |
| Exponent of power law bias, any positive or negative real number. |
| |
| Returns |
| ------- |
| offset : float |
| Optimal offset of the uniform logarithmic spacing of the transform that |
| fulfils a low-ringing condition. |
| |
| Examples |
| -------- |
| >>> from scipy.fft import fhtoffset |
| >>> dln = 0.1 |
| >>> mu = 2.0 |
| >>> initial = 0.5 |
| >>> bias = 0.0 |
| >>> offset = fhtoffset(dln, mu, initial, bias) |
| >>> offset |
| 0.5454581477676637 |
| |
| See Also |
| -------- |
| fht : Definition of the fast Hankel transform. |
| |
| References |
| ---------- |
| .. [1] Hamilton A. J. S., 2000, MNRAS, 312, 257 (astro-ph/9905191) |
| |
| """ |
|
|
| lnkr, q = initial, bias |
|
|
| xp = (mu+1+q)/2 |
| xm = (mu+1-q)/2 |
| y = np.pi/(2*dln) |
| zp = loggamma(xp + 1j*y) |
| zm = loggamma(xm + 1j*y) |
| arg = (LN_2 - lnkr)/dln + (zp.imag + zm.imag)/np.pi |
| return lnkr + (arg - np.round(arg))*dln |
|
|
|
|
| def _fhtq(a, u, inverse=False, *, xp=None): |
| """Compute the biased fast Hankel transform. |
| |
| This is the basic FFTLog routine. |
| """ |
| if xp is None: |
| xp = np |
|
|
| |
| n = a.shape[-1] |
|
|
| |
| A = rfft(a, axis=-1) |
| if not inverse: |
| |
| A *= u |
| else: |
| |
| A /= xp.conj(u) |
| A = irfft(A, n, axis=-1) |
| A = xp.flip(A, axis=-1) |
|
|
| return A |
|
|