forked from scipy/scipy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
_hypergeometric.pxd
136 lines (117 loc) · 3.82 KB
/
_hypergeometric.pxd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
from libc.math cimport fabs, exp, floor, M_PI
import cython
from . cimport sf_error
from ._cephes cimport expm1, poch
cdef extern from "numpy/npy_math.h":
double npy_isnan(double x) nogil
double NPY_NAN
double NPY_INFINITY
cdef extern from 'specfun_wrappers.h':
double hypU_wrap(double, double, double) nogil
double hyp1f1_wrap(double, double, double) nogil
DEF EPS = 2.220446049250313e-16
DEF ACCEPTABLE_RTOL = 1e-7
@cython.cdivision(True)
cdef inline double hyperu(double a, double b, double x) nogil:
if npy_isnan(a) or npy_isnan(b) or npy_isnan(x):
return NPY_NAN
if x < 0.0:
sf_error.error("hyperu", sf_error.DOMAIN, NULL)
return NPY_NAN
if x == 0.0:
if b > 1.0:
# DMLF 13.2.16-18
sf_error.error("hyperu", sf_error.SINGULAR, NULL)
return NPY_INFINITY
else:
# DLMF 13.2.14-15 and 13.2.19-21
return poch(1.0 - b + a, -a)
return hypU_wrap(a, b, x)
@cython.cdivision(True)
cdef inline double hyp1f1(double a, double b, double x) nogil:
if npy_isnan(a) or npy_isnan(b) or npy_isnan(x):
return NPY_NAN
if b <= 0 and b == floor(b):
# There is potentially a pole.
if b <= a < 0 and a == floor(a):
# The Pochammer symbol (a)_n cancels the pole.
return hyp1f1_series_track_convergence(a, b, x)
return NPY_INFINITY
elif a == 0 or x == 0:
return 1
elif a == -1:
return 1 - x / b
elif a == b:
return exp(x)
elif a - b == 1:
return (1 + x / b) * exp(x)
elif a == 1 and b == 2:
return expm1(x) / x
elif a <= 0 and a == floor(a):
# The geometric series is finite in this case, but it could
# still suffer from cancellation.
return hyp1f1_series_track_convergence(a, b, x)
if b > 0 and (fabs(a) + 1) * fabs(x) < 0.9 * b:
# For the kth term of the series we are multiplying by
#
# t_k = (a + k) * x / ((b + k) * (k + 1))
#
# We have that
#
# |t_k| < (|a| + 1) * |x| / |b|,
#
# which means that in this branch we get geometric
# convergence.
return hyp1f1_series(a, b, x)
return hyp1f1_wrap(a, b, x)
@cython.cdivision(True)
cdef inline double hyp1f1_series_track_convergence(
double a,
double b,
double x
) nogil:
# The hypergeometric series can suffer from cancelation or take a
# prohibitive number of terms to converge. This function computes
# the series while monitoring those conditions.
cdef int k
cdef double apk, bpk
cdef double term = 1
cdef double result = 1
cdef double abssum = result
for k in range(1000):
apk = a + k
bpk = b + k
if bpk != 0:
term *= apk * x / bpk / (k + 1)
elif apk == 0:
# The Pochammer symbol in the denominator has become zero,
# but we still have the continuation formula DLMF 13.2.5.
term = 0
else:
# We hit a pole
return NPY_NAN
abssum += fabs(term)
result += term
if fabs(term) <= EPS * fabs(result):
break
else:
sf_error.error("hyp1f1", sf_error.NO_RESULT, NULL)
return NPY_NAN
if k * EPS * abssum <= ACCEPTABLE_RTOL * fabs(result):
return result
sf_error.error("hyp1f1", sf_error.NO_RESULT, NULL)
return NPY_NAN
@cython.cdivision(True)
cdef inline double hyp1f1_series(double a, double b, double x) nogil:
cdef int k
cdef double term = 1
cdef double result = 1
for k in range(500):
term *= (a + k) * x / (b + k) / (k + 1)
result += term
if fabs(term) <= EPS * fabs(result):
break
else:
sf_error.error("hyp1f1", sf_error.NO_RESULT, NULL)
result = NPY_NAN
return result