root/trunk/mathextra/gammadist.d

Revision 71, 6.9 kB (checked in by Don Clugston, 3 years ago)

Moved all files to 'mathextra' subdirectory, and updated module names accordingly.

Line 
1 // incomplete gamma functions
2
3 module mathextra.gammadist;
4 private import std.math;
5 private import mathextra.normaldist;
6 debug import std.stdio;
7
8 unittest {
9     debug writefln("--- UnitTest: " __FILE__ " ---");
10 } 
11
12 // There are still problems with std.math.tgamma.
13 private import mathextra.etcgamma;
14 alias mathextra.etcgamma.tgamma gamma;
15 alias mathextra.etcgamma.lgamma loggamma;
16
17
18 real gammaIncomplete(real a, real x )
19 in {
20    assert(x >= 0);
21    assert(a > 0);
22 }
23 body {
24     /* left tail of incomplete gamma function:
25      *
26      *          inf.      k
27      *   a  -x   -       x
28      *  x  e     >   ----------
29      *           -     -
30      *          k=0   | (a+k+1)
31      *
32      */
33     if (x==0)
34        return 0.0L;
35
36     if ( (x > 1.0L) && (x > a ) )
37         return 1.0L - gammaIncompleteCompl(a,x);
38    
39     real ax = a * log(x) - x - loggamma(a);
40 /+ 
41     if( ax < MINLOGL ) return 0; // underflow
42     //  { mtherr( "igaml", UNDERFLOW ); return( 0.0L ); }
43 +/ 
44     ax = exp(ax);
45    
46     /* power series */
47     real r = a;
48     real c = 1.0L;
49     real ans = 1.0L;
50    
51     do  {
52         r += 1.0L;
53         c *= x/r;
54         ans += c;
55     } while( c/ans > real.epsilon );
56    
57     return ans * ax/a;
58 }
59
60 /** ditto */
61 real gammaIncompleteCompl(real a, real x )
62 in {
63    assert(x >= 0);
64    assert(a > 0);
65 }
66 body {
67     if (x==0)
68        return 1.0L;
69     if ( (x < 1.0L) || (x < a) )
70         return 1.0L - gammaIncomplete(a,x);
71
72    // DAC (Cephes bug fix): This is necessary to avoid
73    // spurious nans, eg
74    // log(x)-x = NaN when x = real.infinity
75     const real MAXLOGL =  1.1356523406294143949492E4L;
76    if (x > MAXLOGL) return 0; // underflow
77    
78     real ax = a * log(x) - x - loggamma(a);
79 //const real MINLOGL = -1.1355137111933024058873E4L;
80 //  if ( ax < MINLOGL ) return 0; // underflow;
81     ax = exp(ax);
82
83    
84     /* continued fraction */
85     real y = 1.0L - a;
86     real z = x + y + 1.0L;
87     real c = 0.0L;
88    
89     real pk, qk, t;
90
91     real pkm2 = 1.0L;
92     real qkm2 = x;
93     real pkm1 = x + 1.0L;
94     real qkm1 = z * x;
95     real ans = pkm1/qkm1;
96    
97     do  {   
98         c += 1.0L;
99         y += 1.0L;
100         z += 2.0L;
101         real yc = y * c;
102         pk = pkm1 * z  -  pkm2 * yc;
103         qk = qkm1 * z  -  qkm2 * yc;
104         if( qk != 0.0L ) {
105             real r = pk/qk;
106             t = fabs( (ans - r)/r );
107             ans = r;
108         } else {
109             t = 1.0L;
110         }
111     pkm2 = pkm1;
112         pkm1 = pk;
113         qkm2 = qkm1;
114         qkm1 = qk;
115        
116         const real BIG = 9.223372036854775808e18L;
117        
118         if ( fabs(pk) > BIG ) {
119             pkm2 /= BIG;
120             pkm1 /= BIG;
121             qkm2 /= BIG;
122             qkm1 /= BIG;
123         }
124     } while ( t > real.epsilon );
125    
126     return ans * ax;
127 }
128
129 /*
130  *      Inverse of complemented incomplete gamma integral
131  *
132  * Given a and y, the function finds x such that
133  *
134  *  gammaIncompleteCompl( a, x ) = p.
135  *
136  * Starting with the approximate value x = a $(POWER t, 3), where
137  * t = 1 - d - normalDistributionInv(p) sqrt(d),
138  * and d = 1/9a,
139  * the routine performs up to 10 Newton iterations to find the
140  * root of incompleteGammaCompl(a,x) - p = 0.
141  */
142
143 // MINLOGL = -1.1355137111933024058873E4L;
144 real gammaIncompleteComplInv(real a, real p)
145 in {
146   assert(p>=0 && p<= 1);
147   assert(a>0);
148 }
149 body {
150     if (p==0) return real.infinity;
151    
152     real y0 = p;
153     const real MAXLOGL =  1.1356523406294143949492E4L;
154     real x0, x1, x, yl, yh, y, d, lgm, dithresh;
155     int i, dir;
156    
157     /* bound the solution */
158     x0 = real.max;
159     yl = 0.0L;
160     x1 = 0.0L;
161     yh = 1.0L;
162     dithresh = 4.0 * real.epsilon;
163    
164     /* approximation to inverse function */
165     d = 1.0L/(9.0L*a);
166     y = 1.0L - d - normalDistributionInv(y0) * sqrt(d);
167     x = a * y * y * y;
168    
169     lgm = loggamma(a);
170    
171     for( i=0; i<10; i++ ) {
172         if( x > x0 || x < x1 )
173             goto ihalve;
174         y = gammaIncompleteCompl(a,x);
175         if ( y < yl || y > yh )
176             goto ihalve;
177         if ( y < y0 ) {
178             x0 = x;
179             yl = y;
180         } else {
181             x1 = x;
182             yh = y;
183         }
184     /* compute the derivative of the function at this point */
185         d = (a - 1.0L) * log(x0) - x0 - lgm;
186         if ( d < -MAXLOGL )
187             goto ihalve;
188         d = -exp(d);
189     /* compute the step to the next approximation of x */
190         d = (y - y0)/d;
191         x = x - d;
192         if ( i < 3 ) continue;
193         if ( fabs(d/x) < dithresh ) return x;
194     }
195    
196     /* Resort to interval halving if Newton iteration did not converge. */
197 ihalve:
198     d = 0.0625L;
199     if ( x0 == real.max ) {
200         if( x <= 0.0L )
201             x = 1.0L;
202         while( x0 == real.max ) {
203             x = (1.0L + d) * x;
204             y = gammaIncompleteCompl( a, x );
205             if ( y < y0 ) {
206                 x0 = x;
207                 yl = y;
208                 break;
209             }
210             d = d + d;
211         }
212     }
213     d = 0.5L;
214     dir = 0;
215
216     for( i=0; i<400; i++ ) {
217         x = x1  +  d * (x0 - x1);
218         y = gammaIncompleteCompl( a, x );
219         lgm = (x0 - x1)/(x1 + x0);
220         if ( fabs(lgm) < dithresh )
221             break;
222         lgm = (y - y0)/y0;
223         if ( fabs(lgm) < dithresh )
224             break;
225         if ( x <= 0.0L )
226             break;
227         if ( y > y0 ) {
228             x1 = x;
229             yh = y;
230             if ( dir < 0 ) {
231                 dir = 0;
232                 d = 0.5L;
233             } else if ( dir > 1 )
234                 d = 0.5L * d + 0.5L;
235             else
236                 d = (y0 - yl)/(yh - yl);
237             dir += 1;
238         } else {
239             x0 = x;
240             yl = y;
241             if ( dir > 0 ) {
242                 dir = 0;
243                 d = 0.5L;
244             } else if ( dir < -1 )
245                 d = 0.5L * d;
246             else
247                 d = (y0 - yl)/(yh - yl);
248             dir -= 1;
249         }
250     }
251     /+
252     if( x == 0.0L )
253         mtherr( "igamil", UNDERFLOW );
254     +/
255     return x;
256 }
257
258 unittest {
259 //Values from Excel's GammaInv(1-p, x, 1)
260 assert(fabs(gammaIncompleteComplInv(1, 0.5) - 0.693147188044814) < 0.00000005);
261 assert(fabs(gammaIncompleteComplInv(12, 0.99) - 5.42818075054289) < 0.00000005);
262 assert(fabs(gammaIncompleteComplInv(100, 0.8) - 91.5013985848288L) < 0.000005);
263
264 assert(gammaIncomplete(1, 0)==0);
265 assert(gammaIncompleteCompl(1, 0)==1);
266 assert(gammaIncomplete(4545, real.infinity)==1);
267
268 // Values from Excel's (1-GammaDist(x, alpha, 1, TRUE))
269
270 assert(fabs(1.0L-gammaIncompleteCompl(0.5, 2) - 0.954499729507309L) < 0.00000005);
271 assert(fabs(gammaIncomplete(0.5, 2) - 0.954499729507309L) < 0.00000005);
272 // Fixed Cephes bug:
273 assert(gammaIncompleteCompl(384, real.infinity)==0);
274 assert(gammaIncompleteComplInv(3, 0)==real.infinity);
275
276 //writefln("%.20g",gammaIncompleteCompl(100, 0));
277 //assert(gammaIncompleteComplInv(8, 0));
278
279 // BUG: infinite loop if p == 0!
280 //writefln(gammaIncompleteComplInv(8, 0));
281
282 //writefln(gammaIncompleteComplInv(8, 1e-50));
283 //writefln(gammaIncompleteComplInv(12, 0.99));
284
285 }
Note: See TracBrowser for help on using the browser.