/*
    Theseus - maximum likelihood superpositioning of macromolecular structures

    Copyright (C) 2004-2015 Douglas L. Theobald

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the:

    Free Software Foundation, Inc.,
    59 Temple Place, Suite 330,
    Boston, MA  02111-1307  USA

    -/_|:|_|_\-
*/

#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <float.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include "DLTmath.h"
#include "statistics.h"
#include "invgauss_dist.h"

/* returns a random variate from the Inverse Gaussian
   distribution. Details of the algorithm are contained in
   appendix 4.d.
   http://www.cbr.washington.edu/papers/zabel/index.html
   http://www.cbr.washington.edu/papers/zabel/app3.doc2.html

   based on:

   Michael, J. R., W.R. Schucany, and R.W. Haas. 1976.
   "Generating random variates using transformations with multiple roots."
   The American Statistician 30: 88-90.
*/
double
invgauss_dev(const double mu, const double lam, const gsl_rng *r2)
{
    double          n, v, w, c, x, p;

    n = gsl_ran_ugaussian(r2);
    v = n * n;
    w = mu * v;
    c = mu / (2.0 * lam);

    x = mu + c * (w - sqrt(w * (4.0 * lam + w)));
    p = mu/(mu + x);

    if (p > gsl_rng_uniform(r2))
        return(x);
    else
        return(mu * mu / x);
}


double
invgauss_pdf(const double x, const double mean, const double lambda)
{
    double          prob;

    if (x == 0.0)
        return(0.0);

    prob = exp(-lambda * mysquare(x - mean) / (2.0 * mean * mean * x));
    prob *= sqrt(lambda / (2.0 * MY_PI * mycube(x)));

    return(prob);
}


double
invgauss_lnpdf(const double x, const double mean, const double lambda)
{
    double          prob;

    prob = -lambda * mysquare(x - mean) / (2.0 * mean * mean * x);
    prob += 0.5 * log(lambda / (2.0 * MY_PI * mycube(x)));

    return(prob);
}


double
invgauss_cdf(const double x, const double mean, const double lambda)
{
    double          prob, posax, negax;

    if (x == 0.0)
        return(0.0);

    posax =  (x - mean) * sqrt(lambda / x) / mean;
    negax = -(x + mean) * sqrt(lambda / x) / mean;

    prob = 0.5 * (1.0 + erf(posax / sqrt(2.0))) + exp(2.0 * lambda / mean) * 0.5 * (1.0 + erf(negax / sqrt(2.0)));

    if (prob < 0.0)
        prob = 0.0;

    return(prob);
}


double
invgauss_sdf(const double x, const double mean, const double lambda)
{
    return(1.0 - invgauss_cdf(x, mean, lambda));
}


double
invgauss_int(const double x, const double y, const double mean, const double lambda)
{
    if (x == 0.0)
        return(invgauss_cdf(y, mean, lambda));
    else
        return(invgauss_cdf(y, mean, lambda) - invgauss_cdf(x, mean, lambda));
}


/* Maximum likelihood fit to the inverse gaussian distribution.
   These are also minimum variance unbiased estimators.

     V. Seshadri _The Inverse Gaussian Distribtution: Statistical
     Theory and Applications_ Springer 1999, p. 7

   invgauss_fit() returns the reduced chi-square statistic for 
   the fit 
*/
double
invgauss_fit(const double *data, const int num, double *mean, double *lambda, double *logL)
{
    double          sum_ave, sum_lambda, numd, invave;
    int             i, nbins = 0;

    numd = (double) num;

    sum_ave = 0.0;
    for (i = 0; i < num; ++i)
    {
        if (data[i] <= 0.0)
        {
            fprintf(stderr, "\n ERROR345: inverse gaussian distributed data must be > 0.0 ");
            return(-1.0);
        }
        else
        {
            sum_ave += data[i];
        }
    }

    *mean = sum_ave / numd;
    invave = 1.0 / *mean;

    /* MM estimator */
/*     double var; */
/*     sum_lambda = var = 0.0; */
/*     for (i = 0; i < num; ++i) */
/*         var += mysquare(data[i] - *mean); */
/*     var /= numd; */
/*     *lambda = *mean * *mean * *mean / var; */
/*     printf("\nMME: %10.5f", *lambda); */

    sum_lambda = 0.0;
    for (i = 0; i < num; ++i)
        sum_lambda += ((1.0 / data[i]) - invave);
    *lambda = numd / sum_lambda;
/*     printf(" MLE: %10.5f", *lambda); */

    return(chi_sqr_adapt(data, num, nbins, logL, *mean, *lambda, invgauss_pdf, invgauss_lnpdf, invgauss_int));
}
