123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
/**
 * Cumulative Probability Distribution Functions
 *
 * Copyright: Based on the CEPHES math library, which is
 *            Copyright (C) 1994 Stephen L. Moshier (moshier@world.std.com).
 * License:   BSD style: $(LICENSE)
 * Authors:   Stephen L. Moshier (original C code), Don Clugston
 */

/**
 * Macros:
 *  NAN = $(RED NAN)
 *  SUP = <span style="vertical-align:super;font-size:smaller">$0</span>
 *  GAMMA =  &#915;
 *  INTEGRAL = &#8747;
 *  INTEGRATE = $(BIG &#8747;<sub>$(SMALL $1)</sub><sup>$2</sup>)
 *  POWER = $1<sup>$2</sup>
 *  BIGSUM = $(BIG &Sigma; <sup>$2</sup><sub>$(SMALL $1)</sub>)
 *  CHOOSE = $(BIG &#40;) <sup>$(SMALL $1)</sup><sub>$(SMALL $2)</sub> $(BIG &#41;)
 *  TABLE_SV = <table border=1 cellpadding=4 cellspacing=0>
 *      <caption>Special Values</caption>
 *      $0</table>
 *  SVH = $(TR $(TH $1) $(TH $2))
 *  SV  = $(TR $(TD $1) $(TD $2))
 */

module tango.math.Probability;
static import tango.math.ErrorFunction;
private import tango.math.GammaFunction;
private import tango.math.Math;
private import tango.math.IEEE;


/***
Cumulative distribution function for the Normal distribution, and its complement.

The normal (or Gaussian, or bell-shaped) distribution is
defined as:

normalDist(x) = 1/$(SQRT) &pi; $(INTEGRAL -$(INFINITY), x) exp( - $(POWER t, 2)/2) dt
    = 0.5 + 0.5 * erf(x/sqrt(2))
    = 0.5 * erfc(- x/sqrt(2))

Note that
normalDistribution(x) = 1 - normalDistribution(-x).

Accuracy:
Within a few bits of machine resolution over the entire
range.

References:
$(LINK http://www.netlib.org/cephes/ldoubdoc.html),
G. Marsaglia, "Evaluating the Normal Distribution",
Journal of Statistical Software <b>11</b>, (July 2004).
*/
real normalDistribution(real a)
{
    return tango.math.ErrorFunction.normalDistributionImpl(a);
}

/** ditto */
real normalDistributionCompl(real a)
{
    return -tango.math.ErrorFunction.normalDistributionImpl(-a);
}

/******************************
 * Inverse of Normal distribution function
 *
 * Returns the argument, x, for which the area under the
 * Normal probability density function (integrated from
 * minus infinity to x) is equal to p.
 *
 * For small arguments 0 < p < exp(-2), the program computes
 * z = sqrt( -2 log(p) );  then the approximation is
 * x = z - log(z)/z  - (1/z) P(1/z) / Q(1/z) .
 * For larger arguments,  x/sqrt(2 pi) = w + w^3 R(w^2)/S(w^2)) ,
 * where w = p - 0.5 .
 */
real normalDistributionInv(real p)
{
    return tango.math.ErrorFunction.normalDistributionInvImpl(p);
}

/** ditto */
real normalDistributionComplInv(real p)
{
    return -tango.math.ErrorFunction.normalDistributionInvImpl(-p);
}

debug(UnitTest) {
unittest {
    assert(feqrel(normalDistributionInv(normalDistribution(0.1)),0.1L)>=real.mant_dig-4);
    assert(feqrel(normalDistributionComplInv(normalDistributionCompl(0.1)),0.1L)>=real.mant_dig-4);
}
}

/** Student's t cumulative distribution function
 *
 * Computes the integral from minus infinity to t of the Student
 * t distribution with integer nu > 0 degrees of freedom:
 *
 *   $(GAMMA)( (nu+1)/2) / ( sqrt(nu &pi;) $(GAMMA)(nu/2) ) *
 * $(INTEGRATE -&infin;, t) $(POWER (1+$(POWER x, 2)/nu), -(nu+1)/2) dx
 *
 * Can be used to test whether the means of two normally distributed populations
 * are equal.
 *
 * It is related to the incomplete beta integral:
 *        1 - studentsDistribution(nu,t) = 0.5 * betaDistribution( nu/2, 1/2, z )
 * where
 *        z = nu/(nu + t<sup>2</sup>).
 *
 * For t < -1.6, this is the method of computation.  For higher t,
 * a direct method is derived from integration by parts.
 * Since the function is symmetric about t=0, the area under the
 * right tail of the density is found by calling the function
 * with -t instead of t.
 */
real studentsTDistribution(int nu, real t)
in{
   assert(nu>0);
}
body{
  /* Based on code from Cephes Math Library Release 2.3:  January, 1995
     Copyright 1984, 1995 by Stephen L. Moshier
 */

    if ( nu <= 0 ) return NaN(TANGO_NAN.STUDENTSDDISTRIBUTION_DOMAIN); // domain error -- or should it return 0?
    if ( t == 0.0 )  return 0.5;

    real rk, z, p;

    if ( t < -1.6 ) {
        rk = nu;
        z = rk / (rk + t * t);
        return 0.5L * betaIncomplete( 0.5L*rk, 0.5L, z );
    }

    /*  compute integral from -t to + t */

    rk = nu;    /* degrees of freedom */

    real x;
    if (t < 0) x = -t; else x = t;
    z = 1.0L + ( x * x )/rk;

    real f, tz;
    int j;

    if ( nu & 1)    {
        /*  computation for odd nu  */
        real xsqk = x/sqrt(rk);
        p = atan( xsqk );
        if ( nu > 1 )   {
            f = 1.0L;
            tz = 1.0L;
            j = 3;
            while(  (j<=(nu-2)) && ( (tz/f) > real.epsilon )  ) {
                tz *= (j-1)/( z * j );
                f += tz;
                j += 2;
            }
            p += f * xsqk/z;
            }
        p *= 2.0L/PI;
    } else {
        /*  computation for even nu */
        f = 1.0L;
        tz = 1.0L;
        j = 2;

        while ( ( j <= (nu-2) ) && ( (tz/f) > real.epsilon )  ) {
            tz *= (j - 1)/( z * j );
            f += tz;
            j += 2;
        }
        p = f * x/sqrt(z*rk);
    }
    if ( t < 0.0L )
        p = -p; /* note destruction of relative accuracy */

    p = 0.5L + 0.5L * p;
    return p;
}

/** Inverse of Student's t distribution
 *
 * Given probability p and degrees of freedom nu,
 * finds the argument t such that the one-sided
 * studentsDistribution(nu,t) is equal to p.
 *
 * Params:
 * nu = degrees of freedom. Must be >1
 * p  = probability. 0 < p < 1
 */
real studentsTDistributionInv(int nu, real p )
in {
   assert(nu>0);
   assert(p>=0.0L && p<=1.0L);
}
body
{
    if (p==0) return -real.infinity;
    if (p==1) return real.infinity;

    real rk, z;
    rk =  nu;

    if ( p > 0.25L && p < 0.75L ) {
        if ( p == 0.5L ) return 0;
        z = 1.0L - 2.0L * p;
        z = betaIncompleteInv( 0.5L, 0.5L*rk, fabs(z) );
        real t = sqrt( rk*z/(1.0L-z) );
        if( p < 0.5L )
            t = -t;
        return t;
    }
    int rflg = -1; // sign of the result
    if (p >= 0.5L) {
        p = 1.0L - p;
        rflg = 1;
    }
    z = betaIncompleteInv( 0.5L*rk, 0.5L, 2.0L*p );

    if (z<0) return rflg * real.infinity;
    return rflg * sqrt( rk/z - rk );
}

debug(UnitTest) {
unittest {

// There are simple forms for nu = 1 and nu = 2.

// if (nu == 1), tDistribution(x) = 0.5 + atan(x)/PI
//              so tDistributionInv(p) = tan( PI * (p-0.5) );
// nu==2: tDistribution(x) = 0.5 * (1 + x/ sqrt(2+x*x) )

assert(studentsTDistribution(1, -0.4)== 0.5 + atan(-0.4)/PI);
assert(studentsTDistribution(2, 0.9) == 0.5L * (1 + 0.9L/sqrt(2.0L + 0.9*0.9)) );
assert(studentsTDistribution(2, -5.4) == 0.5L * (1 - 5.4L/sqrt(2.0L + 5.4*5.4)) );

// return true if a==b to given number of places.
bool isfeqabs(real a, real b, real diff)
{
  return fabs(a-b) < diff;
}

// Check a few spot values with statsoft.com (Mathworld values are wrong!!)
// According to statsoft.com, studentsDistributionInv(10, 0.995)= 3.16927.

// The remaining values listed here are from Excel, and are unlikely to be accurate
// in the last decimal places. However, they are helpful as a sanity check.

//  Microsoft Excel 2003 gives TINV(2*(1-0.995), 10) == 3.16927267160917
assert(isfeqabs(studentsTDistributionInv(10, 0.995), 3.169_272_67L, 0.000_000_005L));


assert(isfeqabs(studentsTDistributionInv(8, 0.6), 0.261_921_096_769_043L,0.000_000_000_05L));
// -TINV(2*0.4, 18) ==  -0.257123042655869

assert(isfeqabs(studentsTDistributionInv(18, 0.4), -0.257_123_042_655_869L, 0.000_000_000_05L));
assert( feqrel(studentsTDistribution(18, studentsTDistributionInv(18, 0.4L)),0.4L)
 > real.mant_dig-5 );
assert( feqrel(studentsTDistribution(11, studentsTDistributionInv(11, 0.9L)),0.9L)
  > real.mant_dig-2);
}
}

/** The F distribution, its complement, and inverse.
 *
 * The F density function (also known as Snedcor's density or the
 * variance ratio density) is the density
 * of x = (u1/df1)/(u2/df2), where u1 and u2 are random
 * variables having $(POWER &chi;,2) distributions with df1
 * and df2 degrees of freedom, respectively.
 *
 * fDistribution returns the area from zero to x under the F density
 * function.   The complementary function,
 * fDistributionCompl, returns the area from x to &infin; under the F density function.
 *
 * The inverse of the complemented F distribution,
 * fDistributionComplInv, finds the argument x such that the integral
 * from x to infinity of the F density is equal to the given probability y.
 *
 * Can be used to test whether the means of multiple normally distributed
 * populations, all with the same standard deviation, are equal;
 * or to test that the standard deviations of two normally distributed
 * populations are equal.
 *
 * Params:
 *  df1 = Degrees of freedom of the first variable. Must be >= 1
 *  df2 = Degrees of freedom of the second variable. Must be >= 1
 *  x  = Must be >= 0
 */
real fDistribution(int df1, int df2, real x)
in {
 assert(df1>=1 && df2>=1);
 assert(x>=0);
}
body{
    real a = cast(real)(df1);
    real b = cast(real)(df2);
    real w = a * x;
    w = w/(b + w);
    return betaIncomplete(0.5L*a, 0.5L*b, w);
}

/** ditto */
real fDistributionCompl(int df1, int df2, real x)
in {
 assert(df1>=1 && df2>=1);
 assert(x>=0);
}
body{
    real a = cast(real)(df1);
    real b = cast(real)(df2);
    real w = b / (b + a * x);
    return betaIncomplete( 0.5L*b, 0.5L*a, w );
}

/*
 * Inverse of complemented F distribution
 *
 * Finds the F density argument x such that the integral
 * from x to infinity of the F density is equal to the
 * given probability p.
 *
 * This is accomplished using the inverse beta integral
 * function and the relations
 *
 *      z = betaIncompleteInv( df2/2, df1/2, p ),
 *      x = df2 (1-z) / (df1 z).
 *
 * Note that the following relations hold for the inverse of
 * the uncomplemented F distribution:
 *
 *      z = betaIncompleteInv( df1/2, df2/2, p ),
 *      x = df2 z / (df1 (1-z)).
*/

/** ditto */
real fDistributionComplInv(int df1, int df2, real p )
in {
 assert(df1>=1 && df2>=1);
 assert(p>=0 && p<=1.0);
}
body{
    real a = df1;
    real b = df2;
    /* Compute probability for x = 0.5.  */
    real w = betaIncomplete( 0.5L*b, 0.5L*a, 0.5L );
    /* If that is greater than p, then the solution w < .5.
       Otherwise, solve at 1-p to remove cancellation in (b - b*w).  */
    if ( w > p || p < 0.001L) {
        w = betaIncompleteInv( 0.5L*b, 0.5L*a, p );
        return (b - b*w)/(a*w);
    } else {
        w = betaIncompleteInv( 0.5L*a, 0.5L*b, 1.0L - p );
        return b*w/(a*(1.0L-w));
    }
}

debug(UnitTest) {
unittest {
// fDistCompl(df1, df2, x) = Excel's FDIST(x, df1, df2)
  assert(fabs(fDistributionCompl(6, 4, 16.5) - 0.00858719177897249L)< 0.0000000000005L);
  assert(fabs((1-fDistribution(12, 23, 0.1)) - 0.99990562845505L)< 0.0000000000005L);
  assert(fabs(fDistributionComplInv(8, 34, 0.2) - 1.48267037661408L)< 0.0000000005L);
  assert(fabs(fDistributionComplInv(4, 16, 0.008) - 5.043_537_593_48596L)< 0.0000000005L);
  // Regression test: This one used to fail because of a bug in the definition of MINLOG.
  assert(feqrel(fDistributionCompl(4, 16, fDistributionComplInv(4,16, 0.008)), 0.008L)>=real.mant_dig-3);
}
}

/** $(POWER &chi;,2) cumulative distribution function and its complement.
 *
 * Returns the area under the left hand tail (from 0 to x)
 * of the Chi square probability density function with
 * v degrees of freedom. The complement returns the area under
 * the right hand tail (from x to &infin;).
 *
 *  chiSqrDistribution(x | v) = ($(INTEGRATE 0, x)
 *          $(POWER t, v/2-1) $(POWER e, -t/2) dt )
 *             / $(POWER 2, v/2) $(GAMMA)(v/2)
 *
 *  chiSqrDistributionCompl(x | v) = ($(INTEGRATE x, &infin;)
 *          $(POWER t, v/2-1) $(POWER e, -t/2) dt )
 *             / $(POWER 2, v/2) $(GAMMA)(v/2)
 *
 * Params:
 *  v  = degrees of freedom. Must be positive.
 *  x  = the $(POWER &chi;,2) variable. Must be positive.
 *
 */
real chiSqrDistribution(real v, real x)
in {
 assert(x>=0);
 assert(v>=1.0);
}
body{
   return gammaIncomplete( 0.5*v, 0.5*x);
}

/** ditto */
real chiSqrDistributionCompl(real v, real x)
in {
 assert(x>=0);
 assert(v>=1.0);
}
body{
    return gammaIncompleteCompl( 0.5L*v, 0.5L*x );
}

/**
 *  Inverse of complemented $(POWER &chi;, 2) distribution
 *
 * Finds the $(POWER &chi;, 2) argument x such that the integral
 * from x to &infin; of the $(POWER &chi;, 2) density is equal
 * to the given cumulative probability p.
 *
 * Params:
 * p = Cumulative probability. 0<= p <=1.
 * v = Degrees of freedom. Must be positive.
 *
 */
real chiSqrDistributionComplInv(real v, real p)
in {
  assert(p>=0 && p<=1.0L);
  assert(v>=1.0L);
}
body
{
   return  2.0 * gammaIncompleteComplInv( 0.5*v, p);
}

debug(UnitTest) {
unittest {
  assert(feqrel(chiSqrDistributionCompl(3.5L, chiSqrDistributionComplInv(3.5L, 0.1L)), 0.1L)>=real.mant_dig-5);
  assert(chiSqrDistribution(19.02L, 0.4L) + chiSqrDistributionCompl(19.02L, 0.4L) ==1.0L);
}
}

/**
 * The &Gamma; distribution and its complement
 *
 * The &Gamma; distribution is defined as the integral from 0 to x of the
 * gamma probability density function. The complementary function returns the
 * integral from x to &infin;
 *
 * gammaDistribution = ($(INTEGRATE 0, x) $(POWER t, b-1)$(POWER e, -at) dt) $(POWER a, b)/&Gamma;(b)
 *
 * x must be greater than 0.
 */
real gammaDistribution(real a, real b, real x)
in {
   assert(x>=0);
}
body {
   return gammaIncomplete(b, a*x);
}

/** ditto */
real gammaDistributionCompl(real a, real b, real x )
in {
   assert(x>=0);
}
body {
   return gammaIncompleteCompl( b, a * x );
}

debug(UnitTest) {
unittest {
    assert(gammaDistribution(7,3,0.18)+gammaDistributionCompl(7,3,0.18)==1);
}
}

/**********************
 *  Beta distribution and its inverse
 *
 * Returns the incomplete beta integral of the arguments, evaluated
 * from zero to x.  The function is defined as
 *
 * betaDistribution = &Gamma;(a+b)/(&Gamma;(a) &Gamma;(b)) *
 * $(INTEGRATE 0, x) $(POWER t, a-1)$(POWER (1-t),b-1) dt
 *
 * The domain of definition is 0 <= x <= 1.  In this
 * implementation a and b are restricted to positive values.
 * The integral from x to 1 may be obtained by the symmetry
 * relation
 *
 *    betaDistributionCompl(a, b, x )  =  betaDistribution( b, a, 1-x )
 *
 * The integral is evaluated by a continued fraction expansion
 * or, when b*x is small, by a power series.
 *
 * The inverse finds the value of x for which betaDistribution(a,b,x) - y = 0
 */
real betaDistribution(real a, real b, real x )
{
   return betaIncomplete(a, b, x );
}

/** ditto */
real betaDistributionCompl(real a, real b, real x)
{
    return betaIncomplete(b, a, 1-x);
}

/** ditto */
real betaDistributionInv(real a, real b, real y)
{
    return betaIncompleteInv(a, b, y);
}

/** ditto */
real betaDistributionComplInv(real a, real b, real y)
{
    return 1-betaIncompleteInv(b, a, y);
}

debug(UnitTest) {
unittest {
    assert(feqrel(betaDistributionInv(2, 6, betaDistribution(2,6, 0.7L)),0.7L)>=real.mant_dig-3);
    assert(feqrel(betaDistributionComplInv(1.3, 8, betaDistributionCompl(1.3,8, 0.01L)),0.01L)>=real.mant_dig-4);
}
}

/**
 * The Poisson distribution, its complement, and inverse
 *
 * k is the number of events. m is the mean.
 * The Poisson distribution is defined as the sum of the first k terms of
 * the Poisson density function.
 * The complement returns the sum of the terms k+1 to &infin;.
 *
 * poissonDistribution = $(BIGSUM j=0, k) $(POWER e, -m) $(POWER m, j)/j!
 *
 * poissonDistributionCompl = $(BIGSUM j=k+1, &infin;) $(POWER e, -m) $(POWER m, j)/j!
 *
 * The terms are not summed directly; instead the incomplete
 * gamma integral is employed, according to the relation
 *
 * y = poissonDistribution( k, m ) = gammaIncompleteCompl( k+1, m ).
 *
 * The arguments must both be positive.
 */
real poissonDistribution(int k, real m )
in {
  assert(k>=0);
  assert(m>0);
}
body {
    return gammaIncompleteCompl( k+1.0, m );
}

/** ditto */
real poissonDistributionCompl(int k, real m )
in {
  assert(k>=0);
  assert(m>0);
}
body {
  return gammaIncomplete( k+1.0, m );
}

/** ditto */
real poissonDistributionInv( int k, real p )
in {
  assert(k>=0);
  assert(p>=0.0 && p<=1.0);
}
body {
    return gammaIncompleteComplInv(k+1, p);
}

debug(UnitTest) {
unittest {
// = Excel's POISSON(k, m, TRUE)
    assert( fabs(poissonDistribution(5, 6.3)
                - 0.398771730072867L) < 0.000000000000005L);
    assert( feqrel(poissonDistributionInv(8, poissonDistribution(8, 2.7e3L)), 2.7e3L)>=real.mant_dig-2);
    assert( poissonDistribution(2, 8.4e-5) + poissonDistributionCompl(2, 8.4e-5) == 1.0L);
}
}

/***********************************
 *  Binomial distribution and complemented binomial distribution
 *
 * The binomial distribution is defined as the sum of the terms 0 through k
 * of the Binomial probability density.
 * The complement returns the sum of the terms k+1 through n.
 *
 binomialDistribution = $(BIGSUM j=0, k) $(CHOOSE n, j) $(POWER p, j) $(POWER (1-p), n-j)

 binomialDistributionCompl = $(BIGSUM j=k+1, n) $(CHOOSE n, j) $(POWER p, j) $(POWER (1-p), n-j)
 *
 * The terms are not summed directly; instead the incomplete
 * beta integral is employed, according to the formula
 *
 * y = binomialDistribution( k, n, p ) = betaDistribution( n-k, k+1, 1-p ).
 *
 * The arguments must be positive, with p ranging from 0 to 1, and k<=n.
 */
real binomialDistribution(int k, int n, real p )
in {
   assert(p>=0 && p<=1.0); // domain error
   assert(k>=0 && k<=n);
}
body{
    real dk, dn, q;
    if( k == n )
        return 1.0L;

    q = 1.0L - p;
    dn = n - k;
    if ( k == 0 ) {
        return pow( q, dn );
    } else {
        return betaIncomplete( dn, k + 1, q );
    }
}

debug(UnitTest) {
unittest {
    // = Excel's BINOMDIST(k, n, p, TRUE)
    assert( fabs(binomialDistribution(8, 12, 0.5)
                - 0.927001953125L) < 0.0000000000005L);
    assert( fabs(binomialDistribution(0, 3, 0.008L)
                - 0.976191488L) < 0.00000000005L);
    assert(binomialDistribution(7,7, 0.3)==1.0);
}
}

 /** ditto */
real binomialDistributionCompl(int k, int n, real p )
in {
   assert(p>=0 && p<=1.0); // domain error
   assert(k>=0 && k<=n);
}
body{
    if ( k == n ) {
        return 0;
    }
    real dn = n - k;
    if ( k == 0 ) {
        if ( p < .01L )
            return -expm1( dn * log1p(-p) );
        else
            return 1.0L - pow( 1.0L-p, dn );
    } else {
        return betaIncomplete( k+1, dn, p );
    }
}

debug(UnitTest){
unittest {
    // = Excel's (1 - BINOMDIST(k, n, p, TRUE))
    assert( fabs(1.0L-binomialDistributionCompl(0, 15, 0.003)
                - 0.955932824838906L) < 0.0000000000000005L);
    assert( fabs(1.0L-binomialDistributionCompl(0, 25, 0.2)
                - 0.00377789318629572L) < 0.000000000000000005L);
    assert( fabs(1.0L-binomialDistributionCompl(8, 12, 0.5)
                - 0.927001953125L) < 0.00000000000005L);
    assert(binomialDistributionCompl(7,7, 0.3)==0.0);
}
}

/** Inverse binomial distribution
 *
 * Finds the event probability p such that the sum of the
 * terms 0 through k of the Binomial probability density
 * is equal to the given cumulative probability y.
 *
 * This is accomplished using the inverse beta integral
 * function and the relation
 *
 * 1 - p = betaDistributionInv( n-k, k+1, y ).
 *
 * The arguments must be positive, with 0 <= y <= 1, and k <= n.
 */
real binomialDistributionInv( int k, int n, real y )
in {
   assert(y>=0 && y<=1.0); // domain error
   assert(k>=0 && k<=n);
}
body{
    real dk, p;
    real dn = n - k;
    if ( k == 0 ) {
        if( y > 0.8L )
            p = -expm1( log1p(y-1.0L) / dn );
        else
            p = 1.0L - pow( y, 1.0L/dn );
    } else {
        dk = k + 1;
        p = betaIncomplete( dn, dk, y );
        if( p > 0.5 )
            p = betaIncompleteInv( dk, dn, 1.0L-y );
        else
            p = 1.0 - betaIncompleteInv( dn, dk, y );
    }
    return p;
}

debug(UnitTest){
unittest {
    real w = binomialDistribution(9, 15, 0.318L);
    assert(feqrel(binomialDistributionInv(9, 15, w), 0.318L)>=real.mant_dig-3);
    w = binomialDistribution(5, 35, 0.718L);
    assert(feqrel(binomialDistributionInv(5, 35, w), 0.718L)>=real.mant_dig-3);
    w = binomialDistribution(0, 24, 0.637L);
    assert(feqrel(binomialDistributionInv(0, 24, w), 0.637L)>=real.mant_dig-3);
    w = binomialDistributionInv(0, 59, 0.962L);
    assert(feqrel(binomialDistribution(0, 59, w), 0.962L)>=real.mant_dig-5);
}
}

/** Negative binomial distribution and its inverse
 *
 * Returns the sum of the terms 0 through k of the negative
 * binomial distribution:
 *
 * $(BIGSUM j=0, k) $(CHOOSE n+j-1, j-1) $(POWER p, n) $(POWER (1-p), j)
 *
 * In a sequence of Bernoulli trials, this is the probability
 * that k or fewer failures precede the n-th success.
 *
 * The arguments must be positive, with 0 < p < 1 and r>0.
 *
 * The inverse finds the argument y such
 * that negativeBinomialDistribution(k,n,y) is equal to p.
 *
 * The Geometric Distribution is a special case of the negative binomial
 * distribution.
 * -----------------------
 * geometricDistribution(k, p) = negativeBinomialDistribution(k, 1, p);
 * -----------------------
 * References:
 * $(LINK http://mathworld.wolfram.com/NegativeBinomialDistribution.html)
 */

real negativeBinomialDistribution(int k, int n, real p )
in {
   assert(p>=0 && p<=1.0); // domain error
   assert(k>=0);
}
body{
    if ( k == 0 ) return pow( p, n );
    return betaIncomplete( n, k + 1, p );
}

/** ditto */
real negativeBinomialDistributionInv(int k, int n, real p )
in {
   assert(p>=0 && p<=1.0); // domain error
   assert(k>=0);
}
body{
    return betaIncompleteInv(n, k + 1, p);
}

debug(UnitTest) {
unittest {
  // Value obtained by sum of terms of MS Excel 2003's NEGBINOMDIST.
  assert( fabs(negativeBinomialDistribution(10, 20, 0.2) - 3.830_52E-08)< 0.000_005e-08);
  assert(feqrel(negativeBinomialDistributionInv(14, 208, negativeBinomialDistribution(14, 208, 1e-4L)), 1e-4L)>=real.mant_dig-3);
}
}