File: incomplete_beta.rb

package info (click to toggle)
ruby-distribution 0.7.3%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: buster, stretch
  • size: 624 kB
  • ctags: 379
  • sloc: ruby: 4,283; makefile: 7
file content (213 lines) | stat: -rw-r--r-- 7,631 bytes parent folder | download | duplicates (3)
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