distribution 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data.tar.gz.sig +0 -0
- data/.gemtest +0 -0
- data/History.txt +13 -1
- data/Manifest.txt +18 -0
- data/README.txt +3 -1
- data/Rakefile +5 -0
- data/lib/distribution.rb +6 -3
- data/lib/distribution/beta.rb +34 -0
- data/lib/distribution/beta/gsl.rb +24 -0
- data/lib/distribution/beta/java.rb +9 -0
- data/lib/distribution/beta/ruby.rb +42 -0
- data/lib/distribution/binomial.rb +1 -1
- data/lib/distribution/binomial/ruby.rb +3 -1
- data/lib/distribution/f.rb +1 -1
- data/lib/distribution/gamma.rb +37 -0
- data/lib/distribution/gamma/gsl.rb +24 -0
- data/lib/distribution/gamma/java.rb +9 -0
- data/lib/distribution/gamma/ruby.rb +53 -0
- data/lib/distribution/math_extension.rb +59 -13
- data/lib/distribution/math_extension/chebyshev_series.rb +411 -0
- data/lib/distribution/math_extension/erfc.rb +79 -0
- data/lib/distribution/math_extension/exponential_integral.rb +63 -0
- data/lib/distribution/math_extension/gammastar.rb +64 -0
- data/lib/distribution/math_extension/gsl_utilities.rb +31 -0
- data/lib/distribution/math_extension/incomplete_beta.rb +213 -0
- data/lib/distribution/math_extension/incomplete_gamma.rb +424 -0
- data/lib/distribution/math_extension/log_utilities.rb +80 -0
- data/lib/distribution/t/ruby.rb +2 -1
- data/spec/beta_spec.rb +82 -0
- data/spec/binomial_spec.rb +1 -1
- data/spec/gamma_spec.rb +85 -0
- data/spec/logistic_spec.rb +3 -3
- data/spec/math_extension_spec.rb +188 -13
- data/spec/t_spec.rb +13 -18
- metadata +24 -5
- metadata.gz.sig +0 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
module Distribution
|
2
|
+
module MathExtension
|
3
|
+
# From GSL-1.9.
|
4
|
+
module ExponentialIntegral
|
5
|
+
class << self
|
6
|
+
def first_order x, scale = 0, with_error = false
|
7
|
+
xmaxt = -Math::LOG_FLOAT_MIN
|
8
|
+
xmax = xmaxt - Math.log(xmaxt)
|
9
|
+
result = nil
|
10
|
+
error = with_error ? nil : 0.0
|
11
|
+
|
12
|
+
if x < -xmax && !scale
|
13
|
+
raise("Overflow Error")
|
14
|
+
elsif x <= -10.0
|
15
|
+
s = 1.0 / x * ( scale ? 1.0 : Math.exp(-x))
|
16
|
+
result_c = ChebyshevSeries.eval(20.0/x+1.0, :ae11, with_error)
|
17
|
+
result_c, result_c_err = result_c if with_error
|
18
|
+
result = s * (1.0 + result_c)
|
19
|
+
error ||= (s * result_c_err) + 2.0*Float::EPSILON * (x.abs + 1.0) * result.abs
|
20
|
+
elsif x <= -4.0
|
21
|
+
s = 1.0 / x * (scale ? 1.0 : Math.exp(-x))
|
22
|
+
result_c = ChebyshevSeries.eval((40.0/x+7.0)/3.0, :ae12, with_error)
|
23
|
+
result_c, result_c_err = result_c if with_error
|
24
|
+
result = s * (1.0 + result_c)
|
25
|
+
error ||= (s * result_c_err) + 2.0*Float::EPSILON * result.abs
|
26
|
+
elsif x <= -1.0
|
27
|
+
ln_term = - Math.log(x.abs)
|
28
|
+
scale_factor = scale ? Math.exp(x) : 1.0
|
29
|
+
result_c = ChebyshevSeries.eval((2.0*x+5.0)/3.0, :e11, with_error)
|
30
|
+
result_c, result_c_err = result_c if with_error
|
31
|
+
result = scale_factor * (ln_term + result_c)
|
32
|
+
error ||= scale_factor * (result_c_err + Float::EPSILON * ln_term.abs) + 2.0*Float::EPSILON*result.abs
|
33
|
+
elsif x == 0.0
|
34
|
+
raise(ArgumentError, "Domain Error")
|
35
|
+
elsif x <= 1.0
|
36
|
+
ln_term = - Math.log(x.abs)
|
37
|
+
scale_factor = scale ? Math.exp(x) : 1.0
|
38
|
+
result_c = ChebyshevSeries.eval(x, :e12, with_error)
|
39
|
+
result_c, result_c_err = result_c if with_error
|
40
|
+
result = scale_factor * (ln_term - 0.6875 + x + result_c)
|
41
|
+
error ||= scale_factor * (result_c_err + Float::EPSILON * ln_term.abs) + 2.0*Float::EPSILON*result.abs
|
42
|
+
elsif x <= 4.0
|
43
|
+
s = 1.0 / x * (scale ? 1.0 : Math.exp(-x))
|
44
|
+
result_c = ChebyshevSeries.eval((8.0/x-5.0)/3.0, :ae13, with_error)
|
45
|
+
result_c, result_c_err = result_c if with_error
|
46
|
+
result = s * (1.0 + result_c)
|
47
|
+
error ||= (s * result_c_err) + 2.0*Float::EPSILON * result.abs
|
48
|
+
elsif x <= xmax || scale
|
49
|
+
s = 1.0 / x * (scale ? 1.0 : Math.exp(-x))
|
50
|
+
result_c = ChebyshevSeries.eval(8.0/x-1.0, :ae14, with_error)
|
51
|
+
result_c, result_c_err = result_c if with_error
|
52
|
+
result = s * (1.0 + result_c)
|
53
|
+
error ||= s * (Float::EPSILON + result_c_err) + 2.0*(x+1.0)*Float::EPSILON * result.abs
|
54
|
+
raise("Underflow Error") if result == 0.0
|
55
|
+
else
|
56
|
+
raise("Underflow Error")
|
57
|
+
end
|
58
|
+
with_error ? [result, error] : result
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Added by John O. Woods, SciRuby project.
|
2
|
+
# Derived from GSL-1.9 source files in the specfunc/ dir.
|
3
|
+
|
4
|
+
module Distribution
|
5
|
+
module MathExtension
|
6
|
+
# Derived from GSL-1.9.
|
7
|
+
module Gammastar
|
8
|
+
C0 = 1.quo(12)
|
9
|
+
C1 = -1.quo(360)
|
10
|
+
C2 = 1.quo(1260)
|
11
|
+
C3 = -1.quo(1680)
|
12
|
+
C4 = 1.quo(1188)
|
13
|
+
C5 = -691.quo(360360)
|
14
|
+
C6 = 1.quo(156)
|
15
|
+
C7 = -3617.quo(122400)
|
16
|
+
|
17
|
+
class << self
|
18
|
+
|
19
|
+
def series x, with_error = false
|
20
|
+
# Use the Stirling series for the correction to Log(Gamma(x)),
|
21
|
+
# which is better behaved and easier to compute than the
|
22
|
+
# regular Stirling series for Gamma(x).
|
23
|
+
y = 1.quo(x*x)
|
24
|
+
ser = C0 + y*(C1 + y*(C2 + y*(C3 + y*(C4 + y*(C5 + y*(C6 + y*C7))))))
|
25
|
+
result = Math.exp(ser/x)
|
26
|
+
with_error ? [result, 2.0 * Float::EPSILON * result * [1, ser/x].max] : result
|
27
|
+
end
|
28
|
+
|
29
|
+
def evaluate x, with_error = false
|
30
|
+
raise(ArgumentError, "x must be positive") if x <= 0
|
31
|
+
if x < 0.5
|
32
|
+
STDERR.puts("Warning: Don't know error on lg_x, error for this function will be incorrect") if with_error
|
33
|
+
lg = Math.lgamma(x).first
|
34
|
+
lg_err = Float::EPSILON # Guess
|
35
|
+
lx = Math.log(x)
|
36
|
+
c = 0.5 * (LN2 + LNPI)
|
37
|
+
lnr_val = lg - (x-0.5)*lx + x - c
|
38
|
+
lnr_err = lg_err + 2.0*Float::EPSILON * ((x+0.5)*lx.abs + c)
|
39
|
+
with_error ? exp_err(lnr_val, lnr_err) : Math.exp(lnr_val)
|
40
|
+
elsif x < 2.0
|
41
|
+
t = 4.0/3.0*(x-0.5) - 1.0
|
42
|
+
ChebyshevSeries.evaluate(:gstar_a, t, with_error)
|
43
|
+
elsif x < 10.0
|
44
|
+
t = 0.25*(x-2.0) - 1.0
|
45
|
+
c = ChebyshevSeries.evaluate(:gstar_b, t, with_error)
|
46
|
+
c, c_err = c if with_error
|
47
|
+
|
48
|
+
result = c / (x*x) + 1.0 + 1.0/(12.0*x)
|
49
|
+
with_error ? [result, c_err / (x*x) + 2.0*Float::EPSILON*result.abs] : result
|
50
|
+
elsif x < 1.0/Math::ROOT4_FLOAT_EPSILON
|
51
|
+
series x, with_error
|
52
|
+
elsif x < 1.0 / Float::EPSILON # Stirling
|
53
|
+
xi = 1.0 / x
|
54
|
+
result = 1.0 + xi/12.0*(1.0 + xi/24.0*(1.0 - xi*(139.0/180.0 + 571.0/8640.0*xi)))
|
55
|
+
result_err = 2.0 * Float::EPSILON * result.abs
|
56
|
+
with_error ? [result,result_err] : result
|
57
|
+
else
|
58
|
+
with_error ? [1.0,1.0/x] : 1.0
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# Added by John O. Woods, SciRuby project.
|
2
|
+
# Derived from GSL-1.9 source files, mostly in the specfunc/ dir.
|
3
|
+
|
4
|
+
module Distribution
|
5
|
+
module MathExtension
|
6
|
+
LNPI = 1.14472988584940017414342735135
|
7
|
+
LN2 = 0.69314718055994530941723212146
|
8
|
+
SQRT2 = 1.41421356237309504880168872421
|
9
|
+
SQRTPI = 1.77245385090551602729816748334
|
10
|
+
|
11
|
+
ROOT3_FLOAT_MIN = Float::MIN ** (1/3.0)
|
12
|
+
ROOT3_FLOAT_EPSILON = Float::EPSILON ** (1/3.0)
|
13
|
+
ROOT4_FLOAT_MIN = Float::MIN ** (1/4.0)
|
14
|
+
ROOT4_FLOAT_EPSILON = Float::EPSILON ** (1/4.0)
|
15
|
+
ROOT5_FLOAT_MIN = Float::MIN ** (1/5.0)
|
16
|
+
ROOT5_FLOAT_EPSILON = Float::EPSILON ** (1/5.0)
|
17
|
+
ROOT6_FLOAT_MIN = Float::MIN ** (1/6.0)
|
18
|
+
ROOT6_FLOAT_EPSILON = Float::EPSILON ** (1/6.0)
|
19
|
+
LOG_FLOAT_MIN = Math.log(Float::MIN)
|
20
|
+
EULER = 0.57721566490153286060651209008
|
21
|
+
|
22
|
+
# e^x taking into account the error thus far (I think)
|
23
|
+
# gsl_sf_exp_err_e
|
24
|
+
def exp_err(x, dx)
|
25
|
+
adx = dx.abs
|
26
|
+
raise("Overflow Error in exp_err: x + adx > LOG_FLOAT_MAX") if x + adx > LOG_FLOAT_MAX
|
27
|
+
raise("Underflow Error in exp_err: x - adx < LOG_FLOAT_MIN") if x - adx < LOG_FLOAT_MIN
|
28
|
+
[Math.exp(x), Math.exp(x) * [Float::EPSILON, Math.exp(adx) - 1.0/Math.exp(adx)] + 2.0 * Float::EPSILON * Math.exp(x).abs]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
# Added by John O. Woods, SciRuby project.
|
2
|
+
# Derived from GSL-1.9 source files in the specfunc/ dir.
|
3
|
+
|
4
|
+
module Distribution
|
5
|
+
module MathExtension
|
6
|
+
module Beta
|
7
|
+
class << self
|
8
|
+
# Based on gsl_sf_lnbeta_e and gsl_sf_lnbeta_sgn_e
|
9
|
+
# Returns result and sign in an array. If with_error is specified, also returns the error.
|
10
|
+
def log_beta(x,y, with_error=false)
|
11
|
+
sign = nil
|
12
|
+
|
13
|
+
raise(ArgumentError, "x and y must be nonzero") if x == 0.0 || y == 0.0
|
14
|
+
raise(ArgumentError, "not defined for negative integers") if [x,y].any? { |v| (v.is_a?(Fixnum) && v < 0) }
|
15
|
+
|
16
|
+
# See if we can handle the positive case with min/max < 0.2
|
17
|
+
if x > 0 && y > 0
|
18
|
+
min, max = [x,y].minmax
|
19
|
+
ratio = min.quo(max)
|
20
|
+
|
21
|
+
if ratio < 0.2
|
22
|
+
gsx = Gammastar.evaluate(x, with_error)
|
23
|
+
gsy = Gammastar.evaluate(y, with_error)
|
24
|
+
gsxy = Gammastar.evaluate(x+y, with_error)
|
25
|
+
lnopr = Log::log_1plusx(ratio, with_error)
|
26
|
+
|
27
|
+
gsx, gsx_err, gsy, gsy_err, gsxy, gsxy_err, lnopr, lnopr_err = [gsx,gsy,gsxy,lnopr].flatten if with_error
|
28
|
+
|
29
|
+
lnpre = Math.log((gsx*gsy).quo(gsxy) * Math::SQRT2 * Math::SQRTPI)
|
30
|
+
lnpre_err = gsx_err.quo(gsx) + gsy_err(gsy) + gsxy_err.quo(gsxy) if with_error
|
31
|
+
|
32
|
+
t1 = min * Math.log(ratio)
|
33
|
+
t2 = 0.5 * Math.log(min)
|
34
|
+
t3 = (x+y-0.5)*lnopr
|
35
|
+
|
36
|
+
lnpow = t1 - t2 - t3
|
37
|
+
lnpow_err = Float::EPSILON * (t1.abs + t2.abs + t3.abs) + (x+y-0.5).abs * lnopr_err if with_error
|
38
|
+
|
39
|
+
result = lnpre + lnpow
|
40
|
+
error = lnpre_err + lnpow_err + 2.0*Float::EPSILON*result.abs if with_error
|
41
|
+
|
42
|
+
return with_error ? [result, 1.0, error] : [result, 1.0]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# General case: fallback
|
47
|
+
lgx, sgx = Math.lgamma(x)
|
48
|
+
lgy, sgy = Math.lgamma(y)
|
49
|
+
lgxy, sgxy = Math.lgamma(x+y)
|
50
|
+
sgn = sgx * sgy * sgxy
|
51
|
+
|
52
|
+
raise("Domain error: sign is -") if sgn == -1
|
53
|
+
|
54
|
+
result = lgx + lgy - lgxy
|
55
|
+
if with_error
|
56
|
+
lgx_err, lgy_err, lgxy_err = begin
|
57
|
+
STDERR.puts("Warning: Error is unknown for Math::lgamma, guessing.")
|
58
|
+
[Math::EPSILON, Math::EPSILON, Math::EPSILON]
|
59
|
+
end
|
60
|
+
|
61
|
+
error = lgx_err + lgy_err + lgxy_err + Float::EPSILON*(lgx.abs+lgy.abs+lgxy.abs) + 2.0*(Float::EPSILON)*result.abs
|
62
|
+
return [result, sgn, error]
|
63
|
+
else
|
64
|
+
return [result, sgn]
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
# Calculate regularized incomplete beta function
|
71
|
+
module IncompleteBeta
|
72
|
+
|
73
|
+
MAX_ITER = 512
|
74
|
+
CUTOFF = 2.0 * Float::MIN
|
75
|
+
|
76
|
+
class << self
|
77
|
+
|
78
|
+
# Evaluate aa * beta_inc(a,b,x) + yy
|
79
|
+
#
|
80
|
+
# No error mode available.
|
81
|
+
#
|
82
|
+
# From GSL-1.9: cdf/beta_inc.c, beta_inc_AXPY
|
83
|
+
def axpy(aa,yy,a,b,x)
|
84
|
+
return aa*0 + yy if x == 0.0
|
85
|
+
return aa*1 + yy if x == 1.0
|
86
|
+
|
87
|
+
ln_beta = Math.logbeta(a, b)
|
88
|
+
ln_pre = -ln_beta + a * Math.log(x) + b * Math::Log.log1p(-x)
|
89
|
+
prefactor = Math.exp(ln_pre)
|
90
|
+
|
91
|
+
if x < (a+1).quo(a+b+2)
|
92
|
+
# Apply continued fraction directly
|
93
|
+
epsabs = yy.quo((aa * prefactor).quo(a)).abs * Float::EPSILON
|
94
|
+
cf = continued_fraction(a, b, x, epsabs)
|
95
|
+
return aa * (prefactor * cf).quo(a) + yy
|
96
|
+
else
|
97
|
+
# Apply continued fraction after hypergeometric transformation
|
98
|
+
epsabs = (aa + yy).quo( (aa*prefactor).quo(b) ) * Float::EPSILON
|
99
|
+
cf = continued_fraction(b, a, 1-x, epsabs)
|
100
|
+
term = (prefactor * cf).quo(b)
|
101
|
+
return aa == -yy ? -aa*term : aa*(1-term)+yy
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
# Evaluate the incomplete beta function
|
107
|
+
# gsl_sf_beta_inc_e
|
108
|
+
def evaluate(a,b,x,with_error=false)
|
109
|
+
raise(ArgumentError, "Domain error: a(#{a}), b(#{b}) must be positive; x(#{x}) must be between 0 and 1, inclusive") if a <= 0 || b <= 0 || x < 0 || x > 1
|
110
|
+
if x == 0
|
111
|
+
return with_error ? [0.0,0.0] : 0.0
|
112
|
+
elsif x == 1
|
113
|
+
return with_error ? [1.0,0.0] : 1.0
|
114
|
+
else
|
115
|
+
|
116
|
+
ln_beta = Beta.log_beta(a,b, with_error)
|
117
|
+
ln_1mx = Log.log_1plusx(-x, with_error)
|
118
|
+
ln_x = Math.log(x)
|
119
|
+
|
120
|
+
ln_beta, ln_beta_err, ln_1mx, ln_1mx_err, ln_x_err = begin
|
121
|
+
#STDERR.puts("Warning: Error is unknown for Math::log, guessing.")
|
122
|
+
[ln_beta,ln_1mx,Float::EPSILON].flatten
|
123
|
+
end
|
124
|
+
|
125
|
+
ln_pre = -ln_beta + a*ln_x + b*ln_1mx
|
126
|
+
ln_pre_err = ln_beta_err + (a*ln_x_err).abs + (b*ln_1mx_err).abs if with_error
|
127
|
+
|
128
|
+
prefactor, prefactor_err = begin
|
129
|
+
if with_error
|
130
|
+
exp_err(ln_pre, ln_pre_err)
|
131
|
+
else
|
132
|
+
[Math.exp(ln_pre), nil]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
if x < (a+1).quo(a+b+2)
|
137
|
+
# Apply continued fraction directly
|
138
|
+
|
139
|
+
cf = continued_fraction(a,b,x, nil, with_error)
|
140
|
+
cf,cf_err = cf if with_error
|
141
|
+
result = (prefactor * cf).quo(a)
|
142
|
+
return with_error ? [result, ((prefactor_err*cf).abs + (prefactor*cf_err).abs).quo(a)] : result
|
143
|
+
else
|
144
|
+
# Apply continued fraction after hypergeometric transformation
|
145
|
+
|
146
|
+
cf = continued_fraction(b, a, 1-x, nil)
|
147
|
+
cf,cf_err = cf if with_error
|
148
|
+
term = (prefactor * cf).quo(b)
|
149
|
+
result = 1 - term
|
150
|
+
|
151
|
+
return with_error ? [result, (prefactor_err*cf).quo(b) + (prefactor*cf_err).quo(b) + 2.0*Float::EPSILON*(1+term.abs)] : result
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
def continued_fraction_cutoff(epsabs)
|
159
|
+
return CUTOFF if epsabs.nil?
|
160
|
+
0.0/0 # NaN
|
161
|
+
end
|
162
|
+
|
163
|
+
# Continued fraction calculation of incomplete beta
|
164
|
+
# beta_cont_frac from GSL-1.9
|
165
|
+
#
|
166
|
+
# If epsabs is set, will execute the version of the GSL function in the cdf folder. Otherwise, does the
|
167
|
+
# basic one in specfunc.
|
168
|
+
def continued_fraction(a,b,x,epsabs=nil,with_error=false)
|
169
|
+
num_term = 1
|
170
|
+
den_term = 1 - (a+b)*x.quo(a+1)
|
171
|
+
k = 0
|
172
|
+
|
173
|
+
den_term = continued_fraction_cutoff(epsabs) if den_term.abs < CUTOFF
|
174
|
+
den_term = 1.quo(den_term)
|
175
|
+
cf = den_term
|
176
|
+
|
177
|
+
1.upto(MAX_ITER) do |k|
|
178
|
+
coeff = k *(b-k)*x.quo(((a-1)+2*k)*(a+2*k)) # coefficient for step 1
|
179
|
+
delta_frac = nil
|
180
|
+
2.times do
|
181
|
+
|
182
|
+
den_term = 1 + coeff*den_term
|
183
|
+
num_term = 1 + coeff.quo(num_term)
|
184
|
+
|
185
|
+
den_term = continued_fraction_cutoff(epsabs) if den_term.abs < CUTOFF
|
186
|
+
num_term = continued_fraction_cutoff(epsabs) if num_term.abs < CUTOFF
|
187
|
+
|
188
|
+
den_term = 1.quo(den_term)
|
189
|
+
|
190
|
+
delta_frac = den_term * num_term
|
191
|
+
cf *= delta_frac
|
192
|
+
|
193
|
+
coeff = -(a+k)*(a+b+k)*x.quo((a+2*k)*(a+2*k+1)) # coefficient for step 2
|
194
|
+
end
|
195
|
+
|
196
|
+
break if (delta_frac-1).abs < 2.0*Float::EPSILON
|
197
|
+
break if !epsabs.nil? && (cf * (delta_frac-1).abs < epsabs)
|
198
|
+
end
|
199
|
+
|
200
|
+
if k > MAX_ITER
|
201
|
+
raise("Exceeded maximum number of iterations") if epsabs.nil?
|
202
|
+
return with_error ? [0.0/0, 0] : 0.0/0 # NaN if epsabs is set
|
203
|
+
end
|
204
|
+
|
205
|
+
with_error ? [cf, k * 4 * Float::EPSILON * cf.abs] : cf
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
@@ -0,0 +1,424 @@
|
|
1
|
+
# Added by John O. Woods, SciRuby project.
|
2
|
+
# Derived from GSL-1.9 source files in the specfunc/ dir.
|
3
|
+
|
4
|
+
# require "statsample"
|
5
|
+
|
6
|
+
module Distribution
|
7
|
+
module MathExtension
|
8
|
+
module IncompleteGamma
|
9
|
+
NMAX = 5000
|
10
|
+
SMALL = Float::EPSILON ** 3
|
11
|
+
PG21 = -2.404113806319188570799476 # PolyGamma[2,1]
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
# Helper function for plot
|
16
|
+
#def range_to_array r
|
17
|
+
# r << (r.last - r.first)/100.0 if r.size == 2 # set dr as Dr/100.0
|
18
|
+
# arr = []
|
19
|
+
# pos = r[0]
|
20
|
+
# while pos <= r[1]
|
21
|
+
# arr << pos
|
22
|
+
# pos += r[2]
|
23
|
+
# end
|
24
|
+
# arr
|
25
|
+
#end
|
26
|
+
#
|
27
|
+
#def plot a, x_range, fun = :p
|
28
|
+
# x_range = range_to_array(x_range) if x_range.is_a?(Array)
|
29
|
+
# y_range = x_range.collect { |x| self.send(fun, a, x) }
|
30
|
+
# graph = Statsample::Graph::Scatterplot.new x_range.to_scale, y_range.to_scale
|
31
|
+
# f = File.new("test.svg", "w")
|
32
|
+
# f.puts(graph.to_svg)
|
33
|
+
# f.close
|
34
|
+
# `google-chrome test.svg`
|
35
|
+
#end
|
36
|
+
|
37
|
+
# The dominant part, D(a,x) := x^a e^(-x) / Gamma(a+1)
|
38
|
+
# gamma_inc_D in GSL-1.9.
|
39
|
+
def d(a, x, with_error = false)
|
40
|
+
error = nil
|
41
|
+
if a < 10.0
|
42
|
+
ln_a = Math.lgamma(a+1.0).first
|
43
|
+
lnr = a * Math.log(x) - x - ln_a
|
44
|
+
result = Math.exp(lnr)
|
45
|
+
error = 2.0 * Float::EPSILON * (lnr.abs + 1.0) + result.abs if with_error
|
46
|
+
with_error ? [result,error] : result
|
47
|
+
else
|
48
|
+
ln_term = ln_term_error = nil
|
49
|
+
if x < 0.5*a
|
50
|
+
u = x/a.to_f
|
51
|
+
ln_u = Math.log(u)
|
52
|
+
ln_term = ln_u - u + 1.0
|
53
|
+
ln_term_error = (ln_u.abs + u.abs + 1.0) * Float::EPSILON if with_error
|
54
|
+
else
|
55
|
+
mu = (x-a)/a.to_f
|
56
|
+
ln_term = Log::log_1plusx_minusx(mu, with_error)
|
57
|
+
ln_term, ln_term_error = ln_term if with_error
|
58
|
+
end
|
59
|
+
gstar = Gammastar.evaluate(a, with_error)
|
60
|
+
gstar,gstar_error = gstar if with_error
|
61
|
+
term1 = Math.exp(a*ln_term) / Math.sqrt(2.0*Math::PI*a)
|
62
|
+
result = term1/gstar
|
63
|
+
error = 2.0*Float::EPSILON*((a*ln_term).abs+1.0) * result.abs + gstar_error/gstar.abs * result.abs if with_error
|
64
|
+
with_error ? [result,error] : result
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# gamma_inc_P_series
|
69
|
+
def p_series(a,x,with_error=false)
|
70
|
+
d = d(a,x,with_error)
|
71
|
+
d, d_err = d if with_error
|
72
|
+
sum = 1.0
|
73
|
+
term = 1.0
|
74
|
+
n = 1
|
75
|
+
1.upto(NMAX-1) do |n|
|
76
|
+
term *= x / (a+n).to_f
|
77
|
+
sum += term
|
78
|
+
break if (term/sum).abs < Float::EPSILON
|
79
|
+
end
|
80
|
+
|
81
|
+
result = d * sum
|
82
|
+
|
83
|
+
if n == NMAX
|
84
|
+
STDERR.puts("Error: n reached NMAX in p series")
|
85
|
+
else
|
86
|
+
return with_error ? [result,d_err * sum.abs + (1.0+n)*Float::EPSILON * result.abs] : result
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# This function does not exist in GSL, but is nonetheless GSL code. It's for calculating two specific ranges of p.
|
91
|
+
def q_asymptotic_uniform_complement a,x,with_error=false
|
92
|
+
q = q_asymptotic_uniform(a, x, with_error)
|
93
|
+
q,q_err = q if with_error
|
94
|
+
result = 1.0 - q
|
95
|
+
return with_error ? [result, q_err + 2.0*Float::EPSILON*result.abs] : result
|
96
|
+
end
|
97
|
+
|
98
|
+
def q_continued_fraction_complement a,x,with_error=false
|
99
|
+
q = q_continued_fraction(a,x,with_error)
|
100
|
+
return with_error ? [1.0 - q.first, q.last + 2.0*Float::EPSILON*(1.0-q.first).abs] : 1.0 - q
|
101
|
+
end
|
102
|
+
|
103
|
+
def q_large_x_complement a,x,with_error=false
|
104
|
+
q = q_large_x(a,x,with_error)
|
105
|
+
return with_error ? [1.0 - q.first, q.last + 2.0*Float::EPSILON*(1.0-q.first).abs] : 1.0 - q
|
106
|
+
end
|
107
|
+
|
108
|
+
# The incomplete gamma function.
|
109
|
+
# gsl_sf_gamma_inc_P_e
|
110
|
+
def p a,x,with_error=false
|
111
|
+
raise(ArgumentError, "Range Error: a must be positive, x must be non-negative") if a <= 0.0 || x < 0.0
|
112
|
+
if x == 0.0
|
113
|
+
return with_error ? [0.0, 0.0] : 0.0
|
114
|
+
elsif x < 20.0 || x < 0.5*a
|
115
|
+
return p_series(a, x, with_error)
|
116
|
+
elsif a > 1e6 && (x-a)*(x-a) < a
|
117
|
+
return q_asymptotic_uniform_complement a, x, with_error
|
118
|
+
elsif a <= x
|
119
|
+
if a > 0.2*x
|
120
|
+
return q_continued_fraction_complement(a, x, with_error)
|
121
|
+
else
|
122
|
+
return q_large_x_complement(a, x, with_error)
|
123
|
+
end
|
124
|
+
elsif (x-a)*(x-a) < a
|
125
|
+
return q_asymptotic_uniform_complement a, x, with_error
|
126
|
+
else
|
127
|
+
return p_series(a, x, with_error)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# gamma_inc_Q_e
|
132
|
+
def q a,x,with_error=false
|
133
|
+
raise(ArgumentError, "Range Error: a and x must be non-negative") if (a < 0.0 || x < 0.0)
|
134
|
+
if x == 0.0
|
135
|
+
return with_error ? [1.0, 0.0] : 1.0
|
136
|
+
elsif a == 0.0
|
137
|
+
return with_error ? [0.0, 0.0] : 0.0
|
138
|
+
elsif x <= 0.5*a
|
139
|
+
# If series is quick, do that.
|
140
|
+
p = p_series(a,x, with_error)
|
141
|
+
p,p_err = p if with_error
|
142
|
+
result = 1.0 - p
|
143
|
+
return with_error ? [result, p_err + 2.0*Float::EPSILON*result.abs] : result
|
144
|
+
elsif a >= 1.0e+06 && (x-a)*(x-a) < a # difficult asymptotic regime, only way to do this region
|
145
|
+
return q_asymptotic_uniform(a, x, with_error)
|
146
|
+
elsif a < 0.2 && x < 5.0
|
147
|
+
return q_series(a,x, with_error)
|
148
|
+
elsif a <= x
|
149
|
+
return x <= 1.0e+06 ? q_continued_fraction(a, x, with_error) : q_large_x(a, x, with_error)
|
150
|
+
else
|
151
|
+
if x > a-Math.sqrt(a)
|
152
|
+
return q_continued_fraction(a, x, with_error)
|
153
|
+
else
|
154
|
+
p = p_series(a, x, with_error)
|
155
|
+
p, p_err = p if with_error
|
156
|
+
result = 1.0 - p
|
157
|
+
return with_error ? [result, p_err + 2.0*Float::EPSILON*result.abs] : result
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# gamma_inc_Q_CF
|
163
|
+
def q_continued_fraction a, x, with_error=false
|
164
|
+
d = d(a, x, with_error)
|
165
|
+
f = f_continued_fraction(a, x, with_error)
|
166
|
+
|
167
|
+
if with_error
|
168
|
+
[d.first*(a/x).to_f*f.first, d.last * ((a/x).to_f*f.first).abs + (d.first*a/x*f.last).abs]
|
169
|
+
else
|
170
|
+
d * (a/x).to_f * f
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# gamma_inc_Q_large_x in GSL-1.9
|
175
|
+
def q_large_x a,x,with_error=false
|
176
|
+
d = d(a,x,with_error)
|
177
|
+
d,d_err = d if with_error
|
178
|
+
sum = 1.0
|
179
|
+
term = 1.0
|
180
|
+
last = 1.0
|
181
|
+
n = 1
|
182
|
+
1.upto(NMAX-1).each do |n|
|
183
|
+
term *= (a-n)/x
|
184
|
+
break if (term/last).abs > 1.0
|
185
|
+
break if (term/sum).abs < Float::EPSILON
|
186
|
+
sum += term
|
187
|
+
last = term
|
188
|
+
end
|
189
|
+
|
190
|
+
result = d*(a/x)*sum
|
191
|
+
error = d_err * (a/x).abs * sum if with_error
|
192
|
+
|
193
|
+
if n == NMAX
|
194
|
+
STDERR.puts("Error: n reached NMAX in q_large_x")
|
195
|
+
else
|
196
|
+
return with_error ? [result,error] : result
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
# Uniform asymptotic for x near a, a and x large
|
201
|
+
# gamma_inc_Q_asymp_unif
|
202
|
+
def q_asymptotic_uniform(a, x, with_error = false)
|
203
|
+
rta = Math.sqrt(a)
|
204
|
+
eps = (x-a).quo(a)
|
205
|
+
|
206
|
+
ln_term = Log::log_1plusx_minusx(eps, with_error)
|
207
|
+
ln_term, ln_term_err = ln_term if with_error
|
208
|
+
|
209
|
+
eta = (eps >= 0 ? 1 : -1) * Math.sqrt(-2*ln_term)
|
210
|
+
|
211
|
+
erfc = Math.erfc_e(eta*rta/SQRT2, with_error)
|
212
|
+
erfc, erfc_err = erfc if with_error
|
213
|
+
|
214
|
+
c0 = c1 = nil
|
215
|
+
if eps.abs < ROOT5_FLOAT_EPSILON
|
216
|
+
c0 = -1.quo(3) + eps*(1.quo(12) - eps*(23.quo(540) - eps*(353.quo(12960) - eps*589.quo(30240))))
|
217
|
+
c1 = -1.quo(540) - eps.quo(288)
|
218
|
+
else
|
219
|
+
rt_term = Math.sqrt(-2 * ln_term.quo(eps*eps))
|
220
|
+
lam = x.quo(a)
|
221
|
+
c0 = (1 - 1/rt_term)/eps
|
222
|
+
c1 = -(eta**3 * (lam*lam + 10*lam + 1) - 12*eps**3).quo(12 * eta**3 * eps**3)
|
223
|
+
end
|
224
|
+
|
225
|
+
r = Math.exp(-0.5*a*eta*eta) / (SQRT2*SQRTPI*rta) * (c0 + c1.quo(a))
|
226
|
+
|
227
|
+
result = 0.5 * erfc + r
|
228
|
+
with_error ? [result, Float::EPSILON + (r*0.5*a*eta*eta).abs + 0.5*erfc_err + 2.0*Float::EPSILON + result.abs] : result
|
229
|
+
end
|
230
|
+
|
231
|
+
# gamma_inc_F_CF
|
232
|
+
def f_continued_fraction a, x, with_error = false
|
233
|
+
hn = 1.0 # convergent
|
234
|
+
cn = 1.0 / SMALL
|
235
|
+
dn = 1.0
|
236
|
+
n = 2
|
237
|
+
2.upto(NMAX-1).each do |n|
|
238
|
+
an = n.odd? ? 0.5*(n-1)/x : (0.5*n-a)/x
|
239
|
+
dn = 1.0 + an * dn
|
240
|
+
dn = SMALL if dn.abs < SMALL
|
241
|
+
cn = 1.0 + an / cn
|
242
|
+
cn = SMALL if cn.abs < SMALL
|
243
|
+
dn = 1.0 / dn
|
244
|
+
delta = cn * dn
|
245
|
+
hn *= delta
|
246
|
+
break if (delta-1.0).abs < Float::EPSILON
|
247
|
+
end
|
248
|
+
|
249
|
+
if n == NMAX
|
250
|
+
STDERR.puts("Error: n reached NMAX in f continued fraction")
|
251
|
+
else
|
252
|
+
with_error ? [hn,2.0*Float::EPSILON * hn.abs + Float::EPSILON*(2.0+0.5*n) * hn.abs] : hn
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def q_series(a,x,with_error=false)
|
257
|
+
term1 = nil
|
258
|
+
sum = nil
|
259
|
+
term2 = nil
|
260
|
+
begin
|
261
|
+
lnx = Math.log(x)
|
262
|
+
el = EULER + lnx
|
263
|
+
c1 = -el
|
264
|
+
c2 = Math::PI * Math::PI / 12.0 - 0.5*el*el
|
265
|
+
c3 = el*(Math::PI*Math::PI/12.0 - el*el/6.0) + PG21/6.0
|
266
|
+
c4 = -0.04166666666666666667 *
|
267
|
+
(-1.758243446661483480 + lnx) *
|
268
|
+
(-0.764428657272716373 + lnx) *
|
269
|
+
( 0.723980571623507657 + lnx) *
|
270
|
+
( 4.107554191916823640 + lnx)
|
271
|
+
c5 = -0.0083333333333333333 *
|
272
|
+
(-2.06563396085715900 + lnx) *
|
273
|
+
(-1.28459889470864700 + lnx) *
|
274
|
+
(-0.27583535756454143 + lnx) *
|
275
|
+
( 1.33677371336239618 + lnx) *
|
276
|
+
( 5.17537282427561550 + lnx)
|
277
|
+
c6 = -0.0013888888888888889 *
|
278
|
+
(-2.30814336454783200 + lnx) *
|
279
|
+
(-1.65846557706987300 + lnx) *
|
280
|
+
(-0.88768082560020400 + lnx) *
|
281
|
+
( 0.17043847751371778 + lnx) *
|
282
|
+
( 1.92135970115863890 + lnx) *
|
283
|
+
( 6.22578557795474900 + lnx)
|
284
|
+
c7 = -0.00019841269841269841
|
285
|
+
(-2.5078657901291800 + lnx) *
|
286
|
+
(-1.9478900888958200 + lnx) *
|
287
|
+
(-1.3194837322612730 + lnx) *
|
288
|
+
(-0.5281322700249279 + lnx) *
|
289
|
+
( 0.5913834939078759 + lnx) *
|
290
|
+
( 2.4876819633378140 + lnx) *
|
291
|
+
( 7.2648160783762400 + lnx)
|
292
|
+
c8 = -0.00002480158730158730 *
|
293
|
+
(-2.677341544966400 + lnx) *
|
294
|
+
(-2.182810448271700 + lnx) *
|
295
|
+
(-1.649350342277400 + lnx) *
|
296
|
+
(-1.014099048290790 + lnx) *
|
297
|
+
(-0.191366955370652 + lnx) *
|
298
|
+
( 0.995403817918724 + lnx) *
|
299
|
+
( 3.041323283529310 + lnx) *
|
300
|
+
( 8.295966556941250 + lnx) *
|
301
|
+
c9 = -2.75573192239859e-6 *
|
302
|
+
(-2.8243487670469080 + lnx) *
|
303
|
+
(-2.3798494322701120 + lnx) *
|
304
|
+
(-1.9143674728689960 + lnx) *
|
305
|
+
(-1.3814529102920370 + lnx) *
|
306
|
+
(-0.7294312810261694 + lnx) *
|
307
|
+
( 0.1299079285269565 + lnx) *
|
308
|
+
( 1.3873333251885240 + lnx) *
|
309
|
+
( 3.5857258865210760 + lnx) *
|
310
|
+
( 9.3214237073814600 + lnx) *
|
311
|
+
c10 = -2.75573192239859e-7 *
|
312
|
+
(-2.9540329644556910 + lnx) *
|
313
|
+
(-2.5491366926991850 + lnx) *
|
314
|
+
(-2.1348279229279880 + lnx) *
|
315
|
+
(-1.6741881076349450 + lnx) *
|
316
|
+
(-1.1325949616098420 + lnx) *
|
317
|
+
(-0.4590034650618494 + lnx) *
|
318
|
+
( 0.4399352987435699 + lnx) *
|
319
|
+
( 1.7702236517651670 + lnx) *
|
320
|
+
( 4.1231539047474080 + lnx) *
|
321
|
+
( 10.342627908148680 + lnx)
|
322
|
+
term1 = a*(c1+a*(c2+a*(c3+a*(c4+a*(c5+a*(c6+a*(c7+a*(c8+a*(c9+a*c10)))))))))
|
323
|
+
end
|
324
|
+
|
325
|
+
n = 1
|
326
|
+
begin
|
327
|
+
t = 1.0
|
328
|
+
sum = 1.0
|
329
|
+
1.upto(NMAX-1).each do |n|
|
330
|
+
t *= -x/(n+1.0)
|
331
|
+
sum += (a+1.0) / (a+n+1.0) * t
|
332
|
+
break if (t/sum).abs < Float::EPSILON
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
if n == NMAX
|
337
|
+
STDERR.puts("Error: n reached NMAX in q_series")
|
338
|
+
else
|
339
|
+
term2 = (1.0 - term1) * a/(a+1.0) * x * sum
|
340
|
+
result = term1+term2
|
341
|
+
with_error ? [result, Float::EPSILON*term1.abs + 2.0*term2.abs + 2.0*Float::EPSILON*result.abs] : result
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
# gamma_inc_series
|
346
|
+
def series a,x,with_error = false
|
347
|
+
q = q_series(a,x,with_error)
|
348
|
+
g = Math.gamma(a)
|
349
|
+
STDERR.puts("Warning: Don't know error for Math.gamma. Error will be incorrect") if with_error
|
350
|
+
# When we get the error from Gamma, switch the comment on the next to lines
|
351
|
+
# with_error ? [q.first*g.first, (q.first*g.last).abs + (q.last*g.first).abs + 2.0*Float::EPSILON*(q.first*g.first).abs] : q*g
|
352
|
+
with_error ? [q.first*g, (q.first*Float::EPSILON).abs + (q.last*g.first).abs + 2.0*Float::EPSILON(q.first*g).abs] : q*g
|
353
|
+
end
|
354
|
+
|
355
|
+
# gamma_inc_a_gt_0
|
356
|
+
def a_greater_than_0 a, x, with_error = false
|
357
|
+
q = q(a,x,with_error)
|
358
|
+
q,q_err = q if with_error
|
359
|
+
g = Math.gamma(a)
|
360
|
+
STDERR.puts("Warning: Don't know error for Math.gamma. Error will be incorrect") if with_error
|
361
|
+
g_err = Float::EPSILON
|
362
|
+
result = g*q
|
363
|
+
error = (g*q_err).abs + (g_err*q).abs if with_error
|
364
|
+
with_error ? [result,error] : result
|
365
|
+
end
|
366
|
+
|
367
|
+
# gamma_inc_CF
|
368
|
+
def continued_fraction a,x, with_error=false
|
369
|
+
f = f_continued_fraction(a,x,with_error)
|
370
|
+
f,f_error = f if with_error
|
371
|
+
pre = Math.exp((a-1.0)*Math.log(x) - x)
|
372
|
+
STDERR.puts("Warning: Don't know error for Math.exp. Error will be incorrect") if with_error
|
373
|
+
pre_error = Float::EPSILON
|
374
|
+
result = f*pre
|
375
|
+
if with_error
|
376
|
+
error = (f_error*pre).abs + (f*pre_error) + (2.0+a.abs)*Float::EPSILON*result.abs
|
377
|
+
[result,error]
|
378
|
+
else
|
379
|
+
result
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
# Unnormalized incomplete gamma function.
|
384
|
+
# gsl_sf_gamma_inc_e
|
385
|
+
def unnormalized a,x,with_error = false
|
386
|
+
raise(ArgumentError, "x cannot be negative") if x < 0.0
|
387
|
+
|
388
|
+
if x == 0.0
|
389
|
+
result = Math.gamma(a.to_f)
|
390
|
+
STDERR.puts("Warning: Don't know error for Math.gamma. Error will be incorrect") if with_error
|
391
|
+
return with_error ? [result, Float::EPSILON] : result
|
392
|
+
elsif a == 0.0
|
393
|
+
return ExponentialIntegral.first_order(x.to_f, with_error)
|
394
|
+
elsif a > 0.0
|
395
|
+
return a_greater_than_0(a.to_f, x.to_f, with_error)
|
396
|
+
elsif x > 0.25
|
397
|
+
# continued fraction seems to fail for x too small
|
398
|
+
return continued_fraction(a.to_f, x.to_f, with_error)
|
399
|
+
elsif a.abs < 0.5
|
400
|
+
return series(a.to_f,x.to_f,with_error)
|
401
|
+
else
|
402
|
+
fa = a.floor.to_f
|
403
|
+
da = a - fa
|
404
|
+
g_da = da > 0.0 ? a_greater_than_0(da, x.to_f, with_error) : ExponentialIntegral.first_order(x.to_f, with_error)
|
405
|
+
g_da, g_da_err = g_da if with_error
|
406
|
+
alpha = da
|
407
|
+
gax = g_da
|
408
|
+
|
409
|
+
# Gamma(alpha-1,x) = 1/(alpha-1) (Gamma(a,x) - x^(alpha-1) e^-x)
|
410
|
+
begin
|
411
|
+
shift = Math.exp(-x + (alpha-1.0)*Math.log(x))
|
412
|
+
gax = (gax-shift) / (alpha-1.0)
|
413
|
+
alpha -= 1.0
|
414
|
+
end while alpha > a
|
415
|
+
|
416
|
+
result = gax
|
417
|
+
return with_error ? [result, 2.0*(1.0 + a.abs) * Float::EPSILON*gax.abs] : result
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|