1 module rmathd.binomial;
2 
3 public import rmathd.common;
4 public import rmathd.beta;
5 //import toms708;
6 //import normal;
7 
8 /*
9 ** dmd binomial.d common.d toms708.d beta.d normal.d && ./binomial
10 */
11 
12 T dbinom(T: double)(T x, T n, T p, int give_log)
13 {
14     /* NaNs propagated correctly */
15     if (isNaN(x) || isNaN(n) || isNaN(p))
16     	return x + n + p;
17 
18     mixin R_D__0!give_log;
19 
20     if (p < 0 || p > 1 || R_D_negInonint!T(n))
21 	    return T.nan;
22     mixin R_D_nonint_check!(x);
23     if (x < 0 || !isFinite(x))
24         return R_D__0;
25 
26     n = nearbyint(n);
27     x = nearbyint(x);
28 
29     return dbinom_raw(x, n, p, 1 - p, give_log);
30 }
31 
32 T pbinom(T: double)(T x, T n, T p, int lower_tail, int log_p)
33 {
34     if (isNaN(x) || isNaN(n) || isNaN(p))
35         return x + n + p;
36     if (!isFinite(n) || !isFinite(p))
37         return T.nan;
38 
39     if(R_nonint!T(n)) {
40         return T.nan;
41     }
42     n = nearbyint(n);
43     if(n < 0 || p < 0 || p > 1)
44         return T.nan;
45 
46     if (x < 0)
47         return R_DT_0!T(lower_tail, log_p);
48     x = floor(x + 1e-7);
49     if (n <= x)
50         return R_DT_1!T(lower_tail, log_p);
51     return pbeta!T(p, x + 1, n - x, !lower_tail, log_p);
52 }
53 
54 /* toms708.c */
55 static T do_search(T)(T y, T *z, T p, T n, T pr, T incr)
56 {
57     if(*z >= p) {
58             /* search to the left */
59         //#ifdef DEBUG_qbinom
60         //    REprintf("\tnew z=%7g >= p = %7g  --> search to left (y--) ..\n", z,p);
61         //#endif
62         for(;;) {
63             T newz;
64             if(y == 0 || (newz = pbinom!T(y - incr, n, pr, /*l._t.*/1, /*log_p*/0)) < p)
65                 return y;
66             y = fmax2!T(0, y - incr);
67             *z = newz;
68         }
69     } else {      /* search to the right */
70         //#ifdef DEBUG_qbinom
71         //    REprintf("\tnew z=%7g < p = %7g  --> search to right (y++) ..\n", z,p);
72         //#endif
73         for(;;) {
74             y = fmin2!T(y + incr, n);
75             if(y == n || (*z = pbinom!T(y, n, pr, /*l._t.*/1, /*log_p*/0)) >= p)
76                 return y;
77         }
78     }
79 }
80 
81 
82 T qbinom(T: double)(T p, T n, T pr, int lower_tail, int log_p)
83 {
84     T q, mu, sigma, gamma, z, y;
85     
86     if (isNaN(p) || isNaN(n) || isNaN(pr))
87         return p + n + pr;
88     
89     if(!isFinite(n) || !isFinite(pr))
90         return T.nan;
91     /* if log_p is true, p = -Inf is a legitimate value */
92     if(!isFinite(p) && !log_p)
93         return T.nan;
94 
95     if(n != floor(n + 0.5))
96         return T.nan;
97     if (pr < 0 || pr > 1 || n < 0)
98         return T.nan;
99 
100     mixin (R_Q_P01_boundaries!(p, 0, n)());
101 
102     if (pr == 0. || n == 0)
103         return 0.;
104 
105     q = 1 - pr;
106     if(q == 0.) return n; /* covers the full range of the distribution */
107     mu = n * pr;
108     sigma = sqrt(n * pr * q);
109     gamma = (q - pr) / sigma;
110 
111     //#ifdef DEBUG_qbinom
112     //    REprintf("qbinom(p=%7g, n=%g, pr=%7g, l.t.=%d, log=%d): sigm=%g, gam=%g\n",
113     //         p,n,pr, lower_tail, log_p, sigma, gamma);
114     //#endif
115 
116     /* Note : "same" code in qpois.c, qbinom.c, qnbinom.c --
117      * FIXME: This is far from optimal [cancellation for p ~= 1, etc]: */
118      mixin R_DT_qIv!(p);
119     if(!lower_tail || log_p) {
120         p = R_DT_qIv; /* need check again (cancellation!): */
121         if (p == 0.)
122             return 0.;
123         if (p == 1.)
124             return n;
125     }
126     /* temporary hack --- FIXME --- */
127     if (p + 1.01*DBL_EPSILON >= 1.) return n;
128 
129     /* y := approx.value (Cornish-Fisher expansion) :  */
130     z = qnorm!T(p, 0., 1., /*lower_tail*/1, /*log_p*/0);
131     y = floor(mu + sigma * (z + gamma * (z*z - 1) / 6) + 0.5);
132 
133     if(y > n) /* way off */ y = n;
134 
135     //#ifdef DEBUG_qbinom
136     //    REprintf("  new (p,1-p)=(%7g,%7g), z=qnorm(..)=%7g, y=%5g\n", p, 1-p, z, y);
137     //#endif
138     z = pbinom!T(y, n, pr, /*lower_tail*/1, /*log_p*/0);
139 
140     /* fuzz to ensure left continuity: */
141     p *= 1 - 64*DBL_EPSILON;
142 
143     if(n < 1e5) return do_search!T(y, &z, p, n, pr, 1);
144     /* Otherwise be a bit cleverer in the search */
145     {
146     T incr = floor(n * 0.001), oldincr;
147     do {
148         oldincr = incr;
149         y = do_search!T(y, &z, p, n, pr, incr);
150         incr = fmax2!T(1, floor(incr/100));
151     } while(oldincr > 1 && incr > n*1e-15);
152     return y;
153     }
154 }
155 
156 T rbinom(T: double)(T nin, T pp)
157 {
158     /* FIXME: These should become THREAD_specific globals : */
159 
160     static T c, fm, npq, p1, p2, p3, p4, qn;
161     static T xl, xll, xlr, xm, xr;
162 
163     static T psave = -1.0;
164     static int nsave = -1;
165     static int m;
166 
167     T f, f1, f2, u, v, w, w2, x, x1, x2, z, z2;
168     T p, q, np, g, r, al, alv, amaxp, ffm, ynorm;
169     int i, ix, k, n;
170 
171     if (!isFinite(nin))
172         return T.nan;
173     r = nearbyint(nin);
174     if (r != nin)
175         return T.nan;
176     if (!isFinite(pp) ||
177     /* n=0, p=0, p=1 are not errors <TSL>*/
178     r < 0 || pp < 0. || pp > 1.)
179         return T.nan;
180 
181     if (r == 0 || pp == 0.) return 0;
182     if (pp == 1.) return r;
183 
184     if (r >= INT_MAX)/* evade integer overflow,
185             and r == INT_MAX gave only even values */
186     return qbinom!T(unif_rand!T(), r, pp, /*lower_tail*/ 0, /*log_p*/ 0);
187     /* else */
188     n = cast(int) r;
189 
190     p = fmin2(pp, 1. - pp);
191     q = 1. - p;
192     np = n * p;
193     r = p / q;
194     g = r * (n + 1);
195 
196     /* Setup, perform only when parameters change [using static (globals): */
197 
198     /* FIXING: Want this thread safe
199        -- use as little (thread globals) as possible
200     */
201     if (pp != psave || n != nsave) {
202     psave = pp;
203     nsave = n;
204     if (np < 30.0) {
205         /* inverse cdf logic for mean less than 30 */
206         qn = R_pow_di!T(q, n);
207         goto L_np_small;
208     } else {
209         ffm = np + p;
210         m = cast(int) ffm;
211         fm = m;
212         npq = np*q;
213         p1 = cast(int)(2.195 * sqrt(npq) - 4.6 * q) + 0.5;
214         xm = fm + 0.5;
215         xl = xm - p1;
216         xr = xm + p1;
217         c = 0.134 + 20.5 / (15.3 + fm);
218         al = (ffm - xl) / (ffm - xl * p);
219         xll = al * (1.0 + 0.5 * al);
220         al = (xr - ffm) / (xr * q);
221         xlr = al * (1.0 + 0.5 * al);
222         p2 = p1 * (1.0 + c + c);
223         p3 = p2 + c / xll;
224         p4 = p3 + c / xlr;
225     }
226     } else if (n == nsave) {
227     if (np < 30.0)
228         goto L_np_small;
229     }
230 
231     /*-------------------------- np = n*p >= 30 : ------------------- */
232     for(;;) {
233       u = unif_rand!T() * p4;
234       v = unif_rand!T();
235       /* triangular region */
236       if (u <= p1) {
237       ix = cast(int)(xm - p1 * v + u);
238       goto finis;
239       }
240       /* parallelogram region */
241       if (u <= p2) {
242       x = xl + (u - p1) / c;
243       v = v * c + 1.0 - fabs(xm - x) / p1;
244       if (v > 1.0 || v <= 0.)
245           continue;
246       ix = cast(int) x;
247       } else {
248       if (u > p3) { /* right tail */
249           ix = cast(int)(xr - log(v) / xlr);
250           if (ix > n)
251           continue;
252           v = v * (u - p3) * xlr;
253       } else {/* left tail */
254           ix = cast(int)(xl + log(v) / xll);
255           if (ix < 0)
256           continue;
257           v = v * (u - p2) * xll;
258       }
259       }
260       /* determine appropriate way to perform accept/reject test */
261       k = abs(ix - m);
262       if (k <= 20 || k >= npq / 2 - 1) {
263       /* explicit evaluation */
264       f = 1.0;
265       if (m < ix) {
266           for (i = m + 1; i <= ix; i++)
267           f *= (g / i - r);
268       } else if (m != ix) {
269           for (i = ix + 1; i <= m; i++)
270           f /= (g / i - r);
271       }
272       if (v <= f)
273           goto finis;
274       } else {
275       /* squeezing using upper and lower bounds on log(f(x)) */
276       amaxp = (k / npq) * ((k * (k / 3. + 0.625) + 0.1666666666666) / npq + 0.5);
277       ynorm = -k * k / (2.0 * npq);
278       alv = log(v);
279       if (alv < ynorm - amaxp)
280           goto finis;
281       if (alv <= ynorm + amaxp) {
282           /* stirling's formula to machine accuracy */
283           /* for the final acceptance/rejection test */
284           x1 = ix + 1;
285           f1 = fm + 1.0;
286           z = n + 1 - fm;
287           w = n - ix + 1.0;
288           z2 = z * z;
289           x2 = x1 * x1;
290           f2 = f1 * f1;
291           w2 = w * w;
292           if (alv <= xm * log(f1 / x1) + (n - m + 0.5) * log(z / w) + (ix - m) * log(w * p / (x1 * q)) + (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / f2) / f2) / f2) / f2) / f1 / 166320.0 + (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / z2) / z2) / z2) / z2) / z / 166320.0 + (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / x2) / x2) / x2) / x2) / x1 / 166320.0 + (13860.0 - (462.0 - (132.0 - (99.0 - 140.0 / w2) / w2) / w2) / w2) / w / 166320.)
293           goto finis;
294       }
295       }
296   }
297 
298  L_np_small:
299     /*---------------------- np = n*p < 30 : ------------------------- */
300 
301   for(;;) {
302      ix = 0;
303      f = qn;
304      u = unif_rand!T();
305      for(;;) {
306      if (u < f)
307          goto finis;
308      if (ix > 110)
309          break;
310      u -= f;
311      ix++;
312      f *= (g / ix - r);
313      }
314   }
315  finis:
316     if (psave > 0.5)
317      ix = n - ix;
318   return cast(T)ix;
319 }
320 
321 
322 
323 void test_binomial()
324 {
325     import std.stdio: writeln;
326     writeln("dbinom: ", dbinom(2., 4., .5, 0));
327     writeln("pbinom: ", pbinom(2., 4., .5, 1, 0));
328     writeln("qbinom: ", qbinom(.8, 4., .5, 1, 0));
329     writeln("rbinom: ", rbinom(20, .3), ", rbinom: ", rbinom(20, .3), ", rbinom: ", rbinom(20, .3));
330 }