test_mpmath.py 68 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159
  1. """
  2. Test SciPy functions versus mpmath, if available.
  3. """
  4. import numpy as np
  5. from numpy.testing import assert_, assert_allclose
  6. from numpy import pi
  7. import pytest
  8. import itertools
  9. from scipy._lib import _pep440
  10. import scipy.special as sc
  11. from scipy.special._testutils import (
  12. MissingModule, check_version, FuncData,
  13. assert_func_equal)
  14. from scipy.special._mptestutils import (
  15. Arg, FixedArg, ComplexArg, IntArg, assert_mpmath_equal,
  16. nonfunctional_tooslow, trace_args, time_limited, exception_to_nan,
  17. inf_to_nan)
  18. from scipy.special._ufuncs import (
  19. _sinpi, _cospi, _lgam1p, _lanczos_sum_expg_scaled, _log1pmx,
  20. _igam_fac)
  21. try:
  22. import mpmath
  23. except ImportError:
  24. mpmath = MissingModule('mpmath')
  25. pytestmark = pytest.mark.thread_unsafe(
  26. reason=("mpmath gmpy2 backend is not thread-safe, "
  27. "see https://github.com/mpmath/mpmath/issues/974"))
  28. # ------------------------------------------------------------------------------
  29. # expi
  30. # ------------------------------------------------------------------------------
  31. @check_version(mpmath, '0.10')
  32. def test_expi_complex():
  33. dataset = []
  34. for r in np.logspace(-99, 2, 10):
  35. for p in np.linspace(0, 2*np.pi, 30):
  36. z = r*np.exp(1j*p)
  37. dataset.append((z, complex(mpmath.ei(z))))
  38. dataset = np.array(dataset, dtype=np.cdouble)
  39. FuncData(sc.expi, dataset, 0, 1).check()
  40. # ------------------------------------------------------------------------------
  41. # expn
  42. # ------------------------------------------------------------------------------
  43. @check_version(mpmath, '0.19')
  44. def test_expn_large_n():
  45. # Test the transition to the asymptotic regime of n.
  46. dataset = []
  47. for n in [50, 51]:
  48. for x in np.logspace(0, 4, 200):
  49. with mpmath.workdps(100):
  50. dataset.append((n, x, float(mpmath.expint(n, x))))
  51. dataset = np.asarray(dataset)
  52. FuncData(sc.expn, dataset, (0, 1), 2, rtol=1e-13).check()
  53. # ------------------------------------------------------------------------------
  54. # hyp0f1
  55. # ------------------------------------------------------------------------------
  56. @check_version(mpmath, '0.19')
  57. def test_hyp0f1_gh5764():
  58. # Do a small and somewhat systematic test that runs quickly
  59. dataset = []
  60. axis = [-99.5, -9.5, -0.5, 0.5, 9.5, 99.5]
  61. for v in axis:
  62. for x in axis:
  63. for y in axis:
  64. z = x + 1j*y
  65. # mpmath computes the answer correctly at dps ~ 17 but
  66. # fails for 20 < dps < 120 (uses a different method);
  67. # set the dps high enough that this isn't an issue
  68. with mpmath.workdps(120):
  69. res = complex(mpmath.hyp0f1(v, z))
  70. dataset.append((v, z, res))
  71. dataset = np.array(dataset)
  72. FuncData(lambda v, z: sc.hyp0f1(v.real, z), dataset, (0, 1), 2,
  73. rtol=1e-13).check()
  74. @check_version(mpmath, '0.19')
  75. def test_hyp0f1_gh_1609():
  76. # this is a regression test for gh-1609
  77. vv = np.linspace(150, 180, 21)
  78. af = sc.hyp0f1(vv, 0.5)
  79. mf = np.array([mpmath.hyp0f1(v, 0.5) for v in vv])
  80. assert_allclose(af, mf.astype(float), rtol=1e-12)
  81. # ------------------------------------------------------------------------------
  82. # hyperu
  83. # ------------------------------------------------------------------------------
  84. @check_version(mpmath, '1.1.0')
  85. def test_hyperu_around_0():
  86. dataset = []
  87. # DLMF 13.2.14-15 test points.
  88. for n in np.arange(-5, 5):
  89. for b in np.linspace(-5, 5, 20):
  90. a = -n
  91. dataset.append((a, b, 0, float(mpmath.hyperu(a, b, 0))))
  92. a = -n + b - 1
  93. dataset.append((a, b, 0, float(mpmath.hyperu(a, b, 0))))
  94. # DLMF 13.2.16-22 test points.
  95. for a in [-10.5, -1.5, -0.5, 0, 0.5, 1, 10]:
  96. for b in [-1.0, -0.5, 0, 0.5, 1, 1.5, 2, 2.5]:
  97. dataset.append((a, b, 0, float(mpmath.hyperu(a, b, 0))))
  98. dataset = np.array(dataset)
  99. FuncData(sc.hyperu, dataset, (0, 1, 2), 3, rtol=1e-15, atol=5e-13).check()
  100. # ------------------------------------------------------------------------------
  101. # hyp2f1
  102. # ------------------------------------------------------------------------------
  103. @check_version(mpmath, '1.0.0')
  104. def test_hyp2f1_strange_points():
  105. pts = [
  106. (2, -1, -1, 0.7), # expected: 2.4
  107. (2, -2, -2, 0.7), # expected: 3.87
  108. ]
  109. pts += list(itertools.product([2, 1, -0.7, -1000], repeat=4))
  110. pts = [
  111. (a, b, c, x) for a, b, c, x in pts
  112. if b == c and round(b) == b and b < 0 and b != -1000
  113. ]
  114. kw = dict(eliminate=True)
  115. dataset = [p + (float(mpmath.hyp2f1(*p, **kw)),) for p in pts]
  116. dataset = np.array(dataset, dtype=np.float64)
  117. FuncData(sc.hyp2f1, dataset, (0,1,2,3), 4, rtol=1e-10).check()
  118. @check_version(mpmath, '0.13')
  119. def test_hyp2f1_real_some_points():
  120. pts = [
  121. (1, 2, 3, 0),
  122. (1./3, 2./3, 5./6, 27./32),
  123. (1./4, 1./2, 3./4, 80./81),
  124. (2,-2, -3, 3),
  125. (2, -3, -2, 3),
  126. (2, -1.5, -1.5, 3),
  127. (1, 2, 3, 0),
  128. (0.7235, -1, -5, 0.3),
  129. (0.25, 1./3, 2, 0.999),
  130. (0.25, 1./3, 2, -1),
  131. (2, 3, 5, 0.99),
  132. (3./2, -0.5, 3, 0.99),
  133. (2, 2.5, -3.25, 0.999),
  134. (-8, 18.016500331508873, 10.805295997850628, 0.90875647507000001),
  135. (-10, 900, -10.5, 0.99),
  136. (-10, 900, 10.5, 0.99),
  137. (-1, 2, 1, 1.0),
  138. (-1, 2, 1, -1.0),
  139. (-3, 13, 5, 1.0),
  140. (-3, 13, 5, -1.0),
  141. (0.5, 1 - 270.5, 1.5, 0.999**2), # from issue 1561
  142. ]
  143. dataset = [p + (float(mpmath.hyp2f1(*p)),) for p in pts]
  144. dataset = np.array(dataset, dtype=np.float64)
  145. with np.errstate(invalid='ignore'):
  146. FuncData(sc.hyp2f1, dataset, (0,1,2,3), 4, rtol=1e-10).check()
  147. @check_version(mpmath, '0.14')
  148. def test_hyp2f1_some_points_2():
  149. # Taken from mpmath unit tests -- this point failed for mpmath 0.13 but
  150. # was fixed in their SVN since then
  151. pts = [
  152. (112, (51,10), (-9,10), -0.99999),
  153. (10,-900,10.5,0.99),
  154. (10,-900,-10.5,0.99),
  155. ]
  156. def fev(x):
  157. if isinstance(x, tuple):
  158. return float(x[0]) / x[1]
  159. else:
  160. return x
  161. dataset = [tuple(map(fev, p)) + (float(mpmath.hyp2f1(*p)),) for p in pts]
  162. dataset = np.array(dataset, dtype=np.float64)
  163. FuncData(sc.hyp2f1, dataset, (0,1,2,3), 4, rtol=1e-10).check()
  164. @check_version(mpmath, '0.13')
  165. def test_hyp2f1_real_some():
  166. dataset = []
  167. for a in [-10, -5, -1.8, 1.8, 5, 10]:
  168. for b in [-2.5, -1, 1, 7.4]:
  169. for c in [-9, -1.8, 5, 20.4]:
  170. for z in [-10, -1.01, -0.99, 0, 0.6, 0.95, 1.5, 10]:
  171. try:
  172. v = float(mpmath.hyp2f1(a, b, c, z))
  173. except Exception:
  174. continue
  175. dataset.append((a, b, c, z, v))
  176. dataset = np.array(dataset, dtype=np.float64)
  177. with np.errstate(invalid='ignore'):
  178. FuncData(sc.hyp2f1, dataset, (0,1,2,3), 4, rtol=1e-9,
  179. ignore_inf_sign=True).check()
  180. @check_version(mpmath, '0.12')
  181. @pytest.mark.slow
  182. def test_hyp2f1_real_random():
  183. npoints = 500
  184. dataset = np.zeros((npoints, 5), np.float64)
  185. np.random.seed(1234)
  186. dataset[:, 0] = np.random.pareto(1.5, npoints)
  187. dataset[:, 1] = np.random.pareto(1.5, npoints)
  188. dataset[:, 2] = np.random.pareto(1.5, npoints)
  189. dataset[:, 3] = 2*np.random.rand(npoints) - 1
  190. dataset[:, 0] *= (-1)**np.random.randint(2, npoints)
  191. dataset[:, 1] *= (-1)**np.random.randint(2, npoints)
  192. dataset[:, 2] *= (-1)**np.random.randint(2, npoints)
  193. for ds in dataset:
  194. if mpmath.__version__ < '0.14':
  195. # mpmath < 0.14 fails for c too much smaller than a, b
  196. if abs(ds[:2]).max() > abs(ds[2]):
  197. ds[2] = abs(ds[:2]).max()
  198. ds[4] = float(mpmath.hyp2f1(*tuple(ds[:4])))
  199. FuncData(sc.hyp2f1, dataset, (0, 1, 2, 3), 4, rtol=1e-9).check()
  200. # ------------------------------------------------------------------------------
  201. # erf (complex)
  202. # ------------------------------------------------------------------------------
  203. @check_version(mpmath, '0.14')
  204. def test_erf_complex():
  205. # need to increase mpmath precision for this test
  206. old_dps, old_prec = mpmath.mp.dps, mpmath.mp.prec
  207. try:
  208. mpmath.mp.dps = 70
  209. x1, y1 = np.meshgrid(np.linspace(-10, 1, 31), np.linspace(-10, 1, 11))
  210. x2, y2 = np.meshgrid(np.logspace(-80, .8, 31), np.logspace(-80, .8, 11))
  211. points = np.r_[x1.ravel(),x2.ravel()] + 1j*np.r_[y1.ravel(), y2.ravel()]
  212. assert_func_equal(sc.erf, lambda x: complex(mpmath.erf(x)), points,
  213. vectorized=False, rtol=1e-13)
  214. assert_func_equal(sc.erfc, lambda x: complex(mpmath.erfc(x)), points,
  215. vectorized=False, rtol=1e-13)
  216. finally:
  217. mpmath.mp.dps, mpmath.mp.prec = old_dps, old_prec
  218. # ------------------------------------------------------------------------------
  219. # lpmv
  220. # ------------------------------------------------------------------------------
  221. @check_version(mpmath, '0.15')
  222. def test_lpmv():
  223. pts = []
  224. for x in [-0.99, -0.557, 1e-6, 0.132, 1]:
  225. pts.extend([
  226. (1, 1, x),
  227. (1, -1, x),
  228. (-1, 1, x),
  229. (-1, -2, x),
  230. (1, 1.7, x),
  231. (1, -1.7, x),
  232. (-1, 1.7, x),
  233. (-1, -2.7, x),
  234. (1, 10, x),
  235. (1, 11, x),
  236. (3, 8, x),
  237. (5, 11, x),
  238. (-3, 8, x),
  239. (-5, 11, x),
  240. (3, -8, x),
  241. (5, -11, x),
  242. (-3, -8, x),
  243. (-5, -11, x),
  244. (3, 8.3, x),
  245. (5, 11.3, x),
  246. (-3, 8.3, x),
  247. (-5, 11.3, x),
  248. (3, -8.3, x),
  249. (5, -11.3, x),
  250. (-3, -8.3, x),
  251. (-5, -11.3, x),
  252. ])
  253. def mplegenp(nu, mu, x):
  254. if mu == int(mu) and x == 1:
  255. # mpmath 0.17 gets this wrong
  256. if mu == 0:
  257. return 1
  258. else:
  259. return 0
  260. return mpmath.legenp(nu, mu, x)
  261. dataset = [p + (mplegenp(p[1], p[0], p[2]),) for p in pts]
  262. dataset = np.array(dataset, dtype=np.float64)
  263. def evf(mu, nu, x):
  264. return sc.lpmv(mu.astype(int), nu, x)
  265. with np.errstate(invalid='ignore'):
  266. FuncData(evf, dataset, (0,1,2), 3, rtol=1e-10, atol=1e-14).check()
  267. # ------------------------------------------------------------------------------
  268. # beta
  269. # ------------------------------------------------------------------------------
  270. @pytest.mark.slow
  271. @check_version(mpmath, '0.15')
  272. def test_beta():
  273. np.random.seed(1234)
  274. b = np.r_[np.logspace(-200, 200, 4),
  275. np.logspace(-10, 10, 4),
  276. np.logspace(-1, 1, 4),
  277. np.arange(-10, 11, 1),
  278. np.arange(-10, 11, 1) + 0.5,
  279. -1, -2.3, -3, -100.3, -10003.4]
  280. a = b
  281. ab = np.array(np.broadcast_arrays(a[:,None], b[None,:])).reshape(2, -1).T
  282. old_dps, old_prec = mpmath.mp.dps, mpmath.mp.prec
  283. try:
  284. mpmath.mp.dps = 400
  285. assert_func_equal(sc.beta,
  286. lambda a, b: float(mpmath.beta(a, b)),
  287. ab,
  288. vectorized=False,
  289. rtol=1e-10,
  290. ignore_inf_sign=True)
  291. assert_func_equal(
  292. sc.betaln,
  293. lambda a, b: float(mpmath.log(abs(mpmath.beta(a, b)))),
  294. ab,
  295. vectorized=False,
  296. rtol=1e-10)
  297. finally:
  298. mpmath.mp.dps, mpmath.mp.prec = old_dps, old_prec
  299. # ------------------------------------------------------------------------------
  300. # loggamma
  301. # ------------------------------------------------------------------------------
  302. LOGGAMMA_TAYLOR_RADIUS = 0.2
  303. @check_version(mpmath, '0.19')
  304. def test_loggamma_taylor_transition():
  305. # Make sure there isn't a big jump in accuracy when we move from
  306. # using the Taylor series to using the recurrence relation.
  307. r = LOGGAMMA_TAYLOR_RADIUS + np.array([-0.1, -0.01, 0, 0.01, 0.1])
  308. theta = np.linspace(0, 2*np.pi, 20)
  309. r, theta = np.meshgrid(r, theta)
  310. dz = r*np.exp(1j*theta)
  311. z = np.r_[1 + dz, 2 + dz].flatten()
  312. dataset = [(z0, complex(mpmath.loggamma(z0))) for z0 in z]
  313. dataset = np.array(dataset)
  314. FuncData(sc.loggamma, dataset, 0, 1, rtol=5e-14).check()
  315. @check_version(mpmath, '0.19')
  316. def test_loggamma_taylor():
  317. # Test around the zeros at z = 1, 2.
  318. r = np.logspace(-16, np.log10(LOGGAMMA_TAYLOR_RADIUS), 10)
  319. theta = np.linspace(0, 2*np.pi, 20)
  320. r, theta = np.meshgrid(r, theta)
  321. dz = r*np.exp(1j*theta)
  322. z = np.r_[1 + dz, 2 + dz].flatten()
  323. dataset = [(z0, complex(mpmath.loggamma(z0))) for z0 in z]
  324. dataset = np.array(dataset)
  325. FuncData(sc.loggamma, dataset, 0, 1, rtol=5e-14).check()
  326. # ------------------------------------------------------------------------------
  327. # rgamma
  328. # ------------------------------------------------------------------------------
  329. @check_version(mpmath, '0.19')
  330. @pytest.mark.slow
  331. def test_rgamma_zeros():
  332. # Test around the zeros at z = 0, -1, -2, ..., -169. (After -169 we
  333. # get values that are out of floating point range even when we're
  334. # within 0.1 of the zero.)
  335. # Can't use too many points here or the test takes forever.
  336. dx = np.r_[-np.logspace(-1, -13, 3), 0, np.logspace(-13, -1, 3)]
  337. dy = dx.copy()
  338. dx, dy = np.meshgrid(dx, dy)
  339. dz = dx + 1j*dy
  340. zeros = np.arange(0, -170, -1).reshape(1, 1, -1)
  341. z = (zeros + np.dstack((dz,)*zeros.size)).flatten()
  342. with mpmath.workdps(100):
  343. dataset = [(z0, complex(mpmath.rgamma(z0))) for z0 in z]
  344. dataset = np.array(dataset)
  345. FuncData(sc.rgamma, dataset, 0, 1, rtol=1e-12).check()
  346. # ------------------------------------------------------------------------------
  347. # digamma
  348. # ------------------------------------------------------------------------------
  349. @check_version(mpmath, '0.19')
  350. @pytest.mark.slow
  351. def test_digamma_roots():
  352. # Test the special-cased roots for digamma.
  353. root = mpmath.findroot(mpmath.digamma, 1.5)
  354. roots = [float(root)]
  355. root = mpmath.findroot(mpmath.digamma, -0.5)
  356. roots.append(float(root))
  357. roots = np.array(roots)
  358. # If we test beyond a radius of 0.24 mpmath will take forever.
  359. dx = np.r_[-0.24, -np.logspace(-1, -15, 10), 0, np.logspace(-15, -1, 10), 0.24]
  360. dy = dx.copy()
  361. dx, dy = np.meshgrid(dx, dy)
  362. dz = dx + 1j*dy
  363. z = (roots + np.dstack((dz,)*roots.size)).flatten()
  364. with mpmath.workdps(30):
  365. dataset = [(z0, complex(mpmath.digamma(z0))) for z0 in z]
  366. dataset = np.array(dataset)
  367. FuncData(sc.digamma, dataset, 0, 1, rtol=1e-14).check()
  368. @check_version(mpmath, '0.19')
  369. def test_digamma_negreal():
  370. # Test digamma around the negative real axis. Don't do this in
  371. # TestSystematic because the points need some jiggering so that
  372. # mpmath doesn't take forever.
  373. digamma = exception_to_nan(mpmath.digamma)
  374. x = -np.logspace(300, -30, 100)
  375. y = np.r_[-np.logspace(0, -3, 5), 0, np.logspace(-3, 0, 5)]
  376. x, y = np.meshgrid(x, y)
  377. z = (x + 1j*y).flatten()
  378. with mpmath.workdps(40):
  379. dataset = [(z0, complex(digamma(z0))) for z0 in z]
  380. dataset = np.asarray(dataset)
  381. FuncData(sc.digamma, dataset, 0, 1, rtol=1e-13).check()
  382. @check_version(mpmath, '0.19')
  383. def test_digamma_boundary():
  384. # Check that there isn't a jump in accuracy when we switch from
  385. # using the asymptotic series to the reflection formula.
  386. x = -np.logspace(300, -30, 100)
  387. y = np.array([-6.1, -5.9, 5.9, 6.1])
  388. x, y = np.meshgrid(x, y)
  389. z = (x + 1j*y).flatten()
  390. with mpmath.workdps(30):
  391. dataset = [(z0, complex(mpmath.digamma(z0))) for z0 in z]
  392. dataset = np.asarray(dataset)
  393. FuncData(sc.digamma, dataset, 0, 1, rtol=1e-13).check()
  394. # ------------------------------------------------------------------------------
  395. # gammainc
  396. # ------------------------------------------------------------------------------
  397. @check_version(mpmath, '0.19')
  398. @pytest.mark.slow
  399. def test_gammainc_boundary():
  400. # Test the transition to the asymptotic series.
  401. small = 20
  402. a = np.linspace(0.5*small, 2*small, 50)
  403. x = a.copy()
  404. a, x = np.meshgrid(a, x)
  405. a, x = a.flatten(), x.flatten()
  406. with mpmath.workdps(100):
  407. dataset = [(a0, x0, float(mpmath.gammainc(a0, b=x0, regularized=True)))
  408. for a0, x0 in zip(a, x)]
  409. dataset = np.array(dataset)
  410. FuncData(sc.gammainc, dataset, (0, 1), 2, rtol=1e-12).check()
  411. # ------------------------------------------------------------------------------
  412. # spence
  413. # ------------------------------------------------------------------------------
  414. @check_version(mpmath, '0.19')
  415. @pytest.mark.slow
  416. def test_spence_circle():
  417. # The trickiest region for spence is around the circle |z - 1| = 1,
  418. # so test that region carefully.
  419. def spence(z):
  420. return complex(mpmath.polylog(2, 1 - z))
  421. r = np.linspace(0.5, 1.5)
  422. theta = np.linspace(0, 2*pi)
  423. z = (1 + np.outer(r, np.exp(1j*theta))).flatten()
  424. dataset = np.asarray([(z0, spence(z0)) for z0 in z])
  425. FuncData(sc.spence, dataset, 0, 1, rtol=1e-14).check()
  426. # ------------------------------------------------------------------------------
  427. # sinpi and cospi
  428. # ------------------------------------------------------------------------------
  429. @check_version(mpmath, '0.19')
  430. def test_sinpi_zeros():
  431. eps = np.finfo(float).eps
  432. dx = np.r_[-np.logspace(0, -13, 3), 0, np.logspace(-13, 0, 3)]
  433. dy = dx.copy()
  434. dx, dy = np.meshgrid(dx, dy)
  435. dz = dx + 1j*dy
  436. zeros = np.arange(-100, 100, 1).reshape(1, 1, -1)
  437. z = (zeros + np.dstack((dz,)*zeros.size)).flatten()
  438. dataset = np.asarray([(z0, complex(mpmath.sinpi(z0)))
  439. for z0 in z])
  440. FuncData(_sinpi, dataset, 0, 1, rtol=2*eps).check()
  441. @check_version(mpmath, '0.19')
  442. def test_cospi_zeros():
  443. eps = np.finfo(float).eps
  444. dx = np.r_[-np.logspace(0, -13, 3), 0, np.logspace(-13, 0, 3)]
  445. dy = dx.copy()
  446. dx, dy = np.meshgrid(dx, dy)
  447. dz = dx + 1j*dy
  448. zeros = (np.arange(-100, 100, 1) + 0.5).reshape(1, 1, -1)
  449. z = (zeros + np.dstack((dz,)*zeros.size)).flatten()
  450. dataset = np.asarray([(z0, complex(mpmath.cospi(z0)))
  451. for z0 in z])
  452. FuncData(_cospi, dataset, 0, 1, rtol=2*eps).check()
  453. # ------------------------------------------------------------------------------
  454. # ellipj
  455. # ------------------------------------------------------------------------------
  456. @check_version(mpmath, '0.19')
  457. def test_dn_quarter_period():
  458. def dn(u, m):
  459. return sc.ellipj(u, m)[2]
  460. def mpmath_dn(u, m):
  461. return float(mpmath.ellipfun("dn", u=u, m=m))
  462. m = np.linspace(0, 1, 20)
  463. du = np.r_[-np.logspace(-1, -15, 10), 0, np.logspace(-15, -1, 10)]
  464. dataset = []
  465. for m0 in m:
  466. u0 = float(mpmath.ellipk(m0))
  467. for du0 in du:
  468. p = u0 + du0
  469. dataset.append((p, m0, mpmath_dn(p, m0)))
  470. dataset = np.asarray(dataset)
  471. FuncData(dn, dataset, (0, 1), 2, rtol=1e-10).check()
  472. # ------------------------------------------------------------------------------
  473. # Wright Omega
  474. # ------------------------------------------------------------------------------
  475. def _mpmath_wrightomega(z, dps):
  476. with mpmath.workdps(dps):
  477. z = mpmath.mpc(z)
  478. unwind = mpmath.ceil((z.imag - mpmath.pi)/(2*mpmath.pi))
  479. res = mpmath.lambertw(mpmath.exp(z), unwind)
  480. return res
  481. @pytest.mark.slow
  482. @check_version(mpmath, '0.19')
  483. def test_wrightomega_branch():
  484. x = -np.logspace(10, 0, 25)
  485. picut_above = [np.nextafter(np.pi, np.inf)]
  486. picut_below = [np.nextafter(np.pi, -np.inf)]
  487. npicut_above = [np.nextafter(-np.pi, np.inf)]
  488. npicut_below = [np.nextafter(-np.pi, -np.inf)]
  489. for i in range(50):
  490. picut_above.append(np.nextafter(picut_above[-1], np.inf))
  491. picut_below.append(np.nextafter(picut_below[-1], -np.inf))
  492. npicut_above.append(np.nextafter(npicut_above[-1], np.inf))
  493. npicut_below.append(np.nextafter(npicut_below[-1], -np.inf))
  494. y = np.hstack((picut_above, picut_below, npicut_above, npicut_below))
  495. x, y = np.meshgrid(x, y)
  496. z = (x + 1j*y).flatten()
  497. dataset = np.asarray([(z0, complex(_mpmath_wrightomega(z0, 25)))
  498. for z0 in z])
  499. FuncData(sc.wrightomega, dataset, 0, 1, rtol=1e-8).check()
  500. @pytest.mark.slow
  501. @check_version(mpmath, '0.19')
  502. def test_wrightomega_region1():
  503. # This region gets less coverage in the TestSystematic test
  504. x = np.linspace(-2, 1)
  505. y = np.linspace(1, 2*np.pi)
  506. x, y = np.meshgrid(x, y)
  507. z = (x + 1j*y).flatten()
  508. dataset = np.asarray([(z0, complex(_mpmath_wrightomega(z0, 25)))
  509. for z0 in z])
  510. FuncData(sc.wrightomega, dataset, 0, 1, rtol=1e-15).check()
  511. @pytest.mark.slow
  512. @check_version(mpmath, '0.19')
  513. def test_wrightomega_region2():
  514. # This region gets less coverage in the TestSystematic test
  515. x = np.linspace(-2, 1)
  516. y = np.linspace(-2*np.pi, -1)
  517. x, y = np.meshgrid(x, y)
  518. z = (x + 1j*y).flatten()
  519. dataset = np.asarray([(z0, complex(_mpmath_wrightomega(z0, 25)))
  520. for z0 in z])
  521. FuncData(sc.wrightomega, dataset, 0, 1, rtol=1e-15).check()
  522. # ------------------------------------------------------------------------------
  523. # lambertw
  524. # ------------------------------------------------------------------------------
  525. @pytest.mark.slow
  526. @check_version(mpmath, '0.19')
  527. def test_lambertw_smallz():
  528. x, y = np.linspace(-1, 1, 25), np.linspace(-1, 1, 25)
  529. x, y = np.meshgrid(x, y)
  530. z = (x + 1j*y).flatten()
  531. dataset = np.asarray([(z0, complex(mpmath.lambertw(z0)))
  532. for z0 in z])
  533. FuncData(sc.lambertw, dataset, 0, 1, rtol=1e-13).check()
  534. # ------------------------------------------------------------------------------
  535. # Systematic tests
  536. # ------------------------------------------------------------------------------
  537. HYPERKW = dict(maxprec=200, maxterms=200)
  538. @pytest.mark.slow
  539. @check_version(mpmath, '0.17')
  540. class TestSystematic:
  541. def test_airyai(self):
  542. # oscillating function, limit range
  543. assert_mpmath_equal(lambda z: sc.airy(z)[0],
  544. mpmath.airyai,
  545. [Arg(-1e8, 1e8)],
  546. rtol=1e-5)
  547. assert_mpmath_equal(lambda z: sc.airy(z)[0],
  548. mpmath.airyai,
  549. [Arg(-1e3, 1e3)])
  550. def test_airyai_complex(self):
  551. assert_mpmath_equal(lambda z: sc.airy(z)[0],
  552. mpmath.airyai,
  553. [ComplexArg()])
  554. def test_airyai_prime(self):
  555. # oscillating function, limit range
  556. assert_mpmath_equal(lambda z: sc.airy(z)[1], lambda z:
  557. mpmath.airyai(z, derivative=1),
  558. [Arg(-1e8, 1e8)],
  559. rtol=1e-5)
  560. assert_mpmath_equal(lambda z: sc.airy(z)[1], lambda z:
  561. mpmath.airyai(z, derivative=1),
  562. [Arg(-1e3, 1e3)])
  563. def test_airyai_prime_complex(self):
  564. assert_mpmath_equal(lambda z: sc.airy(z)[1], lambda z:
  565. mpmath.airyai(z, derivative=1),
  566. [ComplexArg()])
  567. def test_airybi(self):
  568. # oscillating function, limit range
  569. assert_mpmath_equal(lambda z: sc.airy(z)[2], lambda z:
  570. mpmath.airybi(z),
  571. [Arg(-1e8, 1e8)],
  572. rtol=1e-5)
  573. assert_mpmath_equal(lambda z: sc.airy(z)[2], lambda z:
  574. mpmath.airybi(z),
  575. [Arg(-1e3, 1e3)])
  576. def test_airybi_complex(self):
  577. assert_mpmath_equal(lambda z: sc.airy(z)[2], lambda z:
  578. mpmath.airybi(z),
  579. [ComplexArg()])
  580. def test_airybi_prime(self):
  581. # oscillating function, limit range
  582. assert_mpmath_equal(lambda z: sc.airy(z)[3], lambda z:
  583. mpmath.airybi(z, derivative=1),
  584. [Arg(-1e8, 1e8)],
  585. rtol=1e-5)
  586. assert_mpmath_equal(lambda z: sc.airy(z)[3], lambda z:
  587. mpmath.airybi(z, derivative=1),
  588. [Arg(-1e3, 1e3)])
  589. def test_airybi_prime_complex(self):
  590. assert_mpmath_equal(lambda z: sc.airy(z)[3], lambda z:
  591. mpmath.airybi(z, derivative=1),
  592. [ComplexArg()])
  593. def test_bei(self):
  594. assert_mpmath_equal(sc.bei,
  595. exception_to_nan(lambda z: mpmath.bei(0, z, **HYPERKW)),
  596. [Arg(-1e3, 1e3)])
  597. def test_ber(self):
  598. assert_mpmath_equal(sc.ber,
  599. exception_to_nan(lambda z: mpmath.ber(0, z, **HYPERKW)),
  600. [Arg(-1e3, 1e3)])
  601. def test_bernoulli(self):
  602. assert_mpmath_equal(lambda n: sc.bernoulli(int(n))[int(n)],
  603. lambda n: float(mpmath.bernoulli(int(n))),
  604. [IntArg(0, 13000)],
  605. rtol=1e-9, n=13000)
  606. def test_besseli(self):
  607. assert_mpmath_equal(
  608. sc.iv,
  609. exception_to_nan(lambda v, z: mpmath.besseli(v, z, **HYPERKW)),
  610. [Arg(-1e100, 1e100), Arg()],
  611. atol=1e-270,
  612. )
  613. def test_besseli_complex(self):
  614. assert_mpmath_equal(
  615. lambda v, z: sc.iv(v.real, z),
  616. exception_to_nan(lambda v, z: mpmath.besseli(v, z, **HYPERKW)),
  617. [Arg(-1e100, 1e100), ComplexArg()],
  618. )
  619. def test_besselj(self):
  620. assert_mpmath_equal(
  621. sc.jv,
  622. exception_to_nan(lambda v, z: mpmath.besselj(v, z, **HYPERKW)),
  623. [Arg(-1e100, 1e100), Arg(-1e3, 1e3)],
  624. ignore_inf_sign=True,
  625. )
  626. # loss of precision at large arguments due to oscillation
  627. assert_mpmath_equal(
  628. sc.jv,
  629. exception_to_nan(lambda v, z: mpmath.besselj(v, z, **HYPERKW)),
  630. [Arg(-1e100, 1e100), Arg(-1e8, 1e8)],
  631. ignore_inf_sign=True,
  632. rtol=1e-5,
  633. )
  634. def test_besselj_complex(self):
  635. assert_mpmath_equal(
  636. lambda v, z: sc.jv(v.real, z),
  637. exception_to_nan(lambda v, z: mpmath.besselj(v, z, **HYPERKW)),
  638. [Arg(), ComplexArg()]
  639. )
  640. def test_besselk(self):
  641. assert_mpmath_equal(
  642. sc.kv,
  643. mpmath.besselk,
  644. [Arg(-200, 200), Arg(0, np.inf)],
  645. nan_ok=False,
  646. rtol=1e-12,
  647. )
  648. def test_besselk_int(self):
  649. assert_mpmath_equal(
  650. sc.kn,
  651. mpmath.besselk,
  652. [IntArg(-200, 200), Arg(0, np.inf)],
  653. nan_ok=False,
  654. rtol=1e-12,
  655. )
  656. def test_besselk_complex(self):
  657. assert_mpmath_equal(
  658. lambda v, z: sc.kv(v.real, z),
  659. exception_to_nan(lambda v, z: mpmath.besselk(v, z, **HYPERKW)),
  660. [Arg(-1e100, 1e100), ComplexArg()],
  661. )
  662. def test_bessely(self):
  663. def mpbessely(v, x):
  664. r = float(mpmath.bessely(v, x, **HYPERKW))
  665. if abs(r) > 1e305:
  666. # overflowing to inf a bit earlier is OK
  667. r = np.inf * np.sign(r)
  668. if abs(r) == 0 and x == 0:
  669. # invalid result from mpmath, point x=0 is a divergence
  670. return np.nan
  671. return r
  672. assert_mpmath_equal(
  673. sc.yv,
  674. exception_to_nan(mpbessely),
  675. [Arg(-1e100, 1e100), Arg(-1e8, 1e8)],
  676. n=5000,
  677. )
  678. def test_bessely_complex(self):
  679. def mpbessely(v, x):
  680. r = complex(mpmath.bessely(v, x, **HYPERKW))
  681. if abs(r) > 1e305:
  682. # overflowing to inf a bit earlier is OK
  683. with np.errstate(invalid='ignore'):
  684. r = np.inf * np.sign(r)
  685. return r
  686. assert_mpmath_equal(
  687. lambda v, z: sc.yv(v.real, z),
  688. exception_to_nan(mpbessely),
  689. [Arg(), ComplexArg()],
  690. n=15000,
  691. )
  692. def test_bessely_int(self):
  693. def mpbessely(v, x):
  694. r = float(mpmath.bessely(v, x))
  695. if abs(r) == 0 and x == 0:
  696. # invalid result from mpmath, point x=0 is a divergence
  697. return np.nan
  698. return r
  699. assert_mpmath_equal(
  700. lambda v, z: sc.yn(int(v), z),
  701. exception_to_nan(mpbessely),
  702. [IntArg(-1000, 1000), Arg(-1e8, 1e8)],
  703. )
  704. def test_beta(self):
  705. bad_points = []
  706. def beta(a, b, nonzero=False):
  707. if a < -1e12 or b < -1e12:
  708. # Function is defined here only at integers, but due
  709. # to loss of precision this is numerically
  710. # ill-defined. Don't compare values here.
  711. return np.nan
  712. if (a < 0 or b < 0) and (abs(float(a + b)) % 1) == 0:
  713. # close to a zero of the function: mpmath and scipy
  714. # will not round here the same, so the test needs to be
  715. # run with an absolute tolerance
  716. if nonzero:
  717. bad_points.append((float(a), float(b)))
  718. return np.nan
  719. return mpmath.beta(a, b)
  720. assert_mpmath_equal(
  721. sc.beta,
  722. lambda a, b: beta(a, b, nonzero=True),
  723. [Arg(), Arg()],
  724. dps=400,
  725. ignore_inf_sign=True,
  726. )
  727. assert_mpmath_equal(
  728. sc.beta,
  729. beta,
  730. np.array(bad_points),
  731. dps=400,
  732. ignore_inf_sign=True,
  733. atol=1e-11,
  734. )
  735. def test_betainc(self):
  736. assert_mpmath_equal(
  737. sc.betainc,
  738. time_limited()(
  739. exception_to_nan(
  740. lambda a, b, x: mpmath.betainc(a, b, 0, x, regularized=True)
  741. )
  742. ),
  743. [Arg(), Arg(), Arg()],
  744. )
  745. def test_betaincc(self):
  746. assert_mpmath_equal(
  747. sc.betaincc,
  748. time_limited()(
  749. exception_to_nan(
  750. lambda a, b, x: mpmath.betainc(a, b, x, 1, regularized=True)
  751. )
  752. ),
  753. [Arg(), Arg(), Arg()],
  754. dps=400,
  755. )
  756. def test_binom(self):
  757. bad_points = []
  758. def binomial(n, k, nonzero=False):
  759. if abs(k) > 1e8*(abs(n) + 1):
  760. # The binomial is rapidly oscillating in this region,
  761. # and the function is numerically ill-defined. Don't
  762. # compare values here.
  763. return np.nan
  764. if n < k and abs(float(n-k) - np.round(float(n-k))) < 1e-15:
  765. # close to a zero of the function: mpmath and scipy
  766. # will not round here the same, so the test needs to be
  767. # run with an absolute tolerance
  768. if nonzero:
  769. bad_points.append((float(n), float(k)))
  770. return np.nan
  771. return mpmath.binomial(n, k)
  772. assert_mpmath_equal(
  773. sc.binom,
  774. lambda n, k: binomial(n, k, nonzero=True),
  775. [Arg(), Arg()],
  776. dps=400,
  777. )
  778. assert_mpmath_equal(
  779. sc.binom,
  780. binomial,
  781. np.array(bad_points),
  782. dps=400,
  783. atol=1e-14,
  784. )
  785. def test_chebyt_int(self):
  786. assert_mpmath_equal(
  787. lambda n, x: sc.eval_chebyt(int(n), x),
  788. exception_to_nan(lambda n, x: mpmath.chebyt(n, x, **HYPERKW)),
  789. [IntArg(), Arg()],
  790. dps=50,
  791. )
  792. @pytest.mark.xfail(run=False, reason="some cases in hyp2f1 not fully accurate")
  793. def test_chebyt(self):
  794. assert_mpmath_equal(
  795. sc.eval_chebyt,
  796. lambda n, x: time_limited()(
  797. exception_to_nan(mpmath.chebyt)
  798. )(n, x, **HYPERKW),
  799. [Arg(-101, 101), Arg()],
  800. n=10000,
  801. )
  802. def test_chebyu_int(self):
  803. assert_mpmath_equal(
  804. lambda n, x: sc.eval_chebyu(int(n), x),
  805. exception_to_nan(lambda n, x: mpmath.chebyu(n, x, **HYPERKW)),
  806. [IntArg(), Arg()],
  807. dps=50,
  808. )
  809. @pytest.mark.xfail(run=False, reason="some cases in hyp2f1 not fully accurate")
  810. def test_chebyu(self):
  811. assert_mpmath_equal(
  812. sc.eval_chebyu,
  813. lambda n, x: time_limited()(
  814. exception_to_nan(mpmath.chebyu)
  815. )(n, x, **HYPERKW),
  816. [Arg(-101, 101), Arg()],
  817. )
  818. def test_chi(self):
  819. def chi(x):
  820. return sc.shichi(x)[1]
  821. assert_mpmath_equal(chi, mpmath.chi, [Arg()])
  822. # check asymptotic series cross-over
  823. assert_mpmath_equal(chi, mpmath.chi, [FixedArg([88 - 1e-9, 88, 88 + 1e-9])])
  824. def test_chi_complex(self):
  825. def chi(z):
  826. return sc.shichi(z)[1]
  827. # chi oscillates as Im[z] -> +- inf, so limit range
  828. assert_mpmath_equal(
  829. chi,
  830. mpmath.chi,
  831. [ComplexArg(complex(-np.inf, -1e8), complex(np.inf, 1e8))],
  832. rtol=1e-12,
  833. )
  834. def test_ci(self):
  835. def ci(x):
  836. return sc.sici(x)[1]
  837. # oscillating function: limit range
  838. assert_mpmath_equal(ci, mpmath.ci, [Arg(-1e8, 1e8)])
  839. def test_ci_complex(self):
  840. def ci(z):
  841. return sc.sici(z)[1]
  842. # ci oscillates as Re[z] -> +- inf, so limit range
  843. assert_mpmath_equal(
  844. ci,
  845. mpmath.ci,
  846. [ComplexArg(complex(-1e8, -np.inf), complex(1e8, np.inf))],
  847. rtol=1e-8,
  848. )
  849. def test_cospi(self):
  850. eps = np.finfo(float).eps
  851. assert_mpmath_equal(_cospi, mpmath.cospi, [Arg()], nan_ok=False, rtol=2*eps)
  852. def test_cospi_complex(self):
  853. assert_mpmath_equal(
  854. _cospi,
  855. mpmath.cospi,
  856. [ComplexArg()],
  857. nan_ok=False,
  858. rtol=1e-13,
  859. )
  860. def test_digamma(self):
  861. assert_mpmath_equal(
  862. sc.digamma,
  863. exception_to_nan(mpmath.digamma),
  864. [Arg()],
  865. rtol=1e-12,
  866. dps=50,
  867. )
  868. def test_digamma_complex(self):
  869. # Test on a cut plane because mpmath will hang. See
  870. # test_digamma_negreal for tests on the negative real axis.
  871. def param_filter(z):
  872. return np.where((z.real < 0) & (np.abs(z.imag) < 1.12), False, True)
  873. assert_mpmath_equal(
  874. sc.digamma,
  875. exception_to_nan(mpmath.digamma),
  876. [ComplexArg()],
  877. rtol=1e-13,
  878. dps=40,
  879. param_filter=param_filter
  880. )
  881. def test_e1(self):
  882. assert_mpmath_equal(
  883. sc.exp1,
  884. mpmath.e1,
  885. [Arg()],
  886. rtol=1e-14,
  887. )
  888. def test_e1_complex(self):
  889. # E_1 oscillates as Im[z] -> +- inf, so limit range
  890. assert_mpmath_equal(
  891. sc.exp1,
  892. mpmath.e1,
  893. [ComplexArg(complex(-np.inf, -1e8), complex(np.inf, 1e8))],
  894. rtol=1e-11,
  895. )
  896. # Check cross-over region
  897. assert_mpmath_equal(
  898. sc.exp1,
  899. mpmath.e1,
  900. (np.linspace(-50, 50, 171)[:, None]
  901. + np.r_[0, np.logspace(-3, 2, 61), -np.logspace(-3, 2, 11)]*1j).ravel(),
  902. rtol=1e-11,
  903. )
  904. assert_mpmath_equal(
  905. sc.exp1,
  906. mpmath.e1,
  907. (np.linspace(-50, -35, 10000) + 0j),
  908. rtol=1e-11,
  909. )
  910. def test_exprel(self):
  911. assert_mpmath_equal(
  912. sc.exprel,
  913. lambda x: mpmath.expm1(x)/x if x != 0 else mpmath.mpf('1.0'),
  914. [Arg(a=-np.log(np.finfo(np.float64).max),
  915. b=np.log(np.finfo(np.float64).max))],
  916. )
  917. assert_mpmath_equal(
  918. sc.exprel,
  919. lambda x: mpmath.expm1(x)/x if x != 0 else mpmath.mpf('1.0'),
  920. np.array([1e-12, 1e-24, 0, 1e12, 1e24, np.inf]),
  921. rtol=1e-11,
  922. )
  923. assert_(np.isinf(sc.exprel(np.inf)))
  924. assert_(sc.exprel(-np.inf) == 0)
  925. def test_expm1_complex(self):
  926. # Oscillates as a function of Im[z], so limit range to avoid loss of precision
  927. assert_mpmath_equal(
  928. sc.expm1,
  929. mpmath.expm1,
  930. [ComplexArg(complex(-np.inf, -1e7), complex(np.inf, 1e7))],
  931. )
  932. def test_log1p_complex(self):
  933. assert_mpmath_equal(
  934. sc.log1p,
  935. lambda x: mpmath.log(x+1),
  936. [ComplexArg()],
  937. dps=60,
  938. )
  939. def test_log1pmx(self):
  940. assert_mpmath_equal(
  941. _log1pmx,
  942. lambda x: mpmath.log(x + 1) - x,
  943. [Arg()],
  944. dps=60,
  945. rtol=1e-14,
  946. )
  947. def test_ei(self):
  948. assert_mpmath_equal(sc.expi, mpmath.ei, [Arg()], rtol=1e-11)
  949. def test_ei_complex(self):
  950. # Ei oscillates as Im[z] -> +- inf, so limit range
  951. assert_mpmath_equal(
  952. sc.expi,
  953. mpmath.ei,
  954. [ComplexArg(complex(-np.inf, -1e8), complex(np.inf, 1e8))],
  955. rtol=1e-9,
  956. )
  957. def test_ellipe(self):
  958. assert_mpmath_equal(sc.ellipe, mpmath.ellipe, [Arg(b=1.0)])
  959. def test_ellipeinc(self):
  960. assert_mpmath_equal(sc.ellipeinc, mpmath.ellipe, [Arg(-1e3, 1e3), Arg(b=1.0)])
  961. def test_ellipeinc_largephi(self):
  962. assert_mpmath_equal(sc.ellipeinc, mpmath.ellipe, [Arg(), Arg()])
  963. def test_ellipf(self):
  964. assert_mpmath_equal(sc.ellipkinc, mpmath.ellipf, [Arg(-1e3, 1e3), Arg()])
  965. def test_ellipf_largephi(self):
  966. assert_mpmath_equal(sc.ellipkinc, mpmath.ellipf, [Arg(), Arg()])
  967. def test_ellipk(self):
  968. assert_mpmath_equal(sc.ellipk, mpmath.ellipk, [Arg(b=1.0)])
  969. assert_mpmath_equal(
  970. sc.ellipkm1,
  971. lambda m: mpmath.ellipk(1 - m),
  972. [Arg(a=0.0)],
  973. dps=400,
  974. )
  975. def test_ellipkinc(self):
  976. def ellipkinc(phi, m):
  977. return mpmath.ellippi(0, phi, m)
  978. assert_mpmath_equal(
  979. sc.ellipkinc,
  980. ellipkinc,
  981. [Arg(-1e3, 1e3), Arg(b=1.0)],
  982. ignore_inf_sign=True,
  983. )
  984. def test_ellipkinc_largephi(self):
  985. def ellipkinc(phi, m):
  986. return mpmath.ellippi(0, phi, m)
  987. assert_mpmath_equal(
  988. sc.ellipkinc,
  989. ellipkinc,
  990. [Arg(), Arg(b=1.0)],
  991. ignore_inf_sign=True,
  992. )
  993. def test_ellipfun_sn(self):
  994. def sn(u, m):
  995. # mpmath doesn't get the zero at u = 0--fix that
  996. if u == 0:
  997. return 0
  998. else:
  999. return mpmath.ellipfun("sn", u=u, m=m)
  1000. # Oscillating function --- limit range of first argument; the
  1001. # loss of precision there is an expected numerical feature
  1002. # rather than an actual bug
  1003. assert_mpmath_equal(
  1004. lambda u, m: sc.ellipj(u, m)[0],
  1005. sn,
  1006. [Arg(-1e6, 1e6), Arg(a=0, b=1)],
  1007. rtol=1e-8,
  1008. )
  1009. def test_ellipfun_cn(self):
  1010. # see comment in ellipfun_sn
  1011. assert_mpmath_equal(
  1012. lambda u, m: sc.ellipj(u, m)[1],
  1013. lambda u, m: mpmath.ellipfun("cn", u=u, m=m),
  1014. [Arg(-1e6, 1e6), Arg(a=0, b=1)],
  1015. rtol=1e-8,
  1016. )
  1017. def test_ellipfun_dn(self):
  1018. # see comment in ellipfun_sn
  1019. assert_mpmath_equal(
  1020. lambda u, m: sc.ellipj(u, m)[2],
  1021. lambda u, m: mpmath.ellipfun("dn", u=u, m=m),
  1022. [Arg(-1e6, 1e6), Arg(a=0, b=1)],
  1023. rtol=1e-8,
  1024. )
  1025. def test_erf(self):
  1026. assert_mpmath_equal(sc.erf, lambda z: mpmath.erf(z), [Arg()])
  1027. def test_erf_complex(self):
  1028. assert_mpmath_equal(sc.erf, lambda z: mpmath.erf(z), [ComplexArg()], n=200)
  1029. def test_erfc(self):
  1030. assert_mpmath_equal(
  1031. sc.erfc,
  1032. exception_to_nan(lambda z: mpmath.erfc(z)),
  1033. [Arg()],
  1034. rtol=1e-13,
  1035. )
  1036. def test_erfc_complex(self):
  1037. assert_mpmath_equal(
  1038. sc.erfc,
  1039. exception_to_nan(lambda z: mpmath.erfc(z)),
  1040. [ComplexArg()],
  1041. n=200,
  1042. )
  1043. def test_erfi(self):
  1044. assert_mpmath_equal(sc.erfi, mpmath.erfi, [Arg()], n=200)
  1045. def test_erfi_complex(self):
  1046. assert_mpmath_equal(sc.erfi, mpmath.erfi, [ComplexArg()], n=200)
  1047. def test_ndtr(self):
  1048. assert_mpmath_equal(
  1049. sc.ndtr,
  1050. exception_to_nan(lambda z: mpmath.ncdf(z)),
  1051. [Arg()],
  1052. n=200,
  1053. )
  1054. def test_ndtr_complex(self):
  1055. assert_mpmath_equal(
  1056. sc.ndtr,
  1057. lambda z: mpmath.erfc(-z/np.sqrt(2.))/2.,
  1058. [ComplexArg(a=complex(-10000, -10000), b=complex(10000, 10000))],
  1059. n=400,
  1060. )
  1061. def test_log_ndtr(self):
  1062. assert_mpmath_equal(
  1063. sc.log_ndtr,
  1064. exception_to_nan(lambda z: mpmath.log(mpmath.ncdf(z))),
  1065. [Arg()], n=600, dps=300, rtol=1e-13,
  1066. )
  1067. def test_log_ndtr_complex(self):
  1068. assert_mpmath_equal(
  1069. sc.log_ndtr,
  1070. exception_to_nan(lambda z: mpmath.log(mpmath.erfc(-z/np.sqrt(2.))/2.)),
  1071. [ComplexArg(a=complex(-10000, -100), b=complex(10000, 100))],
  1072. n=200, dps=300,
  1073. )
  1074. def test_eulernum(self):
  1075. assert_mpmath_equal(
  1076. lambda n: sc.euler(n)[-1],
  1077. mpmath.eulernum,
  1078. [IntArg(1, 10000)],
  1079. n=10000,
  1080. )
  1081. def test_expint(self):
  1082. assert_mpmath_equal(
  1083. sc.expn,
  1084. mpmath.expint,
  1085. [IntArg(0, 200), Arg(0, np.inf)],
  1086. rtol=1e-13,
  1087. dps=160,
  1088. )
  1089. def test_fresnels(self):
  1090. def fresnels(x):
  1091. return sc.fresnel(x)[0]
  1092. assert_mpmath_equal(fresnels, mpmath.fresnels, [Arg()])
  1093. def test_fresnelc(self):
  1094. def fresnelc(x):
  1095. return sc.fresnel(x)[1]
  1096. assert_mpmath_equal(fresnelc, mpmath.fresnelc, [Arg()])
  1097. def test_gamma(self):
  1098. assert_mpmath_equal(sc.gamma, exception_to_nan(mpmath.gamma), [Arg()])
  1099. def test_gamma_complex(self):
  1100. assert_mpmath_equal(
  1101. sc.gamma,
  1102. exception_to_nan(mpmath.gamma),
  1103. [ComplexArg()],
  1104. rtol=5e-13,
  1105. )
  1106. def test_gammainc(self):
  1107. # Larger arguments are tested in test_data.py:test_local
  1108. assert_mpmath_equal(
  1109. sc.gammainc,
  1110. lambda z, b: mpmath.gammainc(z, b=b, regularized=True),
  1111. [Arg(0, 1e4, inclusive_a=False), Arg(0, 1e4)],
  1112. nan_ok=False,
  1113. rtol=1e-11,
  1114. )
  1115. def test_gammaincc(self):
  1116. # Larger arguments are tested in test_data.py:test_local
  1117. assert_mpmath_equal(
  1118. sc.gammaincc,
  1119. lambda z, a: mpmath.gammainc(z, a=a, regularized=True),
  1120. [Arg(0, 1e4, inclusive_a=False), Arg(0, 1e4)],
  1121. nan_ok=False,
  1122. rtol=1e-11,
  1123. )
  1124. def test_gammaln(self):
  1125. # The real part of loggamma is log(|gamma(z)|).
  1126. def f(z):
  1127. return mpmath.loggamma(z).real
  1128. assert_mpmath_equal(sc.gammaln, exception_to_nan(f), [Arg()])
  1129. @pytest.mark.xfail(run=False)
  1130. def test_gegenbauer(self):
  1131. assert_mpmath_equal(
  1132. sc.eval_gegenbauer,
  1133. exception_to_nan(mpmath.gegenbauer),
  1134. [Arg(-1e3, 1e3), Arg(), Arg()],
  1135. )
  1136. def test_gegenbauer_int(self):
  1137. # Redefine functions to deal with numerical + mpmath issues
  1138. def gegenbauer(n, a, x):
  1139. # Avoid overflow at large `a` (mpmath would need an even larger
  1140. # dps to handle this correctly, so just skip this region)
  1141. if abs(a) > 1e100:
  1142. return np.nan
  1143. # Deal with n=0, n=1 correctly; mpmath 0.17 doesn't do these
  1144. # always correctly
  1145. if n == 0:
  1146. r = 1.0
  1147. elif n == 1:
  1148. r = 2*a*x
  1149. else:
  1150. r = mpmath.gegenbauer(n, a, x)
  1151. # Mpmath 0.17 gives wrong results (spurious zero) in some cases, so
  1152. # compute the value by perturbing the result
  1153. if float(r) == 0 and a < -1 and float(a) == int(float(a)):
  1154. r = mpmath.gegenbauer(n, a + mpmath.mpf('1e-50'), x)
  1155. if abs(r) < mpmath.mpf('1e-50'):
  1156. r = mpmath.mpf('0.0')
  1157. # Differing overflow thresholds in scipy vs. mpmath
  1158. if abs(r) > 1e270:
  1159. return np.inf
  1160. return r
  1161. def sc_gegenbauer(n, a, x):
  1162. r = sc.eval_gegenbauer(int(n), a, x)
  1163. # Differing overflow thresholds in scipy vs. mpmath
  1164. if abs(r) > 1e270:
  1165. return np.inf
  1166. return r
  1167. assert_mpmath_equal(
  1168. sc_gegenbauer,
  1169. exception_to_nan(gegenbauer),
  1170. [IntArg(0, 100), Arg(-1e9, 1e9), Arg()],
  1171. n=40000, dps=100, ignore_inf_sign=True, rtol=1e-6,
  1172. )
  1173. # Check the small-x expansion
  1174. assert_mpmath_equal(
  1175. sc_gegenbauer,
  1176. exception_to_nan(gegenbauer),
  1177. [IntArg(0, 100), Arg(), FixedArg(np.logspace(-30, -4, 30))],
  1178. dps=100, ignore_inf_sign=True,
  1179. )
  1180. @pytest.mark.xfail(run=False)
  1181. def test_gegenbauer_complex(self):
  1182. assert_mpmath_equal(
  1183. lambda n, a, x: sc.eval_gegenbauer(int(n), a.real, x),
  1184. exception_to_nan(mpmath.gegenbauer),
  1185. [IntArg(0, 100), Arg(), ComplexArg()],
  1186. )
  1187. @nonfunctional_tooslow
  1188. def test_gegenbauer_complex_general(self):
  1189. assert_mpmath_equal(
  1190. lambda n, a, x: sc.eval_gegenbauer(n.real, a.real, x),
  1191. exception_to_nan(mpmath.gegenbauer),
  1192. [Arg(-1e3, 1e3), Arg(), ComplexArg()],
  1193. )
  1194. def test_hankel1(self):
  1195. assert_mpmath_equal(
  1196. sc.hankel1,
  1197. exception_to_nan(lambda v, x: mpmath.hankel1(v, x, **HYPERKW)),
  1198. [Arg(-1e20, 1e20), Arg()],
  1199. )
  1200. def test_hankel2(self):
  1201. assert_mpmath_equal(
  1202. sc.hankel2,
  1203. exception_to_nan(lambda v, x: mpmath.hankel2(v, x, **HYPERKW)),
  1204. [Arg(-1e20, 1e20), Arg()],
  1205. )
  1206. @pytest.mark.xfail(run=False, reason="issues at intermediately large orders")
  1207. def test_hermite(self):
  1208. assert_mpmath_equal(
  1209. lambda n, x: sc.eval_hermite(int(n), x),
  1210. exception_to_nan(mpmath.hermite),
  1211. [IntArg(0, 10000), Arg()],
  1212. )
  1213. # hurwitz: same as zeta
  1214. def test_hyp0f1(self):
  1215. # mpmath reports no convergence unless maxterms is large enough
  1216. KW = dict(maxprec=400, maxterms=1500)
  1217. # n=500 (non-xslow default) fails for one bad point
  1218. assert_mpmath_equal(
  1219. sc.hyp0f1,
  1220. lambda a, x: mpmath.hyp0f1(a, x, **KW),
  1221. [Arg(-1e7, 1e7), Arg(0, 1e5)],
  1222. n=5000,
  1223. )
  1224. # NB: The range of the second parameter ("z") is limited from below
  1225. # because of an overflow in the intermediate calculations. The way
  1226. # for fix it is to implement an asymptotic expansion for Bessel J
  1227. # (similar to what is implemented for Bessel I here).
  1228. def test_hyp0f1_complex(self):
  1229. assert_mpmath_equal(
  1230. lambda a, z: sc.hyp0f1(a.real, z),
  1231. exception_to_nan(lambda a, x: mpmath.hyp0f1(a, x, **HYPERKW)),
  1232. [Arg(-10, 10), ComplexArg(complex(-120, -120), complex(120, 120))],
  1233. )
  1234. # NB: The range of the first parameter ("v") are limited by an overflow
  1235. # in the intermediate calculations. Can be fixed by implementing an
  1236. # asymptotic expansion for Bessel functions for large order.
  1237. def test_hyp1f1(self):
  1238. def mpmath_hyp1f1(a, b, x):
  1239. try:
  1240. return mpmath.hyp1f1(a, b, x)
  1241. except ZeroDivisionError:
  1242. return np.inf
  1243. assert_mpmath_equal(
  1244. sc.hyp1f1,
  1245. mpmath_hyp1f1,
  1246. [Arg(-50, 50), Arg(1, 50, inclusive_a=False), Arg(-50, 50)],
  1247. n=500,
  1248. nan_ok=False,
  1249. )
  1250. @pytest.mark.xfail(run=False)
  1251. def test_hyp1f1_complex(self):
  1252. assert_mpmath_equal(
  1253. inf_to_nan(lambda a, b, x: sc.hyp1f1(a.real, b.real, x)),
  1254. exception_to_nan(lambda a, b, x: mpmath.hyp1f1(a, b, x, **HYPERKW)),
  1255. [Arg(-1e3, 1e3), Arg(-1e3, 1e3), ComplexArg()],
  1256. n=2000,
  1257. )
  1258. @nonfunctional_tooslow
  1259. def test_hyp2f1_complex(self):
  1260. # SciPy's hyp2f1 seems to have performance and accuracy problems
  1261. assert_mpmath_equal(
  1262. lambda a, b, c, x: sc.hyp2f1(a.real, b.real, c.real, x),
  1263. exception_to_nan(lambda a, b, c, x: mpmath.hyp2f1(a, b, c, x, **HYPERKW)),
  1264. [Arg(-1e2, 1e2), Arg(-1e2, 1e2), Arg(-1e2, 1e2), ComplexArg()],
  1265. n=10,
  1266. )
  1267. @pytest.mark.xfail(run=False)
  1268. def test_hyperu(self):
  1269. assert_mpmath_equal(
  1270. sc.hyperu,
  1271. exception_to_nan(lambda a, b, x: mpmath.hyperu(a, b, x, **HYPERKW)),
  1272. [Arg(), Arg(), Arg()],
  1273. )
  1274. @pytest.mark.xfail_on_32bit("mpmath issue gh-342: "
  1275. "unsupported operand mpz, long for pow")
  1276. def test_igam_fac(self):
  1277. def mp_igam_fac(a, x):
  1278. return mpmath.power(x, a)*mpmath.exp(-x)/mpmath.gamma(a)
  1279. assert_mpmath_equal(
  1280. _igam_fac,
  1281. mp_igam_fac,
  1282. [Arg(0, 1e14, inclusive_a=False), Arg(0, 1e14)],
  1283. rtol=1e-10,
  1284. dps=29,
  1285. )
  1286. def test_j0(self):
  1287. # The Bessel function at large arguments is j0(x) ~ cos(x + phi)/sqrt(x)
  1288. # and at large arguments the phase of the cosine loses precision.
  1289. #
  1290. # This is numerically expected behavior, so we compare only up to
  1291. # 1e8 = 1e15 * 1e-7
  1292. assert_mpmath_equal(sc.j0, mpmath.j0, [Arg(-1e3, 1e3)])
  1293. assert_mpmath_equal(sc.j0, mpmath.j0, [Arg(-1e8, 1e8)], rtol=1e-5)
  1294. def test_j1(self):
  1295. # See comment in test_j0
  1296. assert_mpmath_equal(sc.j1, mpmath.j1, [Arg(-1e3, 1e3)])
  1297. assert_mpmath_equal(sc.j1, mpmath.j1, [Arg(-1e8, 1e8)], rtol=1e-5)
  1298. @pytest.mark.xfail(run=False)
  1299. def test_jacobi(self):
  1300. assert_mpmath_equal(
  1301. sc.eval_jacobi,
  1302. exception_to_nan(lambda a, b, c, x: mpmath.jacobi(a, b, c, x, **HYPERKW)),
  1303. [Arg(), Arg(), Arg(), Arg()],
  1304. )
  1305. assert_mpmath_equal(
  1306. lambda n, b, c, x: sc.eval_jacobi(int(n), b, c, x),
  1307. exception_to_nan(lambda a, b, c, x: mpmath.jacobi(a, b, c, x, **HYPERKW)),
  1308. [IntArg(), Arg(), Arg(), Arg()],
  1309. )
  1310. def test_jacobi_int(self):
  1311. # Redefine functions to deal with numerical + mpmath issues
  1312. def jacobi(n, a, b, x):
  1313. # Mpmath does not handle n=0 case always correctly
  1314. if n == 0:
  1315. return 1.0
  1316. return mpmath.jacobi(n, a, b, x)
  1317. assert_mpmath_equal(
  1318. lambda n, a, b, x: sc.eval_jacobi(int(n), a, b, x),
  1319. lambda n, a, b, x: exception_to_nan(jacobi)(n, a, b, x, **HYPERKW),
  1320. [IntArg(), Arg(), Arg(), Arg()],
  1321. n=4095,
  1322. dps=50,
  1323. )
  1324. def test_kei(self):
  1325. def kei(x):
  1326. if x == 0:
  1327. # work around mpmath issue at x=0
  1328. return -pi/4
  1329. return exception_to_nan(mpmath.kei)(0, x, **HYPERKW)
  1330. assert_mpmath_equal(sc.kei, kei, [Arg(-1e30, 1e30)], n=1000)
  1331. def test_ker(self):
  1332. assert_mpmath_equal(
  1333. sc.ker,
  1334. exception_to_nan(lambda x: mpmath.ker(0, x, **HYPERKW)),
  1335. [Arg(-1e30, 1e30)],
  1336. n=1000,
  1337. )
  1338. @nonfunctional_tooslow
  1339. def test_laguerre(self):
  1340. assert_mpmath_equal(
  1341. trace_args(sc.eval_laguerre),
  1342. lambda n, x: exception_to_nan(mpmath.laguerre)(n, x, **HYPERKW),
  1343. [Arg(), Arg()],
  1344. )
  1345. def test_laguerre_int(self):
  1346. assert_mpmath_equal(
  1347. lambda n, x: sc.eval_laguerre(int(n), x),
  1348. lambda n, x: exception_to_nan(mpmath.laguerre)(n, x, **HYPERKW),
  1349. [IntArg(), Arg()],
  1350. n=20000,
  1351. )
  1352. @pytest.mark.xfail_on_32bit("see gh-3551 for bad points")
  1353. def test_lambertw_real(self):
  1354. assert_mpmath_equal(
  1355. lambda x, k: sc.lambertw(x, int(k.real)),
  1356. lambda x, k: mpmath.lambertw(x, int(k.real)),
  1357. [ComplexArg(-np.inf, np.inf), IntArg(0, 10)],
  1358. rtol=1e-13, nan_ok=False,
  1359. )
  1360. def test_lanczos_sum_expg_scaled(self):
  1361. maxgamma = 171.624376956302725
  1362. e = np.exp(1)
  1363. g = 6.024680040776729583740234375
  1364. def gamma(x):
  1365. with np.errstate(over='ignore'):
  1366. fac = ((x + g - 0.5)/e)**(x - 0.5)
  1367. if fac != np.inf:
  1368. res = fac*_lanczos_sum_expg_scaled(x)
  1369. else:
  1370. fac = ((x + g - 0.5)/e)**(0.5*(x - 0.5))
  1371. res = fac*_lanczos_sum_expg_scaled(x)
  1372. res *= fac
  1373. return res
  1374. assert_mpmath_equal(
  1375. gamma,
  1376. mpmath.gamma,
  1377. [Arg(0, maxgamma, inclusive_a=False)],
  1378. rtol=1e-13,
  1379. )
  1380. @nonfunctional_tooslow
  1381. def test_legendre(self):
  1382. assert_mpmath_equal(sc.eval_legendre, mpmath.legendre, [Arg(), Arg()])
  1383. def test_legendre_int(self):
  1384. assert_mpmath_equal(
  1385. lambda n, x: sc.eval_legendre(int(n), x),
  1386. lambda n, x: exception_to_nan(mpmath.legendre)(n, x, **HYPERKW),
  1387. [IntArg(), Arg()],
  1388. n=20000,
  1389. )
  1390. # Check the small-x expansion
  1391. assert_mpmath_equal(
  1392. lambda n, x: sc.eval_legendre(int(n), x),
  1393. lambda n, x: exception_to_nan(mpmath.legendre)(n, x, **HYPERKW),
  1394. [IntArg(), FixedArg(np.logspace(-30, -4, 20))],
  1395. )
  1396. @pytest.mark.xfail(run=False, reason="apparently picks wrong function at |z| > 1")
  1397. def test_legenq(self):
  1398. def lqnm(n, m, z):
  1399. return sc.lqmn(m, n, z)[0][-1,-1]
  1400. def legenq(n, m, z):
  1401. if abs(z) < 1e-15:
  1402. # mpmath has bad performance here
  1403. return np.nan
  1404. return exception_to_nan(mpmath.legenq)(n, m, z, type=2)
  1405. assert_mpmath_equal(
  1406. lqnm,
  1407. legenq,
  1408. [IntArg(0, 100), IntArg(0, 100), Arg()],
  1409. )
  1410. @nonfunctional_tooslow
  1411. def test_legenq_complex(self):
  1412. def lqnm(n, m, z):
  1413. return sc.lqmn(int(m.real), int(n.real), z)[0][-1,-1]
  1414. def legenq(n, m, z):
  1415. if abs(z) < 1e-15:
  1416. # mpmath has bad performance here
  1417. return np.nan
  1418. return exception_to_nan(mpmath.legenq)(int(n.real), int(m.real), z, type=2)
  1419. assert_mpmath_equal(
  1420. lqnm,
  1421. legenq,
  1422. [IntArg(0, 100), IntArg(0, 100), ComplexArg()],
  1423. n=100,
  1424. )
  1425. def test_lgam1p(self):
  1426. def param_filter(x):
  1427. # Filter the poles
  1428. return np.where((np.floor(x) == x) & (x <= 0), False, True)
  1429. def mp_lgam1p(z):
  1430. # The real part of loggamma is log(|gamma(z)|)
  1431. return mpmath.loggamma(1 + z).real
  1432. assert_mpmath_equal(
  1433. _lgam1p,
  1434. mp_lgam1p,
  1435. [Arg()],
  1436. rtol=1e-13,
  1437. dps=100,
  1438. param_filter=param_filter,
  1439. )
  1440. def test_loggamma(self):
  1441. def mpmath_loggamma(z):
  1442. try:
  1443. res = mpmath.loggamma(z)
  1444. except ValueError:
  1445. res = complex(np.nan, np.nan)
  1446. return res
  1447. assert_mpmath_equal(
  1448. sc.loggamma,
  1449. mpmath_loggamma,
  1450. [ComplexArg()],
  1451. nan_ok=False,
  1452. distinguish_nan_and_inf=False,
  1453. rtol=5e-14,
  1454. )
  1455. @pytest.mark.xfail(run=False)
  1456. def test_pcfd(self):
  1457. def pcfd(v, x):
  1458. return sc.pbdv(v, x)[0]
  1459. assert_mpmath_equal(
  1460. pcfd,
  1461. exception_to_nan(lambda v, x: mpmath.pcfd(v, x, **HYPERKW)),
  1462. [Arg(), Arg()],
  1463. )
  1464. @pytest.mark.xfail(run=False, reason="it's not the same as the mpmath function --- "
  1465. "maybe different definition?")
  1466. def test_pcfv(self):
  1467. def pcfv(v, x):
  1468. return sc.pbvv(v, x)[0]
  1469. assert_mpmath_equal(
  1470. pcfv,
  1471. lambda v, x: time_limited()(exception_to_nan(mpmath.pcfv))(v, x, **HYPERKW),
  1472. [Arg(), Arg()],
  1473. n=1000,
  1474. )
  1475. def test_pcfw(self):
  1476. def pcfw(a, x):
  1477. return sc.pbwa(a, x)[0]
  1478. def dpcfw(a, x):
  1479. return sc.pbwa(a, x)[1]
  1480. def mpmath_dpcfw(a, x):
  1481. return mpmath.diff(mpmath.pcfw, (a, x), (0, 1))
  1482. # The Zhang and Jin implementation only uses Taylor series and
  1483. # is thus accurate in only a very small range.
  1484. assert_mpmath_equal(
  1485. pcfw,
  1486. mpmath.pcfw,
  1487. [Arg(-5, 5), Arg(-5, 5)],
  1488. rtol=2e-8,
  1489. n=100,
  1490. )
  1491. assert_mpmath_equal(
  1492. dpcfw,
  1493. mpmath_dpcfw,
  1494. [Arg(-5, 5), Arg(-5, 5)],
  1495. rtol=2e-9,
  1496. n=100,
  1497. )
  1498. @pytest.mark.xfail(run=False,
  1499. reason="issues at large arguments (atol OK, rtol not) "
  1500. "and <eps-close to z=0")
  1501. def test_polygamma(self):
  1502. assert_mpmath_equal(
  1503. sc.polygamma,
  1504. time_limited()(exception_to_nan(mpmath.polygamma)),
  1505. [IntArg(0, 1000), Arg()],
  1506. )
  1507. def test_rgamma(self):
  1508. assert_mpmath_equal(
  1509. sc.rgamma,
  1510. mpmath.rgamma,
  1511. [Arg(-8000, np.inf)],
  1512. n=5000,
  1513. nan_ok=False,
  1514. ignore_inf_sign=True,
  1515. )
  1516. def test_rgamma_complex(self):
  1517. assert_mpmath_equal(
  1518. sc.rgamma,
  1519. exception_to_nan(mpmath.rgamma),
  1520. [ComplexArg()],
  1521. rtol=5e-13,
  1522. )
  1523. @pytest.mark.xfail(reason=("see gh-3551 for bad points on 32 bit "
  1524. "systems and gh-8095 for another bad "
  1525. "point"))
  1526. def test_rf(self):
  1527. if _pep440.parse(mpmath.__version__) >= _pep440.Version("1.0.0"):
  1528. # no workarounds needed
  1529. mppoch = mpmath.rf
  1530. else:
  1531. def mppoch(a, m):
  1532. # deal with cases where the result in double precision
  1533. # hits exactly a non-positive integer, but the
  1534. # corresponding extended-precision mpf floats don't
  1535. if float(a + m) == int(a + m) and float(a + m) <= 0:
  1536. a = mpmath.mpf(a)
  1537. m = int(a + m) - a
  1538. return mpmath.rf(a, m)
  1539. assert_mpmath_equal(sc.poch, mppoch, [Arg(), Arg()], dps=400)
  1540. def test_sinpi(self):
  1541. eps = np.finfo(float).eps
  1542. assert_mpmath_equal(
  1543. _sinpi,
  1544. mpmath.sinpi,
  1545. [Arg()],
  1546. nan_ok=False,
  1547. rtol=2*eps,
  1548. )
  1549. def test_sinpi_complex(self):
  1550. assert_mpmath_equal(
  1551. _sinpi,
  1552. mpmath.sinpi,
  1553. [ComplexArg()],
  1554. nan_ok=False,
  1555. rtol=2e-14,
  1556. )
  1557. def test_shi(self):
  1558. def shi(x):
  1559. return sc.shichi(x)[0]
  1560. assert_mpmath_equal(shi, mpmath.shi, [Arg()])
  1561. # check asymptotic series cross-over
  1562. assert_mpmath_equal(shi, mpmath.shi, [FixedArg([88 - 1e-9, 88, 88 + 1e-9])])
  1563. def test_shi_complex(self):
  1564. def shi(z):
  1565. return sc.shichi(z)[0]
  1566. # shi oscillates as Im[z] -> +- inf, so limit range
  1567. assert_mpmath_equal(
  1568. shi,
  1569. mpmath.shi,
  1570. [ComplexArg(complex(-np.inf, -1e8), complex(np.inf, 1e8))],
  1571. rtol=1e-12,
  1572. )
  1573. def test_si(self):
  1574. def si(x):
  1575. return sc.sici(x)[0]
  1576. assert_mpmath_equal(si, mpmath.si, [Arg()])
  1577. def test_si_complex(self):
  1578. def si(z):
  1579. return sc.sici(z)[0]
  1580. # si oscillates as Re[z] -> +- inf, so limit range
  1581. assert_mpmath_equal(
  1582. si,
  1583. mpmath.si,
  1584. [ComplexArg(complex(-1e8, -np.inf), complex(1e8, np.inf))],
  1585. rtol=1e-12,
  1586. )
  1587. def test_spence(self):
  1588. # mpmath uses a different convention for the dilogarithm
  1589. def dilog(x):
  1590. return mpmath.polylog(2, 1 - x)
  1591. # Spence has a branch cut on the negative real axis
  1592. assert_mpmath_equal(
  1593. sc.spence,
  1594. exception_to_nan(dilog),
  1595. [Arg(0, np.inf)],
  1596. rtol=1e-14,
  1597. )
  1598. def test_spence_complex(self):
  1599. def dilog(z):
  1600. return mpmath.polylog(2, 1 - z)
  1601. assert_mpmath_equal(
  1602. sc.spence,
  1603. exception_to_nan(dilog),
  1604. [ComplexArg()],
  1605. rtol=1e-14,
  1606. )
  1607. def test_struveh(self):
  1608. assert_mpmath_equal(
  1609. sc.struve,
  1610. exception_to_nan(mpmath.struveh),
  1611. [Arg(-1e4, 1e4), Arg(0, 1e4)],
  1612. rtol=5e-10,
  1613. )
  1614. def test_struvel(self):
  1615. def mp_struvel(v, z):
  1616. if v < 0 and z < -v and abs(v) > 1000:
  1617. # larger DPS needed for correct results
  1618. old_dps = mpmath.mp.dps
  1619. try:
  1620. mpmath.mp.dps = 500
  1621. return mpmath.struvel(v, z)
  1622. finally:
  1623. mpmath.mp.dps = old_dps
  1624. return mpmath.struvel(v, z)
  1625. assert_mpmath_equal(
  1626. sc.modstruve,
  1627. exception_to_nan(mp_struvel),
  1628. [Arg(-1e4, 1e4), Arg(0, 1e4)],
  1629. rtol=5e-10,
  1630. ignore_inf_sign=True,
  1631. )
  1632. def test_wrightomega_real(self):
  1633. def mpmath_wrightomega_real(x):
  1634. return mpmath.lambertw(mpmath.exp(x), mpmath.mpf('-0.5'))
  1635. # For x < -1000 the Wright Omega function is just 0 to double
  1636. # precision, and for x > 1e21 it is just x to double
  1637. # precision.
  1638. assert_mpmath_equal(
  1639. sc.wrightomega,
  1640. mpmath_wrightomega_real,
  1641. [Arg(-1000, 1e21)],
  1642. rtol=5e-15,
  1643. atol=0,
  1644. nan_ok=False,
  1645. )
  1646. def test_wrightomega(self):
  1647. assert_mpmath_equal(
  1648. sc.wrightomega,
  1649. lambda z: _mpmath_wrightomega(z, 25),
  1650. [ComplexArg()],
  1651. rtol=1e-14,
  1652. nan_ok=False,
  1653. )
  1654. def test_hurwitz_zeta(self):
  1655. assert_mpmath_equal(
  1656. sc.zeta,
  1657. exception_to_nan(mpmath.zeta),
  1658. [Arg(a=1, b=1e10, inclusive_a=False), Arg(a=0, inclusive_a=False)],
  1659. )
  1660. def test_riemann_zeta(self):
  1661. assert_mpmath_equal(
  1662. sc.zeta,
  1663. lambda x: mpmath.zeta(x) if x != 1 else mpmath.inf,
  1664. [Arg(-100, 100)],
  1665. nan_ok=False,
  1666. rtol=5e-13,
  1667. )
  1668. def test_zetac(self):
  1669. assert_mpmath_equal(
  1670. sc.zetac,
  1671. lambda x: mpmath.zeta(x) - 1 if x != 1 else mpmath.inf,
  1672. [Arg(-100, 100)],
  1673. nan_ok=False,
  1674. dps=45,
  1675. rtol=5e-13,
  1676. )
  1677. def test_boxcox(self):
  1678. def mp_boxcox(x, lmbda):
  1679. x = mpmath.mp.mpf(x)
  1680. lmbda = mpmath.mp.mpf(lmbda)
  1681. if lmbda == 0:
  1682. return mpmath.mp.log(x)
  1683. else:
  1684. return mpmath.mp.powm1(x, lmbda) / lmbda
  1685. assert_mpmath_equal(
  1686. sc.boxcox,
  1687. exception_to_nan(mp_boxcox),
  1688. [Arg(a=0, inclusive_a=False), Arg()],
  1689. n=200,
  1690. dps=60,
  1691. rtol=1e-13,
  1692. )
  1693. def test_boxcox1p(self):
  1694. def mp_boxcox1p(x, lmbda):
  1695. x = mpmath.mp.mpf(x)
  1696. lmbda = mpmath.mp.mpf(lmbda)
  1697. one = mpmath.mp.mpf(1)
  1698. if lmbda == 0:
  1699. return mpmath.mp.log(one + x)
  1700. else:
  1701. return mpmath.mp.powm1(one + x, lmbda) / lmbda
  1702. assert_mpmath_equal(
  1703. sc.boxcox1p,
  1704. exception_to_nan(mp_boxcox1p),
  1705. [Arg(a=-1, inclusive_a=False), Arg()],
  1706. n=200,
  1707. dps=60,
  1708. rtol=1e-13,
  1709. )
  1710. def test_spherical_jn(self):
  1711. def mp_spherical_jn(n, z):
  1712. arg = mpmath.mpmathify(z)
  1713. out = (mpmath.besselj(n + mpmath.mpf(1)/2, arg) /
  1714. mpmath.sqrt(2*arg/mpmath.pi))
  1715. if arg.imag == 0:
  1716. return out.real
  1717. else:
  1718. return out
  1719. assert_mpmath_equal(
  1720. lambda n, z: sc.spherical_jn(int(n), z),
  1721. exception_to_nan(mp_spherical_jn),
  1722. [IntArg(0, 200), Arg(-1e8, 1e8)],
  1723. dps=300,
  1724. # underflow of `spherical_jn` is a bit premature; see gh-21629
  1725. param_filter=(None, lambda z: np.abs(z) > 1e-20),
  1726. )
  1727. def test_spherical_jn_complex(self):
  1728. def mp_spherical_jn(n, z):
  1729. arg = mpmath.mpmathify(z)
  1730. out = (mpmath.besselj(n + mpmath.mpf(1)/2, arg) /
  1731. mpmath.sqrt(2*arg/mpmath.pi))
  1732. if arg.imag == 0:
  1733. return out.real
  1734. else:
  1735. return out
  1736. assert_mpmath_equal(
  1737. lambda n, z: sc.spherical_jn(int(n.real), z),
  1738. exception_to_nan(mp_spherical_jn),
  1739. [IntArg(0, 200), ComplexArg()]
  1740. )
  1741. def test_spherical_yn(self):
  1742. def mp_spherical_yn(n, z):
  1743. arg = mpmath.mpmathify(z)
  1744. out = (mpmath.bessely(n + mpmath.mpf(1)/2, arg) /
  1745. mpmath.sqrt(2*arg/mpmath.pi))
  1746. if arg.imag == 0:
  1747. return out.real
  1748. else:
  1749. return out
  1750. assert_mpmath_equal(
  1751. lambda n, z: sc.spherical_yn(int(n), z),
  1752. exception_to_nan(mp_spherical_yn),
  1753. [IntArg(0, 200), Arg(-1e10, 1e10)],
  1754. dps=100,
  1755. )
  1756. def test_spherical_yn_complex(self):
  1757. def mp_spherical_yn(n, z):
  1758. arg = mpmath.mpmathify(z)
  1759. out = (mpmath.bessely(n + mpmath.mpf(1)/2, arg) /
  1760. mpmath.sqrt(2*arg/mpmath.pi))
  1761. if arg.imag == 0:
  1762. return out.real
  1763. else:
  1764. return out
  1765. assert_mpmath_equal(
  1766. lambda n, z: sc.spherical_yn(int(n.real), z),
  1767. exception_to_nan(mp_spherical_yn),
  1768. [IntArg(0, 200), ComplexArg()],
  1769. )
  1770. def test_spherical_in(self):
  1771. def mp_spherical_in(n, z):
  1772. arg = mpmath.mpmathify(z)
  1773. out = (mpmath.besseli(n + mpmath.mpf(1)/2, arg) /
  1774. mpmath.sqrt(2*arg/mpmath.pi))
  1775. if arg.imag == 0:
  1776. return out.real
  1777. else:
  1778. return out
  1779. assert_mpmath_equal(
  1780. lambda n, z: sc.spherical_in(int(n), z),
  1781. exception_to_nan(mp_spherical_in),
  1782. [IntArg(0, 200), Arg()],
  1783. dps=200,
  1784. atol=10**(-278),
  1785. )
  1786. def test_spherical_in_complex(self):
  1787. def mp_spherical_in(n, z):
  1788. arg = mpmath.mpmathify(z)
  1789. out = (mpmath.besseli(n + mpmath.mpf(1)/2, arg) /
  1790. mpmath.sqrt(2*arg/mpmath.pi))
  1791. if arg.imag == 0:
  1792. return out.real
  1793. else:
  1794. return out
  1795. assert_mpmath_equal(
  1796. lambda n, z: sc.spherical_in(int(n.real), z),
  1797. exception_to_nan(mp_spherical_in),
  1798. [IntArg(0, 200), ComplexArg()],
  1799. )
  1800. def test_spherical_kn(self):
  1801. def mp_spherical_kn(n, z):
  1802. arg = mpmath.mpmathify(z)
  1803. out = (mpmath.besselk(n + mpmath.mpf(1)/2, arg) /
  1804. mpmath.sqrt(2*arg/mpmath.pi))
  1805. if mpmath.mpmathify(z).imag == 0:
  1806. return out.real
  1807. else:
  1808. return out
  1809. assert_mpmath_equal(
  1810. lambda n, z: sc.spherical_kn(int(n), z),
  1811. exception_to_nan(mp_spherical_kn),
  1812. [IntArg(0, 150), Arg()],
  1813. dps=100,
  1814. )
  1815. @pytest.mark.xfail(run=False,
  1816. reason="Accuracy issues near z = -1 inherited from kv.")
  1817. def test_spherical_kn_complex(self):
  1818. def mp_spherical_kn(n, z):
  1819. arg = mpmath.mpmathify(z)
  1820. out = (mpmath.besselk(n + mpmath.mpf(1)/2, arg) /
  1821. mpmath.sqrt(2*arg/mpmath.pi))
  1822. if arg.imag == 0:
  1823. return out.real
  1824. else:
  1825. return out
  1826. assert_mpmath_equal(
  1827. lambda n, z: sc.spherical_kn(int(n.real), z),
  1828. exception_to_nan(mp_spherical_kn),
  1829. [IntArg(0, 200), ComplexArg()],
  1830. dps=200,
  1831. )