// $Id: bayesparamlike_1d.cpp,v 1.21 2018/01/03 21:32:54 mkkuhner Exp $

/*
Copyright 2002-2005 Mary K. Kuhner, Peter Beerli, and Joseph Felsenstein

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

*/

/* Authors: Lucian Smith, Mary Kuhner, Jon Yamato and Joseph Felsenstein */


#include <cassert>
#include <cmath>
#include <utility>

#include "bayesparamlike_1d.h"
#include "defaults.h"
#include "errhandling.h"
#include "parameter.h"
#include "mathx.h"

using namespace std;

//------------------------------------------------------------------------------------

BayesParamLike_1D::BayesParamLike_1D (Parameter param)
    :m_raw_data(),
     m_numpoints(0),
     m_smoothed_curve(),
     m_kernelwidth(0.0)
{
    if (param.IsValidParameter())
    {
        m_priortype = param.GetPrior().GetPriorType();
        m_binwidth = param.GetPrior().GetBinwidth();
        m_isvalid = true;
    }
    else
    {
        m_priortype = LINEAR;
        m_binwidth = 0.1;
        m_isvalid = false;
    }
}

//------------------------------------------------------------------------------------

BayesParamLike_1D::~BayesParamLike_1D ()
{
    // intentionally blank
}

//------------------------------------------------------------------------------------

void BayesParamLike_1D::AddPoint(double value, long int freq)
{
    assert(m_isvalid);
    if (!m_isvalid) return;
    if (m_priortype == LOGARITHMIC)
        value = SafeLog(value);
    m_numpoints += freq;
    unsigned long int datasize = m_raw_data.size();
    if (datasize > 0)
        if (m_raw_data[datasize-1].first == value)
        {
            m_raw_data[datasize-1].second += freq;
            return;
        }
    m_raw_data.push_back(make_pair(value, freq));
}

//------------------------------------------------------------------------------------
//Sets up m_smoothed_curve

void BayesParamLike_1D::SmoothCurve()
{
    assert(m_isvalid);
    if (!m_isvalid) return;
    InitializeCurve();
    for (unsigned long int i = 0; i < m_raw_data.size(); i++)
    {
        m_smoothed_curve.AddKernel(m_raw_data[i].first, m_raw_data[i].second, m_kernelwidth);
        // To do adaptive smoothing, kernelwidth would change instead of being
        // a constant member variable here.
    }

    m_smoothed_curve.Integrate();

}

//------------------------------------------------------------------------------------
//We need to create a new BayesCurve object, and to do that we need to
// know all of the things it wants, namely:
//  numpoints:  Total number of points (sum of the freq's in raw_data)
//  width:      m_binwidth
//  minval:     Minimum data point (lowest 'value' in raw_data)
//  maxval:     Maximum data point (highest 'values' in raw_data)
//  maxkernwidth:  The kernel width.  If we were to do adaptive
//                 smoothing, we'd need the highest value here, but we
//                 don't yet.
//
//  The kernel width is calculated from the number of points and from
//   whichever's lower of the interquartile range or the standard deviation.

void BayesParamLike_1D::InitializeCurve()
{
    sort(m_raw_data.begin(), m_raw_data.end());
    double minval = m_raw_data[0].first;
    double maxval = m_raw_data[m_raw_data.size()-1].first;

    unsigned long int tally = 0;
    double lowquart = 0;
    double highquart = 0;
    double average = 0;
    for (unsigned long int i = 0; i < m_raw_data.size(); i++)
    {
        average += (m_raw_data[i].first * m_raw_data[i].second);
        tally += m_raw_data[i].second;
        if (lowquart == 0 && tally > (m_numpoints/4) )
            lowquart = m_raw_data[i].first;
        if (highquart == 0 && tally > (m_numpoints* 3/4) )
            highquart = m_raw_data[i].first;
    }
    assert (tally == m_numpoints); //AddPoint should make this true.
    average = average/m_numpoints;
    double sigma = CalculateStdev(average);
    double minsig = min(sigma, (highquart-lowquart)/1.34);
    if (minsig < 5*m_binwidth)
        minsig = 5*m_binwidth;
    m_kernelwidth = 2.5 * minsig * pow(m_numpoints, -.2);
    //2.5 is a little less than 2.78; it's totally ad hoc, but so is Silverman,
    // basically.
    // LS DEBUG: now that we have bayesian summary files, we can
    // and probably should experiment
    // with using different values for this constant to see how our data in
    // particular interacts with the kernel width.
    bool islog = false;
    if (m_priortype == LOGARITHMIC)
        islog = true;

    m_smoothed_curve.Initialize(m_numpoints, m_binwidth, islog, minval, maxval, m_kernelwidth);
}

//------------------------------------------------------------------------------------
//Private functions for internal calculations.

//CalculateStdev takes a given value (presumably the average) and calculates
// the standard deviation in m_raw_data from that value (sqrt(sum(diff^2)/N-1))

double BayesParamLike_1D::CalculateStdev(double average)
{
    double sigma = 0.0;
    if (m_numpoints <= 1) return sigma; //sigma is incalculable in this case
    for (unsigned long int i = 0; i < m_raw_data.size(); i++)
        sigma += pow(m_raw_data[i].second * (m_raw_data[i].first - average), 2);
    sigma = sigma / (m_numpoints - 1);
    sigma = sqrt(sigma);
    return sigma;
}

//------------------------------------------------------------------------------------

long int BayesParamLike_1D::GetNumUniquePoints()
{
    return static_cast<long int>(m_raw_data.size());
}

//____________________________________________________________________________________
