/*
    Theseus - maximum likelihood superpositioning of macromolecular structures

    Copyright (C) 2004-2015 Douglas L. Theobald

    This file is part of THESEUS.

    THESEUS 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 3 of
    the License, or (at your option) any later version.

    THESEUS 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 THESEUS in the file 'COPYING'. If not, see
    <http://www.gnu.org/licenses/>.

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

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#ifdef   __linux__
  #include <getopt.h>
#endif
#include <ctype.h>
#include <math.h>
#include <float.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <time.h>
#include <gsl/gsl_math.h>
#include <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include <gsl/gsl_sort.h>
#include <gsl/gsl_statistics_double.h>
#include <gsl/gsl_sf_psi.h>

#include "distfit_local.h"

#define VERSION "0.90"
#define DISTNUM 24

static double
**ReadValsMulti(const char *listfile_name, int *col, int *len);

/* array of function names */
char
dist_name[DISTNUM][32] =
{
    "Normal",      "Laplace",   "EVD",        "Logistic",
    "Cauchy",      "Uniform",   "Weibull",
    "Gamma",       "ChisqrGen", "Lognormal",
    "Invgamma",    "Invgauss",  "Recinvgauss", "Pareto",
    "Exponential", "Invchisqr", "Chisqr",      "Chi",
    "Rayleigh", "Rice*",
    "Maxwell",     "Beta",      "BetaSym", "BetaPrime"     //"Binomial"
};

/* array of function pointers */
DistFit
distfit_func[DISTNUM] =
{
    &normal_fit,    &laplace_fit,   &EVD_fit,         &logistic_fit,
    &cauchy_fit,    &uniform_fit,   &weibull_fit,
    &gamma_fit,     &chisqrgen_fit, &lognormal_fit,
    &invgamma_fit,  &invgauss_fit,  &recinvgauss_fit, &pareto_fit,
    &exp_fit,       &invchisqr_fit, &chisqr_fit,      &chi_fit,
    &rayleigh_fit,  &rice_fit,
    &maxwell_fit,   &beta_fit,      &betasym_fit,  &betaprime_fit   //&binomial_fit
};

/* array of function pointers */
DistDev
distdev_func[DISTNUM] =
{
    &normal_dev,    &laplace_dev,   &EVD_dev,         &logistic_dev,
    &cauchy_dev,    &uniform_dev,   &weibull_dev,
    &gamma_dev,     &chisqrgen_dev, &lognormal_dev,
    &invgamma_dev,  &invgauss_dev,  &recinvgauss_dev, &pareto_dev,
    &exp_dev,       &invchisqr_dev, &chisqr_dev,      &chi_dev,
    &rayleigh_dev,  &rice_dev,
    &maxwell_dev,   &beta_dev,      &betasym_dev,     &betaprime_dev //&binomial_dev
};

DistPDF
distpdf_func[DISTNUM] =
{
    &normal_pdf,    &laplace_pdf,   &EVD_pdf,         &logistic_pdf,
    &cauchy_pdf,    &uniform_pdf,   &weibull_pdf,
    &gamma_pdf,     &chisqrgen_pdf, &lognormal_pdf,
    &invgamma_pdf,  &invgauss_pdf,  &recinvgauss_pdf, &pareto_pdf,
    &exp_pdf,       &invchisqr_pdf, &chisqr_pdf,      &chi_pdf,
    &rayleigh_pdf,  &rice_pdf,
    &maxwell_pdf,   &beta_pdf,      &betasym_pdf,     &betaprime_pdf //&binomial_pdf
};

DistCDF
distcdf_func[DISTNUM] =
{
    &normal_cdf,    &laplace_cdf,   &EVD_cdf,         &logistic_cdf,
    &cauchy_cdf,    &uniform_cdf,   &weibull_cdf,
    &gamma_cdf,     &chisqrgen_cdf, &lognormal_cdf,
    &invgamma_cdf,  &invgauss_cdf,  &recinvgauss_cdf, &pareto_cdf,
    &exp_cdf,       &invchisqr_cdf, &chisqr_cdf,      &chi_cdf,
    &rayleigh_cdf,  &rice_cdf,
    &maxwell_cdf,   &beta_cdf,      &betasym_cdf,     &betaprime_cdf //&binomial_cdf
};

DistSDF
distsdf_func[DISTNUM] =
{
    &normal_sdf,    &laplace_sdf,   &EVD_sdf,         &logistic_sdf,
    &cauchy_sdf,    &uniform_sdf,   &weibull_sdf,
    &gamma_sdf,     &chisqrgen_sdf, &lognormal_sdf,
    &invgamma_sdf,  &invgauss_sdf,  &recinvgauss_sdf, &pareto_sdf,
    &exp_sdf,       &invchisqr_sdf, &chisqr_sdf,      &chi_sdf,
    &rayleigh_sdf,  &rice_sdf,
    &maxwell_sdf,   &beta_sdf,      &betasym_sdf,     &betaprime_sdf //&binomial_sdf
};

DistInt
distint_func[DISTNUM] =
{
    &normal_int,    &laplace_int,   &EVD_int,         &logistic_int,
    &cauchy_int,    &uniform_int,   &weibull_int,
    &gamma_int,     &chisqrgen_int, &lognormal_int,
    &invgamma_int,  &invgauss_int,  &recinvgauss_int, &pareto_int,
    &exp_int,       &invchisqr_int, &chisqr_int,      &chi_int,
    &rayleigh_int,  &rice_int,
    &maxwell_int,   &beta_int,      &betasym_int,     &betaprime_int //&binomial_int
};

int
dist_pnum[DISTNUM] =
{
    2, 2, 2, 2,
    2, 2, 2,
    2, 2, 2,
    2, 2, 2, 2,
    1, 1, 1, 1,
    1, 2,
    1, 2, 1, 2 //2
};


int
dist_nonneg[DISTNUM] =
{
    0, 0, 0, 0,
    0, 0, 1,
    1, 1, 0,
    0, 0, 0, 0,
    1, 0, 1, 1,
    1, 1,
    1, 0, 0, 1 //1
};


int
dist_posdef[DISTNUM] =
{
    0, 0, 0, 0,
    0, 0, 0,
    0, 0, 1,
    1, 1, 1, 1,
    0, 1, 0, 0,
    0, 0,
    0, 0, 0, 0 //0
};

int
dist_beta[DISTNUM] =
{
    0, 0, 0, 0,
    0, 0, 0,
    0, 0, 0,
    0, 0, 0, 0,
    0, 0, 0, 0,
    0, 0,
    0, 1, 1, 0 //0
};

DFParams        DfParams;
DFParams       *dfparams = &DfParams;
DistStats     **diststats = NULL;
int             distsort[DISTNUM];
double         *pdfparams = NULL;
double         *cdfparams = NULL;
double         *sdfparams = NULL;
double         *intparams = NULL;


/*
Calculate the bias in the entropy estimate due to deviation from Normality.
Based on on Edgeworth expansion of a PDF in terms of its cumulants (moments).

See:

Marc M. Van Hulle (2005)
"Multivariate Edgeworth-based entropy estimation."
2005 IEEE Workshop on Machine Learning for Signal Processing,
Conference Proceedings
28-28 Sept. 2005
pp 311 - 316

or

Marc M. Van Hulle (2005)
"Edgeworth Approximation of Multivariate Differential Entropy"
Neural Computation 17, 1903–1910

See equation 2.2.

This bias term is substracted from the usual multivariate Gaussian
entropy:

0.5 * d * log(2.0 * M_PI * M_E) + 0.5 * lndet

where lndet is the log of the determinant of the d*d covariance matrix.

This could be combined more intelligently/efficiently with the
covariance calculation.
*/
/*
Comon, P. (1994)
"Independent component analysis, a new concept?"
Signal processing 36, 287–314.

Amari, S.-I., Cichocki, A. and Yang, H. H. (1996)
"A new learning algorithm for blind signal separation."
Advances in neural information processing systems 8
Eds. D. Touretzky, M. Mozer, and M. Hasselmo.
MIT Press, Cambridge.
757–763 (1996).
*/
double
CalcEdgeworthVanHulleEntropy(double **vec, int dim, int len)
{
    int            i, j, k, m;
    double        *ave = NULL;
    double        *std = NULL;
    double        *eval = NULL;
    double       **dif = MatAlloc(dim,len);
    double         term1, term2, term3;
    double         term4, term5, term6;
    double         t3, t4;
    double         kappa_iii, kappa_iij, kappa_ijk;
    double         kappa_iiii;
    double         entropy, bias, lnscale, lndet, sum, var;
    double       **cor = MatAlloc(dim,dim);
    double       **cov = MatAlloc(dim,dim);
    double         invlen = 1.0/len;


    ave  = malloc(dim * sizeof(double));
    std  = malloc(dim * sizeof(double));
    eval = malloc(dim * sizeof(double));

    /* First, normalize data vector to 0 mean, unit 1 variance */
    for (i = 0; i < dim; ++i)
        ave[i] = average(vec[i], len);

    //VecPrint(ave, dim);

    for (i = 0; i < dim; ++i)
        for (j = 0; j < len; ++j)
            dif[i][j] = vec[i][j] - ave[i];

    for (i = 0; i < dim; ++i)
    {
        var = 0.0;
        for (j = 0; j < len; ++j)
            var += dif[i][j] * dif[i][j];

        std[i] = sqrt(var * invlen);
    }

    //VecPrint(std, dim);

    /* Save the determinant of the scale transformation */
    lnscale = 0.0;
    for (i = 0; i < dim; ++i)
        lnscale += log(std[i]);

    /* rescale centered data */
    for (i = 0; i < dim; ++i)
        std[i] = 1.0 / std[i];

    for (i = 0; i < dim; ++i)
        for (j = 0; j < len; ++j)
            dif[i][j] *= std[i];

    /* Calculate the covariance matrix of transformed data (= correlation matrix) */
    for (i = 0; i < dim; ++i)
    {
        for (j = 0; j <= i; ++j)
        {
            sum = 0.0;
            for (k = 0; k < len; ++k)
                sum += dif[i][k] * dif[j][k];

            cor[i][j] = cor[j][i] = sum * invlen;
        }
    }

//     printf ("\n\nEdgeworth correlation matrix:");
//     MatPrintLowerDiag(cor, dim);
//
//     for (i = 0; i < dim; ++i)
//         for (j = 0; j < dim; ++j)
//             cov[i][j] = cor[i][j] / (std[i] * std[j]);
//
//     printf ("\n\nEdgeworth covariance matrix:");
//     MatPrintLowerDiag(cov, dim);

    EigenvalsGSL((const double **) cor, dim, eval);

    //VecPrint(eval, dim);

    lndet = 0.0;
    for (i = 0; i < dim; i++)
    {
        if (isgreater(eval[i], DBL_EPSILON))
        {
            lndet += log(eval[i]);
        }
        else
        {
            printf("\n WARNING: excluding eigenvalue %d from determinant calculation", i);
            printf("\n WARNING: eigenvalue[%d] = %g < %g", i, eval[i], FLT_EPSILON);
        }
    }

    term1 = 0.0;
    term4 = 0.0;
    term5 = 0.0;
    term6 = 0.0;
    for (i = 0; i < dim; ++i)
    {
        kappa_iii = 0.0;
        kappa_iiii = 0.0;
        for (j = 0; j < len; ++j)
        {
            t3 = dif[i][j] * dif[i][j] * dif[i][j];
            kappa_iii += t3; /* skewness */
            kappa_iiii += t3 * dif[i][j]; /* kurtosis */
        }

        kappa_iii *= invlen;
        kappa_iiii *= invlen;
        kappa_iiii -= 3.0;

        t3 = kappa_iii * kappa_iii;
        t4 = kappa_iiii * kappa_iiii;
        term1 += t3;
        term4 += t4;
        term5 += t3*t3;
        term6 += t3 * kappa_iiii;
    }

    term2 = 0.0;
    for (i = 0; i < dim; ++i)
    {
        for (j = 0; j < dim; ++j)
        {
            if (i != j)
            {
                kappa_iij = 0.0;
                for (k = 0; k < len; ++k)
                    kappa_iij += dif[i][k] * dif[i][k] * dif[j][k];

                kappa_iij *= invlen;

                term2 += kappa_iij * kappa_iij;
            }
        }
    }

    term3 = 0.0;
    for (i = 0; i < dim; ++i)
    {
        for (j = 0; j < i; ++j)
        {
            for (k = 0; k < j; ++k)
            {
                kappa_ijk = 0.0;
                for (m = 0; m < len; ++m)
                    kappa_ijk += dif[i][m] * dif[j][m] * dif[k][m];

                kappa_ijk *= invlen;

                term3 += kappa_ijk * kappa_ijk;
            }
        }
    }

    /* There are d \kappa_{i,i,i} terms, 2 {d \choose 2} \kappa_{i,i,j} terms,
       and {d \choose 3} \kappa_{i,j,k} terms.
       gsl_sf_choose (unsigned int n, unsigned int m) */

    /* The following is based on Comon, P. (1994) Signal processing 36, 287–314.
       See eqn 3.4 (Theorem 14).
       The similar equations (7 & 8) in Amari, Cichocki, and Yang (1996) seem to be wrong.  */

    bias = (term1 + 3.0 * term2 + term3 / 6.0) / 12.0 + term4/48.0 + 7.0*term5/48.0 - term6/8.0;

    printf("\nEdgeworth term1: %g", term1/ 12.0);
    printf("\nEdgeworth term2: %g", 3.0*term2/ 12.0);
    printf("\nEdgeworth term3: %g", term3/(6.0*12.0));
    printf("\nEdgeworth term4: %g", +term4/48.0);
    printf("\nEdgeworth term5: %g", +7.0*term5/48.0);
    printf("\nEdgeworth term6: %g\n", - term6/8.0);

    printf("\nln(det):           %14.3f", lndet);

    entropy = 0.5 * dim * log(2.0 * M_PI * M_E) + 0.5 * lndet;

    printf("\nwhite entropy:     %14.3f", entropy);
    printf("\nbias:              %14.3f", bias);
    printf("\nln(scale):         %14.3f", lnscale);

    printf("\nNaive N-entropy:   %14.3f", entropy + lnscale);

    //entropy = entropy - bias + lnscale;

    printf("\nEdgeworth entropy: %14.3f", entropy - term1/12.0 + lnscale);
    printf("\nEdgeworth entropy (4th order corrections): %14.3f", entropy - bias + lnscale);
    printf("\n\n");

//     term1 = 0.0;
//     term4 = 0.0;
//     term5 = 0.0;
//     term6 = 0.0;
//     for (i = 0; i < dim; ++i)
//     {
//         kappa_iii = 0.0;
//         kappa_iiii = 0.0;
//         for (j = 0; j < len; ++j)
//         {
//             t3 = dif[i][j] * dif[i][j] * dif[i][j];
//             kappa_iii += t3; /* skewness */
//             kappa_iiii += t3 * dif[i][j]; /* kurtosis */
//         }
//
//         kappa_iii *= invlen;
//         kappa_iiii *= invlen;
//         kappa_iiii -= 3.0;
//
//         t3 = kappa_iii * kappa_iii;
//         t4 = kappa_iiii * kappa_iiii;
//         term1 += t3;
//         term4 += t4;
//         term5 += t4 * kappa_iiii; // k_4^3;
//         term6 += t3 * kappa_iiii; // k_3^2 k_4
//     }
//
//     bias = (term1 + 3.0 * term2 + term3 / 6.0) / 12.0 + term4/48.0 - term5/16.0 - 5.0*term6/8.0;
//
//     printf("\nEdgeworth term1: %g", term1/ 12.0);
//     printf("\nEdgeworth term2: %g", 3.0*term2/ 12.0);
//     printf("\nEdgeworth term3: %g", term3/(6.0*12.0));
//     printf("\nEdgeworth term4: %g", +term4/48.0);
//     printf("\nEdgeworth term5: %g", -term5/16.0);
//     printf("\nEdgeworth term6: %g\n", - 5.0*term6/8.0);
//
//     printf("\nln(det):           %14.3f", lndet);
//
//     entropy = 0.5 * dim * log(2.0 * M_PI * M_E) + 0.5 * lndet;
//
//     printf("\nwhite entropy:     %14.3f", entropy);
//     printf("\nbias:              %14.3f", bias);
//     printf("\nln(scale):         %14.3f", lnscale);
//
//     printf("\nNaive N-entropy:   %14.3f", entropy + lnscale);
//
//     //entropy = entropy - bias + lnscale;
//
//     printf("\nEdgeworth entropy: %14.3f", entropy - term1/12.0 + lnscale);
//     printf("\nEdgeworth entropy: %14.3f", entropy - bias + lnscale);
//     printf("\n\n");

    MatDestroy(&dif);
    MatDestroy(&cor);
    MatDestroy(&cov);
    MyFree(eval);
    MyFree(std);
    MyFree(ave);

    return(entropy);
}


double
FindMode(const double *x, const int len)
{
    int     i;
    int     i_beg, i_end, i_min, intsize;
    double  range, range_min, mode;


    i_min = i_beg = 0;
    i_end = len -1;
    range_min = 0.0;

    intsize = (i_end - i_beg) / 2.0;

    while (intsize > 1)
    {
        for (i = i_beg; i < i_end - intsize; ++i)
        {
            range = x[i + intsize] - x[i];

            if (i == i_beg)
            {
                i_min = i_beg;
                range_min = range;
            }
            else if (range < range_min)
            {
                i_min = i;
                range_min = range;
            }
        }

        i_beg = i_min;
        i_end = i_min + intsize;
        intsize = (i_end - i_beg) / 2.0;
    }

    if (intsize == 1)
        mode = (x[i_end] + x[i_beg]) / 2.0;
    else
        mode = x[i_beg];

    return(mode);
}


void
CalcDistStats(const double *array, const int len)
{
    double        *x = malloc(len * sizeof(double));
    double         pstats[10];

    memcpy(x, array, len * sizeof(double));

    gsl_sort(x, 1, len);
    pstats[0] = gsl_stats_mean(x, 1, len);
    pstats[1] = gsl_stats_sd_with_fixed_mean(x, 1, len, pstats[0]);
    pstats[2] = gsl_stats_skew_m_sd(x, 1, len, pstats[0], pstats[1]);
    pstats[3] = gsl_stats_kurtosis_m_sd(x, 1, len, pstats[0], pstats[1]);
    pstats[4] = gsl_stats_median_from_sorted_data(x, 1, len);
    pstats[5] = gsl_stats_quantile_from_sorted_data(x, 1, len, 0.025);
    pstats[6] = gsl_stats_quantile_from_sorted_data(x, 1, len, 0.975);
    pstats[7] = gsl_stats_quantile_from_sorted_data(x, 1, len, 0.05);
    pstats[8] = gsl_stats_quantile_from_sorted_data(x, 1, len, 0.95);
    pstats[9] = FindMode(x, len);

    printf("\nmode approximation: %g\n", pstats[9]);

    printf("\n       ave (+/-       SD)    skew    kurt     median ( 95%% credible interval ) ( 90%% credible interval )");
    printf("\n--------------------------------------------------------------------------------------------------------");

    printf("\n% 10.2f (+/- %8.2f) % 7.2f % 7.2f % 10.2f (% 10.2f - % 10.2f) (% 10.2f - % 10.2f)\n\n",
           pstats[0], pstats[1], pstats[2], pstats[3], pstats[4], pstats[5], pstats[6], pstats[7], pstats[8]);

    MyFree(x);
}


int
ParseCmdLine(int *argc, char **argv[])
{
    int             option;

    /* get the options */
    while ((option = getopt (*argc, *argv, "1b:B:c:C:d:D:g:G:hI:m:n:op:P:s:S:t:T:vz")) != -1)
    {
        switch (option)
        {
            case '1':
                dfparams->neg = 1;
                break;

            case 'b':
                dfparams->bootstrap = (int) strtol(optarg, NULL, 10);
                break;

            case 'B':
                dfparams->burnin = (double) strtod(optarg, NULL);
                break;

            case 'c':
                dfparams->col = (int) strtol(optarg, NULL, 10) - 1;

                if (dfparams->col > 15)
                {
                    Usage();
                    exit(EXIT_FAILURE);
                }
                break;

            case 'C':
                cdfparams = calloc(3, sizeof(double));
                sscanf(optarg, "%lf:%lf:%lf", &cdfparams[0], &cdfparams[1], &cdfparams[2]);
                break;

            case 'd':
                if (isdigit(optarg[0]))
                {
                    dfparams->distindex = (int) strtol(optarg, NULL, 10);
                    if (dfparams->distindex < 0 || dfparams->distindex >= DISTNUM)
                    {
                        printf("\n  Bad -d \"%s\" \n", optarg);
                        Usage();
                        exit(EXIT_FAILURE);
                    }
                }
                else
                {
                    strtolower(optarg);

                    if      (strncmp(optarg, "normal", 4) == 0)
                        dfparams->distindex = 0;
                    else if (strncmp(optarg, "laplace", 4) == 0)
                        dfparams->distindex = 1;
                    else if (strncmp(optarg, "evd", 3) == 0)
                        dfparams->distindex = 2;
                    else if (strncmp(optarg, "logistic", 4) == 0)
                        dfparams->distindex = 3;
                    else if (strncmp(optarg, "cauchy", 4) == 0)
                        dfparams->distindex = 4;
                    else if (strncmp(optarg, "uniform", 4) == 0)
                        dfparams->distindex = 5;
                    else if (strncmp(optarg, "weibull", 4) == 0)
                        dfparams->distindex = 6;
                    else if (strncmp(optarg, "gamma", 4) == 0)
                        dfparams->distindex = 7;
                    else if (strncmp(optarg, "chisqrgen", 7) == 0)
                        dfparams->distindex = 8;
                    else if (strncmp(optarg, "lognormal", 4) == 0)
                        dfparams->distindex = 9;
                    else if (strncmp(optarg, "invgamma", 5) == 0)
                        dfparams->distindex = 10;
                    else if (strncmp(optarg, "invgauss", 5) == 0)
                        dfparams->distindex = 11;
                    else if (strncmp(optarg, "recinvgauss", 3) == 0)
                        dfparams->distindex = 12;
                    else if (strncmp(optarg, "pareto", 4) == 0)
                        dfparams->distindex = 13;
                    else if (strncmp(optarg, "exponential", 4) == 0)
                        dfparams->distindex = 14;
                    else if (strncmp(optarg, "invchisqr", 4) == 0)
                        dfparams->distindex = 15;
                    else if (strncmp(optarg, "chisqr", 4) == 0)
                        dfparams->distindex = 16;
                    else if (strncmp(optarg, "chi", 4) == 0)
                        dfparams->distindex = 17;
                    else if (strncmp(optarg, "rayleigh", 3) == 0)
                        dfparams->distindex = 18;
                    else if (strncmp(optarg, "maxwell", 3) == 0)
                        dfparams->distindex = 19;
                    else if (strncmp(optarg, "beta", 4) == 0)
                        dfparams->distindex = 20;
                    else if (strncmp(optarg, "betasym", 4) == 0)
                        dfparams->distindex = 21;
                    else if (strncmp(optarg, "betaprime", 4) == 0)
                        dfparams->distindex = 22;
                    else
                    {
                        printf("\n  Bad -d \"%s\" \n", optarg);
                        Usage();
                        exit(EXIT_FAILURE);
                    }
                }
                break;

            case 'D':
                dfparams->distdev = (int) strtol(optarg, NULL, 10);
                break;

            case 'g':
                dfparams->digamma = (double) strtod(optarg, NULL);
                break;

            case 'G':
                dfparams->invdigamma = (double) strtod(optarg, NULL);
                break;

            case 'h':
                dfparams->histo = 1;
                break;

            case 'I':
                intparams = calloc(4, sizeof(double));
                sscanf(optarg, "%lf:%lf:%lf:%lf", &intparams[0], &intparams[1], &intparams[2], &intparams[3]);
                break;

            case 'm':
                dfparams->mix = (int) strtol(optarg, NULL, 10);
                break;

            case 'n':
                dfparams->randnum = (int) strtol(optarg, NULL, 10);
                break;

            case 'p':
                sscanf(optarg, "%lf:%lf", &dfparams->p1, &dfparams->p2);
                break;

            case 'P':
                pdfparams = calloc(3, sizeof(double));
                sscanf(optarg, "%lf:%lf:%lf", &pdfparams[0], &pdfparams[1], &pdfparams[2]);
                break;

            case 's':
                dfparams->seed = (int) strtol(optarg, NULL, 10);
                break;

            case 'S':
                sdfparams = calloc(3, sizeof(double));
                sscanf(optarg, "%lf:%lf:%lf", &sdfparams[0], &sdfparams[1], &sdfparams[2]);
                break;

            case 't':
                dfparams->transform = (int) strtol(optarg, NULL, 10);

                if (dfparams->transform == 0)
                    dfparams->transform = 6;

                break;

            case 'T':
                dfparams->trigamma = (double) strtod(optarg, NULL);
                break;

            case 'v':
                Version();
                exit(EXIT_SUCCESS);
                break;

            case 'o':
                dfparams->fixones = 1;
                break;

            case 'z':
                dfparams->fixzeros = 1;
                break;

            default:
                fprintf(stderr, "\n Bad option '-%c' \n\n", optopt);
                Usage();
                exit(EXIT_FAILURE);
                break;
        }
    }

    *argv += optind; /* now argv is set with first arg = argv[0] */

    return(*argc - optind); /* number of nonoption args */
}


int
main(int argc, char *argv[])
{
    int             narguments, length, transformed;
    int             i;
    double         *array = NULL, *freq = NULL, *warray = NULL;
    double          ave, median, adev, mdev, sdev, var, skew, kurt, hrange, lrange;

    setrlimit(RLIMIT_CORE, 0);

    dfparams->distnum = 1;
    dfparams->isbeta = 1;
    dfparams->isneg = 0;
    dfparams->distindex = -1;
    dfparams->bootstrap = 1;
    dfparams->histo = 0;
    dfparams->transform = 1;
    dfparams->col = 0;
    dfparams->seed = -1;
    dfparams->p1 = 1.0;
    dfparams->p2 = 1.0;
    dfparams->distdev = -1;
    dfparams->randnum = 100;
    dfparams->mix = 0;
    dfparams->fixzeros = 0;
    dfparams->fixones = 0;
    dfparams->digamma = 0.0;
    dfparams->trigamma = 0.0;
    dfparams->invdigamma = 0.0;
    dfparams->neg = 0;
    dfparams->burnin = 0.0;

    //printf("\n###### %e\n", normal_sdf(5.0, 0.0, 1.0));

    if(argc == 1)
    {
        Usage();
        exit(EXIT_FAILURE);
    }

    narguments = ParseCmdLine(&argc, &argv);

    if (dfparams->seed == -1)
        dfparams->seed = (unsigned long) time(NULL) + getpid();

    if (dfparams->digamma > 0.0)
    {
        printf("\n Digamma(%g):  %g \n\n", dfparams->digamma, gsl_sf_psi(dfparams->digamma));

        exit(EXIT_SUCCESS);
    }

    if (dfparams->invdigamma > 0.0)
    {
        double y;
        double x = dfparams->invdigamma;
        double lambda;

        if (dfparams->neg == 1)
            x = -x;

        // Inverse digamma (psi) function.  The digamma function is the
        // derivative of the log gamma function.  This calculates the value
        // Y > 0 for a value X such that digamma(Y) = X.
        //
        // This algorithm is from Paul Fackler:
        // http://www4.ncsu.edu/~pfackler/

        lambda = 1.0;
        y = exp(x);
        while (lambda > DBL_EPSILON)
        {
            y += lambda*copysign(1.0, x - gsl_sf_psi(y));
            lambda *= 0.5;
        }

        printf("\n Inverse Digamma(%g):  %g \n\n", dfparams->invdigamma, y);

        exit(EXIT_SUCCESS);
    }

    if (dfparams->trigamma > 0.0)
    {
        double x = dfparams->trigamma;

        x = gsl_sf_psi_1(x);
        //x = 1.0/sqrt(x);

        printf("\n Trigamma(%g):  %g \n\n", dfparams->trigamma, x);

        exit(EXIT_SUCCESS);
    }

    if (pdfparams)
    {
        if (dfparams->distindex == -1)
            dfparams->distindex = 0;

        printf("\n PDF %s(%f,%f,%f):  %e \n\n",
               dist_name[dfparams->distindex],
               pdfparams[0], pdfparams[1], pdfparams[2],
               distpdf_func[dfparams->distindex](pdfparams[0], pdfparams[1], pdfparams[2]));

        MyFree(pdfparams);
        exit(EXIT_SUCCESS);
    }
    else if (cdfparams)
    {
        if (dfparams->distindex == -1)
            dfparams->distindex = 0;

        printf("\n CDF %s(%f,%f,%f):  %e \n\n",
               dist_name[dfparams->distindex],
               cdfparams[0], cdfparams[1], cdfparams[2],
               distcdf_func[dfparams->distindex](cdfparams[0], cdfparams[1], cdfparams[2]));

        MyFree(cdfparams);
        exit(EXIT_SUCCESS);
    }
    else if (sdfparams)
    {
        if (dfparams->distindex == -1)
            dfparams->distindex = 0;

        printf("\n SDF %s(%f,%f,%f):  %e \n\n",
               dist_name[dfparams->distindex],
               sdfparams[0], sdfparams[1], sdfparams[2],
               distsdf_func[dfparams->distindex](sdfparams[0], sdfparams[1], sdfparams[2]));

        MyFree(sdfparams);
        exit(EXIT_SUCCESS);
    }
    else if (intparams)
    {
        if (dfparams->distindex == -1)
            dfparams->distindex = 0;

        printf("\n Int %s(%f,%f,%f,%f):  %e \n\n",
               dist_name[dfparams->distindex],
               intparams[0], intparams[1], intparams[2], intparams[3],
               distint_func[dfparams->distindex](intparams[0], intparams[1], intparams[2], intparams[3]));

        MyFree(intparams);
        exit(EXIT_SUCCESS);
    }
    else if (dfparams->distdev > -1)
    {
        PrintRandDist("tmpdist.txt", distdev_func[dfparams->distdev], dfparams->p1, dfparams->p2, dfparams->randnum, dfparams->seed);
        exit(EXIT_SUCCESS);
    }

    if (narguments == 1)
    {
        if (dfparams->histo == 0)
        {
            /* get data values from file */
            //array = getvals(argv[0], &length, dfparams->col);
            double **matrix = NULL;
            int colnum;
            matrix = ReadValsMulti(argv[0], &colnum, &length);
            array = malloc(length * sizeof(double));
            for (i = 0; i < length; ++i)
                array[i] = matrix[dfparams->col][i];
            dfparams->length = length;

            if (length < 4)
            {
                fprintf(stderr,
                        "\n You really can't fit anything with %d data points! \n",
                        length);
                Usage();
                exit(EXIT_FAILURE);
            }

            if (dfparams->fixzeros > 0)
                for (i = 0; i < length; ++i)
                    if (array[i] <= 0.0)
                        array[i] = DBL_EPSILON;

            if (dfparams->fixones > 0)
                for (i = 0; i < length; ++i)
                    if (array[i] == 1.0)
                        array[i] = 1.0 - DBL_EPSILON;

            warray = calloc((length+1), sizeof(double));

            for (i = 0; i < length; ++i)
                warray[i] = array[i];

            MatDestroy(&matrix);
        }
        else
        {
            /* data is arranged as a histogram */
            FILE                *listfile = NULL;
            int                  ch;

            listfile = fopen(argv[0], "r");
            if (listfile == NULL)
            {
                fprintf(stderr,
                        "\n ERROR69: cannot open first file \"%s\" \n",
                        argv[0]);
                exit(EXIT_FAILURE);
            }

            length = 0;
            while(1)
            {
                ch = getc(listfile);

                if (ch == EOF || ch == '\n')
                    ++length;

                if (ch == EOF)
                    break;
            }

            array =  calloc((length+1), sizeof(double));
            warray = calloc((length+1), sizeof(double));
            freq =   calloc((length+1), sizeof(double));
            length = gethistvals(listfile, array, freq, length);
            dfparams->length = length;

            for (i = 0; i < length; ++i)
                warray[i] = array[i];

            fclose(listfile);
        }
    }
    else
    {
        fprintf(stderr, "\n ERROR00: Distribution file unspecified \n\n");
        Usage();
        exit(EXIT_FAILURE);
    }

    if (dfparams->distindex > -1)
        dfparams->distnum = 1;
    else
        dfparams->distnum = DISTNUM;

    diststats = calloc(dfparams->distnum, sizeof(DistStats *));
    for(i = 0; i < dfparams->distnum; ++i)
        diststats[i] = calloc(1, sizeof(DistStats));

    if (dfparams->distindex == -1)
    {
        for(i = 0; i < dfparams->distnum; ++i)
        {
            strcpy(diststats[i]->dist_name, dist_name[i]);
            diststats[i]->distfit_func = distfit_func[i];
        }
    }
    else
    {
        strcpy(diststats[0]->dist_name, dist_name[dfparams->distindex]);
        diststats[0]->distfit_func = distfit_func[dfparams->distindex];
    }

    //PrintRandDist("tmpdist.txt", distdev_func[dfparams->distdev], dfparams->p1, dfparams->p2, dfparams->randnum, dfparams->seed);

    moments(array, length, &ave, &median, &adev, &mdev, &sdev, &var, &skew, &kurt, &hrange, &lrange);

    double **arrayp = &array;
    CalcEdgeworthVanHulleEntropy(arrayp, 1, length);

    printf("\n\nData      average      median     ave dev     med dev     std dev    variance    skewness    kurtosis\n");
    printf(  "----  ----------- ----------- ----------- ----------- ----------- ----------- ----------- -----------\n");
    printf("%-5d % 11.3e % 11.3e % 11.3e % 11.3e % 11.3e % 11.3e % 11.3e % 11.3e\n",
           length, ave, median, adev, mdev, sdev, var, skew, kurt);
    printf("\n         range \n");
    printf("-------------------------- \n");
    printf("% 11.3e to % 11.3e\n\n", lrange, hrange);

    CalcDistStats(array, length);

    if (dfparams->mix > 0)
    {
        double     *mean = malloc(dfparams->mix * sizeof(double));
        double     *var = malloc(dfparams->mix * sizeof(double));
        double     *mixp = malloc(dfparams->mix * sizeof(double));
        double      omni_logL, mix_chi2, logL;

        omni_logL = DistFitMix(normal_pdf, normal_fit_w, normal_init_mix_params,
                               array, length, dfparams->mix, mean, var, mixp);

        for(i = 0; i < dfparams->mix; ++i)
            printf("  \n Mix %3d:  mean: % 11.3e var: % 11.3e MixP: % 9.6f",
                   i+1, mean[i], var[i], mixp[i]);

        mix_chi2 = chi_sqr_adapt_mix(array, length, 0, &logL, mean, var,
                                     mixp, dfparams->mix,
                                     normal_pdf, normal_lnpdf, normal_int);

        printf("\nlogL:% 11.3f n:%5d AIC:% 11.3f chi^2:% 9.6f\n\n",
               omni_logL, length, omni_logL - 3 * dfparams->mix + 1, mix_chi2);

        MyFree(mean);
        MyFree(var);
        MyFree(mixp);
    }

    /* do the bootstrap if so desired */
    if (dfparams->distnum == 1 && dfparams->bootstrap > 1)
    {
        BootFit(diststats, dfparams->distindex, array, length, dfparams);
    }
    else if(dfparams->distnum == 1)
    {
        SingleDistFit(distfit_func, dfparams->distindex, dist_name[dfparams->distindex], diststats[0], array, length, dfparams);
    }
    else
    {
        for (i = 0; i < dfparams->transform; ++i)
        {
            transformed = TransformData(i, array, warray, length, dfparams->distnum);

            if (transformed == 0)
                continue;

            TestData(array, length);

            if (dfparams->histo == 1)
            {
                MultiHistFit(array, freq, length);
            }
            else
            {
                /* fit the data to each distribution */
                MultiDistFit(distfit_func, dist_name, diststats, dfparams->distnum, array, length, dfparams);

                /* sort the results by AIC, best fits at top (most positive AIC) */
                InsortDiststats(diststats, dfparams->distnum);

                /* Calcualte Akaike weights and Bayesian posterior probabilities */
                CalcAkaike(diststats, dfparams->distnum);

                /* print out the final results */
                PrintDistStats(diststats, dfparams->distnum);
            }
        }
    }

    free(array);
    free(warray);
    if (dfparams->histo == 1)
        free(freq);
    for(i = 0; i < dfparams->distnum; ++i)
        MyFree(diststats[i]);
    MyFree(diststats);

    return (EXIT_SUCCESS);
}


void
TestData(const double *data, const int length)
{
    int             i;

    dfparams->isbeta = 1;
    dfparams->isneg = 0;
    dfparams->iszero = 0;
    for (i = 0; i < length; ++i)
    {
        if (data[i] > 1.0)
        {
            dfparams->isbeta = 0;
            break;
        }
    }

    for (i = 0; i < length; ++i)
    {
        if (data[i] == 0.0)
        {
            dfparams->isbeta = 0;
            dfparams->iszero = 1;
            break;
        }
    }

    for (i = 0; i < length; ++i)
    {
        if (data[i] < 0.0)
        {
            dfparams->isbeta = 0;
            dfparams->isneg = 1;
            break;
        }
    }
}


int
TestDistVsData(const int ndist)
{
    if (dist_beta[ndist] == 1 && dfparams->isbeta == 0)
        return(0);
    else if (dist_posdef[ndist] == 1 && dfparams->isneg == 1)
        return(0);
    else if (dist_nonneg[ndist] == 1 && dfparams->isneg == 1)
        return(0);
    else if (dist_posdef[ndist] == 1 && dfparams->iszero == 1)
        return(0);
    else if (dist_nonneg[ndist] == 1 && dfparams->iszero == 1) /* should return 1, but there are problems fitting data with zeros */
        return(0);
    else
        return(1);
}


void
PrintRandDist(char *fname, double (*dist_dev)(double param1, double param2, const gsl_rng *r2),
              double param1, double param2, int ndata, const unsigned long seed)
{
    int                    i;
    double                 *array = NULL;
    char                   argstring[64];
    FILE                   *tmpfile = NULL;
    const gsl_rng_type     *T = NULL;
    gsl_rng                *r2 = NULL;

    gsl_rng_env_setup();
    gsl_rng_default_seed = seed;
    T = gsl_rng_ranlxs2;
    r2 = gsl_rng_alloc(T);

    array = malloc(ndata * sizeof(double));

    for (i = 0; i < ndata; ++i)
        array[i] = dist_dev(param1, param2, r2);
        /* array[i] = uniform_dev(0, 1) * sin(uniform_dev(0, 1)); */

    tmpfile = fopen(fname, "w");

    sprintf(&argstring[0], "%% .%de\n", DBL_DIG+1);

    for (i = 0; i < ndata; ++i)
        fprintf(tmpfile, argstring, array[i]);

    fflush(NULL);

    fclose(tmpfile);
    MyFree(array);
    gsl_rng_free(r2);
}


int
TransformData(int transform, double *array, double *warray, int length, int distnum)
{
    int             j;

    switch(transform)
    {
        case 0:
            printf(" raw data:\n");

            for (j = 0; j < length; ++j)
                array[j] = warray[j];
            break;

        case 1:
            printf(" data^2:\n");

            for (j = 0; j < length; ++j)
                array[j] = mysquare(warray[j]);
            break;

        case 2:
            printf(" exp(data):\n");

            for (j = 0; j < length; ++j)
                array[j] = exp(warray[j]);
            break;

        case 3:
            printf(" cos(data):\n");

            for (j = 0; j < length; ++j)
                array[j] = cos(warray[j]);
            break;

        case 4:
            for (j = 0; j < length; ++j)
            {
                if (warray[j] <= 0.0)
                    return(0);
                else
                    array[j] = sqrt(warray[j]);
            }
            printf(" sqrt(data):\n");
            break;

        case 5:
            for (j = 0; j < length; ++j)
            {
                if (warray[j] <= 0.0)
                    return(0);
                else
                    array[j] = log(warray[j]);
            }

            printf(" log(data):\n");
            break;
    }

    fflush(NULL);

    return(1);
}


void
MultiHistFit(double *array, double *freq, int length)
{
    double          param1, param2, chisq, logL;

    chisq = gamma_histfit(array, freq, length, &param1, &param2, &logL);
    printf(" gamma         >>> %11.3e %11.3e %11.3e %11.5f \n", param1, param2, chisq, logL/length);
    fflush(NULL);

    chisq = weibull_histfit(array, freq, length, &param1, &param2, &logL);
    printf(" weibull       >>> %11.3e %11.3e %11.3e %11.5f \n", param1, param2, chisq, logL/length);
    fflush(NULL);

    chisq = exp_histfit(array, freq, length, &param1, &param2, &logL);
    param2 = 0.0;
    printf(" exp           >>> %11.3e %11.3e %11.3e %11.5f \n", param1, param2, chisq, logL/length);
    fflush(NULL);
}


void
BootFit(DistStats **diststats, int distindex, double *array, int length, DFParams *dfparams)
{
    int             i, j, k, m, bootstrap = dfparams->bootstrap;
    double         *jackarray = NULL, *p = NULL;
    unsigned int   *weights = NULL;
    double          param1, param2, chisq, logL, AIC, BIC;
    double          param1_2, param2_2, logL_2, AIC_2, BIC_2;
    const gsl_rng_type     *T = NULL;
    gsl_rng                *r2 = NULL;

    T = gsl_rng_ranlxs2;
    r2 = gsl_rng_alloc(T);

    weights = calloc(length, sizeof(unsigned int));
    p = calloc(length, sizeof(double));
    jackarray = calloc(length, sizeof(double));

    for (i = 0; i < length; ++i)
        p[i] = 1.0 / length;

    param1_2 = param2_2 = logL_2 = AIC_2 = BIC_2 = 0.0;
    for (i = 0; i < dfparams->bootstrap; ++i)
    {
        if (i > 0)
            //multinomial_eqdev(length, (1.0 / length), weights, length);
            gsl_ran_multinomial (r2, length, length, p, weights);

        j = k = 0;
        while(j < length && k < length)
        {
            for (m = 0; m < weights[k]; ++m, ++j)
                jackarray[j] = array[k];

            ++k;
        }

        param1 = param2 = chisq = logL = AIC = BIC = 0.0;
        chisq = diststats[0]->distfit_func(jackarray, length, &param1, &param2, &logL);

        if (j < 13)
        {
            AIC = logL - ((2.0 * length) / (length - 3.0));
            BIC = logL - 2.0 * log(length);
        }
        else
        {
            AIC = logL - (length / (length - 2.0));
            BIC = logL - log(length);
        }

        diststats[0]->param1 += param1;
        diststats[0]->param2 += param2;
        diststats[0]->chisq += chisq;
        diststats[0]->logL += logL;
        diststats[0]->logLper += (logL / (double) length);
        diststats[0]->AIC += AIC;
        diststats[0]->BIC += BIC;

        param1_2 += mysquare(param1);
        param2_2 += mysquare(param2);
        logL_2 += mysquare(logL);
        AIC_2 += mysquare(AIC);
        BIC_2 += mysquare(BIC);
    }

    /* method of moments determination of variances and std devs (1st and 2nd raw sample moments) */
    param1_2 = sqrt(param1_2/ (double) bootstrap - mysquare(diststats[0]->param1/ (double) bootstrap)) ;
    param2_2 = sqrt(param2_2 / (double) bootstrap - mysquare(diststats[0]->param2 / (double) bootstrap));
    logL_2 = sqrt(logL_2 / (double) bootstrap - mysquare(diststats[0]->logL / (double) bootstrap));
    AIC_2 = sqrt(AIC_2 / (double) bootstrap - mysquare(diststats[0]->AIC / (double) bootstrap));
    BIC_2 = sqrt(BIC_2 / (double) bootstrap - mysquare(diststats[0]->BIC / (double) bootstrap));

    diststats[0]->param1 /= (double) bootstrap;
    diststats[0]->param2 /= (double) bootstrap;
    diststats[0]->chisq /= (double) bootstrap;
    diststats[0]->logL /= (double) bootstrap;
    diststats[0]->logLper /= (double) bootstrap;
    diststats[0]->AIC /= (double) bootstrap;
    diststats[0]->BIC /= (double) bootstrap;

    printf(" seed = %lu \n", dfparams->seed);
    fflush(NULL);
    PrintDistStats(diststats, 1);

    diststats[0]->param1 = param1_2;
    diststats[0]->param2 = param2_2;
    diststats[0]->chisq = 0.0;
    diststats[0]->logL = logL_2;
    diststats[0]->logLper = logL_2 / (double) length;
    diststats[0]->AIC = AIC_2;
    diststats[0]->BIC = BIC_2;
    strcpy(diststats[0]->dist_name, "SD");

    PrintDistSD(diststats);

    MyFree(weights);
    MyFree(jackarray);
    MyFree(p);
    gsl_rng_free(r2);
}


void
SingleDistFit(DistFit *distfit_func, int distindex, char *dist_name,
              DistStats *diststats, double *array, int length, DFParams *dfparams)
{
    double         param1, param2, chisq, logLper, logL, AIC, BIC;

    param1 = param2 = chisq = logLper = logL = AIC = BIC = 0.0;
    strcpy(diststats->dist_name, dist_name);

    if (TestDistVsData(distindex) == 0)
        return;

    chisq = distfit_func[distindex](array, length, &param1, &param2, &logL);
    logLper = logL / (double) length;

    if (chisq == -1.0)
        diststats->AIC = -DBL_MAX;

    if (dist_pnum[distindex] == 2)
    {
        AIC = logL - ((2.0 * length) / (length - 3.0));
        BIC = logL - 2.0 * log(length);
    }
    else if (dist_pnum[distindex] == 1)
    {
        AIC = logL - (length / (length - 2.0));
        BIC = logL - log(length);
    }

    if (fabs(chisq) > 1e6)
        chisq = DBL_MAX;

    if (logLper < -1e7)
        logLper = -DBL_MAX;

    if (logL < -1e11)
        logL = -DBL_MAX;

    if (AIC < -1e11)
        AIC = -DBL_MAX;

    if (BIC < -1e11)
        BIC = -DBL_MAX;

    diststats->param1 = param1;
    diststats->param2 = param2;
    diststats->chisq = chisq;
    diststats->logL = logL;
    diststats->logLper = logLper;
    diststats->AIC = AIC;
    diststats->BIC = BIC;
}


void
MultiDistFit(DistFit *distfit_func, char dist_name[][32],
             DistStats **diststats, int distnum, double *array, int length, DFParams *dfparams)
{
    int            j;
    double         param1, param2, chisq, logLper, logL, AIC, BIC;

    for (j = 0; j < distnum; ++j)
    {
        param1 = param2 = chisq = logLper = logL = AIC = BIC = 0.0;
        strcpy(diststats[j]->dist_name, dist_name[j]);

        if (TestDistVsData(j) == 0)
        {
            diststats[j]->param1 = param1;
            diststats[j]->param2 = param2;
            diststats[j]->chisq = chisq;
            diststats[j]->logL = logL;
            diststats[j]->logLper = logLper;
            diststats[j]->AIC = AIC;
            diststats[j]->BIC = BIC;
            continue;
        }

/*         printf("\n Fitting %s ...\n", dist_name[j]); */
/*         fflush(NULL); */

        chisq = distfit_func[j](array, length, &param1, &param2, &logL);
        logLper = logL / (double) length;

        if (dist_pnum[j] == 2)
        {
            AIC = logL - ((2.0 * length) / (length - 3.0));
            BIC = logL - 2.0 * log(length);
        }
        else if (dist_pnum[j] == 1)
        {
            AIC = logL - (length / (length - 2.0));
            BIC = logL - log(length);
        }

        if (logLper < -1e7)
            logLper = -DBL_MAX;

        if (logL < -1e11)
            logL = -DBL_MAX;

        if (AIC < -1e11)
            AIC = -DBL_MAX;

        if (BIC < -1e11)
            BIC = -DBL_MAX;

        diststats[j]->param1 = param1;
        diststats[j]->param2 = param2;
        diststats[j]->chisq = chisq;
        diststats[j]->logL = logL;
        diststats[j]->logLper = logLper;
        diststats[j]->AIC = AIC;
        diststats[j]->BIC = BIC;

        if (!isfinite(diststats[j]->AIC))
            diststats[j]->AIC = -DBL_MAX;

        if (!isfinite(diststats[j]->BIC))
            diststats[j]->BIC = -DBL_MAX;

        if (!isfinite(diststats[j]->logL))
            diststats[j]->logL = -DBL_MAX;

        if (!isfinite(diststats[j]->logLper))
            diststats[j]->logLper = -DBL_MAX;

        if (!isfinite(diststats[j]->chisq))
            diststats[j]->chisq = DBL_MAX;

        if (chisq == -1.0)
            diststats[j]->AIC = -DBL_MAX;
    }
}


void
CalcAkaike(DistStats **diststats, int distnum)
{
    double         wtsum, largest;
    int            i;

    largest = -DBL_MAX;
    for(i = 0; i < distnum; ++i)
    {
        if (TestDistVsData(i) == 0)
            continue;

        if (largest < diststats[i]->AIC)
            largest = diststats[i]->AIC;
    }

    wtsum = 0.0;
    for(i = 0; i < distnum; ++i)
    {
        if (TestDistVsData(i) == 0)
            continue;

/*
        if(diststats[i]->AIC == -DBL_MAX ||
           diststats[i]->chisq < 0.0)
            continue;
 */

        diststats[i]->akaikewt = exp(diststats[i]->AIC - largest);

        //printf("\nweight[%d]:%e %e %e", i, diststats[i]->akaikewt, largest, diststats[i]->AIC);

        if (!isfinite(diststats[i]->akaikewt))
            diststats[i]->akaikewt = 0.0;

        wtsum += diststats[i]->akaikewt;
    }

    for(i = 0; i < distnum; ++i)
    {

        if (TestDistVsData(i) == 0)
            continue;
/*
        if(diststats[i]->AIC == -DBL_MAX || diststats[i]->chisq < 0.0)
            diststats[i]->bayesprob = 0.0;
        else
 */

            diststats[i]->bayesprob = diststats[i]->akaikewt / wtsum;
    }
}


void
PrintDistStats(DistStats **diststats, int distnum)
{
    int            i, idist, tst;

    printf(" Distribution      param1      param2     chi^2     logL/num           logL            AIC            BIC  bayes \n");
    printf(" ------------ ----------- ----------- --------- ------------ -------------- -------------- -------------- ------ \n");
    fflush(NULL);

    for(idist = 0; idist < distnum; ++idist)
    {
        i = distsort[idist];

        tst = TestDistVsData(i);
        if (tst == 0)
            continue;

        if(diststats[i]->AIC == -DBL_MAX ||
           diststats[i]->chisq < 0.0)
            continue;

        if (fabs(diststats[i]->chisq) > 1e6)
            diststats[i]->chisq = DBL_MAX;

        if (diststats[i]->logLper < -1e7)
            diststats[i]->logLper = -DBL_MAX;

        if (diststats[i]->logL < -1e11)
            diststats[i]->logL = -DBL_MAX;

        if (diststats[i]->AIC < -1e11)
            diststats[i]->AIC = -DBL_MAX;

        if (fabs(diststats[i]->chisq) < 1e6)
        {
            printf(" %-12s %11.3e %11.3e %9.2f %12.3f %14.1f %14.1f %14.1f %6.3f\n",
                   diststats[i]->dist_name,
                   diststats[i]->param1, diststats[i]->param2,
                   diststats[i]->chisq,  diststats[i]->logLper,
                   diststats[i]->logL,   diststats[i]->AIC, diststats[i]->BIC,
                   diststats[i]->bayesprob);
        }
        else
        {
            printf(" %-12s %11.3e %11.3e %9.9s %12.3f %14.1f %14.1f %14.1f %6.3f\n",
                   diststats[i]->dist_name,
                   diststats[i]->param1, diststats[i]->param2,
                   "BIG",  diststats[i]->logLper,
                   diststats[i]->logL,   diststats[i]->AIC, diststats[i]->BIC,
                   diststats[i]->bayesprob);
        }

        fflush(NULL);
    }

    printf("\n * broken \n\n");
    fflush(NULL);
}


void
PrintDistSD(DistStats **diststats)
{
    printf(" %-12s %11.3e %11.3e           %12.3f %14.1f %14.1f %14.1f ",
           diststats[0]->dist_name,
           diststats[0]->param1, diststats[0]->param2,
           diststats[0]->logLper, diststats[0]->logL,
           diststats[0]->AIC,   diststats[0]->BIC);
    fflush(NULL);

    printf("\n");
    fflush(NULL);
}


/* big to little, reverse of the usual sort */
void
InsortDiststats_old(DistStats **diststats, int distnum)
{
    int             i, j;
    DistStats      *temp = NULL;

    for (i = 0; i < distnum; ++i)
        distsort[i] = i;

    for (i = 1; i < distnum; ++i)
    {
        j = i;
        temp = diststats[j];

        while ((j > 0) && (diststats[j-1]->AIC < temp->AIC))
        {
            diststats[j] = diststats[j-1];
            j--;
        }

        diststats[j] = temp;
    }
}


void
InsortDiststats(DistStats **diststats, int distnum)
{
    int             i, j, tmpj;
    double          tempAIC;

    for (i = 0; i < distnum; ++i)
        distsort[i] = i;

    for (i = 1; i < distnum; ++i)
    {
        j = i;
        tempAIC = diststats[distsort[j]]->AIC;
        tmpj = distsort[j];

        while ((j > 0) && (diststats[distsort[j-1]]->AIC < tempAIC))
        {
            distsort[j] = distsort[j-1];
            j--;
        }

        distsort[j] = tmpj;
    }

//     for (i = 0; i < distnum; ++i)
//         printf("\n %3d %3d %e", i, distsort[i], diststats[distsort[i]]->AIC);
//
//     exit(0);
}


void
printlist(double *list, int length)
{
    int            i;
    for (i=0; i < length; ++i)
        printf("\n %7.4e", list[i]);
    putchar('\n');
    fflush(NULL);
}


void
Usage(void)
{
    fputc('\n', stderr);
    fprintf(stderr, "distfit v. %s\n", VERSION);
    fprintf(stderr, "Maximum Likelihood fitting of statistical distributions to data \n\n");
    fprintf(stderr, "Usage: \n");
    fprintf(stderr, "distfit [-bcdDnpCIPSstv] <distfile> \n\n");
    fprintf(stderr, "distfile should contain values in columns separated by whitespace \n\n");
    fprintf(stderr, "  -b {# bootstrap replicates} -- # of bootstrap replicates \n");
    fprintf(stderr, "  -c {column #} -- which data column to read (max = 16) \n");
    fprintf(stderr, "  -d {distribution} -- to fit a specific distribution, use w/ -bCIPS \n");
    fprintf(stderr, "     {0}  Normal (def), (1)  Laplace,   (2)  EVD,      (3)  Logistic, \n");
    fprintf(stderr, "     (4)  Cauchy,       (5)  Uniform,   (6)  Weibull,  (7)  Gamma, \n");
    fprintf(stderr, "     (8)  ChiSqrGen,    (9)  Lognormal, (10) InvGamma, (11) InvGauss, \n");
    fprintf(stderr, "     (12) RecInvGauss,  (13) Pareto,    (14) Exp,      (15) InvChiSqr, \n");
    fprintf(stderr, "     (16) ChiSqr,       (17) Chi,       (18) Rayleigh  (19) Rice, \n");
    fprintf(stderr, "     (20) Maxwell,      (21) Beta,      (22) BetaSymm, (23) BetaPrime \n");
    fprintf(stderr, "  -D {distribution} -- distribution to simulate, use w/ -np \n");
    fprintf(stderr, "  -g {x} -- calculate digamma(x) \n");
    fprintf(stderr, "  -G {x} -- calculate inverse digamma(x) (i.e., find y for x = digamma(y)) \n");
    fprintf(stderr, "  -m {# of mixtures} -- fit a normal mixture distribution \n");
    fprintf(stderr, "  -n {# replicates} -- number of variates to simulate \n");
    fprintf(stderr, "  -p {p1:p2} -- two parameters for a specified distribution (ran # gen) \n");
    fprintf(stderr, "  -C {x:p1:p2} -- Calculate CDF for x and params p1 and p2 \n");
    fprintf(stderr, "     (e.g., loc and scale) for distribution specified with -d \n");
    fprintf(stderr, "  -I {x:y:p1:p2} -- Integrate PDF betwee x and y, params p1 and p2 \n");
    fprintf(stderr, "  -P {x:p1:p2} -- Calculate PDF for x and params p1 and p2 \n");
    fprintf(stderr, "  -S {x:p1:p2} -- Calculate SDF for x and params p1 and p2 \n");
    fprintf(stderr, "  -s {seed} -- specify a random seed, must be an integer \n");
    fprintf(stderr, "  -t -- do a series of simple data transformations and refit \n");
    fprintf(stderr, "  -T {x} -- calculate trigamma(x) \n");
    fprintf(stderr, "  -z -- \"fix\" zeros by setting to DBL_EPSILON (%e) \n", DBL_EPSILON);
    fprintf(stderr, "  -o -- \"fix\" ones by setting to 1.0 - DBL_EPSILON \n");
    fprintf(stderr, "  -v -- version \n");
    fprintf(stderr, "  * broken \n\n");
    fflush(NULL);
}


void
Version(void)
{
    fprintf(stderr,
            "\n  \'distfit\' version %s compiled on %s %s\n  by user %s with machine \"%s\" \n",
            VERSION, __DATE__, __TIME__, getenv("USER"), getenv("HOST"));
    fflush(NULL);
}


int
gethistvals(FILE *listfile, double *data, double *freq, int length)
{
    int            i;
    char           line[512];

    rewind(listfile);
    i = 0;
    while(fgets(line, 512, listfile) && i < length)
    {
        sscanf(line, "%le %le", &data[i], &freq[i]);
        ++i;
    }

    length = i;

    data[length] = freq[length] = DBL_MAX; /* sentinel required for quicksort */

/*     for (i = 0; i < length; ++i) */
/*         printf("\n%4d %lf %lf", i, data[i], freq[i]); */

    return(length);
}


static int
GetFileLen(FILE *fp)
{
    int            ch, filelen = 0;

    rewind(fp);

    while(!feof(fp))
    {
        ch = getc(fp);

        if (ch == EOF || ch == '\n')
            ++filelen;

        if (ch == EOF)
            break;
    }

    return(filelen);
}


static int
GetFileColLen(FILE *fp)
{
    int            linelen = 4096;
    char           line[linelen];
    char           element[linelen];
    int            i, j, ch, elemlen, nvals, nscan;
    double         tmp;

    rewind(fp);

    /* keep getting lines as long as the first data element is not a number */
    while(fgets(line, linelen, fp))
    {
        nscan = sscanf(line, "%le", &tmp);
        if (nscan > 0)
            break;
    }

    /* null-terminate the line, instead of newline */
    for (i = 0; i < strlen(line); ++i)
        if (line[i] == '\r' || line[i] == '\n' || line[i] == '#')
            line[i] = '\0';

    i = 0; /* column counter */
    j = 0; /* line position counter */
    ch = line[0];
    while(ch != EOF && ch != '\0' && j < linelen)
    {
        /* move through whitespace */
        while (isspace(ch) && ch != EOF && ch != '\0' && j < linelen)
        {
            ++j;
            ch = line[j];
        }

        nscan = sscanf(&line[j], "%s", element);

        if (nscan > 0)
        {
            nvals = sscanf(element, "%le", &tmp);
            //printf("\ni:%d  j:%d  nvals:%d  elemlen:%d  \n%s %g\n", i, j, nvals, elemlen, element, tmp);

            if(nvals > 0 && isfinite(tmp) && (fabs(tmp) < DBL_MAX))
                ++i;
        }

        elemlen = strlen(element);

        //printf("\ni:%d  j:%d  nvals:%d  elemlen:%d  \n%s\n", i, j, nvals, elemlen, element);

        j += elemlen;
        ch = line[j];
    }

    return(i);
}


/*
Reads an entire multi-record, multi-column file of floating point numbers.
Columns are free-form, separated by white space.
Allows comments (midline OK) using '#'.
Also allows blank internal lines.
Actually, it ignores any lines beginning with a word (i.e., a non-number).
Longest line allowed is 4096 chars.
In principle, the number of records and columns is unlimited.
Returns a (col x recordlen) array of numbers.
Each column in the file ends up being a contiguous vector of doubles.
*/
static double
**ReadValsMulti(const char *listfile_name, int *col, int *len)
{
    int            i, j, k, m;
    int            ch, elemlen, breakout, nscan, nvals, cnt, filelen;
    int            linelen = 4096;
    FILE          *listfile = NULL;
    double       **array = NULL;
    char           line[linelen];
    char           element[linelen];
    double         tmp;


    /* Open the file */
    listfile = fopen(listfile_name, "r");
    if (listfile == NULL)
    {
        fprintf(stderr,
                "\n ERROR_069: cannot open first file \"%s\" \n",
                listfile_name);
        exit(EXIT_FAILURE);
    }

    /* Count the number of lines in the file; serves as initial (max) guess at number of records */
    filelen = GetFileLen(listfile);

    /* Now get the number of columns in the first real data record */
    *col = GetFileColLen(listfile);

    array = MatAlloc(*col, filelen);

    /* Start over --- read the data into the array */
    rewind(listfile);

    breakout = 0;
    cnt = 0; /* counter for actual number of data records (not including blank lines, comments, other crap) */
    for(k = 0; k < filelen; ++k)
    {
        nscan = -1;
        while(nscan <= 0)
        {
            if (fgets(line, linelen, listfile) == NULL)
            {
                breakout = 1;
                break;
            }
            else
            {
                nscan = sscanf(line, "%le", &tmp);
            }
        }

        for (m = 0; m < strlen(line); ++m)
            if (line[m] == '\r' || line[m] == '\n' || line[m] == '#')
                line[m] = '\0';

        i = 0; /* column counter */
        j = 0; /* line position counter */
        ch = line[0];
        while(breakout == 0 && ch != EOF && ch != '\0' && j < linelen)
        {
            while (isspace(ch) && ch != EOF && ch != '\0' && j < linelen)
            {
                ++j;
                ch = line[j];
            }

            nscan = sscanf(&line[j], "%s", element);

            if (nscan > 0)
            {
                nvals = sscanf(element, "%le", &tmp);
                //printf("\ni:%d  j:%d  nvals:%d  elemlen:%d  \n%s %g\n", i, j, nvals, elemlen, element, tmp);

                if(nvals <= 0 || !isfinite(tmp) || fabs(tmp) > DBL_MAX)
                {
                    fprintf(stderr, "\n ERROR_169: value line %d column %d has a problem: %g\n", k, i, tmp);
                }
                else
                {
                    if (i == 0)
                        cnt++;

                    array[i][k] = tmp;
                    ++i;
                }
            }

            elemlen = strlen(element);
            j += elemlen;
            ch = line[j];
        }
//         printf("\n");
//         for (i = 0; i < *col; ++i)
//             printf("%g ", array[i][k]);
    }

    *len = cnt;

    fclose(listfile);

    printf("\nFile length:            %d", filelen);
    printf("\nNumber of data records: %d", *len);
    printf("\nNumber of data columns: %d\n", *col);

    return(array);
}


double
*getvals(char *listfile_name, int *length, int column)
{
    int            i, numscanned, numvals;
    FILE          *listfile = NULL;
    double        *array = NULL;
    char           columns[16][512];
    char           line[512], *comment;
    int            ch;

    listfile = fopen(listfile_name, "r");
    if (listfile == NULL)
    {
        fprintf(stderr,
                "\n ERROR69: cannot open first file \"%s\" \n",
                listfile_name);
        exit(EXIT_FAILURE);
    }

    *length = 0;
    while(1)
    {
        ch = getc(listfile);

        if (ch == EOF || ch == '\n')
            ++(*length);

        if (ch == EOF)
            break;
    }

    array = calloc((*length + 1), sizeof(double));

    rewind(listfile);

    i = 0;
    while(fgets(line, 512, listfile) && i < *length)
    {
        if (line[0] == '#')
        {
            --(*length);
            continue;
        }

        comment = strchr(line, '#');

        if (comment)
            *comment = '\0';

/*         numscanned = sscanf(line, "%le %le %le %le %le %le %le %le %le %le %le %le %le %le %le %le", */
/*                             &columns[0], &columns[1], &columns[2],  &columns[3], */
/*                             &columns[4], &columns[5], &columns[6],  &columns[7], */
/*                             &columns[8], &columns[9], &columns[10], &columns[11], */
/*                             &columns[12], &columns[13], &columns[14], &columns[15]); */

        numscanned = sscanf(line, "%s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s",
                            &columns[0][0], &columns[1][0], &columns[2][0],  &columns[3][0],
                            &columns[4][0], &columns[5][0], &columns[6][0],  &columns[7][0],
                            &columns[8][0], &columns[9][0], &columns[10][0], &columns[11][0],
                            &columns[12][0], &columns[13][0], &columns[14][0], &columns[15][0]);

        numvals = sscanf(&columns[column][0], "%le", &array[i]);

        //array[i] = columns[column];
        /* printf("\n %d \n", numscanned); */

        if(numvals < 1 || !isfinite(array[i]) || array[i] == DBL_MAX || array[i] == -DBL_MAX || numscanned <= column)
        {
            --(*length);
            continue;
        }
        else
        {
            ++i;
        }
    }

    *length = i;

    array[*length] = DBL_MAX; /* sentinel required for quicksort */

    fclose(listfile);

    return(array);
}


/* The following functions are used for mixture model analysis */
static void
CalcProbs(DistPDF distpdf, const double *x, double **probs, const int n,
          const double *mixp, const int mixn, const double *mean,
          const double *var)
{
    int         i, j;
    double      sump;

    for (i = 0; i < n; ++i)
    {
        sump = 0.0;
        for (j = 0; j < mixn; ++j)
        {
            probs[j][i] = mixp[j] * distpdf(x[i], mean[j], var[j]);
            sump += probs[j][i];
        }

        for (j = 0; j < mixn; ++j)
            probs[j][i] /= sump;
    }

/*     for (i = 0; i < n; ++i) */
/*         printf("\n%3d %f", i, probs[0][i]); */
}


static void
CalcMixProbs(double **probs, const int n, double *mixp, const int mixn)
{
    int         i, j;

    for (j = 0; j < mixn; ++j)
    {
        mixp[j] = 0.0;
        for (i = 0; i < n; ++i)
            mixp[j] += probs[j][i];

        mixp[j] /= n;
    }
}


static void
InitMixProbs(double **probs, double *mixp, const int n, const int mixn)
{
    int         i, j;
    double      sum;

    for (i = 0; i < mixn; ++i)
    {
        probs[i] = malloc(n * sizeof(double));
        memsetd(probs[i], 1.0 / mixn, n);
        mixp[i] = 1.0 / mixn;
    }

    for (i = 0; i < n; ++i)
    {
        sum = 0.0;
        for (j = 0; j < mixn; ++j)
            sum += probs[j][i];

        for (j = 0; j < mixn; ++j)
            probs[j][i] /= sum;
    }
}


static double
CalcOmniLogL(DistPDF distpdf, const double *x, const int n, const int mixn,
             double *mean, double *var, double *mixp)
{
    int             i, j;
    double          omni_logL, sum;

    omni_logL = 0.0;
    for (i = 0; i < n; ++i)
    {
        sum = 0.0;
        for (j = 0; j < mixn; ++j)
            sum += mixp[j] * distpdf(x[i], mean[j], var[j]);

        omni_logL += log(sum);
    }

    return(omni_logL);
}


double
DistFitMix(DistPDF distpdf, DistFitW distfitw, InitMixParams distinit,
           const double *x, const int n, const int mixn,
           double *param1, double *param2, double *mixp)
{
    double          omni_logL, logL;
    double        **probs = malloc(mixn * sizeof(double *));
    double         *oldprobs = malloc(n * sizeof(double));
    int             i, j, maxiters = 50000;
    double          tol = 1e-9;

    InitMixProbs(probs, mixp, n, mixn);
    distinit(x, n, mixn, param1, param2);

    for (i = 0; i < maxiters; ++i)
    {
        memcpy(oldprobs, probs[0], n * sizeof(double));

        for (j = 0; j < mixn; ++j)
            printf("%5d:%-2d % f % f % f\n", i, j, param1[j], param2[j], mixp[j]);

        CalcProbs(distpdf, x, probs, n, mixp, mixn, param1, param2);
        CalcMixProbs(probs, n, mixp, mixn);

        for (j = 0; j < mixn; ++j)
            distfitw(x, n, probs[j], &param1[j], &param2[j], &logL);

        if (VecEq(oldprobs, probs[0], n, tol) && i > 4)
            break;
    }

    for (i = 0; i < n; ++i)
    {
        printf("\n## %3d", i+1);
        for (j = 0; j < mixn; ++j)
            printf(" % f", probs[j][i]);
        printf("   % f", x[i]);
    }

    putchar('\n');

    omni_logL = CalcOmniLogL(distpdf, x, n, mixn, param1, param2, mixp);

    for (i = 0; i < mixn; ++i)
        MyFree(probs[i]);
    MyFree(probs);
    MyFree(oldprobs);

    return(omni_logL);
}
