/*
    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 <float.h>
#include <string.h>
#include <gsl/gsl_sf_gamma.h>
#include <gsl/gsl_sf_psi.h>
#include <gsl/gsl_sf_pow_int.h>
#include <gsl/gsl_math.h>
#include "DLTmath.h"
#include "statistics.h"
#include "gamma_dist.h"
#include "invgamma_dist.h"

static void
evalinvgammaML(const double lnave, const double harmave, const double c,
               double *fx, double *dfx);


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

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

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

   Mode = b/(c+1)
*/
double
invgamma_dev(const double b, const double c, const gsl_rng *r2)
{
    return(1.0 / gamma_dev(1.0/b, c, r2));
}


double
invgamma_pdf(const double x, const double b, const double c)
{
    double          p;

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


double
invgamma_lnpdf(const double x, const double b, const double c)
{
    if (x <= 0.0)
        return (-INFINITY);
    else 
        return (c*log(b) - lgamma(c) - (1.0+c)*log(x) - (b/x));
}


double
invgamma_cdf(const double x, const double b, const double c)
{
    return(gsl_sf_gamma_inc_Q(c, b/x));
}


double
invgamma_sdf(const double x, const double b, const double c)
{
    return(1.0 - invgamma_cdf(x, b, c));
}


double
invgamma_int(const double x, const double y, const double b, const double c)
{
/*     printf("\n~~~~~~~~% 12.6e % 12.6e", */
/*            integrate_qsimp(invgamma_pdf, b, c, x, y), */
/*            invgamma_sdf(x, b, c) - invgamma_sdf(y, b, c)); */
    return(invgamma_cdf(y, b, c) - invgamma_cdf(x, b, c));
}


/* This is correct, but I haven't proven it. */
double
invgamma_ent(const double b, const double c)
{
/*     printf("\n**********logL: % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
/*            lgamma(c), log(b), -(1.0 + c) * gsl_sf_psi(c), c, */
/*            -(lgamma(c) + log(b) - (1.0 +c) * gsl_sf_psi(c) + c)); */
//    return(-(lgamma(c) + log(b) - (1.0 + c) * gsl_sf_psi(c) + c));
    return(-(lgamma(c) + log(b) - (1.0 + c) * gsl_sf_psi(c) + c));
}


double
invgamma_logL(const double *data, const int num, const double b, const double c)
{
    int             i;
    double          logsum, invsum, logL;

    logsum = invsum = 0.0;
    for (i = 0; i < num; ++i)
    {
        logsum += log(data[i]);
        invsum += 1.0 / data[i];
    }

    logL = -(1.0 + c) * logsum - b * invsum + num * c * log(b) - num * lgamma(c);

    return(logL);
}


/* We must find the root of:

       F1 = ln((N*c)/\Sum(1/x_i)) - digamma(c) - 1/N \Sum ln(x_i) = 0

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

       F1' =  1/c - trigamma(c)
*/
/* since the second derivative is independent of the data, this also 
   corresponds to the "method of scoring for parameters" as well as simple
   Newton-Raphson */
static void
evalinvgammaML(const double ave_ln, const double harm_ave, const double c,
               double *fx, double *dfx)
{
    //*fx = log(c * harmave) - gsl_sf_psi(c) - lnave;
    *fx = log(c * harm_ave) - gsl_sf_psi(c) - ave_ln;
    //*dfx = 1.0/c - gsl_sf_psi_1(c);
    *dfx = 1.0/c - gsl_sf_psi_1(c);
}


/* Fit an inverse gamma distribution by maximum likelihood. */
double
invgamma_fit(const double *data, const int num, double *b, double *c, double *logL)
{
    double          lnave, harmave, aveinv, invvar, x;
    double         *invx = malloc(num * sizeof(double));
    double          fx = 0.0, dfx = 1.0, fxdfx = 0.0;
    int             i, skip;
    int             maxit = 300;
    double          tol = 1e-9;

/*     double tmp = invgamma_transfit(data, num, b, c, logL); */
/*     printf("\n %-12s %11.3e %11.3e %9.2f %12.3f %14.1f %14.1f %14.1f %6.3f", */
/*            "invg_trans", *b, *c, tmp, *logL/num, *logL, *logL, *logL, 1.0); */

    lnave = harmave = 0.0;
    skip = 0;
    for (i = 0; i < num; ++i)
    {
        x = data[i];
        if (x > 0.0)
        {
            invx[i] = 1.0/x;
            lnave += log(x);      /* logarithmic average */
            harmave += (invx[i]); /* harmonic average */
        }
        else if(x == 0.0)
        {
            invx[i] = 0.0;
            ++skip;
            continue;
        }
        else
        {
            fprintf(stderr, "\n ERROR345: inverse gamma distributed data must be >= 0.0 ");
            return(-1.0);
        }
    }
    lnave /= (double) (num - skip);
    aveinv = harmave / (double) (num - skip);
    harmave = (double) (num - skip) / harmave;

    /* MMEs are:
           E(x) = b/(c-1)               if c > 1
           Var(x) = b^2/((c-1)^2(c-2))  if c > 2
       The MLE of b:
           b = c * Ave(x)_harmonic
       Using the first and third eqns above gives:
           c = E(x) / (E(x) - Ave(x)_harmonic)
       This is always positive as E(x) > Ave(x)_harmonic.
       In fact it is always >= 1.0.
    */

    invvar = 0.0;
    for (i = 0; i < num; ++i)
        invvar += mysquare(invx[i] - aveinv);
    invvar /= (double) (num - 1 - skip);

    /* MMEs from the regular gamma distribution (using inverse transformed data) */
    *c = aveinv * aveinv / invvar;
    *b = *c * harmave; /* this is actually the MLE given known c */

    /* Newton-Raphson to find ML estimate of c.
       We must find the root of:

           F1 = ln((N*c)/\Sum(1/x_i)) - digamma(c) - 1/N \Sum ln(x_i) = 0

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

           F1' = 1/c - trigamma(c)
    */
/*     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 < maxit; ++i)
    {
        evalinvgammaML(lnave, harmave, *c, &fx, &dfx);

        fxdfx = fx/dfx;

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

        *c -= fxdfx; /* Newton-Raphson correction */

        if (*c <= 0.0)
            *c = 0.1;

        /* plug in result to get b */
        *b = *c * harmave;
/*         printf("\n% 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
/*                *b, *c, fx, dfx, fx/dfx); */
    }
/*     printf("\niter = %d", i); */
    /* invgamma_logL(*b, *c); */

    if (i == maxit)
        printf("\n\n ERROR543: Newton-Raphson ML fit in invgamma_fit() did not converge.\n");

    free(invx);

    return(chi_sqr_adapt(data, num, 0, logL, *b, *c, invgamma_pdf, invgamma_lnpdf, invgamma_int));
}


/* Bayesian max posterior mode fit, assuming a known shape param c, using reference prior 1/b for
   the scale parameter b.
   Actually its must ML now, or equally ref prior for b is a constant \prop 1.
   The 'missing' param indicates the number of the smallest data points to be excluded and estimated
   via EM (these are usually one or more zero eigenvalues or variances in the procrustes case)

   ***NB***:  you MUST pass this fxn something reasonable in b, as this is an EM algorithm
   that uses the current value of b to find the expectation of the smallest missing value.
*/
/* Very picky function, requires data be ordered small to large (missing values first) */
double
invgamma_fixed_c_EM_fit(const double *data, const int num, const int missing, double *b, const double c, double *logL)
{
    int             i;
    int             maxiters = 100;
    double          precision = FLT_EPSILON;
    double          chi2 = 0.0, sum, expinvxn, xn1;
    double          oldb;
//    double          oldlogL = -DBL_MAX;
//    double tmp;

//    *logL=-DBL_MAX;

    /* data assumed to be ordered small to big (missing first) */
    sum = 0.0;
    for (i = missing; i < num; ++i)
    {
        if (data[i] > 0.0)
        {
            sum += 1.0 / data[i];;
        }
        else
        {
            fprintf(stderr, "\n WARNING456: Inverse gamma distributed data must be > 0.0 ");
            fprintf(stderr, "\n WARNING456: NOT Continuing but omitting data[%d] = %e; chi^2 will be wrong \n\n", i, data[i]);
            exit(EXIT_FAILURE);
        }
    }

    xn1 = data[missing];
//printf("\n");
    for (i = 0; i < maxiters; ++i)
    {
        oldb = *b;
        expinvxn = ExpInvXn(*b, c, xn1);

/*         printf(">>>> %3d expinvxn:%8.3e, sum:%8.3e\n", i, expinvxn, sum); */
/*         double tmp = (3.0/data[1] - 2.0/data[2]); */
/*         printf(">>>>     ordersta:%8.3e -- %8.3e\n", tmp, fabs(tmp-expinvxn)/expinvxn); */
/*         tmp = 0.5 * (1.0/data[2] - 3.0/data[3] + 2.0/data[1]); */
/*         printf(">>>>     ordersta:%8.3e -- %8.3e\n", tmp, fabs(tmp-expinvxn)/expinvxn); */
/*         printf(">>>>     ntsmalle:%8.3e\n", 1.0/data[1]); */
/*         printf("missing = %d\n", missing); */
/*         fflush(NULL); */
        //expinvxn = 7e2;

        //*b = (num*c - 2.0) / (sum + missing*expinvxn); /* bayes for invgamma variance */
        //*b = (num*c + 2.0) / (sum + missing*expinvxn); /* bayes for gamma precision param */
        *b = (num*c) / (sum + missing*expinvxn);

//        oldlogL = *logL;
//        *logL = invgamma_logL(data + missing, num-missing, *b, c) + missing * log(invgamma_cdf(xn1, *b, c));
        //*logL += -(1.0 + c) * ExpLogXn(*b, c, xn1) - *b * expinvxn + c * log(*b) - lgamma(c);
//        printf("\nlogL: %3d % 12.6e % 12.6e % 12.6e", i, *logL, *logL-oldlogL, invgamma_ent(*b, c));

        if (fabs(*b - oldb) < fabs(*b * precision))
            break;
    }
//printf("\n");
    //chi2 = chi_sqr_adapt(newdata, num-missing, 0, logL, *b, c, invgamma_pdf, invgamma_lnpdf, invgamma_int);

    return(chi2);
}


double
invgamma_fixed_c_ML_fit(const double *data, const int num, const int missing, double *b, const double c, double *logL)
{
    int             i;
    int             maxiters = 100;
    double          precision = FLT_EPSILON;
    double          chi2 = 0.0, sum, expinvxn, xn1;
    double          oldb;
//    double          oldlogL = -DBL_MAX;
//    double tmp;

//    *logL=-DBL_MAX;

    /* data assumed to be ordered small to big (missing first) */
    sum = 0.0;
    for (i = missing; i < num; ++i)
    {
        if (data[i] > 0.0)
        {
            sum += 1.0 / data[i];;
        }
        else
        {
            fprintf(stderr, "\n WARNING456: Inverse gamma distributed data must be > 0.0 ");
            fprintf(stderr, "\n WARNING456: NOT Continuing but omitting data[%d] = %e; chi^2 will be wrong \n\n", i, data[i]);
            exit(EXIT_FAILURE);
        }
    }

    xn1 = data[missing];
//printf("\n");
    for (i = 0; i < maxiters; ++i)
    {
        oldb = *b;
        expinvxn = xn1;

/*         printf(">>>> %3d expinvxn:%8.3e, sum:%8.3e\n", i, expinvxn, sum); */
/*         double tmp = (3.0/data[1] - 2.0/data[2]); */
/*         printf(">>>>     ordersta:%8.3e -- %8.3e\n", tmp, fabs(tmp-expinvxn)/expinvxn); */
/*         tmp = 0.5 * (1.0/data[2] - 3.0/data[3] + 2.0/data[1]); */
/*         printf(">>>>     ordersta:%8.3e -- %8.3e\n", tmp, fabs(tmp-expinvxn)/expinvxn); */
/*         printf(">>>>     ntsmalle:%8.3e\n", 1.0/data[1]); */
/*         printf("missing = %d\n", missing); */
/*         fflush(NULL); */
        //expinvxn = 7e2;

        //*b = (num*c - 2.0) / (sum + missing*expinvxn); /* bayes for invgamma variance */
        //*b = (num*c + 2.0) / (sum + missing*expinvxn); /* bayes for gamma precision param */
        *b = (num*c) / (sum + missing*expinvxn);

//        oldlogL = *logL;
//        *logL = invgamma_logL(data + missing, num-missing, *b, c) + missing * log(invgamma_cdf(xn1, *b, c));
        //*logL += -(1.0 + c) * ExpLogXn(*b, c, xn1) - *b * expinvxn + c * log(*b) - lgamma(c);
//        printf("\nlogL: %3d % 12.6e % 12.6e % 12.6e", i, *logL, *logL-oldlogL, invgamma_ent(*b, c));

        if (fabs(*b - oldb) < fabs(*b * precision))
            break;
    }
//printf("\n");
    //chi2 = chi_sqr_adapt(newdata, num-missing, 0, logL, *b, c, invgamma_pdf, invgamma_lnpdf, invgamma_int);

    return(chi2);
}


double
invgamma_fixed_c_EM_fit_bayes(const double *data, const int num, const int missing, double *b, const double c, double *logL)
{
    int             i;
    int             maxiters = 100;
    double          precision = FLT_EPSILON;
    double          chi2 = 0.0, sum, expinvxn, xn1;
    double          oldb;
//    double          oldlogL = -DBL_MAX;

//    *logL=-DBL_MAX;

    /* data assumed to be ordered small to big (missing first) */
    sum = 0.0;
    for (i = missing; i < num; ++i)
    {
        if (data[i] > 0.0)
        {
            sum += 1.0 / data[i];;
        }
        else
        {
            fprintf(stderr, "\n WARNING456: Inverse gamma distributed data must be > 0.0 ");
            fprintf(stderr, "\n WARNING456: Continuing but omitting data[%d] = %e; chi^2 will be wrong \n\n", i, data[i]);
        }
    }

    xn1 = data[missing];
//printf("\n");
    for (i = 0; i < maxiters; ++i)
    {
        oldb = *b;
        expinvxn = ExpInvXn(*b, c, xn1);

        //printf(">>>> %3d expinvxn:%8.3e, sum:%8.3e\n", i, expinvxn, sum);

        *b = (num*c - 1.0) / (sum + missing*expinvxn); /* bayes for invgamma variance */
        //*b = (num*c + 1.0) / (sum + missing*expinvxn); /* bayes for gamma precision param */
        //*b = (num*c) / (sum + missing*expinvxn);

//        oldlogL = *logL;
//        *logL = invgamma_logL(data + missing, num-missing, *b, c) + missing * log(invgamma_cdf(xn1, *b, c));
        //*logL += -(1.0 + c) * ExpLogXn(*b, c, xn1) - *b * expinvxn + c * log(*b) - lgamma(c);
//        printf("\nlogL: %3d % 12.6e % 12.6e % 12.6e", i, *logL, *logL-oldlogL, invgamma_ent(*b, c));

        if (fabs(*b - oldb) < fabs(*b * precision))
            break;
    }
//printf("\n");
    //chi2 = chi_sqr_adapt(newdata, num-missing, 0, logL, *b, c, invgamma_pdf, invgamma_lnpdf, invgamma_int);

    return(chi2);
}



double
invgamma_fixed_c_EM_fit_old(const double *data, const int num, const int missing, double *b, const double c, double *logL)
{
    int             i;
    double          chi2, sum, expinvxn, xn1;
    double         *newdata = NULL;
    double          oldb;

    newdata = malloc(num * sizeof(double));
    memcpy(newdata, data, num * sizeof(double));
    qsort(newdata, num, sizeof(double), dblcmp_rev);
    /* qsort-dblcmp_rev sorts big to small */

    sum = 0.0;
    for (i = 0; i < num - missing; ++i)
    {
        if (newdata[i] > 0.0)
        {
            sum += 1.0 / newdata[i];;
        }
        else
        {
            fprintf(stderr, "\n WARNING456: Inverse gamma distributed data must be > 0.0 ");
            fprintf(stderr, "\n WARNING456: Continuing but omitting data[%d] = %e; chi^2 will be wrong \n\n", i, data[i]);
        }
    }

    xn1 = newdata[num-missing-1];

    for (i = 0; i < 100; ++i)
    {
        oldb = *b;
        expinvxn = ExpInvXn(*b, c, xn1);

        //printf(">>>> %3d expinvxn:%8.3e, sum:%8.3e\n", i, expinvxn, sum);

        //*b = (num*c - 1.0) / (sum + missing*expinvxn); /* bayes for invgamma variance */
        //*b = (num*c + 1.0) / (sum + missing*expinvxn); /* bayes for gamma precision param */
        *b = (num*c) / (sum + missing*expinvxn);

        if (fabs(*b - oldb) < fabs(*b * FLT_EPSILON))
            break;
    }

    chi2 = chi_sqr_adapt(newdata, num-missing, 0, logL, *b, c, invgamma_pdf, invgamma_lnpdf, invgamma_int);

    free(newdata);

    return(chi2);
}



/* This is more stable than the straight ML fit of the inverse gamma.
   This one transforms the data to its inverse first, then fits that with the gamma fit. */
double
invgamma_gamma_fit(const double *data, const int num, double *b, double *c, double *logL)
{
    int             i, j;
    double         *x = malloc(num * sizeof(double));
    double          chi2;

    j = 0;
    for (i = 0; i < num; ++i)
    {
        if (data[i] > 0.0)
        {
            x[j] = 1.0 / data[i];
            ++j;
        }
        else
        {
            fprintf(stderr, "\n WARNING345: Inverse gamma distributed data must be > 0.0 ");
            fprintf(stderr, "\n WARNING345: Continuing but omitting data[%d] = %e \n\n", i, data[i]);
        }
    }

    if (j < 2)
    {
        fprintf(stderr, "\n ERROR346: Number of data points > 0.0 is only %d ", j);
        fprintf(stderr, "\n ERROR346: Aborting calculation \n\n");
        exit(EXIT_FAILURE);
    }

    gamma_fit_no_stats(x, j, b, c);

    *b = 1.0 / *b;

    chi2 = chi_sqr_adapt(data, num, 0, logL, *b, *c, invgamma_pdf, invgamma_lnpdf, invgamma_int);

    free(x);

    return(chi2);
}


/* We must find the root of:

       F1 = ln(b) - digamma(c) - 1/N \Sum ln(x_i) = 0

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

       F1' = -trigamma(c)
*/
static void
evalinvgammaEM(const double lnave, const double b, const double c,
               double *fx, double *dfx)
{
    *fx = log(b) - gsl_sf_psi(c) - lnave;
    *dfx = -gsl_sf_psi_1(c);
}


/* Fit a gamma distribution by maximum likelihood.
   Uses Newton-Raphson and E-M algorithm.
   This is an alternating conditional probability algorithm.
   Alternates between N-R for c (using current b)
   and ML for b (based on current c).
   Takes more iterations than the pure N-R ML method below.
   Global convergence is probably poorer too.
*/
double
invgamma_cond_alt_fit(const double *data, const int num, double *b, double *c, double *logL)
{
    double          lnave, harmave, ave, var, fx = 0.0, dfx = 1.0;
    double          guess_b, guess_c;
    int             i;
    double          tol = 1e-9;
    int             maxit = 300;

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

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

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

    guess_c = *c = 1.0;
    guess_b = *b = *c * harmave;

    /* Maximum likelihood fit.
       Uses Newton-Raphson to find ML estimate of c.
       We must find the root of:

           F1 = ln(b) - digamma(c) - 1/N \Sum ln(x_i) = 0

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

           F1' = -trigamma(c)
    */
    printf("            b             c            fx           dfx @\n");
    for (i = 0; i < maxit; ++i)
    {
        printf("% 10.6e % 10.6e % 10.6e % 10.6e % 10.6e\n",
               *b, *c, fx, dfx, fx/dfx);
        evalinvgammaEM(lnave, *b, *c, &fx, &dfx);

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

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

        if (*c <= 0.0)
            *c = 0.1;

        if (*b <= 0.0)
            *b = 0.1;

        /* plug in result to get b */
        *b = *c * harmave;
    }
    printf("iter = %d\n", i);
    /* invgamma_logL(*b, *c); */
    fflush(NULL);

    if (i == maxit)
    {
        SCREAMS("no convergence!!!!!!!!!!!!!!!!");
        *b = guess_b;
        *c = guess_c;
    }

    return(chi_sqr_adapt(data, num, 0, logL, *b, *c, invgamma_pdf, invgamma_lnpdf, invgamma_int));
}


/* HypergeometricPFQ[{a, a}, {1 + a, 1 + a}, -c]  (Mathematica)
   Hypergeometric_2F2[{a, a}, {1 + a, 1 + a}, -c]
   http://en.wikipedia.org/wiki/Hypergeometric_series
   http://functions.wolfram.com/HypergeometricFunctions/Hypergeometric2F2/02/
*/
static double
hyperg2f2_invgamma(const double c, const double bn)
{
    int         k;
    double      newsum, oldsum, term;
    double      precision = FLT_EPSILON;

    newsum = oldsum = 0.0;
    for (k = 1; k < GSL_SF_FACT_NMAX; ++k)
    {
        oldsum = newsum;

        term = c / (c + k);
        newsum += term * term * gsl_pow_int(-bn, k) / gsl_sf_fact(k);

        //printf("\n%4d term:% 8.3e  newsum:% 8.3e  powterm:% 8.3e  fact:% 8.3e", k, term, newsum, gsl_pow_int(-bn, k), gsl_sf_fact(k));

        if (fabs(newsum - oldsum) <= precision)
            break;
    }

    return(newsum + 1.0);
}


static double
meijerg_invgamma(const double b, const double c, const double n)
{
    double      term, bn = b/n;

    term = pow(bn, c) * hyperg2f2_invgamma(c, bn)/(c*c);

    return(term + gsl_sf_gamma(c) * (gsl_sf_psi_n(0, c) - log(bn)));
}


double
ExpXn(const double b, const double c, const double n)
{
    return(b * gsl_sf_gamma_inc(c-1.0, b/n) / gsl_sf_gamma_inc(c, b/n));
}


double
ExpLogXn(const double b, const double c, const double n)
{
    return(log(n) - meijerg_invgamma(b, c, n) / gsl_sf_gamma_inc(c, b/n));
}


double
ExpInvXn(const double b, const double c, const double xn1)
{
    double          ratio = b/xn1;
    double          result; // = gsl_sf_gamma_inc(c + 1.0, ratio) / (b * gsl_sf_gamma_inc(c, ratio));

    result = (0.5 / b) + ((sqrt(ratio) * exp(-ratio)) / (b * sqrt(3.1415926535) * erfc(sqrt(ratio))));

    if (ratio < 100.0)
        return(result);
    else
        return(1.0/xn1 + 1.0/b);
}


/* Newton-Raphson to find ML estimate of c.
   We must find the root of:

       F1 = N * ln(b) - N * digamma(c) - \Sum ln(x_i) = 0

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

       F1' = - N * trigamma(c)
*/
/* experience shows that this converges much slower than the other method (evalinvgammaML) */
void
evalinvgammaML2(const int num, const double sum_ln, const double b, const double c,
               double *fx, double *dfx)
{
    *fx = num * log(b) - sum_ln - num * gsl_sf_psi(c);
    *dfx = - num * gsl_sf_psi_1(c);
}


double
invgamma_EMsmall_fit(const double *data, const int numt, int missing, double *b, double *c, double *logL)
{
    double          sum_ln, sum_inv, ave_ln, harm_ave, x;
    double         *newdata = malloc(numt * sizeof(double));
    double          chi2 = 0.0, xn1, expinvxn, explogxn;
    double          fx = 0.0, dfx = 1.0, fxdfx = 0.0;
    int             i, numm;
    int             maxit = 500;
    double          tol = 1e-6; //FLT_EPSILON;

    memcpy(newdata, data, numt * sizeof(double));
    qsort(newdata, numt, sizeof(double), dblcmp_rev);
    /* qsort-dblcmp_rev sorts big to small */

    numm = numt - missing;

    sum_ln = sum_inv = 0.0;
    for (i = 0; i < numm; ++i)
    {
        x = newdata[i];
        if (x > DBL_MIN)
        {
            sum_ln += log(x); /* logarithmic average */
            sum_inv += 1.0/x; /* harmonic average */
        }
        else
        {
            fprintf(stderr, "\n ERROR348: inverse gamma distributed data must be >= 0.0 \n\n");
            missing = numt - i;
            break;
        }
    }

    numm = numt - missing;

    ave_ln = sum_ln / numm;
    // ave_inv = sum_inv / numm;
    harm_ave = numm / sum_inv;

    /* MMEs are:
           E(x) = b/(c-1)               if c > 1
           Var(x) = b^2/((c-1)^2(c-2))  if c > 2
       The MLE of b:
           b = c * Ave(x)_harmonic
       Using the first and third eqns above gives:
           c = E(x) / (E(x) - Ave(x)_harmonic)
       This is always positive as E(x) > Ave(x)_harmonic.
       In fact it is always >= 1.0.
    */

//    var_inv = 0.0;
//    for (i = 0; i < numm; ++i)
//    {
//        tmp1 = invx[i] - ave_inv;
//        var_inv += tmp1 * tmp1;
//    }
//    var_inv /= (numm - 1);

    /* MMEs from the regular gamma distribution (using inverse transformed data) */
//    *c = ave_inv * ave_inv / var_inv;
//    *b = *c * harm_ave; /* this is actually the MLE given known c */

    xn1 = newdata[numm - 1];
//    printf("\nxn1 = %8.3e", xn1);

    /* this is a combo Newton-Raphson and EM algorithm
       every NR iteration, the new estimates of b and c are used to calculate
       new expectations */
    /* Newton-Raphson to find ML estimate of c.
       We must find the root of:

           F1 = ln((N*c)/\Sum(1/x_i)) - digamma(c) - 1/N \Sum ln(x_i) = 0

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

           F1' = 1/c - trigamma(c)
    */
/*     printf("\n                   b             c            fx           dfx"); */
/*     printf("\n    % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
/*            *b, *c, fx, dfx, fx/dfx); */
    fflush(NULL);
    for (i = 0; i < maxit; ++i)
    {
        expinvxn = ExpInvXn(*b, *c, xn1);
        explogxn = ExpLogXn(*b, *c, xn1);

        ave_ln = (sum_ln + missing * explogxn) / numt;
        harm_ave = numt / (sum_inv + missing * expinvxn);
/*         printf("\n*****      ave_ln,    harm_ave,    expinvxn,     explogxn"); */
/*         printf("\n***** % 10.6e % 10.6e % 10.6e % 10.6e\n\n", ave_ln, harm_ave, expinvxn, explogxn); */
/*         fflush(NULL); */
        evalinvgammaML(ave_ln, harm_ave, *c, &fx, &dfx);
//        evalinvgammaML2(numt, (sum_ln + missing * explogxn), *b, *c, &fx, &dfx);

        fxdfx = fx/dfx;

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

        *c -= fxdfx; /* Newton-Raphson correction */

        if (*c <= 0.0)
            *c = 0.1;

        /* plug in result to get b */

        //*b = (numt * *c - 1.0) / (sum_inv + missing * expinvxn); /* bayes for invgamma variance */
        //*b = (num*c + 1.0) / (sum + missing*expinvxn); /* bayes for gamma precision param */
        *b = *c *harm_ave;
        //*b = (numt * *c) / (sum_inv + missing * ExpInvXn(*b, *c, xn1));
/*         printf("\n%3d % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e", */
/*                i, *b, *c, fx, dfx, fx/dfx); */
    }
//    printf("\niter = %d", i);

    if (i == maxit)
    {
        printf("\n\n>->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->");
        printf("\n ERROR545: Newton-Raphson ML fit in invgamma_EMsmall_fit() did not converge.\n");
        printf("\n                b             c            fx           dfx        fx/dfx");
        printf("\n %3d % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e ", i, *b, *c, fx, dfx, fx/dfx);
        printf("\n>->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->->\n\n");
    }

    //chi2 = chi_sqr_adapt(newdata, numm, 0, logL, *b, *c, invgamma_pdf, invgamma_lnpdf, invgamma_int);

    free(newdata);

    return(chi2);
}


double
invgamma_bayes_fit(const double *data, const int num, double *b, double *c, double *logL)
{
    int             i, j;
    double         *x = malloc(num * sizeof(double));
    double          chi2;

    j = 0;
    for (i = 0; i < num; ++i)
    {
        if (data[i] > 0.0)
        {
            x[j] = 1.0 / data[i];
            ++j;
        }
        else
        {
            fprintf(stderr, "\n WARNING345: Inverse gamma distributed data must be > 0.0 ");
            fprintf(stderr, "\n WARNING345: Continuing but omitting data[%d] = %e \n\n", i, data[i]);
        }
    }

    if (j < 2)
    {
        fprintf(stderr, "\n ERROR346: Number of data points > 0.0 is only %d ", j);
        fprintf(stderr, "\n ERROR346: Aborting calculation \n\n");
        exit(EXIT_FAILURE);
    }

    gamma_bayes_ref_fit_no_stats(x, j, b, c);

    *b = 1.0 / *b;

    chi2 = chi_sqr_adapt(data, num, 0, logL, *b, *c, invgamma_pdf, invgamma_lnpdf, invgamma_int);

    free(x);

    return(chi2);
}


double
invgamma1_fit(const double *data, const int num, double *b, double *nullp, double *logL)
{
    int             i, j;
    double         *x = malloc(num * sizeof(double));
    double          chi2;

    j = 0;
    for (i = 0; i < num; ++i)
    {
        if (data[i] > 0.0)
        {
            x[j] = 1.0 / data[i];
            ++j;
        }
    }

    gamma1_fit(x, j, b, nullp, logL);

    *b = 1.0 / *b;

    chi2 = chi_sqr_adapt(x, j, 0, logL, *b, 1.0, invgamma_pdf, invgamma_lnpdf, invgamma_int);

    free(x);

    return(chi2);
}


double
invgamma_fit_guess(const double *data, const int num, double *b, double *c, double *logL)
{
    int             i;
    double          invb;
    double         *x = malloc(num * sizeof(double));

    for (i = 0; i < num; ++i)
        x[i] = 1.0 / data[i];

    gamma_fit_guess(x, num, &invb, c, logL);
/*     if (fabs(invb) < DBL_EPSILON) */
/*         *b = DBL_MAX; */
/*     else */
    *b = 1.0 / invb;

    free(x);

    return(chi_sqr_adapt(data, num, 0, logL, *b, *c, invgamma_pdf, invgamma_lnpdf, invgamma_int));
}


double
invgamma_minc_fit(const double *data, const int num, double *b, double *c, const double minc, double *logL)
{
    int             i;
    double          invb;
    double         *x = malloc(num * sizeof(double));

    for (i = 0; i < num; ++i)
        x[i] = 1.0/data[i];

    gamma_minc_fit(x, num, &invb, c, minc, logL);
    *b = 1.0 / invb;

    free(x);

    return(chi_sqr_adapt(data, num, 0, logL, *b, *c, invgamma_pdf, invgamma_lnpdf, invgamma_int));
}


double
invgamma_minc_opt_fit(const double *data, const int num, double *b, double *c, const double minc, double *logL)
{
    int             i;
    double          invb;
    double         *x = malloc(num * sizeof(double));

    for (i = 0; i < num; ++i)
        x[i] = 1.0/data[i];

    gamma_minc_opt_fit(x, num, &invb, c, minc, logL);
    *b = 1.0 / invb;

    free(x);

    return(chi_sqr_adapt(data, num, 0, logL, *b, *c, invgamma_pdf, invgamma_lnpdf, invgamma_int));
}


static void
evalinvgammaMLmode(const double lnave, const double invave, const double b,
                   const double mode, double *fx, double *dfx)
{
/*     double          invmode = 1.0 / mode; */

/*     *fx = invmode */
/*           + (invmode * log(b)) */
/*           - (invmode * gsl_sf_psi(b * invmode - 1.0)) */
/*           - (invmode * lnave) */
/*           - invave; */

    double term = b/mode - 1.0;

    *fx = term/b - invave - lnave/mode + log(b)/mode - gsl_sf_psi(term) / mode;
    *dfx = -term/(b*b) + 2.0/(b*mode) - gsl_sf_psi_1(term)/(mode*mode);

/*     *fx = ((1.0 + log(b) - gsl_sf_psi(b/mode - 1.0) - lnave) / mode) - invave - (1.0 / b); */
/*  */
/*     *dfx = ((1.0 / b) - (gsl_sf_psi_1(b/mode - 1.0) / mode)) / mode + (1.0 / (b*b)); */
}


double
invgamma_mode_fit(const double *data, const int num, double *b, double *c, const double mode, double *logL)
{
    double          lnave, ave, invave, x;
    double          fx = 0.0, dfx = 1.0, fxdfx = 0.0;
    int             i;
    int             maxit = 40;
    double          tol = 1e-9;
    /* double          mode = 2.0; */

    ave = lnave = invave = 0.0;
    for (i = 0; i < num; ++i)
    {
        x = data[i];
        if (x > 0.0)
        {
            ave += x;
            lnave += log(x); /* logarithmic average */
            invave += 1.0/x; /* inverse average */
        }
        else
        {
            fprintf(stderr, "\n ERROR345: inverse gamma distributed data must be > 0.0 ");
            return(-1.0);
        }
    }

    ave /= num;
    lnave /= num;
    invave /= num;

    if (ave > mode)
        *b = 2.0 * mode * ave / (ave - mode);
    else
        *b = 1.0;

    if (*b > mode)
        *c = (*b - mode) / mode;
    else
        *c = 1.0;

    for (i = 0; i < maxit; ++i)
    {
        printf("% 10.6e % 10.6e % 10.6e % 10.6e % 10.6e\n",
               *b, *c, fx, dfx, fxdfx);

        evalinvgammaMLmode(lnave, invave, *b, mode, &fx, &dfx);

        fxdfx = fx/dfx;

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

        *b -= fxdfx; /* Newton-Raphson correction */

        if (*b <= 0.0)
            *b = 0.1;

        /* plug in result to get c */
        if (*b > mode)
            *c = (*b - mode) / mode;
        else
            *c = 1.0;
    }

    printf("iter = %d\n", i);
    /* invgamma_logL(*b, *c); */

    if (i == maxit)
        printf("\n\n ERROR543: Newton-Raphson ML fit in invgamma_fit() did not converge.\n");

    return(chi_sqr_adapt(data, num, 0, logL, *b, *c, invgamma_pdf, invgamma_lnpdf, invgamma_int));
}



/* We must find the root of:

       F1 = log(c) + 1 - digamma(c) - \sum{log(x)}/K - \sum{1/x}/K = 0

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

       F1' =  1/c - trigamma(c)
*/
static void
evalinvgamma_eq_bc_ML(const double lnave, const double invave, const double c,
               double *fx, double *dfx)
{
    *fx = log(c) + 1.0 - gsl_sf_psi(c) - lnave - invave;
    *dfx = 1.0/c - gsl_sf_psi_1(c);
}


/* Fit an inverse gamma distribution by maximum likelihood,
   assuming b = c.
*/
double
invgamma_eq_bc_fit(const double *data, const int num, double *b, double *c, double *logL, int init)
{
    double          lnave, invave, invvar, x;
    double          fx = 0.0, dfx = 1.0, fxdfx = 0.0;
    int             i, skip;
    int             maxit = 300;
    double          tol = 1e-9;

/*     double tmp = invgamma_transfit(data, num, b, c, logL); */
/*     printf("\n %-12s %11.3e %11.3e %9.2f %12.3f %14.1f %14.1f %14.1f %6.3f", */
/*            "invg_trans", *b, *c, tmp, *logL/num, *logL, *logL, *logL, 1.0); */

    lnave = invave = 0.0;
    skip = 0;
    for (i = 0; i < num; ++i)
    {
        x = data[i];
        if (x > 0.0)
        {
            lnave += log(x); /* logarithmic average */
            invave += 1.0/x; /* harmonic average */
        }
        else if(x == 0.0)
        {
            ++skip;
            continue;
        }
        else
        {
            fprintf(stderr, "\n ERROR345: inverse gamma distributed data must be > 0.0 ");
            return(-1.0);
        }
    }

    lnave /= (num - skip);
    invave /= (num - skip);

    /* MMEs are:
           E(x) = b/(c-1)               if c > 1
           Var(x) = b^2/((c-1)^2(c-2))  if c > 2
       The MLE of b:
           b = c * Ave(x)_harmonic
       Using the first and third eqns above gives:
           c = E(x) / (E(x) - Ave(x)_harmonic)
       This is always positive as E(x) > Ave(x)_harmonic.
       In fact it is always >= 1.0.
    */

    if (init == 0)
    {
        invvar = 0.0;
        for (i = 0; i < num; ++i)
            invvar += mysquare(1.0/data[i] - invave);
        invvar /= (double) (num - 1 - skip);

        /* MMEs from the regular gamma distribution (using inverse transformed data) */
        *c = invave * invave  / invvar;
        *b = *c/*  = 1.0 */;
    }
    else /* use the values passed as initial guesses */
    {
        *c = *b;
    }

    if (!isfinite(*b) || !isfinite(*c))
    {
        printf("\n ERROR04: b(%e) or c(%e) parameter in invgamma_eq_bc_fit() not finite\n",
               *b, *c);
        fflush(NULL);
        exit(EXIT_FAILURE);
    }

    /* Newton-Raphson to find ML estimate of c.
       We must find the root of:

           F1 = log(c) + 1 - digamma(c) - \sum{log(x)}/K - \sum{1/x}/K = 0

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

           F1' = 1/c - trigamma(c)
    */
    printf("            b             c            fx           dfx ~\n");
    printf("% 10.6e % 10.6e % 10.6e % 10.6e % 10.6e\n",
           *b, *c, fx, dfx, fx/dfx);

    for (i = 0; i < maxit; ++i)
    {
        evalinvgamma_eq_bc_ML(lnave, invave, *c, &fx, &dfx);

        if (!isfinite(*c))
        {
            printf("\n ERROR05: c(%e) parameter in invgamma_eq_bc_fit() not finite\n",
                   *c);
            printf("\n%3d: % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e\n\n",
                   i, *b, *c, fx, dfx, fx/dfx);
            fflush(NULL);
            exit(EXIT_FAILURE);
        }

        fxdfx = fx/dfx;

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

        *c -= fxdfx; /* Newton-Raphson correction */

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

        /* plug in result to get b */
        *b = *c;
        printf("%3d: % 10.6e % 10.6e % 10.6e % 10.6e % 10.6e\n",
               i, *b, *c, fx, dfx, fx/dfx);
    }
/*     printf("\niter = %d", i); */
    /* invgamma_logL(*b, *c); */

    if (i == maxit)
        printf("\n\n ERROR543: Newton-Raphson ML fit in invgamma_eq_bc_fit() did not converge.\n");

    return(chi_sqr_adapt(data, num, 0, logL, *b, *c, invgamma_pdf, invgamma_lnpdf, invgamma_int));
}
