/*
    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 <gsl/gsl_rng.h>
#include <gsl/gsl_randist.h>
#include "DLTmath.h"
#include "statistics.h"
#include "chi_dist.h"
#include "maxwell_dist.h"

/* A Maxwell dist with alpha=1 is the same as the chi distribution with n=3 */
/* E[x] = 2.0*sqrt(2.0/MY_PI)/alpha */

double
maxwell_dev(const double alpha, const double nullv, const gsl_rng *r2)
{
    return (chi_dev(3.0, nullv, r2) / sqrt(alpha));
}


double
maxwell_pdf(const double x, const double alpha, const double nullv)
{
    if (x <= 0.0)
        return (0.0);
    else
        return (sqrt(2.0/MY_PI)*pow(alpha,1.5)*x*x*exp(-0.5*alpha*x*x));
}


double
maxwell_lnpdf(const double x, const double alpha, const double nullv)
{
    return (0.5*log(2.0/MY_PI) + 1.5*log(alpha) + 2.0*log(x) - 0.5*alpha*x*x);
}


double
maxwell_cdf(const double x, const double alpha, const double nullv)
{
    if (x <= 0.0)
        return (0.0);
    else
        return(erf(x*sqrt(0.5*alpha)) - x*exp(-0.5*alpha*x*x)*sqrt(2.0 * alpha/MY_PI));
}


double
maxwell_sdf(const double x, const double alpha, const double nullv)
{
    if (x <= 0.0)
        return (1.0);
    else
        return (1.0 - maxwell_cdf(x, alpha, nullv));
}


double
maxwell_int(const double x, const double y, const double alpha, const double nullv)
{
    if (x <= 0.0)
        return (maxwell_cdf(y, alpha, nullv));
    else
        return (maxwell_cdf(y, alpha, nullv) - maxwell_cdf(x, alpha, nullv));
}


/* Given in Verdugo Lazo and Rathie 1978 IEEE Trans Info Theory IT-24:1:120-122,
   but wrong. Maybe. */
double
maxwell_logL(const double alpha, const double nullv)
{
    return (-0.5 * log(2.0 * MY_PI / alpha) - MY_EULER + 0.5);
}


/* fit a maxwell distribution by maximum likelihood */
double
maxwell_fit(const double *data, const int num, double *alpha, double *nullv, double *prob)
{
    double          avesqr;
    int             i;

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

    *alpha = 3.0 * num / avesqr;
    /* printf(" Maxwell logL: %f\n", maxwell_logL(*alpha, *nullv)); */

    return(chi_sqr_adapt(data, num, 0, prob, *alpha, *nullv, maxwell_pdf, maxwell_lnpdf, maxwell_int));
}
