test_wright_bessel.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. # Reference MPMATH implementation:
  2. #
  3. # import mpmath
  4. # from mpmath import nsum
  5. #
  6. # def Wright_Series_MPMATH(a, b, z, dps=50, method='r+s+e', steps=[1000]):
  7. # """Compute Wright' generalized Bessel function as Series.
  8. #
  9. # This uses mpmath for arbitrary precision.
  10. # """
  11. # with mpmath.workdps(dps):
  12. # res = nsum(lambda k: z**k/mpmath.fac(k) * mpmath.rgamma(a*k+b),
  13. # [0, mpmath.inf],
  14. # tol=dps, method=method, steps=steps
  15. # )
  16. #
  17. # return res
  18. from itertools import product
  19. import pytest
  20. import numpy as np
  21. from numpy.testing import assert_equal, assert_allclose
  22. import scipy.special as sc
  23. from scipy.special import log_wright_bessel, loggamma, rgamma, wright_bessel
  24. @pytest.mark.parametrize('a', [0, 1e-6, 0.1, 0.5, 1, 10])
  25. @pytest.mark.parametrize('b', [0, 1e-6, 0.1, 0.5, 1, 10])
  26. def test_wright_bessel_zero(a, b):
  27. """Test at x = 0."""
  28. assert_equal(wright_bessel(a, b, 0.), rgamma(b))
  29. assert_allclose(log_wright_bessel(a, b, 0.), -loggamma(b))
  30. @pytest.mark.parametrize('b', [0, 1e-6, 0.1, 0.5, 1, 10])
  31. @pytest.mark.parametrize('x', [0, 1e-6, 0.1, 0.5, 1])
  32. def test_wright_bessel_iv(b, x):
  33. """Test relation of wright_bessel and modified bessel function iv.
  34. iv(z) = (1/2*z)**v * Phi(1, v+1; 1/4*z**2).
  35. See https://dlmf.nist.gov/10.46.E2
  36. """
  37. if x != 0:
  38. v = b - 1
  39. wb = wright_bessel(1, v + 1, x**2 / 4.)
  40. # Note: iv(v, x) has precision of less than 1e-12 for some cases
  41. # e.g v=1-1e-6 and x=1e-06)
  42. assert_allclose(np.power(x / 2., v) * wb,
  43. sc.iv(v, x),
  44. rtol=1e-11, atol=1e-11)
  45. @pytest.mark.parametrize('a', [0, 1e-6, 0.1, 0.5, 1, 10])
  46. @pytest.mark.parametrize('b', [1, 1 + 1e-3, 2, 5, 10])
  47. @pytest.mark.parametrize('x', [0, 1e-6, 0.1, 0.5, 1, 5, 10, 100])
  48. def test_wright_functional(a, b, x):
  49. """Test functional relation of wright_bessel.
  50. Phi(a, b-1, z) = a*z*Phi(a, b+a, z) + (b-1)*Phi(a, b, z)
  51. Note that d/dx Phi(a, b, x) = Phi(a, b-1, x)
  52. See Eq. (22) of
  53. B. Stankovic, On the Function of E. M. Wright,
  54. Publ. de l' Institut Mathematique, Beograd,
  55. Nouvelle S`er. 10 (1970), 113-124.
  56. """
  57. assert_allclose(wright_bessel(a, b - 1, x),
  58. a * x * wright_bessel(a, b + a, x)
  59. + (b - 1) * wright_bessel(a, b, x),
  60. rtol=1e-8, atol=1e-8)
  61. # grid of rows [a, b, x, value, accuracy] that do not reach 1e-11 accuracy
  62. # see output of:
  63. # cd scipy/scipy/_precompute
  64. # python wright_bessel_data.py
  65. grid_a_b_x_value_acc = np.array([
  66. [0.1, 100.0, 709.7827128933841, 8.026353022981087e+34, 2e-8],
  67. [0.5, 10.0, 709.7827128933841, 2.680788404494657e+48, 9e-8],
  68. [0.5, 10.0, 1000.0, 2.005901980702872e+64, 1e-8],
  69. [0.5, 100.0, 1000.0, 3.4112367580445246e-117, 6e-8],
  70. [1.0, 20.0, 100000.0, 1.7717158630699857e+225, 3e-11],
  71. [1.0, 100.0, 100000.0, 1.0269334596230763e+22, np.nan],
  72. [1.0000000000000222, 20.0, 100000.0, 1.7717158630001672e+225, 3e-11],
  73. [1.0000000000000222, 100.0, 100000.0, 1.0269334595866202e+22, np.nan],
  74. [1.5, 0.0, 500.0, 15648961196.432373, 3e-11],
  75. [1.5, 2.220446049250313e-14, 500.0, 15648961196.431465, 3e-11],
  76. [1.5, 1e-10, 500.0, 15648961192.344728, 3e-11],
  77. [1.5, 1e-05, 500.0, 15648552437.334162, 3e-11],
  78. [1.5, 0.1, 500.0, 12049870581.10317, 2e-11],
  79. [1.5, 20.0, 100000.0, 7.81930438331405e+43, 3e-9],
  80. [1.5, 100.0, 100000.0, 9.653370857459075e-130, np.nan],
  81. ])
  82. @pytest.mark.xfail
  83. @pytest.mark.parametrize(
  84. 'a, b, x, phi',
  85. grid_a_b_x_value_acc[:, :4].tolist())
  86. def test_wright_data_grid_failures(a, b, x, phi):
  87. """Test cases of test_data that do not reach relative accuracy of 1e-11"""
  88. assert_allclose(wright_bessel(a, b, x), phi, rtol=1e-11)
  89. @pytest.mark.parametrize(
  90. 'a, b, x, phi, accuracy',
  91. grid_a_b_x_value_acc.tolist())
  92. def test_wright_data_grid_less_accurate(a, b, x, phi, accuracy):
  93. """Test cases of test_data that do not reach relative accuracy of 1e-11
  94. Here we test for reduced accuracy or even nan.
  95. """
  96. if np.isnan(accuracy):
  97. assert np.isnan(wright_bessel(a, b, x))
  98. else:
  99. assert_allclose(wright_bessel(a, b, x), phi, rtol=accuracy)
  100. @pytest.mark.parametrize(
  101. 'a, b, x',
  102. list(
  103. product([0, 0.1, 0.5, 1.5, 5, 10], [1, 2], [1e-3, 1, 1.5, 5, 10])
  104. )
  105. )
  106. def test_log_wright_bessel_same_as_wright_bessel(a, b, x):
  107. """Test that log_wright_bessel equals log of wright_bessel."""
  108. assert_allclose(
  109. log_wright_bessel(a, b, x),
  110. np.log(wright_bessel(a, b, x)),
  111. rtol=1e-8,
  112. )
  113. # Computed with, see also mp_wright_bessel from wright_bessel_data.py:
  114. #
  115. # from functools import lru_cache
  116. # import mpmath as mp
  117. #
  118. # @lru_cache(maxsize=1_000_000)
  119. # def rgamma_cached(x, dps):
  120. # with mp.workdps(dps):
  121. # return mp.rgamma(x)
  122. #
  123. # def mp_log_wright_bessel(a, b, x, dps=100, maxterms=10_000, method="d"):
  124. # """Compute log of Wright's generalized Bessel function as Series with mpmath."""
  125. # with mp.workdps(dps):
  126. # a, b, x = mp.mpf(a), mp.mpf(b), mp.mpf(x)
  127. # res = mp.nsum(lambda k: x**k / mp.fac(k)
  128. # * rgamma_cached(a * k + b, dps=dps),
  129. # [0, mp.inf],
  130. # tol=dps, method=method, steps=[maxterms]
  131. # )
  132. # return mp.log(res)
  133. #
  134. # Sometimes, one needs to set maxterms as high as 1_00_000 to get accurate results for
  135. # phi.
  136. # At the end of the day, we can only hope that results are correct for very large x,
  137. # e.g. by the asymptotic series, as there is no way to produce those in "exact"
  138. # arithmetic.
  139. # Note: accuracy = np.nan means log_wright_bessel returns nan.
  140. @pytest.mark.parametrize(
  141. 'a, b, x, phi, accuracy',
  142. [
  143. (0, 0, 0, -np.inf, 1e-11),
  144. (0, 0, 1, -np.inf, 1e-11),
  145. (0, 1, 1.23, 1.23, 1e-11),
  146. (0, 1, 1e50, 1e50, 1e-11),
  147. (1e-5, 0, 700, 695.0421608273609, 1e-11),
  148. (1e-5, 0, 1e3, 995.40052566540066, 1e-11),
  149. (1e-5, 100, 1e3, 640.8197935670078, 1e-11),
  150. (1e-3, 0, 1e4, 9987.2229532297262, 1e-11),
  151. (1e-3, 0, 1e5, 99641.920687169507, 1e-11),
  152. (1e-3, 0, 1e6, 994118.55560054416, 1e-11), # maxterms=1_000_000
  153. (1e-3, 10, 1e5, 99595.47710802537, 1e-11),
  154. (1e-3, 50, 1e5, 99401.240922855647, 1e-3),
  155. (1e-3, 100, 1e5, 99143.465191656527, np.nan),
  156. (0.5, 0, 1e5, 4074.1112442197941, 1e-11),
  157. (0.5, 0, 1e7, 87724.552120038896, 1e-11),
  158. (0.5, 100, 1e5, 3350.3928746306163, np.nan),
  159. (0.5, 100, 1e7, 86696.109975301719, 1e-11),
  160. (1, 0, 1e5, 634.06765787997266, 1e-11),
  161. (1, 0, 1e8, 20003.339639312035, 1e-11),
  162. (1.5, 0, 1e5, 197.01777556071194, 1e-11),
  163. (1.5, 0, 1e8, 3108.987414395706, 1e-11),
  164. (1.5, 100, 1e8, 2354.8915946283275, np.nan),
  165. (5, 0, 1e5, 9.8980480013203547, 1e-11),
  166. (5, 0, 1e8, 33.642337258687465, 1e-11),
  167. (5, 0, 1e12, 157.53704288117429, 1e-11),
  168. (5, 100, 1e5, -359.13419630792148, 1e-11),
  169. (5, 100, 1e12, -337.07722086995229, 1e-4),
  170. (5, 100, 1e20, 2588.2471229986845, 2e-6),
  171. (100, 0, 1e5, -347.62127990460517, 1e-11),
  172. (100, 0, 1e20, -313.08250350969449, 1e-11),
  173. (100, 100, 1e5, -359.1342053695754, 1e-11),
  174. (100, 100, 1e20, -359.1342053695754, 1e-11),
  175. ]
  176. )
  177. def test_log_wright_bessel(a, b, x, phi, accuracy):
  178. """Test for log_wright_bessel, in particular for large x."""
  179. if np.isnan(accuracy):
  180. assert np.isnan(log_wright_bessel(a, b, x))
  181. else:
  182. assert_allclose(log_wright_bessel(a, b, x), phi, rtol=accuracy)