/*
    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 <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <float.h>
#include "statistics.h"
#include "DLTmath.h"
#include "halfnormgamma_dist.h"
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>

/* The Half-normal gamma distribution has the form,

       p(x) dx = (b/a) (x/a)^(b-1) exp(-(x/a)^b) dx

       a > 0        scale parameter
       b > 0        shape parameter
       0 <= x < +inf
*/

double
halfnormgamma_dev(const double a, const double b, const gsl_rng *r2)
{
    return (a * pow(-log(gsl_rng_uniform(r2)), (1.0 / b)));
}


double
halfnormgamma_pdf(const double x, const double a, const double b)
{
    double          p;

    if (x < 0.0)
    {
        return(0.0);
    }
    else if (x == 0.0)
    {
        if (b == 1.0)
            return(1.0 / a);
        else
            return(0.0);
    }
    else if (b == 1.0)
    {
        return(exp(-x / a) / a);
    }
    else
    {
        p = (b / a) * exp(-pow(x/a, b) + (b - 1.0) * log(x / a));
        return(p);
    }
}


double
halfnormgamma_lnpdf(const double x, const double a, const double b)
{
    double          p;

    p = log(b / a) + ((b - 1.0) * log(x / a)) - pow(x / a, b);

    return(p);
}


double
halfnormgamma_cdf(const double x, const double a, const double b)
{
    return(1.0 - halfnormgamma_sdf(x, a, b));
}


double
halfnormgamma_sdf(const double x, const double a, const double b)
{
    if (x == 0.0)
        return(1.0);
    else
        return(exp(-pow((x / a), b)));
}


double
halfnormgamma_int(const double x, const double y, const double a, const double b)
{
    return(halfnormgamma_sdf(x, a, b) - halfnormgamma_sdf(y, a, b));
}


/* From Cover and Thomas (1991) _Elements of Information Theory_,
   pp. 486-487. But this appears to be wrong. Do not use. */
double
halfnormgamma_logL(const double a, const double b)
{
    return(0);
}


/* Maximum likelihood fit of data to a Half-normal gamma distribution
   Based on _Statistical Distributions_ 3rd ed.
   Evans, Hastings, and Peacock, p 193, 196.
   and on my own derivation of the Jacobian matrix (ugh!).
*/
double
halfnormgamma_fit(const double *x, const int nx, double *eta, double *beta, double *prob)
{
    double           nd = (double) nx;
    double           a, b;
    double           guesses[2];
    double          *array;
    int              i;
    double           tol = 1e-8;

    /* initial eta and beta guesses are rank estimates:
       eta = 63.21 percentile x value (1 - e^-1)
       beta = ln(ln(2) / ln(median / eta)
    */
    array = (double *) malloc(nx * sizeof(double));

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

    qsort(array, nx, sizeof(double), dblcmp);

    a = guesses[0] = array[(int)floor(0.632120559 * nd)];
    b = guesses[1] = log(log(2)) / log(array[(int) floor(0.5 * nd)] / guesses[0]);

    if (guesses[0] < 0.0)
        guesses[0] = tol;

    if (guesses[1] < 0.0)
        guesses[1] = tol;

    if (mnewt_halfnormgammaML(x, nx, 100, guesses, tol, tol) == 1)
    {
        *eta  = guesses[0];
        *beta = guesses[1];
    }
    else /* newton-raphson did not converge, use rough rank estimates from above */
    {
        *eta  = a;
        *beta = b;
    }

    free(array);

    return(chi_sqr_adapt(x, nx, 0, prob, *eta, *beta, halfnormgamma_pdf, halfnormgamma_lnpdf, halfnormgamma_int));
}

