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

/* The laplace distribution has the form

   (1/2b) * exp[-|x - a|/b]

   AKA the double exponential. Much more peaked than the
   normal, symmetric. Kurtosis of 3.

   http://mathworld.wolfram.com/LaplaceDistribution.html
*/
double
laplace_dev(const double mean, const double scale, const gsl_rng *r2)
{
    return (mean + scale * log(gsl_rng_uniform(r2) / gsl_rng_uniform(r2)));
}


double
laplace_pdf(const double x, const double mean, const double scale)
{
   return(exp(-fabs(x - mean) / scale) / (2.0 * scale));
}


double
laplace_lnpdf(const double x, const double mean, const double scale)
{
    return(-fabs(x - mean) / scale - log(2.0 * scale));
}


double
laplace_cdf(const double x, const double mean, const double scale)
{
    double          p;

    if (x < mean)
        p = 0.5 * exp(-(mean - x) / scale);
    else
        p = 1.0 - 0.5 * exp(-(x - mean) / scale);

    return (p);
}


double
laplace_sdf(const double x, const double mean, const double scale)
{
    double          p;

    if (x < mean)
        p = 1.0 - 0.5 * exp(-(mean - x) / scale);
    else
        p = 0.5 * exp(-(x - mean) / scale);

    return (p);
}


double
laplace_int(const double x, const double y, const double mean, const double scale)
{
    double          p;

    p = laplace_cdf(y, mean, scale) - laplace_cdf(x, mean, scale);

    return (p);
}


double
laplace_logL(const double mean, const double scale)
{
    return(-log(2.0 * scale) - 1.0);
}


/* method of moments fit of data to a laplace distribution */
double
laplace_MMfit(double *x, int n, double *mean, double *scale, double *logL)
{
    double           nd = (double) n;
    double           ave, b;
    int              i;

    ave = 0.0;
    for (i = 0; i < n; ++i)
        ave += x[i];
    ave /= nd;

    b = 0.0;
    for (i = 0; i < n; ++i)
        b += fabs(x[i] - ave);
    b /= nd;

    *mean = ave;
    *scale = b;

    return(chi_sqr_adapt(x, n, 0, logL, *mean, *scale, laplace_pdf, laplace_lnpdf, laplace_int));
}


/* maximum likelihood fit of data to a laplace distribution */
double
laplace_fit(const double *x, const int n, double *mean, double *scale, double *logL)
{
    double           nd = (double) n;
    double           ave, b;
    double          *array;
    int              i;

    array = (double *) malloc((n+1) * sizeof(double));

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

    array[n] = DBL_MAX;

    /* qsort(array, n, sizeof(double), dblcmp); */
    quicksort(array, n);

    if (n % 2 == 0)
        ave = (array[n / 2] + array[(n - 1) / 2]) / 2.0;
    else
        ave = array[(n - 1) / 2];

    b = 0.0;
    for (i = 0; i < n; ++i)
        b += fabs(x[i] - ave);
    b /= nd;

    *mean = ave;
    *scale = b;

    free(array);

    return(chi_sqr_adapt(x, n, 0, logL, *mean, *scale, laplace_pdf, laplace_lnpdf, laplace_int));
}
