/*
 * statistics - Some assorted statistics functions.
 *
 * Copyright (C) 2013,2021 Christoph Zurnieden
 *
 * Calc is open software; you can redistribute it and/or modify it under
 * the terms of the version 2.1 of the GNU Lesser General Public License
 * as published by the Free Software Foundation.
 *
 * Calc is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General
 * Public License for more details.
 *
 * A copy of version 2.1 of the GNU Lesser General Public License is
 * distributed with calc under the filename COPYING-LGPL.  You should have
 * received a copy with calc; if not, write to Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Under source code control:	2013/08/11 01:31:28
 * File existed as early as:	2013
 */


static resource_debug_level;
resource_debug_level = config("resource_debug", 0);


/*
 *  get dependencies
 */
read -once factorial2 brentsolve


/*******************************************************************************
 *
 *
 *                Continuous distributions
 *
 *
 ******************************************************************************/

/* regularized incomplete gamma function like in Octave, hence the name */
define gammaincoctave(z,a){
  local tmp;
  tmp = gamma(z);
  return (tmp-gammainc(a,z))/tmp;
}

/* Inverse incomplete beta function. Old and slow. */
static __CZ__invbeta_a;
static __CZ__invbeta_b;
static __CZ__invbeta_x;
define __CZ__invbeta(x){
   return __CZ__invbeta_x-__CZ__ibetaas63(x,__CZ__invbeta_a,__CZ__invbeta_b);
}

define invbetainc_slow(x,a,b){
  local flag ret eps;
  /* place checks and balances here */
  eps = epsilon();
  if(.5 < x){
    __CZ__invbeta_x = 1 - x;
    __CZ__invbeta_a = b;
    __CZ__invbeta_b = a;
    flag = 1;
  }
  else{
    __CZ__invbeta_x = x;
    __CZ__invbeta_a = a;
    __CZ__invbeta_b = b;
    flag = 0;
  }

  ret =  brentsolve2(0,1,1);

  if(flag == 1)
    ret = 1-ret;
  epsilon(eps);
  return ret;
}

/* Inverse incomplete beta function. Still old but not as slow as the function
   above. */
/*
  Purpose:

    invbetainc computes inverse of the incomplete Beta function.

  Licensing:

    This code is distributed under the GNU LGPL license.

  Modified:

    10 August 2013

  Author:

    Original FORTRAN77 version by GW Cran, KJ Martin, GE Thomas.
    C version by John Burkardt.
    Calc version by Christoph Zurnieden

  Reference:

    GW Cran, KJ Martin, GE Thomas,
    Remark AS R19 and Algorithm AS 109:
    A Remark on Algorithms AS 63: The Incomplete Beta Integral
    and AS 64: Inverse of the Incomplete Beta integral,
    Applied Statistics,
    Volume 26, Number 1, 1977, pages 111-114.

  Parameters:

    Input,  P, Q, the parameters of the incomplete
    Beta function.

    Input,  BETA, the logarithm of the value of
    the complete Beta function.

    Input,  ALPHA, the value of the incomplete Beta
    function.  0 <= ALPHA <= 1.

    Output,  the argument of the incomplete
    Beta function which produces the value ALPHA.

  Local Parameters:

    Local, SAE, the most negative decimal exponent
    which does not cause an underflow.
*/
define invbetainc(x,a,b){
  return __CZ__invbetainc(a,b,lnbeta(a,b),x);
}

define __CZ__invbetainc(p,q,beta,alpha){
  local a acu adj fpu g h iex indx pp prev qq r s sae sq t tx value;
  local w xin y yprev places eps;

  /* Dirty trick, don't try at home */
  eps= epsilon(epsilon()^2);
  sae = -((log(1/epsilon())/log(2))//2);
  fpu = 10.0^sae;

  places = highbit(1 + int(1/epsilon())) + 1;
  value = alpha;
  if( p <= 0.0 ){
    epsilon(eps);
    return newerror("invbeta: argument p <= 0");
  }
  if( q <= 0.0 ){
    epsilon(eps);
    return newerror("invbeta: argument q <= 0");
  }

  if( alpha < 0.0 || 1.0 < alpha ){
    epsilon(eps);
    return newerror("invbeta: argument alpha out of domain");
  }
  if( alpha == 0.0 ){
    epsilon(eps);
    return 0;
  }
  if( alpha == 1.0 ){
    epsilon(eps);
    return 1;
  }
  if ( 0.5 < alpha ){
    a = 1.0 - alpha;
    pp = q;
    qq = p;
    indx = 1;
  }
  else{
    a = alpha;
    pp = p;
    qq = q;
    indx = 0;
  }
  r = sqrt ( - ln ( a * a ) );

  y = r-(2.30753+0.27061*r)/(1.0+(0.99229+0.04481*r)*r);

  if ( 1.0 < pp && 1.0 < qq ){
    r = ( y * y - 3.0 ) / 6.0;
    s = 1.0 / ( pp + pp - 1.0 );
    t = 1.0 / ( qq + qq - 1.0 );
    h = 2.0 / ( s + t );
    w = y*sqrt(h+r)/h-(t-s)*(r+5.0/6.0-2.0/(3.0*h));
    value = pp / ( pp + qq * exp ( w + w ) );
  }
  else{
    r = qq + qq;
    t = 1.0 / ( 9.0 * qq );
    t = r *  ( 1.0 - t + y * sqrt ( t )^ 3 );

    if ( t <= 0.0 ){
      value = 1.0 - exp ( ( ln ( ( 1.0 - a ) * qq ) + beta ) / qq );
    }
    else{
      t = ( 4.0 * pp + r - 2.0 ) / t;

      if ( t <= 1.0 ) {
        value = exp ( ( ln ( a * pp ) + beta ) / pp );
      }
      else{
        value = 1.0 - 2.0 / ( t + 1.0 );
      }
    }
  }
  r = 1.0 - pp;
  t = 1.0 - qq;
  yprev = 0.0;
  sq = 1.0;
  prev = 1.0;

  if ( value < 0.0001 )
    value = 0.0001;

  if ( 0.9999 < value )
    value = 0.9999;

  acu = 10^sae;

  for ( ; ; ){
    y = bround(__CZ__ibetaas63( value, pp, qq, beta),places);
    xin = value;
    y = bround(exp(ln(y-a)+(beta+r*ln(xin)+t*ln(1.0- xin ) )),places);

    if ( y * yprev <= 0.0 ) {
      prev = max ( sq, fpu );
    }

    g = 1.0;

    for ( ; ; ){
      for ( ; ; ){
        adj = g * y;
        sq = adj * adj;
        if ( sq < prev ){
          tx = value - adj;
          if ( 0.0 <= tx && tx <= 1.0 )  break;
        }
        g = g / 3.0;
      }
      if ( prev <= acu ){
        if ( indx )
          value = 1.0 - value;
        epsilon(eps);
        return value;
      }
      if ( y * y <= acu ){
        if ( indx )
          value = 1.0 - value;
        epsilon(eps);
        return value;
      }
      if ( tx != 0.0 && tx != 1.0 )
        break;
      g = g / 3.0;
    }
    if ( tx == value )  break;
    value = tx;
    yprev = y;
  }
  if ( indx )
    value = 1.0 - value;

  epsilon(eps);
  return value;
}

/*******************************************************************************
 *
 *
 *                Beta distribution
 *
 *
 ******************************************************************************/

define betapdf(x,a,b){
  if(x<0 || x>1) return newerror("betapdf: parameter x out of domain");
  if(a<=0) return newerror("betapdf: parameter a out of domain");
  if(b<=0) return newerror("betapdf: parameter b out of domain");

  return 1/beta(a,b) *x^(a-1)*(1-x)^(b-1);
}

define betacdf(x,a,b){
  if(x<0 || x>1) return newerror("betacdf: parameter x out of domain");
  if(a<=0) return newerror("betacdf: parameter a out of domain");
  if(b<=0) return newerror("betacdf: parameter b out of domain");

  return betainc(x,a,b);
}

define betacdfinv(x,a,b){
  return invbetainc(x,a,b);
}

define betamedian(a,b){
  local t106 t104 t103 t105 approx ret;
  if(a == b) return 1/2;
  if(a == 1 && b > 0) return 1-(1/2)^(1/b);
  if(a > 0 && b == 1) return   (1/2)^(1/a);
  if(a == 3 && b == 2){
    /* Yes, the author is not ashamed to ask Maxima for the exact solution
       of a quartic equation. */
    t103 = ( (2^(3/2))/27 +4/27  )^(1/3);
    t104 = sqrt( ( 9*t103^2 + 4*t103 + 2  )/(t103)  )/3;
    t105 = -t103-2/(9*t103) +8/9;
    t106 = sqrt( (27*t104*t105+16)/(t104)  )/(2*3^(3/2));
    return -t106+t104/2+1/3;
  }
  if(a == 2 && b == 3){
    t103 = ( (2^(3/2))/27 +4/27  )^(1/3);
    t104 = sqrt( ( 9*t103^2 + 4*t103 + 2  )/(t103)  )/3;
    t105 = -t103-2/(9*t103) +8/9;
    t106 = sqrt( (27*t104*t105+16)/(t104)  )/(2*3^(3/2));
    return 1-(-t106+t104/2+1/3);
  }
  return invbetainc(1/2,a,b);
}

define betamode(a,b){
  if(a + b == 2) return newerror("betamod: a + b = 2 = division by zero");
  return (a-1)/(a+b-2);
}

define betavariance(a,b){
  return (a*b)/( (a+b)^2*(a+b+1) );
}

define betalnvariance(a,b){
  return polygamma(1,a)-polygamma(a+b);
}

define betaskewness(a,b){
  return (2*(b-a)*sqrt(a+b+1))/( (a+b+1)*sqrt(a*b) );
}

define betakurtosis(a,b){
  local num denom;

  num = 6*( (a-b)^2*(a+b+1)-a*b*(a+b+2));
  denom = a*b*(a+b+2)*(a+b+3);
  return num/denom;
}

define betaentropy(a,b){
  return lnbeta(a,b)-(a-1)*psi(a)-(b-1)*psi(b)+(a+b+1)*psi(a+b);

}

/*******************************************************************************
 *
 *
 *                Normal (Gaussian) distribution
 *
 *
 ******************************************************************************/


define normalpdf(x,mu,sigma){
  return 1/(sqrt(2*pi()*sigma^2))*exp( ( (x-mu)^2 )/( 2*sigma^2 ) );
}

define normalcdf(x,mu,sigma){
  return 1/2*(1+erf( ( x-mu )/( sqrt(2*sigma^2) )  )  );
}

define probit(p){
  if(p<0 || p > 1) return newerror("probit: p out of domain 0<=p<=1");
  return sqrt(2)*ervinv(2*p-1);
}

define normalcdfinv(p,mu,sigma){
  if(p<0 || p > 1) return newerror("normalcdfinv: p out of domain 0<=p<=1");
  return mu+ sigma*probit(p);
}

define normalmean(mu,sigma){return mu;}

define normalmedian(mu,sigma){return mu;}

define normalmode(mu,sigma){return mu;}

define normalvariance(mu,sigma){return sigma^2;}

define normalskewness(mu,sigma){return 0;}

define normalkurtosis(mu,sigma){return 0;}

define normalentropy(mu,sigma){
  return 1/3*ln( 2*pi()*exp(1)*sigma^2 );
}

/* moment generating f. */
define normalmgf(mu,sigma,t){
  return exp(mu*t+1/2*sigma^2*t^2);
}

/* characteristic f. */
define normalcf(mu,sigma,t){
  return exp(mu*t-1/2*sigma^2*t^2);
}


/*******************************************************************************
 *
 *
 *                Chi-squared distribution
 *
 *
 ******************************************************************************/

define chisquaredpdf(x,k){
  if(!isint(k) || k<0) return newerror("chisquaredpdf: k not in N");
  if(im(x) || x<0) return newerror("chisquaredpdf: x not in +R");
  /* The gamma function does not check for half integers, do it here? */
  return 1/(2^(k/2)*gamma(k/2))*x^((k/2)-1)*exp(-x/2);
}

define chisquaredpcdf(x,k){
  if(!isint(k) || k<0) return newerror("chisquaredcdf: k not in N");
  if(im(x) || x<0) return newerror("chisquaredcdf: x not in +R");

  return 1/(gamma(k/2))*gammainc(k/2,x/2);
}

define chisquaredmean(x,k){return k;}

define chisquaredmedian(x,k){
  /* TODO: implement a FAST inverse incomplete gamma-{q,p} function */
  return k*(1-2/(9*k))^3;
}

define chisquaredmode(x,k){return max(k-2,0);}
define chisquaredvariance(x,k){return 2*k;}
define chisquaredskewness(x,k){return sqrt(8/k);}
define chisquaredkurtosis(x,k){return 12/k;}
define chisquaredentropy(x,k){
  return k/2+ln(2*gamma(k/2)) + (1-k/2)*psi(k/2);
}

define chisquaredmfg(k,t){
  if(t>=1/2)return newerror("chisquaredmfg: t >= 1/2");
  return (1-2*t)^(k/2);
}

define chisquaredcf(k,t){
  return (1-2*1i*t)^(k/2);
}


/*
 * restore internal function from resource debugging
 */
config("resource_debug", resource_debug_level),;
if (config("resource_debug") & 3) {
    print "gammaincoctave(z,a)";
    print "invbetainc(x,a,b)";
    print "betapdf(x,a,b)";
    print "betacdf(x,a,b)";
    print "betacdfinv(x,a,b)";
    print "betamedian(a,b)";
    print "betamode(a,b)";
    print "betavariance(a,b)";
    print "betalnvariance(a,b)";
    print "betaskewness(a,b)";
    print "betakurtosis(a,b)";
    print "betaentropy(a,b)";
    print "normalpdf(x,mu,sigma)";
    print "normalcdf(x,mu,sigma)";
    print "probit(p)";
    print "normalcdfinv(p,mu,sigma)";
    print "normalmean(mu,sigma)";
    print "normalmedian(mu,sigma)";
    print "normalmode(mu,sigma)";
    print "normalvariance(mu,sigma)";
    print "normalskewness(mu,sigma)";
    print "normalkurtosis(mu,sigma)";
    print "normalentropy(mu,sigma)";
    print "normalmgf(mu,sigma,t)";
    print "normalcf(mu,sigma,t)";
    print "chisquaredpdf(x,k)";
    print "chisquaredpcdf(x,k)";
    print "chisquaredmean(x,k)";
    print "chisquaredmedian(x,k)";
    print "chisquaredmode(x,k)";
    print "chisquaredvariance(x,k)";
    print "chisquaredskewness(x,k)";
    print "chisquaredkurtosis(x,k)";
    print "chisquaredentropy(x,k)";
    print "chisquaredmfg(k,t)";
    print "chisquaredcf(k,t)";
}

