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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
|
# Added by John O. Woods, SciRuby project.
# Derived from GSL-1.9 source files in the specfunc/ dir.
module Distribution
module MathExtension
module Beta
class << self
# Based on gsl_sf_lnbeta_e and gsl_sf_lnbeta_sgn_e
# Returns result and sign in an array. If with_error is specified, also returns the error.
def log_beta(x,y, with_error=false)
sign = nil
raise(ArgumentError, "x and y must be nonzero") if x == 0.0 || y == 0.0
raise(ArgumentError, "not defined for negative integers") if [x,y].any? { |v| (v.is_a?(Fixnum) && v < 0) }
# See if we can handle the positive case with min/max < 0.2
if x > 0 && y > 0
min, max = [x,y].minmax
ratio = min.quo(max)
if ratio < 0.2
gsx = Gammastar.evaluate(x, with_error)
gsy = Gammastar.evaluate(y, with_error)
gsxy = Gammastar.evaluate(x+y, with_error)
lnopr = Log::log_1plusx(ratio, with_error)
gsx, gsx_err, gsy, gsy_err, gsxy, gsxy_err, lnopr, lnopr_err = [gsx,gsy,gsxy,lnopr].flatten if with_error
lnpre = Math.log((gsx*gsy).quo(gsxy) * Math::SQRT2 * Math::SQRTPI)
lnpre_err = gsx_err.quo(gsx) + gsy_err(gsy) + gsxy_err.quo(gsxy) if with_error
t1 = min * Math.log(ratio)
t2 = 0.5 * Math.log(min)
t3 = (x+y-0.5)*lnopr
lnpow = t1 - t2 - t3
lnpow_err = Float::EPSILON * (t1.abs + t2.abs + t3.abs) + (x+y-0.5).abs * lnopr_err if with_error
result = lnpre + lnpow
error = lnpre_err + lnpow_err + 2.0*Float::EPSILON*result.abs if with_error
return with_error ? [result, 1.0, error] : [result, 1.0]
end
end
# General case: fallback
lgx, sgx = Math.lgamma(x)
lgy, sgy = Math.lgamma(y)
lgxy, sgxy = Math.lgamma(x+y)
sgn = sgx * sgy * sgxy
raise("Domain error: sign is -") if sgn == -1
result = lgx + lgy - lgxy
if with_error
lgx_err, lgy_err, lgxy_err = begin
STDERR.puts("Warning: Error is unknown for Math::lgamma, guessing.")
[Math::EPSILON, Math::EPSILON, Math::EPSILON]
end
error = lgx_err + lgy_err + lgxy_err + Float::EPSILON*(lgx.abs+lgy.abs+lgxy.abs) + 2.0*(Float::EPSILON)*result.abs
return [result, sgn, error]
else
return [result, sgn]
end
end
end
end
# Calculate regularized incomplete beta function
module IncompleteBeta
MAX_ITER = 512
CUTOFF = 2.0 * Float::MIN
class << self
# Evaluate aa * beta_inc(a,b,x) + yy
#
# No error mode available.
#
# From GSL-1.9: cdf/beta_inc.c, beta_inc_AXPY
def axpy(aa,yy,a,b,x)
return aa*0 + yy if x == 0.0
return aa*1 + yy if x == 1.0
ln_beta = Math.logbeta(a, b)
ln_pre = -ln_beta + a * Math.log(x) + b * Math::Log.log1p(-x)
prefactor = Math.exp(ln_pre)
if x < (a+1).quo(a+b+2)
# Apply continued fraction directly
epsabs = yy.quo((aa * prefactor).quo(a)).abs * Float::EPSILON
cf = continued_fraction(a, b, x, epsabs)
return aa * (prefactor * cf).quo(a) + yy
else
# Apply continued fraction after hypergeometric transformation
epsabs = (aa + yy).quo( (aa*prefactor).quo(b) ) * Float::EPSILON
cf = continued_fraction(b, a, 1-x, epsabs)
term = (prefactor * cf).quo(b)
return aa == -yy ? -aa*term : aa*(1-term)+yy
end
end
# Evaluate the incomplete beta function
# gsl_sf_beta_inc_e
def evaluate(a,b,x,with_error=false)
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
if x == 0
return with_error ? [0.0,0.0] : 0.0
elsif x == 1
return with_error ? [1.0,0.0] : 1.0
else
ln_beta = Beta.log_beta(a,b, with_error)
ln_1mx = Log.log_1plusx(-x, with_error)
ln_x = Math.log(x)
ln_beta, ln_beta_err, ln_1mx, ln_1mx_err, ln_x_err = begin
#STDERR.puts("Warning: Error is unknown for Math::log, guessing.")
[ln_beta,ln_1mx,Float::EPSILON].flatten
end
ln_pre = -ln_beta + a*ln_x + b*ln_1mx
ln_pre_err = ln_beta_err + (a*ln_x_err).abs + (b*ln_1mx_err).abs if with_error
prefactor, prefactor_err = begin
if with_error
exp_err(ln_pre, ln_pre_err)
else
[Math.exp(ln_pre), nil]
end
end
if x < (a+1).quo(a+b+2)
# Apply continued fraction directly
cf = continued_fraction(a,b,x, nil, with_error)
cf,cf_err = cf if with_error
result = (prefactor * cf).quo(a)
return with_error ? [result, ((prefactor_err*cf).abs + (prefactor*cf_err).abs).quo(a)] : result
else
# Apply continued fraction after hypergeometric transformation
cf = continued_fraction(b, a, 1-x, nil)
cf,cf_err = cf if with_error
term = (prefactor * cf).quo(b)
result = 1 - term
return with_error ? [result, (prefactor_err*cf).quo(b) + (prefactor*cf_err).quo(b) + 2.0*Float::EPSILON*(1+term.abs)] : result
end
end
end
def continued_fraction_cutoff(epsabs)
return CUTOFF if epsabs.nil?
0.0/0 # NaN
end
# Continued fraction calculation of incomplete beta
# beta_cont_frac from GSL-1.9
#
# If epsabs is set, will execute the version of the GSL function in the cdf folder. Otherwise, does the
# basic one in specfunc.
def continued_fraction(a,b,x,epsabs=nil,with_error=false)
num_term = 1
den_term = 1 - (a+b)*x.quo(a+1)
k = 0
den_term = continued_fraction_cutoff(epsabs) if den_term.abs < CUTOFF
den_term = 1.quo(den_term)
cf = den_term
1.upto(MAX_ITER) do |k|
coeff = k *(b-k)*x.quo(((a-1)+2*k)*(a+2*k)) # coefficient for step 1
delta_frac = nil
2.times do
den_term = 1 + coeff*den_term
num_term = 1 + coeff.quo(num_term)
den_term = continued_fraction_cutoff(epsabs) if den_term.abs < CUTOFF
num_term = continued_fraction_cutoff(epsabs) if num_term.abs < CUTOFF
den_term = 1.quo(den_term)
delta_frac = den_term * num_term
cf *= delta_frac
coeff = -(a+k)*(a+b+k)*x.quo((a+2*k)*(a+2*k+1)) # coefficient for step 2
end
break if (delta_frac-1).abs < 2.0*Float::EPSILON
break if !epsabs.nil? && (cf * (delta_frac-1).abs < epsabs)
end
if k > MAX_ITER
raise("Exceeded maximum number of iterations") if epsabs.nil?
return with_error ? [0.0/0, 0] : 0.0/0 # NaN if epsabs is set
end
with_error ? [cf, k * 4 * Float::EPSILON * cf.abs] : cf
end
end
end
end
end
|