/*
    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 <math.h>
#include <stdlib.h>
#include <string.h>
#include <float.h>
#include <gsl/gsl_sf_gamma.h>
#include <gsl/gsl_sf_psi.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include "DLTmath.h"
#include "statistics.h"
#include "gamma_dist.h"

/* The Gamma distribution of order a > 0 is defined by:

   p(x) dx = {1 / (b * \Gamma(c))} (x/b)^{c-1} e^{-x/b} dx

   0 <= x < +inf
   scale parameter b > 0
   shape parameter c > 0.

   If X and Y are independent gamma-distributed random
   variables of order c1 and c2 with the same scale parameter b, then
   X+Y has gamma distribution of order c1 + c2.

   Some algorithms below are from Knuth, vol 2, 2nd ed, p. 129.
*/

double
gamma_dev(const double b, const double c, const gsl_rng *r2)
{
    return(gsl_ran_gamma(r2, c, b));
}


double
gamma_pdf(const double x, const double b, const double c)
{
    double          p;
    double          lgamma_c;

    if (x < 0.0)
    {
        return (0.0);
    }
    else if (x == 0.0)
    {
        if (b == 1.0)
            return (1.0 / b);
        else
            return (0.0);
    }
    else if (c == 1.0)
    {
        return (exp(-x / b) / b);
    }
    else
    {
        lgamma_c = lgamma(c);
        p = (c - 1.0)*log(x/b) - (x/b) - log(b) - lgamma_c;

        return (exp(p));
    }
}


double
gamma_lnpdf(const double x, const double b, const double c)
{
    double          p;
    double          lgamma_c;

    if (x < 0.0)
    {
        return(0.0);
    }
    else if (x == 0.0 && b == 1.0)
    {
        return (log(1.0 / b));
    }
    else if (c == 1.0)
    {
        return ((-x / b) - log(b));
    }
    else
    {
        lgamma_c = lgamma(c);
        p = (c - 1.0)*log(x/b) - (x/b) - log(b) - lgamma_c;

        return (p);
    }
}


double
gamma_cdf(const double x, const double b, const double c)
{
    return(1.0 - gamma_sdf(x, b, c));
}


double
gamma_sdf(const double x, const double b, const double c)
{
    /*     double p, q; */
    /*     grat1(c, x / b, exp(-x/b)*powf(x/b,c)/tgamma(c), &p, &q, 1e-15); */
    /*     printf("\n%f %f grat1 %e, InGamma %e, diff %e", */
    /*            c, x / b, */
    /*            q, */
    /* gamain(x / b, c, gamln(x/b)), */
    /* 1.0 - InGamma(c, x / b), */
    /* regularizedGammaQ(c, x / b, 1e-14, 500), */
    /* 1.0 - InGamma(c, x / b)-q */
    /* gammq(1e8, 1e8)) );*/

    return(gsl_sf_gamma_inc_Q(c, x / b));
}


double
gamma_int(const double x, const double y, const double b, const double c)
{
    return(gamma_sdf(x, b, c) - gamma_sdf(y, b, c));
}


double
gamma_logL(const double b, const double c)
{
    return(-(lgamma(c) + log(b) + (1.0 - c) * gsl_sf_psi(c) + c));
}


/* fit a gamma distribution by matching moments
   Based on _Statistical Distributions_ 3rd ed. Evans, Hastings, and Peacock, p 101. */
double
gamma_MMfit(const double *data, const int num, double *b, double *c, double *logL)
{
    double          ave, var;
    int             i;

    ave = 0.0;

    for (i = 0; i < num; ++i)
        ave += data[i];

    ave /= (double) num;

    var = 0.0;

    for (i = 0; i < num; ++i)
        var += mysquare(data[i] - ave);

    var /= (double) num - 1;

    *b = mysquare(ave) / var;
    *c = var / ave;

    return(chi_sqr_adapt(data, num, 0, logL, *b, *c, gamma_pdf, gamma_lnpdf, gamma_int));
}


/* fit a gamma distribution by "quasimaximum likelihood" method of

       EW Stacy (1973)
       "Quasimaximum likelihood estimators for two-parameter gamma distributions."
       IBM Jc Res Dev 17:115-124

   also given in Kotz and Johnson, p. 367 Eqns 17.65a & 17.65b.
   This is unbiased for 1/c and for b. */
double
gamma_Stacyfit(const double *data, const int num, double *b, double *c, double *logL)
{
    int             i;
    double          ave, nave, sum, invnum = 1.0 / num;
    double         *z = malloc(num * sizeof(double));

    memcpy(z, data, num * sizeof(double));

    ave = 0.0;

    for (i = 0; i < num; ++i)
        ave += z[i];

    ave /= (double) num;

    nave = (double) num * ave;

    for (i = 0; i < num; ++i)
        z[i] /= nave;

    sum = 0.0;

    for (i = 0; i < num; ++i)
        sum += (z[i] - invnum) * log(z[i]);


    *c = (num - 1) / (num * sum);
    *b = ave / *c;

    free(z);

    return(chi_sqr_adapt(data, num, 0, logL, *b, *c, gamma_pdf, gamma_lnpdf, gamma_int));
}


void
gamma_Stacyfit2(const double *z, const int num, double *b, double *c)
{
    int             i;
    double          ave, nave, sum, invnum, invnave, zi;

    ave = 0.0;

    for (i = 0; i < num; ++i)
        ave += z[i];

    ave /= num;

    nave = num * ave;
    invnum = 1.0 / num;
    invnave = 1.0 / nave;

    sum = 0.0;

    for (i = 0; i < num; ++i)
    {
        zi = z[i] * invnave;
        sum += (zi - invnum) * log(zi);
    }

    *c = (num - 1) / (num * sum);

    /*     if (*c < tol) */
    /*         *c = tol; */

    *b = ave / *c;
}


/* double */
/* mysign(const double x) */
/* { */
/*     if (x > 0.0) */
/*         return(1.0); */
/*     else if (x == 0.0) */
/*         return(0.0); */
/*     else */
/*         return(-1.0); */
/* } */
/*  */
/*  */
/* double */
/* invpsi(const double X) */
/* { */
/*     double L = 1.0; */
/*     double Y = exp(X); */
/*     while (L > 1e-8) */
/*     { */
/*         Y += L * mysign(X - gsl_sf_psi(Y)); */
/*         L *= 0.5; */
/*     } */
/*  */
/*     return(Y); */
/* } */


/* Based on _Statistical Distributions_ 3rd ed. Evans, Hastings, and Peacock, p 41.
   must find root of:

       F1 = log(c) - digamma(c) - log(E(x_i)/(\Prod(x_i))^(1/n)) = 0

   where the first derivative with repect to c (dF1/dc) is:

       F1' = 1/c - trigamma(c) = 0
*/
static void
evalgammaML(const double logterm, const double c, double *fx, double *dfx)
{
    *fx = log(c) - gsl_sf_psi(c) + logterm;
    /*     printf("\n c:%e log(c):% e -gsl_sf_psi_1(c):% e sum:% e logterm:%e", */
    /*            c, log(c), - gsl_sf_psi(c), log(c) - gsl_sf_psi(c), logterm); */
    /*     fflush(NULL); */
    *dfx = 1.0/c - gsl_sf_psi_1(c);
}

/* NB******* this is wrong, there is a square root omitted */
/* Find the Bayesian point estimate, the posterior mode, assuming a
   noninformative improper Jeffreys' prior for the shape and scale parameters:

       (c * Trigamma(c) - 1) / b

   Jeffreys prior for the gamma from:

   Liseo, Brunero (1993)
   "Elimination of Nuisance Parameters with Reference Priors."
   Biometrika 80(2): 295-304.

   The log likelihood, with the Jeffreys' prior, is:

       L(b,c|x) = c \Sum(ln(x_i)) - (1/b)\Sum(x) - Nc ln(b) - N \Gamma(c)
                  -N ln(b) - (N/2) ln(c \Trigamma(c) - 1)

   We must find root of:

       F1 = (1/N)\Sum(ln(x_i)) - ln(E(x_i)) + ln(c+1) - digamma(c)
            - (1/2) (\Trigamma(c) + c \Quadgamma(c)) / (c \Trigamma(c) - 1) = 0

   where the first derivative (dF1/dc) is:

       F1' = 1/(c+1) - \Trigamma(c)
             + (1/2) (\Trigamma(c)^2 + 2\Quadgamma(c) + c^2 \Quadgamma(c)^2 - c (c \Trigamma(c) - 1) \Pentagamma(c)) / (c \Trigamma(c) - 1)^2

   Derivatives verified with Mathematica.
*/
static void
evalgammaBayes(const double logterm, const double c, double *fx, double *dfx)
{
    double         pg1, pg2, pg3, pg4, cpg2m1;

    pg1 = gsl_sf_psi(c);
    pg2 = gsl_sf_psi_1(c);
    pg3 = gsl_sf_psi_n(2, c);
    pg4 = gsl_sf_psi_n(3, c);
//    pg3 = s_PolyGamma(c, 3);
//    pg4 = s_PolyGamma(c, 4);
    cpg2m1 = c * pg2 - 1.0;

    *fx = log(c + 1.0) - pg1 + logterm - 0.5 * (pg2 + c * pg3) / cpg2m1;
    *dfx = 1.0 / (c + 1.0) - pg2 + 0.5 * (pg2*pg2 + 2.0*pg3 + c*c*pg3*pg3 - c * cpg2m1*pg4) / (cpg2m1 * cpg2m1);
}


/* Find the Bayesian point estimate, the posterior mode, assuming a
   noninformative improper Bernardo reference prior for the shape and scale parameters:

       sqrt(Trigamma(c) - 1/c) / b

   Reference prior for the gamma from (p298):

   Liseo, Brunero (1993)
   "Elimination of Nuisance Parameters with Reference Priors."
   Biometrika 80(2): 295-304.

   The joint log probability, with the prior, is:

       L(b,c|x) = (c-1) \Sum(ln(x_i)) - (1/b)\Sum(x) - Nc ln(b) - N \LnGamma(c)
                  -(1/2) ln(\Trigamma(c) - 1/c)

   We must find root of:

                                                           \Quadgamma(c) + 1/c^2
       F1 = \Sum(ln(x_i)) - N ln(b) - N Digamma(c) - (1/2) ---------------------  = 0
                                                            \Trigamma(c) - 1/c

   where the first derivative (dF1/dc) is:

                   /                                                                        \
                   | (\Quadgamma(c) + 1/c^2)^2    2 - c^3 Pentagamma(c)                      |
       F1' = (1/2) | ------------------------- + -----------------------   - 2N \Trigamma(c) |
                   | (\Trigamma(c) - 1/c)^2       c^2 (c \Trigamma(c) - 1)                   |
                   \                                                                        /

   Derivatives verified with Mathematica.
*/
static void
evalgammaBayesRef(const double sumlnx, const double b, const double c, const int num, double *fx, double *dfx)
{
    double         DiG, TriG, QuaG, PenG;

    DiG  = gsl_sf_psi(c);
    TriG = gsl_sf_psi_1(c);
    QuaG = gsl_sf_psi_n(2, c);
    PenG = gsl_sf_psi_n(3, c);
//    QuaG = s_PolyGamma(c, 3);
//    PenG = s_PolyGamma(c, 4);

    *fx = sumlnx - num * log(b) - num * DiG - 0.5 * (QuaG + 1.0/(c*c)) / (TriG - 1.0/c);
    *dfx = 0.5 * ((QuaG + 1.0/(c*c))*(QuaG + 1.0/(c*c)) / (TriG - 1.0/c)*(TriG - 1.0/c) + (2.0 - c*c*c*PenG) / (c*c*(TriG - 1.0)) - 2.0*num*TriG);
}


/* static  */void
evalgammaEM(const double logterm, const double b, const double c, double *fx, double *dfx)
{
    *fx = - gsl_sf_psi(c) + logterm - log(b) ;
    *dfx = - gsl_sf_psi_1(c);
}


/* Fit the parameters of a gamma distribution by maximum likelihood.
   Based on _Statistical Distributions_ 3rd ed. Evans, Hastings, and Peacock, p 101. */
double
gamma_fit(const double *data, const int num, double *b, double *c, double *logL)
{
    double          ave, /* var,  */logterm, logdata, fx, dfx, fxdfx;
    int             i, maxiter = 500;
    double          tol = FLT_EPSILON;

    fx = 0.0;
    dfx = DBL_MAX;

    ave = 0.0;

    for (i = 0; i < num; ++i)
    {
        if (data[i] <= 0.0)
        {
            fprintf(stderr, "\n ERROR345: gamma distributed data must be > 0.0 \n\n");
            return(DBL_MAX);
        }
        else
            ave += data[i];
    }

    ave /= num;

    /*     var = 0.0; */
    /*     for (i = 0; i < num; ++i) */
    /*     { */
    /*         if (data[i] > tol) */
    /*             var += mysquare(data[i] - ave); */
    /*     } */
    /*     var /= newnum; */
    /*  */
    /*     guess_b = *b = var / ave; */
    /*     guess_c = *c = mysquare(ave) / var; */

    gamma_Stacyfit2(data, num, b, c);

    if (*b < DBL_EPSILON)
        *b = DBL_EPSILON;

    if (*c < DBL_EPSILON)
        *c = DBL_EPSILON;

    if (*b > FLT_MAX)
        *b = FLT_MAX;

    if (*c > FLT_MAX)
        *c = FLT_MAX;

    /* Maximum likelihood fit. */
    /* Use Newton-Raphson to find ML estimate of c
       Based on _Statistical Distributions_ 3rd ed. Evans, Hastings, and Peacock, p 41.
       must find root of:

          F1 = log(c) - digamma(c) + \Sum(x_i)/n - log(E(x_i)) = 0

       where the first derivative (dF1/dc) is:

          F1' = 1/c - trigamma(c) = 0
    */
    logdata = 0.0;

    for (i = 0; i < num; ++i)
        logdata += log(data[i]);

    logdata /= num;

    logterm = logdata - log(ave);

    if (fabs(logterm) < DBL_EPSILON * 0.5 *(logdata + log(ave)))
    {
        fprintf(stderr, "\n ERROR349: data are too similar to fit to a gamma distribution \n\n");
        *c = DBL_MAX;
        *b = DBL_EPSILON;
        return(DBL_MAX);
    }

    /*     printf("\n            b             c            fx           dfx        fx/dfx"); */
    /*     printf("\n% 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
    /*         *b, *c, fx, dfx, fx/dfx); */

    for (i = 0; i < maxiter; ++i)
    {
        evalgammaML(logterm, *c, &fx, &dfx);

        fxdfx = fx / dfx;
        *c -= fxdfx; /* Newton-Raphson correction */
        /* *b = ave / *c; */

        if (*c <= DBL_EPSILON)
            *c = 0.5 * (fxdfx + *c);

        if (fabs(fxdfx) < tol * *c || fabs(fx) < tol)
            break; /* success */

        /*         printf("\n%3d % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
        /*                i, ave / *c, *c, fx, dfx, fx/dfx); */
    }

    /*         printf("\ndone: %3d % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
    /*                i, ave / *c, *c, fx, dfx, fx/dfx); */

    /* plug in result to get b */
    *b = ave / *c;

    /* printf("\n newton():% e   invpsi(): % e", *c, invpsi(logterm - log(*b))); */
    if (i == maxiter)
    {
        printf("\n WARNING02: Newton-Raphson failed to converge in gamma_fit()\n");

        for (i = 0; i < num; ++i)
            printf("%3d %f\n", i, data[i]);

        printf("            b             c            fx           dfx        fx/dfx       logterm\n");
        printf("% 10.6e % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e\n",
               *b, *c, fx, dfx, fx/dfx, logterm);

        fflush(NULL);
        /*         *b = guess_b; */
        /*         *c = guess_c; */
    }

    return(chi_sqr_adapt(data, num, 0, logL, *b, *c, gamma_pdf, gamma_lnpdf, gamma_int));
}


/* Fit the parameters of a gamma distribution by maximum likelihood.
   Based on _Statistical Distributions_ 3rd ed. Evans, Hastings, and Peacock, p 101. */
void
gamma_fit_no_stats(const double *data, const int num, double *b, double *c)
{
    double          ave, /* var,  */logterm, logdata, fx, dfx, fxdfx;
    int             i, maxiter = 500;
    double          tol = FLT_EPSILON;

    fx = 0.0;
    dfx = DBL_MAX;

    ave = 0.0;

    for (i = 0; i < num; ++i)
    {
        if (data[i] <= 0.0)
        {
            fprintf(stderr, "\n ERROR345: Gamma distributed data must be > 0.0 ");
            fprintf(stderr, "\n ERROR345: Aborting calculation \n\n");
            exit(EXIT_FAILURE);
        }
        else if (!isfinite(data[i]))
        {
            fprintf(stderr, "\n ERROR346: Gamma distributed data must be finite(!), data[%d] = %f ",
                    i, data[i]);
            fprintf(stderr, "\n ERROR346: Aborting calculation \n\n");
            exit(EXIT_FAILURE);
        }
        else
        {
            ave += data[i];
        }
    }

    ave /= num;

    /*     var = 0.0; */
    /*     for (i = 0; i < num; ++i) */
    /*     { */
    /*         if (data[i] > tol) */
    /*             var += mysquare(data[i] - ave); */
    /*     } */
    /*     var /= newnum; */
    /*  */
    /*     guess_b = *b = var / ave; */
    /*     guess_c = *c = mysquare(ave) / var; */

    gamma_Stacyfit2(data, num, b, c);

    if (*b < DBL_EPSILON)
        *b = DBL_EPSILON;

    if (*c < DBL_EPSILON)
        *c = DBL_EPSILON;

    if (*b > FLT_MAX)
        *b = FLT_MAX;

    if (*c > FLT_MAX)
        *c = FLT_MAX;

    /* Maximum likelihood fit. */
    /* Use Newton-Raphson to find ML estimate of c
       Based on _Statistical Distributions_ 3rd ed. Evans, Hastings, and Peacock, p 41.
       must find root of:

          F1 = log(c) - digamma(c) + \Sum(log(x_i))/n - log(E(x_i)) = 0

       where the first derivative (dF1/dc) is:

          F1' = 1/c - trigamma(c) = 0
    */
    logdata = 0.0;

    for (i = 0; i < num; ++i)
        logdata += log(data[i]);

    logdata /= num;

    logterm = logdata - log(ave);

    /* printf("% 10.6e % 10.6e\n", fabs(logterm), FLT_EPSILON * 0.5 * (logdata + log(ave))); */
    if (fabs(logterm) < fabs(FLT_EPSILON * 0.5 *(logdata + log(ave))))
    {
        fprintf(stderr, "\n ERROR349: data are too similar to fit to a gamma distribution \n\n");
        *c = FLT_MAX;
        *b = ave / *c;
        return;
    }

    /*     printf("\n            b             c            fx           dfx        fx/dfx"); */
    /*     printf("\n% 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
    /*         *b, *c, fx, dfx, fx/dfx); */

    for (i = 0; i < maxiter; ++i)
    {
        evalgammaML(logterm, *c, &fx, &dfx);

        fxdfx = fx / dfx;
        *c -= fxdfx; /* Newton-Raphson correction */
        /* *b = ave / *c; */

        if (*c <= DBL_EPSILON)
            *c = 0.5 * (fxdfx + *c);

        if (fabs(fxdfx) < tol * *c)
            break; /* success */

        /*         printf("\n%3d % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
        /*                i, ave / *c, *c, fx, dfx, fx/dfx); */
    }

    /*         printf("\ndone: %3d % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
    /*                i, ave / *c, *c, fx, dfx, fx/dfx); */

    /* plug in result to get b */
    *b = ave / *c;

    /* printf("\n newton():% e   invpsi(): % e", *c, invpsi(logterm - log(*b))); */
    if (i == maxiter)
    {
        printf("\n WARNING02: Newton-Raphson failed to converge in gamma_fit()\n");

        for (i = 0; i < num; ++i)
            printf("%3d %f\n", i, data[i]);

        printf("            b             c            fx           dfx        fx/dfx       logterm\n");
        printf("% 10.6e % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e\n",
               *b, *c, fx, dfx, fx/dfx, logterm);

        fflush(NULL);
        /*         *b = guess_b; */
        /*         *c = guess_c; */
    }
}


/* Bayesian fit, point estimates.
   Fit the parameters of a gamma distribution by maximum probability.
   Finds the Bayesian point estimate, the posterior mode, assuming a
   noninformative Jeffreys' prior for the shape and scale parameters:

       (c * Digamma(c) - 1) / b

   Jeffreys prior for the gamma from:

   Liseo, Brunero (1993)
   "Elimination of Nuisance Parameters with Reference Priors."
   Biometrika 80(2): 295-304.
*/
void
gamma_bayes_fit_no_stats(const double *data, const int num, double *b, double *c)
{
    double          ave, /* var,  */logterm, logdata, fx, dfx, fxdfx;
    int             i, maxiter = 500;
    double          tol = FLT_EPSILON;

    fx = 0.0;
    dfx = DBL_MAX;

    ave = 0.0;

    for (i = 0; i < num; ++i)
    {
        if (data[i] <= 0.0)
        {
            fprintf(stderr, "\n ERROR345: Gamma distributed data must be > 0.0 ");
            fprintf(stderr, "\n ERROR345: Aborting calculation \n\n");
            exit(EXIT_FAILURE);
        }
        else if (!isfinite(data[i]))
        {
            fprintf(stderr, "\n ERROR346: Gamma distributed data must be finite(!), data[%d] = %f ",
                    i, data[i]);
            fprintf(stderr, "\n ERROR346: Aborting calculation \n\n");
            exit(EXIT_FAILURE);
        }
        else
        {
            ave += data[i];
        }
    }

    ave /= num;

    /*     var = 0.0; */
    /*     for (i = 0; i < num; ++i) */
    /*     { */
    /*         if (data[i] > tol) */
    /*             var += mysquare(data[i] - ave); */
    /*     } */
    /*     var /= newnum; */
    /*  */
    /*     guess_b = *b = var / ave; */
    /*     guess_c = *c = mysquare(ave) / var; */

    gamma_Stacyfit2(data, num, b, c);

    if (*b < DBL_EPSILON)
        *b = DBL_EPSILON;

    if (*c < DBL_EPSILON)
        *c = DBL_EPSILON;

    if (*b > FLT_MAX)
        *b = FLT_MAX;

    if (*c > FLT_MAX)
        *c = FLT_MAX;

    /* Bayesian maximum posterior probability fit.
       Use Newton-Raphson to find ML estimate of c.

       The log likelihood, with the Jeffreys' prior, is:

           L(b,c|x) = c \Sum(ln(x_i)) - (1/b)\Sum(x) - Nc ln(b) - N \Gamma(c)
                      -N ln(b) - (N/2) ln(c \Trigamma(c) - 1)
    */
    logdata = 0.0;

    for (i = 0; i < num; ++i)
        logdata += log(data[i]);

    logdata /= num;

    logterm = logdata - log(ave);

    /* printf("% 10.6e % 10.6e\n", fabs(logterm), FLT_EPSILON * 0.5 * (logdata + log(ave))); */
    if (fabs(logterm) < fabs(FLT_EPSILON * 0.5 *(logdata + log(ave))))
    {
        fprintf(stderr, "\n ERROR349: data are too similar to fit to a gamma distribution \n\n");
        *c = FLT_MAX;
        *b = ave / *c;
        return;
    }

    /*     printf("\n            b             c            fx           dfx        fx/dfx"); */
    /*     printf("\n% 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
    /*         *b, *c, fx, dfx, fx/dfx); */

    for (i = 0; i < maxiter; ++i)
    {
        evalgammaBayes(logterm, *c, &fx, &dfx);

        fxdfx = fx / dfx;
        *c -= fxdfx; /* Newton-Raphson correction */
        /* *b = ave / *c; */

        if (*c <= DBL_EPSILON)
            *c = 0.5 * (fxdfx + *c);

        if (fabs(fxdfx) < tol * *c)
            break; /* success */

        /*         printf("\n%3d % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
        /*                i, ave / *c, *c, fx, dfx, fx/dfx); */
    }

    /*         printf("\ndone: %3d % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
    /*                i, ave / *c, *c, fx, dfx, fx/dfx); */

    /* plug in result to get b */
    *b = ave / (*c + 1);

    /* printf("\n newton():% e   invpsi(): % e", *c, invpsi(logterm - log(*b))); */
    if (i == maxiter)
    {
        printf("\n WARNING02: Newton-Raphson failed to converge in gamma_fit()\n");

        for (i = 0; i < num; ++i)
            printf("%3d %f\n", i, data[i]);

        printf("            b             c            fx           dfx        fx/dfx       logterm\n");
        printf("% 10.6e % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e\n",
               *b, *c, fx, dfx, fx/dfx, logterm);

        fflush(NULL);
        /*         *b = guess_b; */
        /*         *c = guess_c; */
    }
}


/* Bayesian fit with a Bernardo reference prior on the scale and shape params (see evalgammaBayesRef()) */
void
gamma_bayes_ref_fit_no_stats(const double *data, const int num, double *b, double *c)
{
    double          sumx, /* var,  */logterm, logdata, fx, dfx, fxdfx;
    int             i, maxiter = 500;
    double          tol = FLT_EPSILON;

    fx = 0.0;
    dfx = DBL_MAX;

    sumx = 0.0;

    for (i = 0; i < num; ++i)
    {
        if (data[i] <= 0.0)
        {
            fprintf(stderr, "\n ERROR345: Gamma distributed data must be > 0.0 ");
            fprintf(stderr, "\n ERROR345: Aborting calculation \n\n");
            exit(EXIT_FAILURE);
        }
        else if (!isfinite(data[i]))
        {
            fprintf(stderr, "\n ERROR346: Gamma distributed data must be finite(!), data[%d] = %f ",
                    i, data[i]);
            fprintf(stderr, "\n ERROR346: Aborting calculation \n\n");
            exit(EXIT_FAILURE);
        }
        else
        {
            sumx += data[i];
        }
    }

    /*     var = 0.0; */
    /*     for (i = 0; i < num; ++i) */
    /*     { */
    /*         if (data[i] > tol) */
    /*             var += mysquare(data[i] - ave); */
    /*     } */
    /*     var /= newnum; */
    /*  */
    /*     guess_b = *b = var / ave; */
    /*     guess_c = *c = mysquare(ave) / var; */

    gamma_Stacyfit2(data, num, b, c);

    if (*b < DBL_EPSILON)
        *b = DBL_EPSILON;

    if (*c < DBL_EPSILON)
        *c = DBL_EPSILON;

    if (*b > FLT_MAX)
        *b = FLT_MAX;

    if (*c > FLT_MAX)
        *c = FLT_MAX;

    /* Bayesian maximum posterior probability fit.
       Use Newton-Raphson to find ML estimate of c.

       The log likelihood, with the Jeffreys' prior, is:

           L(b,c|x) = c \Sum(ln(x_i)) - (1/b)\Sum(x) - Nc ln(b) - N \Gamma(c)
                      -N ln(b) - (N/2) ln(c \Trigamma(c) - 1)
    */
    logdata = 0.0;

    for (i = 0; i < num; ++i)
        logdata += log(data[i]);

    logterm = logdata - log(sumx);

    /* printf("% 10.6e % 10.6e\n", fabs(logterm), FLT_EPSILON * 0.5 * (logdata + log(ave))); */
    if (fabs(logterm) < fabs(FLT_EPSILON * 0.5 *(logdata + log(sumx))))
    {
        fprintf(stderr, "\n ERROR349: data are too similar to fit to a gamma distribution \n\n");
        *c = FLT_MAX;
        *b = sumx / (*c * num);
        //return;
    }

    /*     printf("\n            b             c            fx           dfx        fx/dfx"); */
    /*     printf("\n% 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
    /*         *b, *c, fx, dfx, fx/dfx); */

    for (i = 0; i < maxiter; ++i)
    {
        //evalgammaBayes(logterm, *c, &fx, &dfx);
        evalgammaBayesRef(logdata, *b, *c, num, &fx, &dfx);

        fxdfx = fx / dfx;
        *c -= fxdfx; /* Newton-Raphson correction */
        /* *b = ave / *c; */

        if (*c <= DBL_EPSILON)
            *c = 0.5 * (fxdfx + *c);

        if (fabs(fxdfx) < tol * *c)
            break; /* success */

        /*         printf("\n%3d % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
        /*                i, ave / *c, *c, fx, dfx, fx/dfx); */
    }

    /*         printf("\ndone: %3d % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
    /*                i, ave / *c, *c, fx, dfx, fx/dfx); */

    /* plug in result to get b */
    *b = sumx / (*c * num + 1);

    /* printf("\n newton():% e   invpsi(): % e", *c, invpsi(logterm - log(*b))); */
    if (i == maxiter)
    {
        printf("\n WARNING02: Newton-Raphson failed to converge in gamma_fit()\n");

        for (i = 0; i < num; ++i)
            printf("%3d %f\n", i, data[i]);

        printf("            b             c            fx           dfx        fx/dfx       logterm\n");
        printf("% 10.6e % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e\n",
               *b, *c, fx, dfx, fx/dfx, logterm);

        fflush(NULL);
        /*         *b = guess_b; */
        /*         *c = guess_c; */
    }
}


double
gamma1_fit(const double *data, const int num, double *b, double *nullp, double *logL)
{
    double          ave;
    int             i;

    ave = 0.0;

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

    ave /= num;

    *b = ave;

    return(chi_sqr_adapt(data, num, 0, logL, *b, 1.0, gamma_pdf, gamma_lnpdf, gamma_int));
}


double
gamma_fit_guess(const double *data, const int num, double *b, double *c, double *logL)
{
    double          ave, /* var,  */logterm, fx, dfx;
    int             i, maxiter = 50;
    double          tol = 1e-10;

    fx = 0.0;
    dfx = DBL_MAX;

    ave = 0.0;

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

    ave /= num;

    if (*b < DBL_EPSILON)
        *b = DBL_EPSILON;

    if (*c < DBL_EPSILON)
        *c = DBL_EPSILON;

    if (*b > FLT_MAX)
        *b = FLT_MAX;

    if (*c > FLT_MAX)
        *c = FLT_MAX;

    /* Maximum likelihood fit. */
    /* Use Newton-Raphson to find ML estimate of c
       Based on _Statistical Distributions_ 3rd ed. Evans, Hastings, and Peacock, p 41.
       must find root of:

          F1 = log(c) - digamma(c) + \Sum(x_i)/n - log(E(x_i)) = 0

       where the first derivative (dF1/dc) is:

          F1' = 1/c - trigamma(c) = 0
    */
    logterm = 0.0;

    for (i = 0; i < num; ++i)
    {
        if (data[i] == 0.0)
            continue;

        logterm += log(data[i]);
    }

    logterm /= num;

    logterm = logterm - log(ave);

    if (fabs(logterm) < DBL_EPSILON)
    {
        *c = FLT_MAX;
        *b = ave / *c;
        *logL = FLT_MAX;
        return(0.0);
    }

    /*     printf("\n            b             c            fx           dfx        fx/dfx"); */
    /*     printf("\n% 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
    /*         *b, *c, fx, dfx, fx/dfx); */

    for (i = 0; i < maxiter; ++i)
    {
        evalgammaML(logterm, *c, &fx, &dfx);
        /* evalgammaEM(logterm, *b, *c, &fx, &dfx); */

        *c -= (fx / dfx); /* Newton-Raphson correction */
        /* *b = ave / *c; */

        if (*c < DBL_EPSILON)
        {
            *c = DBL_EPSILON;
            break;
        }

        if (fabs(fx) < tol)
            break; /* success */

        /*         printf("\n%3d % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
        /*                i, ave / *c, *c, fx, dfx, fx/dfx); */
    }

    /* plug in result to get b */
    *b = ave / *c;

    if (i == maxiter)
    {
        printf("Newton-Raphson in gamma_fit() bonked, too many iterations: %d\n", i);
        /*         *b = guess_b; */
        /*         *c = guess_c; */
    }

    return(chi_sqr_adapt(data, num, 0, logL, *b, *c, gamma_pdf, gamma_lnpdf, gamma_int));
}


double
gamma_minc_fit(const double *data, const int num, double *b, double *c, const double minc, double *logL)
{
    double          ave, var, logterm, fx, dfx, /* guess_b,  */guess_c;
    int             i;
    double          tol = 1e-8;
    int             maxiter = 50;

    fx = 0.0;
    dfx = DBL_MAX;

    ave = 0.0;

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

    ave /= num;

    var = 0.0;

    for (i = 0; i < num; ++i)
        var += mysquare(data[i] - ave);

    var /= num - 1;

    /* guess_b = *b = var / ave; */
    guess_c = *c = mysquare(ave) / var;

    /* gamma_Stacyfit2(data, num, b, c); */

    /*     guess_b = *b; */
    /*     guess_c = *c; */

    /* Maximum likelihood fit. */
    logterm = 0.0;

    for (i = 0; i < num; ++i)
    {
        if (data[i] == 0.0)
            continue;

        logterm += log(data[i]);
    }

    logterm /= num;

    logterm = logterm - log(ave);

    /*     printf("\n            b             c            fx           dfx"); */
    /*     printf("\n% 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
    /*         *b, *c, fx, dfx, fx/dfx); */

    for (i = 0; i < maxiter; ++i)
    {
        evalgammaML(logterm, *c, &fx, &dfx);

        if (fabs(fx) < tol)
            break; /* success */

        *c -= (fx / dfx); /* Newton-Raphson correction */

        if (*c <= 0.0)
            *c = 0.5 * (fx / dfx + *c);

        /*         printf("\n% 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
        /*                ave / *c, *c, fx, dfx, fx/dfx); */
    }

    if (i == maxiter)
        *c = guess_c;

    if (*c < minc)
        *c = minc;

    /* plug in result to get b */
    *b = ave / *c;

    return(chi_sqr_adapt(data, num, 0, logL, *b, *c, gamma_pdf, gamma_lnpdf, gamma_int));
}


static void
evalgammaML_minc_opt(const double logterm, const double minc, const double s, double *fx, double *dfx)
{
    const double    term = minc + s*s;
    *fx = log(term) - gsl_sf_psi(term) - logterm;
    *dfx = (2.0 * s / term) - (2.0 * s * gsl_sf_psi_1(term));
}


double
gamma_minc_opt_fit(const double *data, const int num, double *b, double *c, const double minc, double *logL)
{
    double          ave, var, logterm, fx = 0.0, dfx = 0.0, /*guess_b,  guess_c, */ s;
    int             i;
    double          tol = 1e-8;

    ave = 0.0;

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

    ave /= (double) num;

    var = 0.0;

    for (i = 0; i < num; ++i)
        var += mysquare(data[i] - ave);

    var /= (double) num - 1;

    /* guess_b = *b = var / ave; */
    /* guess_c = *c = mysquare(ave) / var; */

    if (*c <= minc)
        s = 0.01;
    else
        s = sqrt(*c - minc);

    /* Constrained maximum likelihood fit. */
    /* Use Newton-Raphson to find ML estimate of c */
    logterm = 0.0;

    for (i = 0; i < num; ++i)
    {
        if (data[i] == 0.0)
            continue;

        logterm += log(data[i]);
    }

    logterm /= (double) num;

    logterm = logterm - log(ave);

    printf("% 10.6e % 10.6e % 10.6e % 10.6e\n", s, fx, dfx, fx/dfx);

    for (i = 0; i < 20; ++i)
    {
        evalgammaML_minc_opt(logterm, minc, s, &fx, &dfx);

        if (fabs(fx) < tol)
            break; /* success */

        s -= (fx / dfx); /* Newton-Raphson correction */
        printf("% 10.6e % 10.6e % 10.6e % 10.6e\n", s, fx, dfx, fx/dfx);
    }

    if (minc + s*s < minc)
        *c = minc;
    else
        *c = minc + s*s;

    /* plug in result to get b */
    *b = ave / *c;

    return(chi_sqr_adapt(data, num, 0, logL, *b, *c, gamma_pdf, gamma_lnpdf, gamma_int));
}


/* fit a gamma distribution by maximum likelihood, given a histogram
   Based on _Statistical Distributions_ 3rd ed. Evans, Hastings, and Peacock, p 101. */
double
gamma_histfit(double *data, double *freq, int num, double *b, double *c, double *logL)
{
    double          norm, ave, var, logterm, fx, dfx, guess_b, guess_c;
    int             i;
    double          tol = 1e-8;

    norm = 0.0;

    for (i = 0; i < num; ++i)
        norm += freq[i];

    ave = 0.0;

    for (i = 0; i < num; ++i)
    {
        ave += (freq[i] * data[i]);
    }

    ave /= norm;

    var = 0.0;

    for (i = 0; i < num; ++i)
        var += (freq[i] * mysquare(data[i] - ave));

    var /= norm;

    guess_b = *b = var / ave;
    guess_c = *c = mysquare(ave) / var;

    SCREAMF(*b);
    SCREAMF(*c);

    /* guess_b = guess_c = 0.0; */
    /* Maximum likelihood fit. */
    /* Use Newton-Raphson to find ML estimate of c = c
       Based on _Statistical Distributions_ 3rd ed. Evans, Hastings, and Peacock, p 41.
       must find root of:

          F1 = log(c) - digamma(c) - log(E(x_i)/(\Prod(x_i))^(1/n)) = 0

       where the first derivative with repect to c (dF1/dc) is:

          F1' = 1/c - trigamma(c) = 0
    */
    logterm = 0.0;

    for (i = 0; i < num; ++i)
    {
        if (data[i] == 0.0)
            continue;

        logterm += (freq[i] * log(data[i]));
    }

    logterm /= norm;

    logterm = logterm - log(ave);

    for (i = 0; i < 200; ++i)
    {
        evalgammaML(logterm, *c, &fx, &dfx);

        if (fabs(fx) < tol)
            break;                /* success */

        *c -= (fx / dfx);     /* Newton-Raphson is simple */
    }

    /* SCREAMD(i); */

    /* plug in result to get b */
    *b = ave / *c;

    if (i == 200)
    {
        *b = guess_b;
        *c = guess_c;
    }

    SCREAMF(*b);
    SCREAMF(*c);

    return(chi_sqr_hist(data, freq, num, logL, *b, *c, gamma_pdf, gamma_lnpdf, gamma_int));
}
