distribution 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|