_spherical_bessel.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. from functools import wraps
  2. import scipy._lib.array_api_extra as xpx
  3. import numpy as np
  4. from ._ufuncs import (_spherical_jn, _spherical_yn, _spherical_in,
  5. _spherical_kn, _spherical_jn_d, _spherical_yn_d,
  6. _spherical_in_d, _spherical_kn_d)
  7. def use_reflection(sign_n_even=None, reflection_fun=None):
  8. # - If reflection_fun is not specified, reflects negative `z` and multiplies
  9. # output by appropriate sign (indicated by `sign_n_even`).
  10. # - If reflection_fun is specified, calls `reflection_fun` instead of `fun`.
  11. # See DLMF 10.47(v) https://dlmf.nist.gov/10.47
  12. def decorator(fun):
  13. def standard_reflection(n, z, derivative):
  14. # sign_n_even indicates the sign when the order `n` is even
  15. sign = np.where(n % 2 == 0, sign_n_even, -sign_n_even)
  16. # By the chain rule, differentiation at `-z` adds a minus sign
  17. sign = -sign if derivative else sign
  18. # Evaluate at positive z (minus negative z) and adjust the sign
  19. return fun(n, -z, derivative) * sign
  20. @wraps(fun)
  21. def wrapper(n, z, derivative=False):
  22. z = np.asarray(z)
  23. if np.issubdtype(z.dtype, np.complexfloating):
  24. return fun(n, z, derivative) # complex dtype just works
  25. f2 = standard_reflection if reflection_fun is None else reflection_fun
  26. return xpx.apply_where(z.real >= 0, (n, z),
  27. lambda n, z: fun(n, z, derivative),
  28. lambda n, z: f2(n, z, derivative))[()]
  29. return wrapper
  30. return decorator
  31. @use_reflection(+1) # See DLMF 10.47(v) https://dlmf.nist.gov/10.47
  32. def spherical_jn(n, z, derivative=False):
  33. r"""Spherical Bessel function of the first kind or its derivative.
  34. Defined as [1]_,
  35. .. math:: j_n(z) = \sqrt{\frac{\pi}{2z}} J_{n + 1/2}(z),
  36. where :math:`J_n` is the Bessel function of the first kind.
  37. Parameters
  38. ----------
  39. n : int, array_like
  40. Order of the Bessel function (n >= 0).
  41. z : complex or float, array_like
  42. Argument of the Bessel function.
  43. derivative : bool, optional
  44. If True, the value of the derivative (rather than the function
  45. itself) is returned.
  46. Returns
  47. -------
  48. jn : ndarray
  49. Notes
  50. -----
  51. For real arguments greater than the order, the function is computed
  52. using the ascending recurrence [2]_. For small real or complex
  53. arguments, the definitional relation to the cylindrical Bessel function
  54. of the first kind is used.
  55. The derivative is computed using the relations [3]_,
  56. .. math::
  57. j_n'(z) = j_{n-1}(z) - \frac{n + 1}{z} j_n(z).
  58. j_0'(z) = -j_1(z)
  59. .. versionadded:: 0.18.0
  60. References
  61. ----------
  62. .. [1] https://dlmf.nist.gov/10.47.E3
  63. .. [2] https://dlmf.nist.gov/10.51.E1
  64. .. [3] https://dlmf.nist.gov/10.51.E2
  65. .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
  66. Handbook of Mathematical Functions with Formulas,
  67. Graphs, and Mathematical Tables. New York: Dover, 1972.
  68. Examples
  69. --------
  70. The spherical Bessel functions of the first kind :math:`j_n` accept
  71. both real and complex second argument. They can return a complex type:
  72. >>> from scipy.special import spherical_jn
  73. >>> spherical_jn(0, 3+5j)
  74. (-9.878987731663194-8.021894345786002j)
  75. >>> type(spherical_jn(0, 3+5j))
  76. <class 'numpy.complex128'>
  77. We can verify the relation for the derivative from the Notes
  78. for :math:`n=3` in the interval :math:`[1, 2]`:
  79. >>> import numpy as np
  80. >>> x = np.arange(1.0, 2.0, 0.01)
  81. >>> np.allclose(spherical_jn(3, x, True),
  82. ... spherical_jn(2, x) - 4/x * spherical_jn(3, x))
  83. True
  84. The first few :math:`j_n` with real argument:
  85. >>> import matplotlib.pyplot as plt
  86. >>> x = np.arange(0.0, 10.0, 0.01)
  87. >>> fig, ax = plt.subplots()
  88. >>> ax.set_ylim(-0.5, 1.5)
  89. >>> ax.set_title(r'Spherical Bessel functions $j_n$')
  90. >>> for n in np.arange(0, 4):
  91. ... ax.plot(x, spherical_jn(n, x), label=rf'$j_{n}$')
  92. >>> plt.legend(loc='best')
  93. >>> plt.show()
  94. """
  95. n = np.asarray(n, dtype=np.dtype("long"))
  96. if derivative:
  97. return _spherical_jn_d(n, z)
  98. else:
  99. return _spherical_jn(n, z)
  100. @use_reflection(-1) # See DLMF 10.47(v) https://dlmf.nist.gov/10.47
  101. def spherical_yn(n, z, derivative=False):
  102. r"""Spherical Bessel function of the second kind or its derivative.
  103. Defined as [1]_,
  104. .. math:: y_n(z) = \sqrt{\frac{\pi}{2z}} Y_{n + 1/2}(z),
  105. where :math:`Y_n` is the Bessel function of the second kind.
  106. Parameters
  107. ----------
  108. n : int, array_like
  109. Order of the Bessel function (n >= 0).
  110. z : complex or float, array_like
  111. Argument of the Bessel function.
  112. derivative : bool, optional
  113. If True, the value of the derivative (rather than the function
  114. itself) is returned.
  115. Returns
  116. -------
  117. yn : ndarray
  118. Notes
  119. -----
  120. For real arguments, the function is computed using the ascending
  121. recurrence [2]_. For complex arguments, the definitional relation to
  122. the cylindrical Bessel function of the second kind is used.
  123. The derivative is computed using the relations [3]_,
  124. .. math::
  125. y_n' = y_{n-1} - \frac{n + 1}{z} y_n.
  126. y_0' = -y_1
  127. .. versionadded:: 0.18.0
  128. References
  129. ----------
  130. .. [1] https://dlmf.nist.gov/10.47.E4
  131. .. [2] https://dlmf.nist.gov/10.51.E1
  132. .. [3] https://dlmf.nist.gov/10.51.E2
  133. .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
  134. Handbook of Mathematical Functions with Formulas,
  135. Graphs, and Mathematical Tables. New York: Dover, 1972.
  136. Examples
  137. --------
  138. The spherical Bessel functions of the second kind :math:`y_n` accept
  139. both real and complex second argument. They can return a complex type:
  140. >>> from scipy.special import spherical_yn
  141. >>> spherical_yn(0, 3+5j)
  142. (8.022343088587197-9.880052589376795j)
  143. >>> type(spherical_yn(0, 3+5j))
  144. <class 'numpy.complex128'>
  145. We can verify the relation for the derivative from the Notes
  146. for :math:`n=3` in the interval :math:`[1, 2]`:
  147. >>> import numpy as np
  148. >>> x = np.arange(1.0, 2.0, 0.01)
  149. >>> np.allclose(spherical_yn(3, x, True),
  150. ... spherical_yn(2, x) - 4/x * spherical_yn(3, x))
  151. True
  152. The first few :math:`y_n` with real argument:
  153. >>> import matplotlib.pyplot as plt
  154. >>> x = np.arange(0.0, 10.0, 0.01)
  155. >>> fig, ax = plt.subplots()
  156. >>> ax.set_ylim(-2.0, 1.0)
  157. >>> ax.set_title(r'Spherical Bessel functions $y_n$')
  158. >>> for n in np.arange(0, 4):
  159. ... ax.plot(x, spherical_yn(n, x), label=rf'$y_{n}$')
  160. >>> plt.legend(loc='best')
  161. >>> plt.show()
  162. """
  163. n = np.asarray(n, dtype=np.dtype("long"))
  164. if derivative:
  165. return _spherical_yn_d(n, z)
  166. else:
  167. return _spherical_yn(n, z)
  168. @use_reflection(+1) # See DLMF 10.47(v) https://dlmf.nist.gov/10.47
  169. def spherical_in(n, z, derivative=False):
  170. r"""Modified spherical Bessel function of the first kind or its derivative.
  171. Defined as [1]_,
  172. .. math:: i_n(z) = \sqrt{\frac{\pi}{2z}} I_{n + 1/2}(z),
  173. where :math:`I_n` is the modified Bessel function of the first kind.
  174. Parameters
  175. ----------
  176. n : int, array_like
  177. Order of the Bessel function (n >= 0).
  178. z : complex or float, array_like
  179. Argument of the Bessel function.
  180. derivative : bool, optional
  181. If True, the value of the derivative (rather than the function
  182. itself) is returned.
  183. Returns
  184. -------
  185. in : ndarray
  186. Notes
  187. -----
  188. The function is computed using its definitional relation to the
  189. modified cylindrical Bessel function of the first kind.
  190. The derivative is computed using the relations [2]_,
  191. .. math::
  192. i_n' = i_{n-1} - \frac{n + 1}{z} i_n.
  193. i_1' = i_0
  194. .. versionadded:: 0.18.0
  195. References
  196. ----------
  197. .. [1] https://dlmf.nist.gov/10.47.E7
  198. .. [2] https://dlmf.nist.gov/10.51.E5
  199. .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
  200. Handbook of Mathematical Functions with Formulas,
  201. Graphs, and Mathematical Tables. New York: Dover, 1972.
  202. Examples
  203. --------
  204. The modified spherical Bessel functions of the first kind :math:`i_n`
  205. accept both real and complex second argument.
  206. They can return a complex type:
  207. >>> from scipy.special import spherical_in
  208. >>> spherical_in(0, 3+5j)
  209. (-1.1689867793369182-1.2697305267234222j)
  210. >>> type(spherical_in(0, 3+5j))
  211. <class 'numpy.complex128'>
  212. We can verify the relation for the derivative from the Notes
  213. for :math:`n=3` in the interval :math:`[1, 2]`:
  214. >>> import numpy as np
  215. >>> x = np.arange(1.0, 2.0, 0.01)
  216. >>> np.allclose(spherical_in(3, x, True),
  217. ... spherical_in(2, x) - 4/x * spherical_in(3, x))
  218. True
  219. The first few :math:`i_n` with real argument:
  220. >>> import matplotlib.pyplot as plt
  221. >>> x = np.arange(0.0, 6.0, 0.01)
  222. >>> fig, ax = plt.subplots()
  223. >>> ax.set_ylim(-0.5, 5.0)
  224. >>> ax.set_title(r'Modified spherical Bessel functions $i_n$')
  225. >>> for n in np.arange(0, 4):
  226. ... ax.plot(x, spherical_in(n, x), label=rf'$i_{n}$')
  227. >>> plt.legend(loc='best')
  228. >>> plt.show()
  229. """
  230. n = np.asarray(n, dtype=np.dtype("long"))
  231. if derivative:
  232. return _spherical_in_d(n, z)
  233. else:
  234. return _spherical_in(n, z)
  235. def spherical_kn_reflection(n, z, derivative=False):
  236. # More complex than the other cases, and this will likely be re-implemented
  237. # in C++ anyway. Would require multiple function evaluations. Probably about
  238. # as fast to just resort to complex math, and much simpler.
  239. return spherical_kn(n, z + 0j, derivative=derivative).real
  240. @use_reflection(reflection_fun=spherical_kn_reflection)
  241. def spherical_kn(n, z, derivative=False):
  242. r"""Modified spherical Bessel function of the second kind or its derivative.
  243. Defined as [1]_,
  244. .. math:: k_n(z) = \sqrt{\frac{\pi}{2z}} K_{n + 1/2}(z),
  245. where :math:`K_n` is the modified Bessel function of the second kind.
  246. Parameters
  247. ----------
  248. n : int, array_like
  249. Order of the Bessel function (n >= 0).
  250. z : complex or float, array_like
  251. Argument of the Bessel function.
  252. derivative : bool, optional
  253. If True, the value of the derivative (rather than the function
  254. itself) is returned.
  255. Returns
  256. -------
  257. kn : ndarray
  258. Notes
  259. -----
  260. The function is computed using its definitional relation to the
  261. modified cylindrical Bessel function of the second kind.
  262. The derivative is computed using the relations [2]_,
  263. .. math::
  264. k_n' = -k_{n-1} - \frac{n + 1}{z} k_n.
  265. k_0' = -k_1
  266. .. versionadded:: 0.18.0
  267. References
  268. ----------
  269. .. [1] https://dlmf.nist.gov/10.47.E9
  270. .. [2] https://dlmf.nist.gov/10.51.E5
  271. .. [AS] Milton Abramowitz and Irene A. Stegun, eds.
  272. Handbook of Mathematical Functions with Formulas,
  273. Graphs, and Mathematical Tables. New York: Dover, 1972.
  274. Examples
  275. --------
  276. The modified spherical Bessel functions of the second kind :math:`k_n`
  277. accept both real and complex second argument.
  278. They can return a complex type:
  279. >>> from scipy.special import spherical_kn
  280. >>> spherical_kn(0, 3+5j)
  281. (0.012985785614001561+0.003354691603137546j)
  282. >>> type(spherical_kn(0, 3+5j))
  283. <class 'numpy.complex128'>
  284. We can verify the relation for the derivative from the Notes
  285. for :math:`n=3` in the interval :math:`[1, 2]`:
  286. >>> import numpy as np
  287. >>> x = np.arange(1.0, 2.0, 0.01)
  288. >>> np.allclose(spherical_kn(3, x, True),
  289. ... - 4/x * spherical_kn(3, x) - spherical_kn(2, x))
  290. True
  291. The first few :math:`k_n` with real argument:
  292. >>> import matplotlib.pyplot as plt
  293. >>> x = np.arange(0.0, 4.0, 0.01)
  294. >>> fig, ax = plt.subplots()
  295. >>> ax.set_ylim(0.0, 5.0)
  296. >>> ax.set_title(r'Modified spherical Bessel functions $k_n$')
  297. >>> for n in np.arange(0, 4):
  298. ... ax.plot(x, spherical_kn(n, x), label=rf'$k_{n}$')
  299. >>> plt.legend(loc='best')
  300. >>> plt.show()
  301. """
  302. n = np.asarray(n, dtype=np.dtype("long"))
  303. if derivative:
  304. return _spherical_kn_d(n, z)
  305. else:
  306. return _spherical_kn(n, z)