_fftlog.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. """Fast Hankel transforms using the FFTLog algorithm.
  2. The implementation closely follows the Fortran code of Hamilton (2000).
  3. added: 14/11/2020 Nicolas Tessore <n.tessore@ucl.ac.uk>
  4. """
  5. from ._basic import _dispatch
  6. from scipy._lib.uarray import Dispatchable
  7. from ._fftlog_backend import fhtoffset
  8. from scipy._lib._array_api import xp_capabilities
  9. import numpy as np
  10. __all__ = ['fht', 'ifht', 'fhtoffset']
  11. @xp_capabilities(allow_dask_compute=True)
  12. @_dispatch
  13. def fht(a, dln, mu, offset=0.0, bias=0.0):
  14. r'''Compute the fast Hankel transform.
  15. Computes the discrete Hankel transform of a logarithmically spaced periodic
  16. sequence using the FFTLog algorithm [1]_, [2]_.
  17. Parameters
  18. ----------
  19. a : array_like (..., n)
  20. Real periodic input array, uniformly logarithmically spaced. For
  21. multidimensional input, the transform is performed over the last axis.
  22. dln : float
  23. Uniform logarithmic spacing of the input array.
  24. mu : float
  25. Order of the Hankel transform, any positive or negative real number.
  26. offset : float, optional
  27. Offset of the uniform logarithmic spacing of the output array.
  28. bias : float, optional
  29. Exponent of power law bias, any positive or negative real number.
  30. Returns
  31. -------
  32. A : array_like (..., n)
  33. The transformed output array, which is real, periodic, uniformly
  34. logarithmically spaced, and of the same shape as the input array.
  35. See Also
  36. --------
  37. ifht : The inverse of `fht`.
  38. fhtoffset : Return an optimal offset for `fht`.
  39. Notes
  40. -----
  41. This function computes a discrete version of the Hankel transform
  42. .. math::
  43. A(k) = \int_{0}^{\infty} \! a(r) \, J_\mu(kr) \, k \, dr \;,
  44. where :math:`J_\mu` is the Bessel function of order :math:`\mu`. The index
  45. :math:`\mu` may be any real number, positive or negative. Note that the
  46. numerical Hankel transform uses an integrand of :math:`k \, dr`, while the
  47. mathematical Hankel transform is commonly defined using :math:`r \, dr`.
  48. The input array `a` is a periodic sequence of length :math:`n`, uniformly
  49. logarithmically spaced with spacing `dln`,
  50. .. math::
  51. a_j = a(r_j) \;, \quad
  52. r_j = r_c \exp[(j-j_c) \, \mathtt{dln}]
  53. centred about the point :math:`r_c`. Note that the central index
  54. :math:`j_c = (n-1)/2` is half-integral if :math:`n` is even, so that
  55. :math:`r_c` falls between two input elements. Similarly, the output
  56. array `A` is a periodic sequence of length :math:`n`, also uniformly
  57. logarithmically spaced with spacing `dln`
  58. .. math::
  59. A_j = A(k_j) \;, \quad
  60. k_j = k_c \exp[(j-j_c) \, \mathtt{dln}]
  61. centred about the point :math:`k_c`.
  62. The centre points :math:`r_c` and :math:`k_c` of the periodic intervals may
  63. be chosen arbitrarily, but it would be usual to choose the product
  64. :math:`k_c r_c = k_j r_{n-1-j} = k_{n-1-j} r_j` to be unity. This can be
  65. changed using the `offset` parameter, which controls the logarithmic offset
  66. :math:`\log(k_c) = \mathtt{offset} - \log(r_c)` of the output array.
  67. Choosing an optimal value for `offset` may reduce ringing of the discrete
  68. Hankel transform.
  69. If the `bias` parameter is nonzero, this function computes a discrete
  70. version of the biased Hankel transform
  71. .. math::
  72. A(k) = \int_{0}^{\infty} \! a_q(r) \, (kr)^q \, J_\mu(kr) \, k \, dr
  73. where :math:`q` is the value of `bias`, and a power law bias
  74. :math:`a_q(r) = a(r) \, (kr)^{-q}` is applied to the input sequence.
  75. Biasing the transform can help approximate the continuous transform of
  76. :math:`a(r)` if there is a value :math:`q` such that :math:`a_q(r)` is
  77. close to a periodic sequence, in which case the resulting :math:`A(k)` will
  78. be close to the continuous transform.
  79. References
  80. ----------
  81. .. [1] Talman J. D., 1978, J. Comp. Phys., 29, 35
  82. .. [2] Hamilton A. J. S., 2000, MNRAS, 312, 257 (astro-ph/9905191)
  83. Examples
  84. --------
  85. This example is the adapted version of ``fftlogtest.f`` which is provided
  86. in [2]_. It evaluates the integral
  87. .. math::
  88. \int^\infty_0 r^{\mu+1} \exp(-r^2/2) J_\mu(kr) k dr
  89. = k^{\mu+1} \exp(-k^2/2) .
  90. >>> import numpy as np
  91. >>> from scipy import fft
  92. >>> import matplotlib.pyplot as plt
  93. Parameters for the transform.
  94. >>> mu = 0.0 # Order mu of Bessel function
  95. >>> r = np.logspace(-7, 1, 128) # Input evaluation points
  96. >>> dln = np.log(r[1]/r[0]) # Step size
  97. >>> offset = fft.fhtoffset(dln, initial=-6*np.log(10), mu=mu)
  98. >>> k = np.exp(offset)/r[::-1] # Output evaluation points
  99. Define the analytical function.
  100. >>> def f(x, mu):
  101. ... """Analytical function: x^(mu+1) exp(-x^2/2)."""
  102. ... return x**(mu + 1)*np.exp(-x**2/2)
  103. Evaluate the function at ``r`` and compute the corresponding values at
  104. ``k`` using FFTLog.
  105. >>> a_r = f(r, mu)
  106. >>> fht = fft.fht(a_r, dln, mu=mu, offset=offset)
  107. For this example we can actually compute the analytical response (which in
  108. this case is the same as the input function) for comparison and compute the
  109. relative error.
  110. >>> a_k = f(k, mu)
  111. >>> rel_err = abs((fht-a_k)/a_k)
  112. Plot the result.
  113. >>> figargs = {'sharex': True, 'sharey': True, 'constrained_layout': True}
  114. >>> fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4), **figargs)
  115. >>> ax1.set_title(r'$r^{\mu+1}\ \exp(-r^2/2)$')
  116. >>> ax1.loglog(r, a_r, 'k', lw=2)
  117. >>> ax1.set_xlabel('r')
  118. >>> ax2.set_title(r'$k^{\mu+1} \exp(-k^2/2)$')
  119. >>> ax2.loglog(k, a_k, 'k', lw=2, label='Analytical')
  120. >>> ax2.loglog(k, fht, 'C3--', lw=2, label='FFTLog')
  121. >>> ax2.set_xlabel('k')
  122. >>> ax2.legend(loc=3, framealpha=1)
  123. >>> ax2.set_ylim([1e-10, 1e1])
  124. >>> ax2b = ax2.twinx()
  125. >>> ax2b.loglog(k, rel_err, 'C0', label='Rel. Error (-)')
  126. >>> ax2b.set_ylabel('Rel. Error (-)', color='C0')
  127. >>> ax2b.tick_params(axis='y', labelcolor='C0')
  128. >>> ax2b.legend(loc=4, framealpha=1)
  129. >>> ax2b.set_ylim([1e-9, 1e-3])
  130. >>> plt.show()
  131. '''
  132. return (Dispatchable(a, np.ndarray),)
  133. @xp_capabilities(allow_dask_compute=True)
  134. @_dispatch
  135. def ifht(A, dln, mu, offset=0.0, bias=0.0):
  136. r"""Compute the inverse fast Hankel transform.
  137. Computes the discrete inverse Hankel transform of a logarithmically spaced
  138. periodic sequence. This is the inverse operation to `fht`.
  139. Parameters
  140. ----------
  141. A : array_like (..., n)
  142. Real periodic input array, uniformly logarithmically spaced. For
  143. multidimensional input, the transform is performed over the last axis.
  144. dln : float
  145. Uniform logarithmic spacing of the input array.
  146. mu : float
  147. Order of the Hankel transform, any positive or negative real number.
  148. offset : float, optional
  149. Offset of the uniform logarithmic spacing of the output array.
  150. bias : float, optional
  151. Exponent of power law bias, any positive or negative real number.
  152. Returns
  153. -------
  154. a : array_like (..., n)
  155. The transformed output array, which is real, periodic, uniformly
  156. logarithmically spaced, and of the same shape as the input array.
  157. See Also
  158. --------
  159. fht : Definition of the fast Hankel transform.
  160. fhtoffset : Return an optimal offset for `ifht`.
  161. Notes
  162. -----
  163. This function computes a discrete version of the Hankel transform
  164. .. math::
  165. a(r) = \int_{0}^{\infty} \! A(k) \, J_\mu(kr) \, r \, dk \;,
  166. where :math:`J_\mu` is the Bessel function of order :math:`\mu`. The index
  167. :math:`\mu` may be any real number, positive or negative. Note that the
  168. numerical inverse Hankel transform uses an integrand of :math:`r \, dk`, while the
  169. mathematical inverse Hankel transform is commonly defined using :math:`k \, dk`.
  170. See `fht` for further details.
  171. """
  172. return (Dispatchable(A, np.ndarray),)