// $Id: likelihood.cpp,v 1.111 2018/01/03 21:33:02 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: Peter Beerli, Mary Kuhner, Jon Yamato and Joseph Felsenstein */


// parameter likelihood calculation
//
// <Single | Replicate | Region | GammaRegion>PostLike::Calculate()
// returns the total log-likelihood for a set of genealogies
// given a parameter vector P (this is written log(L(P)/L(Po))
// when there is one unique vector of driving values "Po").
//
//------------------------------------------------------------------------------------
//
// Calling sequence and like:
//
// SinglePostLike gets passed into a Maximizer constructor
//          the data-pointer is set, but there is no data yet.
// after a single chain Maximizer::Calculate() uses SinglePostLike::Calculate()
// if Replication: creation of ReplicatePostLike
// if more than 1 Region creation of RegionPostLike
// at the end of program RegionPostLike dies
//
//   GradientGuideSetup(thisToDolist); assigns zeroes and ones to a
//           vector that allows the gradient calculator to skip
//           over parameters that should not be maximized.
//

#include <cassert>
#include <iostream>                     // debugging

#include "vectorx.h"
#include "likelihood.h"
#include "forceparam.h"
#include "force.h"
#include "definitions.h"
#include "treesum.h"
#include "collector.h"
#include "mathx.h"
#include "runreport.h"                  // for FillGeyerWeights() reporting
#include "forcesummary.h"               // for ctor and FillForces()

#ifndef NDEBUG
//#define TEST
#endif // NDEBUG

#ifdef DMALLOC_FUNC_CHECK
#include "/usr/local/include/dmalloc.h"
#endif

using namespace std;

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

const double POSTERIOR_EPSILON = 0.00001;

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

PostLike::PostLike(const ForceSummary& fs, const long thisNregion, const long thisNreplicate, const long thisNParam)
{
    m_growthstart = 0L;
    m_growthend = 0L;
    m_nForces = fs.GetNForces();
    m_nRegions = thisNregion;
    m_nReplicate = thisNreplicate;
    m_nParam = thisNParam;
    m_pGammaRegionPostLike = NULL;
    m_s_is_here = FLAGLONG;
    m_forcestags = fs.GetForceTags();
    FillForces (fs);
}

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

PostLike::~PostLike (void)
{
    // deliberately blank--we don't own those pointers
}

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

bool PostLike::isLinearParam(long whichparam)
{
    if (m_growthstart != m_growthend
        && (whichparam >= m_growthstart && whichparam < m_growthend))
        return true;
    else if (whichparam == m_s_is_here)
        return true;
    return false;
}

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

void PostLike::FillForces(const ForceSummary& fs)
{
    const ForceVec& theseforces(fs.GetAllForces());
    ForceVec::const_iterator fit;
    vector < PLForces * >::iterator last;
    bool hasgrowth(false), hasLogisticSelection(false);
    bool hassticklogselect(false);
    long growthsize = 0L;
    long thetastart = 0L;
    long thetaend = 0L;
    long diseasestart = 0L;
    long diseaseend = 0L;
    long recstart = 0L;
    PLForces *forceptr = NULL;
    PLForces *coalptr = NULL;
    PLForces *growptr = NULL;
    PLForces *sticklogselptr = NULL;
    long start = 0L, end = 0L;
    long counter = 0;
    for (fit = theseforces.begin(); fit != theseforces.end(); ++fit)
    {
        if ((*fit)->GetTag() == force_LOGSELECTSTICK)
        {
            forceptr = new StickSelectPL(fs);
        }
        else
        {
            forceptr = (*fit)->GetPLForceFunction();
        }

        if (!forceptr)
        {
            if (force_REGION_GAMMA == (*fit)->GetTag() ||
                force_DIVERGENCE == (*fit)->GetTag())
                continue; // by design, there's no PL force associated with this force
            string msg = "PostLike::FillForces(), unable to retrieve a PLForces pointer ";
            msg += "for force " + ToString((*fit)->GetTag()) + ".";
            throw implementation_error(msg);
        }
        m_forces.push_back(forceptr);
        last = m_forces.end() - 1;
        start = end;
        end += (*fit)->GetNParams();
        (*last)->SetStart(start);
        (*last)->SetEnd(end);

        // fill of indicator for faster derivatives calculation
        LongVec1d temptype(end - start, counter);
        copy (temptype.begin (), temptype.end (), back_inserter(m_parameter_types));
        counter++;

        if((*fit)->GetTag()==force_GROW || (*fit)->GetTag()==force_EXPGROWSTICK)
        {
            m_growthstart = start;
            m_growthend = end;
            growthsize  = end - start;
            hasgrowth = true;
            growptr = forceptr;
        }
        else if((*fit)->GetTag()==force_COAL)
        {
            thetastart = start;
            thetaend = end;
            coalptr = forceptr;
        }
        else if (force_LOGISTICSELECTION == (*fit)->GetTag())
        {
            hasLogisticSelection = true;
            m_s_is_here = start;
        }
        else if ((*fit)->GetTag() == force_DISEASE)
        {
            diseasestart = start;
            diseaseend = end;
        }
        else if ((*fit)->GetTag() == force_LOGSELECTSTICK)
        {
            hassticklogselect = true;
            hasLogisticSelection = true; // JDEBUG--we probably won't want
            // to set this long term
            m_s_is_here = start;
            sticklogselptr = forceptr;
        }
        else if ((*fit)->GetTag() == force_REC)
        {
            recstart = start;
        }

        if (m_minvalues.size() < m_nParam) // then initialization is in progress
        {
            double minvalue((*fit)->GetMaximizerMinVal()), maxvalue((*fit)->GetMaximizerMaxVal());
            long nparams((*fit)->GetNParams());
            for (long i = 0; i < nparams; i++)
            {
                m_minvalues.push_back(minvalue);
                m_maxvalues.push_back(maxvalue);
            }
        }
    }

    if(hasgrowth)
    {
        if (hasLogisticSelection)
        {
            string msg = "PostLike::FillForces(), detected the presence of both ";
            msg += "the growth force and the logistic selection force.  For at least ";
            msg += "the time being, these can\'t both be \"on\" at the same time.";
            throw implementation_error(msg);
        }
        CoalesceGrowPL *temp = (dynamic_cast<CoalesceGrowPL *>(coalptr));
        temp->SetGrowthStart(m_growthstart);
        GrowPL *temp2 = (dynamic_cast<GrowPL *>(growptr));
        temp2->SetThetaStart(thetastart);
    }

    bool hasLPForces((fs.GetNLocalPartitionForces() != 0));
    bool hasRec(fs.CheckForce(force_REC));
    if(hasLPForces && hasRec)
    {
        CoalescePL *temp = dynamic_cast<CoalescePL *>(coalptr);
        if (hasgrowth)
        {
            CoalesceGrowPL *gtemp = dynamic_cast<CoalesceGrowPL *>(temp);
            // JCHECK2--will want to change this....
            gtemp->SetTimeManager(fs.CreateTimeManager());
        }
        temp->SetLocalPartForces(fs);
    }

    if(hassticklogselect)
    {
        StickSelectPL *temp = dynamic_cast<StickSelectPL *>
            (sticklogselptr);
        temp->SetThetastart(thetastart);
        temp->SetThetaend(thetaend);
        temp->SetSelCoeffIndex(m_s_is_here);
        // JDEBUG--When we allow for more than two states, this will need to
        // change...(at the interface level and usage, here)
        temp->SetToSmallAIndex(diseasestart + 2);
        temp->SetToBigAIndex(diseasestart + 1);
        // the existence or non-existence of the localpartforces
        // vector will be used to flag to the plforces that there is
        // or is not recombination
        if (hasRec)
        {
            temp->SetLocalPartForces(fs);
            temp->SetRecRateIndex(recstart);
        }
        // now purge all the other plforceobjects but ours.....
        m_forces.clear();
        m_forces.push_back(sticklogselptr);
        // JCHECK
        // we also need to sqrunge m_parameter_types so that
        // all parameters now map to our new singular plforce object
        m_parameter_types.assign(m_parameter_types.size(),0L);
    }

}

//------------------------------------------------------------------------------------
// Copies force parameters into a single DoubleVec1d param0,
// for speed's sake.  Requires output parameter param0
// to have a size of m_nParam when it's sent to this method.
// Called only by SetParam0().

void PostLike::Linearize(const ForceParameters * pForceParameters, DoubleVec1d& param0)
{
    assert(param0.size() == m_nParam);
    vector <force_type>::const_iterator fit;
    DoubleVec1d::iterator pit = param0.begin();

    for (fit = m_forcestags.begin(); fit != m_forcestags.end(); ++fit)
    {
        if (*fit == force_COAL)
        {
            //We want the regional values even if we're doing the multiregion
            // estimate, because each region's trees will still have been
            // created using the regional-scaled values, not the global.
            const DoubleVec1d& thetas = pForceParameters->GetRegionalThetas();
            copy (thetas.begin(), thetas.end(), pit);
            pit += thetas.size();
        }
        else if (IsMigrationLike(*fit))
        {
            const DoubleVec1d& migrations = pForceParameters->GetMigRates();
            copy (migrations.begin(), migrations.end(), pit);
            pit += migrations.size();
        }
        else if (*fit == force_DISEASE)
        {
            const DoubleVec1d& transitions = pForceParameters->GetDiseaseRates();
            copy (transitions.begin(), transitions.end(), pit);
            pit += transitions.size();
        }
        else if (*fit == force_REC)
        {
            double recRate = pForceParameters->GetRecRates().front();
            *pit = recRate;
            pit++;
        }
        else if (*fit == force_GROW)
        {
            const DoubleVec1d& growths = pForceParameters->GetGrowthRates();
            copy (growths.begin(), growths.end(), pit);
            pit += growths.size();
        }
        else if (*fit == force_LOGISTICSELECTION)
        {
            const DoubleVec1d& logisticSelCoeff = pForceParameters->GetLogisticSelectionCoefficient();
            if (1 != logisticSelCoeff.size())
            {
                string msg = "PostLike::Linearize() received a ";
                msg += ToString(logisticSelCoeff.size());
                msg += "-element vector for the logistic selection coefficient; ";
                msg += "this must be a one-element vector.";
                throw implementation_error(msg);
            }
            *pit++ = logisticSelCoeff[0];
        }
    }
} // void PostLike::Linearize

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

// Default implementation of a function to compute and return the following:
//
// sum_over_all_trees( Prob(G|param)/Prob(G|param0) )
// == sum_over_unique_trees( nG * Prob(G|param)/Prob(G|param0) )
//
// where param0 is the starting point ("driving values") in parameter space,
//       param  is the current parameter vector,
//       G      is one of the genealogies over which we're summing,
//  and  nG     is the number of identical copies of genealogy G.
//
// Input paramters:  param, lparam (log values of param), lnProbGPo,
//                   pGenealogies.
// Output parameters:  ln_ProbGP_over_ProbGPo, sum_ProbGP_over_ProbGPo.
//
// This function fills ln_ProbGP_over_ProbGPo with log(Prob(G|P)/Prob(G|Po)),
// and sets sumG_ProbGP_over_ProbGPo to
// sum_over_unique_genealogies(nG * Prob(G|P)/Prob(G|Po)).
//
// This function can also be used with Geyer replicates, with input parameter
// lnProb(G|param0) replaced by "lnProb(G|param0),"
// where "lnProb(G|param0)" = log(sum_over_replicates((NtrR/wR)*Prob(G|param0R))),
//       NtrR    is the total number of (non-unique) trees in replicate R,
//       wR      is the Geyer weight assigned to replicate R,
//  and  param0R is the starting point ("driving value") for replicate R.
//
// This function returns false if Prob(G|param) is 0 for all G
// (underflow can cause this); otherwise it returns true.
// "False" means "param" (the parameter values P) is utterly incapable
// of producing the set of genealogy trees "pGenealogies."
// When false is returned, -DBL_BIG is returned in lnL.

bool PostLike::Calc_sumG_ProbGP_over_ProbGPo(const DoubleVec1d& param,
                                             const DoubleVec1d& lparam,
                                             const DoubleVec1d& ln_ProbGPo,
                                             DoubleVec1d& ln_ProbGP_over_ProbGPo,
                                             double& sumG_ProbGP_over_ProbGPo,
                                             const vector<TreeSummary*> *pGenealogies)
{
    vector<double>::const_iterator i;
    vector<double>::iterator j;
    vector<TreeSummary*>::const_iterator currentGenealogy;
    long ncopies = 0;

    sumG_ProbGP_over_ProbGPo = 0.0;

    for (currentGenealogy = pGenealogies->begin(),
             i = ln_ProbGPo.begin(),
             j = ln_ProbGP_over_ProbGPo.begin();
         currentGenealogy != pGenealogies->end();
         ++i, ++currentGenealogy, ++j)
    {
        // Store log( Prob(G|P)/"Prob(G|Po)" ) in vector ln_ProbGP_over_ProbGPo.
        // Vector ln_ProbGPo already contains "log(Prob(G|Po)),"
        // which may include Geyer weights and replicate-dependent
        // driving values as described above.
        *j = Calc_lnProbGP(param, lparam, (*currentGenealogy)) - *i;

        // Our tree-generating procedure can generate identical trees,
        // both in shape and branch length (e.g., we find and store a good tree,
        // then because it's good, each proposed change to it gets rejected
        // for several proposed rearrangements, causing us to sample and store it
        // again after these rounds of rejections).
        // "ncopies" is the number of identical copies of the given tree.
        ncopies = (*currentGenealogy)->GetNCopies();
        sumG_ProbGP_over_ProbGPo += SafeProductWithExp(1.0*ncopies, (*j));
    }

    if (sumG_ProbGP_over_ProbGPo > 0.0)
        return true;

#ifdef TEST
    cerr << "PostLike::Calculate_sumG_ProbGP_over_ProbGPo() -- zero "
         << "likelihood, returning \"false\"..." << endl;
#endif // TEST
    return false;
}

//------------------------------------------------------------------------------------
// Calculates and returns log(PointProb(G|param) * WaitProb(G|param))
// for parameter vector "param" whose logarithms are "lparam,"
// for the genealogy pointed to by pThisGenealogy.
// NOTE:  This basic calculation is used in both
// likelihood and Bayesian analyses.
// NOTE:  If the caller is calculating lnL for a GammaRegionPostLike
// object, then this otherwise-generic function stores two numbers
// in the GammaRegionPostLike object for speed improvements in the latter.

double PostLike::Calc_lnProbGP(const DoubleVec1d& param, const DoubleVec1d& lparam, const TreeSummary *pThisGenealogy)
{
    vector<PLForces*>::const_iterator i;
    double total_lnPointProb(0.0), total_lnWaitProb(0.0),
        current_lnPointProb(0.0), current_lnWaitProb(0.0);

    for (i = m_forces.begin(); i != m_forces.end (); ++i)
    {
        current_lnPointProb = (*i)->lnPoint(param, lparam, pThisGenealogy);
        current_lnWaitProb  = (*i)->lnWait(param, pThisGenealogy);
        total_lnPointProb += current_lnPointProb;
        total_lnWaitProb  += current_lnWaitProb;
    }

    if (m_pGammaRegionPostLike)
    {
        m_pGammaRegionPostLike->Store_Current_lnPoint(total_lnPointProb);
        m_pGammaRegionPostLike->Store_Current_lnWait(total_lnWaitProb);
    }

    return total_lnPointProb + total_lnWaitProb;
}

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

double PostLike::Calc_lnProbGS(const DoubleVec1d& param, const DoubleVec1d& lparam, const TreeSummary *pThisGenealogy)
{
    // we cannot use GetStickSelectPL since that is, correctly, const
    // and for some reason the calculators are non-const....
    // So, we end up duplicating the code from GetStickSelectPL here.

    // JDEBUG: this will probably go away when we refactor PLForces
    // as modular equation components.
    if (m_forces.size() != 1)
    {
        assert(0);
        string msg = "PostLike::Calc_lnProbGS() has been called when";
        msg += "there exist zero or many PLForces.";
        throw implementation_error(msg);
    }

    StickSelectPL* stickpl = dynamic_cast<StickSelectPL*>(m_forces[0]);
    if (stickpl == NULL)
    {
        assert(0);
        string msg = "PostLike::Calc_lnProbGS() has been called when";
        msg += "there is no StickSelectPLForce.";
        throw implementation_error(msg);
    }

    return stickpl->lnPTreeStick(param,lparam,pThisGenealogy);
}

//------------------------------------------------------------------------------------
// Evil kludge for stair rearranger to access the Mean and
// Variance calculators used by the StickSelectPLforce.
//
// It may throw an implementation error.

const StickSelectPL& PostLike::GetStickSelectPL() const
{
    // JDEBUG: this will probably go away when we refactor PLForces
    // as modular equation components.
    if (m_forces.size() != 1)
    {
        assert(0);
        string msg = "PostLike::GetStickSelectPL() has been called when";
        msg += "there exist zero or many PLForces.";
        throw implementation_error(msg);
    }

    const StickSelectPL* stickpl = dynamic_cast<const StickSelectPL*>
        (m_forces[0]);
    if (stickpl == NULL)
    {
        assert(0);
        string msg = "PostLike::GetStickSelectPL() has been called when";
        msg += "there is no StickSelectPLForce.";
        throw implementation_error(msg);
    }

    return *stickpl;

}

//------------------------------------------------------------------------------------
// Single-region, single-replicate likelihood.

SinglePostLike::SinglePostLike(const ForceSummary &thisforces,
                               const long thisNregion,
                               const long thisNreplicate,
                               const long thisNParam):
    PostLike(thisforces, thisNregion, thisNreplicate, thisNParam),
    m_param0(thisNParam), m_lparam0(thisNParam),
    m_last_lnL(0.0), m_totalNumTrees(0.0)
{
    // intentionally blank
}

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

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

//------------------------------------------------------------------------------------
// Called only by Setup().

void SinglePostLike::SetParam0()
{
    // fills m_param0 by reference
    Linearize(&(m_data->forceParameters), m_param0);
    LogVec0(m_param0, m_lparam0);
    for(unsigned long i=0; i < m_param0.size(); i++)
    {
        if(isLinearParam(i))
            m_lparam0[i] = m_param0[i];
    }
}

//------------------------------------------------------------------------------------
// Calculates log(Prob(G|param0)) for the given
// genealogies G, and stores these in m_lnProbGPo.
// Also stores the total number of (non-unique) trees.

void SinglePostLike::Setup(TreeCollector * pTreeCollector)
{
    m_data = pTreeCollector;
    m_totalNumTrees = static_cast<double>(m_data->TreeCount());
    vector<TreeSummary*> genealogies = pTreeCollector->treeSummaries;
    if (m_lnProbGPo.size() != genealogies.size())
    {
        m_lnProbGPo.resize(genealogies.size(), 0.0);
        m_ln_ProbGP_over_ProbGPo.resize(genealogies.size(), 0.0);
    }
    SetParam0();
    for (unsigned long G = 0; G < genealogies.size(); G++)
        m_lnProbGPo[G] = Calc_lnProbGP(m_param0, m_lparam0, genealogies[G]);
}

//------------------------------------------------------------------------------------
// Updates m_ln_ProbGP_over_ProbGPo = log(Prob(G|P)/Prog(G|Po)) and m_last_lnL.
// Returns lnL = log((1/N)*sum_over_genealogies(Prob(G|P)/Prob(G|Po)),
// for the underlying trees G and driving values Po.
// (G and Po are retrieved from private member variables.)
//
// If Prob(G|P) is 0 for all G (underflow can cause this),
// we return false and lnL = -DBL_BIG.  Otherwise we return true.

bool SinglePostLike::Calculate(const DoubleVec1d& param,
                               const DoubleVec1d& lparam,
                               double& lnL)
{
    double sumG_ProbGP_over_ProbGPo(0.0);
    if (!PostLike::Calc_sumG_ProbGP_over_ProbGPo(param, lparam,
                                                 m_lnProbGPo,
                                                 m_ln_ProbGP_over_ProbGPo,
                                                 sumG_ProbGP_over_ProbGPo,
                                                 &(m_data->treeSummaries)))
    {
        m_last_lnL = lnL = -DBL_BIG;
        return false;
    }

    // sumG_ProbGP_over_ProbGPo is guaranteed to be positive (nonzero).
    m_last_lnL = lnL = log(sumG_ProbGP_over_ProbGPo/m_totalNumTrees);
    return true;
}

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

DoubleVec1d SinglePostLike::GetMeanParam0()
{
    return m_param0;
    // The "mean driving values" only require a computation
    // in the multi-replicate or multi-region cases.
}

//------------------------------------------------------------------------------------
// Single-region, multi-replicate likelihood.

ReplicatePostLike::ReplicatePostLike(const ForceSummary &thisforces,
                                     const long thisNregion,
                                     const long thisNreplicate,
                                     const long thisNParam):
    PostLike(thisforces, thisNregion, thisNreplicate,
             thisNParam), m_last_lnL(0.0)
{
    // intentionally blank
}

//------------------------------------------------------------------------------------
// FillGeyerWeights() is a helper function to Setup(), that deals with
// the Thompson-Geyer weighting for combining trees created under differing
// driving values.  GeyerLike() and GeyerStart() are helpers to
// FillGeyerWeights().
//
// FillGeyerWeights() overwrites m_probg0 and should be called
// after CreateProbg0(), just after the initial g0 values
// are calculated but before they are used.
//
// Once FillGeyerWeights() has been called then Calculate() may be called
// normally (and the derivatives hanging off of its results, etc.)
//
// GeyerLike() is the core likelihood function used in FillGeyerWeights()
//
// GeyerStart() is used to initialize the weights within FillGeyerWeights()

bool ReplicatePostLike::FillGeyerWeights()
{
    m_logGeyerWeights = DoubleVec1d(m_param0.size(), 0.0);
    RunReport& runreport(registry.GetRunReport());

    // compute single-replicate likelihoods as starting values
    // for the iteration
    unsigned long rep, nreps(m_param0.size());
    if (!GeyerStart(m_logGeyerWeights))
    {
        string msg = "FillGeyerWeights: starting value failure\n";
        runreport.ReportDebug(msg);
    }
    // we normalize to the largest to preserve machine calculability, and
    // assert while this may shift the resulting curve(s), it should not
    // affect its mean or spread.
    ScaleLargestToZero(m_logGeyerWeights);
    TransformProbG0With(m_logGeyerWeights);
    DoubleVec1d oldlike(nreps);
    for(rep = 0; rep < nreps; ++rep)
    {
        Calculate(m_param0[rep],m_lparam0[rep],oldlike[rep]);
    }
    bool alldone;
    long cnt(0);
    do {
        ++cnt;
        alldone = true;
        for(rep = 0; rep < nreps; ++rep)
        {
            if (!GeyerLike(m_param0[rep],m_lparam0[rep],m_logGeyerWeights[rep],oldlike))
            {
                string msg = "FillGeyerWeights:  overflow in iteration " +
                    ToString(cnt) + " for replicate " + ToString(rep) + "\n";
                runreport.ReportDebug(msg);
            }
        }
        TransformProbG0With(m_logGeyerWeights);
        DoubleVec1d newlike(nreps);
        for(rep = 0; rep < nreps; ++rep)
        {
            Calculate(m_param0[rep],m_lparam0[rep],newlike[rep]);
            if (fabs(newlike[rep]-oldlike[rep]) > POSTERIOR_EPSILON)
                alldone = false;
        }
        oldlike = newlike;
    } while (!alldone && cnt < defaults::geyeriters);

    if (cnt == defaults::geyeriters)
    {
        string msg = "Internal error: defaults::geyeriters needs to be";
        msg += " increased.\n";
        runreport.ReportUrgent(msg);
    }

    // we normalize to the largest to preserve machine calculability, and
    // assert while this may shift the resulting curve(s), it should not
    // affect its mean or spread.
    ScaleLargestToZero(m_logGeyerWeights);

    // now calculate the weighted P(G|theta0)
    TransformProbG0With(m_logGeyerWeights);

    return true;

}

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

void ReplicatePostLike::TransformProbG0With(const DoubleVec1d& weights)
{
    unsigned long rep, nreps(m_param0.size());
    DoubleVec1d nuniquetrees(nreps,0.0), ntrees(nreps,0.0);
    for(rep = 0; rep < nreps; ++rep)
    {
        nuniquetrees[rep] = m_data[rep]->treeSummaries.size();
        ntrees[rep] = static_cast<double>(m_data[rep]->TreeCount());
    }

    for(rep = 0; rep < nreps; ++rep)
    {
        unsigned long tree;
        for(tree = 0; tree < nuniquetrees[rep]; ++tree)
        {
            unsigned long rep2;
            m_lnProbGPo[rep][tree] = 0.0;
            for(rep2 = 0; rep2 < nreps; ++rep2)
            {
                m_lnProbGPo[rep][tree] += SafeProductWithExp(ntrees[rep2],
                                                             Calc_lnProbGP(m_param0[rep2],m_lparam0[rep2],
                                                                           m_data[rep]->treeSummaries[tree])
                                                             - weights[rep2]);
            }
            m_lnProbGPo[rep][tree] = log(m_lnProbGPo[rep][tree]);
        }
    }
}

//------------------------------------------------------------------------------------
// sum_over_trees: ratio of numerator = P(G|theta_eval)
//                          denominator = sum_over_reps
//                                           ptrees[rep] * P(G|theta0) /
//                                              oldlike[rep]
//
//                 ptrees[rep] = ntrees[rep] / sum_over_reps(ntrees[rep])

bool ReplicatePostLike::GeyerLike(const DoubleVec1d& param,
                                  const DoubleVec1d& lparam, double& newlike, const DoubleVec1d& oldlike)
{
    unsigned long rep, nreps(m_param0.size()), tree;
    vector<unsigned long> nuniquetrees(nreps,0);
    DoubleVec1d ptrees(nreps,0.0);
    for(rep = 0; rep < nreps; ++rep)
    {
        nuniquetrees[rep] = m_data[rep]->treeSummaries.size();
        ptrees[rep] = static_cast<double>(m_data[rep]->TreeCount());
    }

    // do some precalculations for speed
    double totaltrees(accumulate(ptrees.begin(), ptrees.end(), 0.0));
    // type 'double' needed for use in std::bind2nd
    transform(ptrees.begin(), ptrees.end(), ptrees.begin(), bind2nd(divides<double>(), totaltrees));

    newlike = 0.0;
    for(rep = 0; rep < nreps; ++rep)
    {
        for(tree = 0; tree < nuniquetrees[rep]; ++tree)
        {
            TreeSummary* tr(m_data[rep]->treeSummaries[tree]);
            long ncopies(tr->GetNCopies());

            double denom(0.0);
            unsigned long trrep;
            for(trrep = 0; trrep < nreps; ++trrep)
            {
                denom += SafeProductWithExp(ptrees[trrep], m_pg0[rep][tree][trrep] - oldlike[trrep]);
            }
            if (denom)
                denom = log(denom);
            else
            {
                newlike = DBL_BIG;
                return false;
            }

            newlike += SafeProductWithExp(ncopies, Calc_lnProbGP(param,lparam,tr) - denom);
        }
    }

    if (!newlike)
    {
        newlike = -DBL_BIG;
        return false;
    }
    else
        newlike = log(newlike) - log(totaltrees);

    return true;
}

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

bool ReplicatePostLike::GeyerStart (DoubleVec1d& lnL)
{
    unsigned long rep, nreps(m_param0.size());
    for(rep = 0; rep < nreps; ++rep)
    {
        unsigned long nuniquetrees = m_data[rep]->treeSummaries.size();
        // double needed here for some earlier versions of cmath
        double totaltrees = static_cast<double>(m_data[rep]->TreeCount());
        lnL[rep] = 0.0;

        unsigned long tree;
        for(tree = 0; tree < nuniquetrees; ++tree)
        {
            TreeSummary* tr(m_data[rep]->treeSummaries[tree]);
            lnL[rep] += SafeProductWithExp(tr->GetNCopies(),
                                           m_lnProbGPo[rep][tree]);
            // m_pg0[rep][tree][rep]);
        }

        if (!lnL[rep])
        {
            lnL[rep] = -DBL_BIG;
            return false;
        }
        else
            lnL[rep] = log(lnL[rep]) - log(totaltrees);

    }
    return true;

}

//------------------------------------------------------------------------------------
// Called only by Setup().

void ReplicatePostLike::SetParam0 ()
{
    m_param0.clear();
    m_lparam0.clear();
    for (unsigned long rep = 0; rep < m_nReplicate; rep++)
    {
        DoubleVec1d param0(m_nParam), lparam0(m_nParam);
        Linearize(&(m_data[rep]->forceParameters), param0);
        LogVec0(param0, lparam0);
        m_param0.push_back(param0);
        m_lparam0.push_back(lparam0);
    }
}

//------------------------------------------------------------------------------------
// Fills m_lnProbGPo by calling PostLike::Calc_lnProbGP() // with replicate/tree data.

void ReplicatePostLike::CreateProbG0()
{
    m_pg0.resize(m_nReplicate);
    for (unsigned long rep = 0; rep < m_nReplicate; rep++)
    {
        m_pg0[rep].resize(m_data[rep]->treeSummaries.size());
        for (unsigned long G = 0; G < m_data[rep]->treeSummaries.size(); G++)
        {
            m_lnProbGPo[rep][G] = Calc_lnProbGP(m_param0[rep], m_lparam0[rep],
                                                m_data[rep]->treeSummaries[G]);

            // now do the likelihood under each other replicate
            m_pg0[rep][G].resize(m_nReplicate);
            for(unsigned long trrep = 0; trrep < m_nReplicate; ++trrep)
            {
                m_pg0[rep][G][trrep] = ((trrep == rep) ? m_lnProbGPo[rep][G] :
                                        Calc_lnProbGP(m_param0[trrep],m_lparam0[trrep],
                                                      m_data[rep]->treeSummaries[G]));
            }
        }
    }
}

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

bool ReplicatePostLike::Setup(const vector<TreeCollector*>& treedata)
{
    m_data = treedata;
    SetParam0();

    m_lnProbGPo.clear();
    m_ln_ProbGP_over_ProbGPo.clear();

    for (unsigned long rep = 0; rep < m_nReplicate; rep++)
    {
        DoubleVec1d numtrees_zeroes(m_data[rep]->treeSummaries.size(), 0.0);
        m_lnProbGPo.push_back(numtrees_zeroes);
        m_ln_ProbGP_over_ProbGPo.push_back(numtrees_zeroes);
    }

    CreateProbG0();
    return FillGeyerWeights();
}

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

bool ReplicatePostLike::Calculate(const DoubleVec1d& param, const DoubleVec1d& lparam, double& lnL)
{
    double sum_over_replicates(0.0), sumForThisReplicate(0.0);

    for (unsigned long rep = 0; rep < m_nReplicate; rep++)
    {
        if (!PostLike::Calc_sumG_ProbGP_over_ProbGPo(param, lparam,
                                                     m_lnProbGPo[rep],
                                                     m_ln_ProbGP_over_ProbGPo[rep],
                                                     sumForThisReplicate,
                                                     &(m_data[rep]->treeSummaries)))
        {
            lnL = -DBL_BIG;
            m_last_lnL = lnL;
            return false;
        }
        sum_over_replicates += sumForThisReplicate;
    }

    if (sum_over_replicates > 0.0)
    {
        m_last_lnL = lnL = log(sum_over_replicates);
        return true;
    }

    m_last_lnL = lnL = -DBL_BIG;
    return false;
}

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

DoubleVec1d ReplicatePostLike::GetMeanParam0 ()
{
    long size = m_param0.size();
    DoubleVec1d meanparam0(m_param0.begin()->size());
    DoubleVec2d::iterator p0;
    DoubleVec1d::iterator i;
    for (p0 = m_param0.begin(); p0 != m_param0.end(); ++p0)
    {
        transform (meanparam0.begin(), meanparam0.end(),
                   (*p0).begin(), meanparam0.begin(), plus<double>());
    }
    for (i = meanparam0.begin(); i != meanparam0.end(); ++i)
    {
        (*i) /= size;
    }
    return meanparam0;
}

//------------------------------------------------------------------------------------
// Multiple-region, multiple-replicate likelihood.

RegionPostLike::RegionPostLike(const ForceSummary &thisforces, const long thisNregion, const long thisNreplicate,
                               const long thisNParam, DoubleVec2d paramscalars) :
    PostLike(thisforces, thisNregion, thisNreplicate, thisNParam),
    m_sumG_ProbGP_over_ProbGPo(thisNregion, 0.0), m_paramscalars(paramscalars),
    m_totalNumTrees(thisNregion, 0.0), m_last_lnL(0.0)
{
    m_lparamscalars = CreateVec2d(thisNregion, thisNParam, 1.0);

    for(long reg = 0; reg < thisNregion; ++reg)
        LogVec0(m_paramscalars[reg], m_lparamscalars[reg]);
}

//------------------------------------------------------------------------------------
// Called only by Setup().

void RegionPostLike::SetParam0()
{
    m_param0.clear();
    m_lparam0.clear();

    for (unsigned long reg = 0; reg < m_nRegions; reg++)
    {
        DoubleVec1d param0(m_nParam), lparam0(m_nParam);
        DoubleVec2d tmpParam0;
        DoubleVec2d tmpLParam0;

        for (unsigned long rep = 0; rep < m_nReplicate; rep++)
        {
            // param0 filled by reference
            Linearize(&(m_data[reg][rep]->forceParameters), param0);
            transform(param0.begin(), param0.end(), m_paramscalars[reg].begin(),
                      param0.begin(), multiplies<double>());
            LogVec0(param0, lparam0);
            for (unsigned long i = 0; i < m_nParam; i++)
                if (isLinearParam(i))
                    lparam0[i] = param0[i];
            tmpParam0.push_back(param0);
            tmpLParam0.push_back(lparam0);
        }
        m_param0.push_back(tmpParam0);
        m_lparam0.push_back(tmpLParam0);
        tmpParam0.clear();
        tmpLParam0.clear();
    }
}

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

void RegionPostLike::Setup(const vector<vector<TreeCollector*> >& treedata, const DoubleVec2d& logGeyerWeights)
{
    m_data = treedata;
    SetParam0();

    if (!(m_lnProbGPo.empty() && m_ln_ProbGP_over_ProbGPo.empty() &&
          m_pg0.empty()))
    {
        assert(0);
        string msg = "RegionPostLike::Setup() has been called more than once.";
        throw implementation_error(msg);
    }
    m_pg0.resize(m_nRegions);

    for (unsigned long reg = 0; reg < m_nRegions; reg++)
    {
        m_pg0[reg].resize(m_nReplicate);
        DoubleVec2d numreps_zerovectors;
        for (unsigned long rep = 0; rep < m_nReplicate; rep++)
        {
            DoubleVec1d numtrees_zeroes(m_data[reg][rep]->treeSummaries.size(), 0.0);
            numreps_zerovectors.push_back(numtrees_zeroes);
            m_pg0[reg][rep].resize(m_data[reg][rep]->treeSummaries.size());
        }
        m_lnProbGPo.push_back(numreps_zerovectors);
        m_ln_ProbGP_over_ProbGPo.push_back(numreps_zerovectors);
    }

    // Note:  m_sumG_ProbGP_over_ProbGPo was created in the constructor.

    double totalNumTreesThisRegion(0.0);

    for (unsigned long reg = 0; reg < m_nRegions; reg++)
    {
        totalNumTreesThisRegion = 0.0;
        for (unsigned long rep = 0; rep < m_nReplicate; rep++)
        {
            totalNumTreesThisRegion +=
                static_cast<double>(m_data[reg][rep]->TreeCount());
            for (unsigned long G = 0;
                 G < m_data[reg][rep]->treeSummaries.size(); G++)
            {
                m_lnProbGPo[reg][rep][G] =
                    Calc_lnProbGP(m_param0[reg][rep],
                                  m_lparam0[reg][rep],
                                  m_data[reg][rep]->treeSummaries[G]);

                // now do the likelihood under each other replicate
                unsigned long trrep;
                m_pg0[reg][rep][G].resize(m_nReplicate);
                for(trrep = 0; trrep < m_nReplicate; ++trrep)
                {
                    m_pg0[reg][rep][G][trrep] =
                        ((trrep == rep) ? m_lnProbGPo[reg][rep][G] :
                         Calc_lnProbGP(m_param0[reg][trrep],m_lparam0[reg][trrep],
                                       m_data[reg][rep]->treeSummaries[G]));
                }
            }
        }
        m_totalNumTrees[reg] = totalNumTreesThisRegion;
    }

    for(unsigned long reg = 0; reg < m_nRegions; ++reg)
    {
        TransformProbG0With(logGeyerWeights[reg], reg);
    }
}

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

bool RegionPostLike::Calculate(const DoubleVec1d& param, const DoubleVec1d& lparam, double& lnL)
{
    bool usingGeyer = (m_nReplicate > 1 ? true : false);
    double total_lnL(0.0), sum_forThisReplicate(0.0);

    for (unsigned long reg = 0; reg < m_nRegions; reg++)
    {
        // Scale the parameter vector, e.g., to account for inherent
        // differences between nuclear DNA and Y-chromosome DNA and mtDNA.

        DoubleVec1d scaledparam(param.size()), scaledlparam(lparam.size());
        transform(param.begin(), param.end(), m_paramscalars[reg].begin(),
                  scaledparam.begin(), multiplies<double>());
        transform(lparam.begin(),lparam.end(), m_lparamscalars[reg].begin(),
                  scaledlparam.begin(), plus<double>());

        m_sumG_ProbGP_over_ProbGPo[reg] = 0.0;
        for (unsigned long rep = 0; rep < m_nReplicate; rep++)
        {
            if (!PostLike::Calc_sumG_ProbGP_over_ProbGPo(scaledparam,
                                                         scaledlparam,
                                                         m_lnProbGPo[reg][rep],
                                                         m_ln_ProbGP_over_ProbGPo[reg][rep],
                                                         sum_forThisReplicate,
                                                         &(m_data[reg][rep]->treeSummaries)))
            {
                m_last_lnL = lnL = -DBL_BIG;
                return false;
            }
            m_sumG_ProbGP_over_ProbGPo[reg] += sum_forThisReplicate;
        }
        if (!usingGeyer)
            total_lnL += log(m_sumG_ProbGP_over_ProbGPo[reg]/m_totalNumTrees[reg]);
        else
            total_lnL += log(m_sumG_ProbGP_over_ProbGPo[reg]);
    }

    m_last_lnL = lnL = total_lnL;
    return true;
}

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

DoubleVec1d RegionPostLike::GetMeanParam0 ()
{
    long size = 0;
    DoubleVec1d meanparam0(m_param0.begin()->begin()->size());

    DoubleVec3d::iterator p00;
    DoubleVec2d::iterator p01;
    DoubleVec1d::iterator i;

    for (p00 = m_param0.begin(); p00 != m_param0.end(); ++p00)
    {
        size += p00->size();
        for (p01 = p00->begin(); p01 != p00->end(); ++p01)
            transform(meanparam0.begin(), meanparam0.end(),
                      p01->begin(), meanparam0.begin(), plus<double>());
    }

    for (i = meanparam0.begin(); i != meanparam0.end(); ++i)
    {
        (*i) /= size;
    }

    return meanparam0;
}

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

void RegionPostLike::TransformProbG0With(const DoubleVec1d& weights, long region)
{
    unsigned long rep, nreps(m_param0[region].size());
    if (nreps == 1) return;
    DoubleVec1d nuniquetrees(nreps,0.0), ntrees(nreps,0.0);
    for(rep = 0; rep < nreps; ++rep)
    {
        nuniquetrees[rep] = m_data[region][rep]->treeSummaries.size();
        ntrees[rep] = static_cast<double>(m_data[region][rep]->TreeCount());
    }

    for(rep = 0; rep < nreps; ++rep)
    {
        unsigned long tree;
        for(tree = 0; tree < nuniquetrees[rep]; ++tree)
        {
            unsigned long rep2;
            m_lnProbGPo[region][rep][tree] = 0.0;
            for(rep2 = 0; rep2 < nreps; ++rep2)
            {
                m_lnProbGPo[region][rep][tree] +=
                    SafeProductWithExp(ntrees[rep2],
                                       Calc_lnProbGP(m_param0[region][rep2],
                                                     m_lparam0[region][rep2],
                                                     m_data[region][rep]->treeSummaries[tree]
                                           ) - weights[rep2]);
            }
            m_lnProbGPo[region][rep][tree] = SafeLog(m_lnProbGPo[region][rep][tree]);
        }
    }
}

// sum_over_trees: ratio of numerator = P(G|theta_eval)
//                          denominator = sum_over_reps
//                                           ptrees[rep] * P(G|theta0) /
//                                              oldlike[rep]
//
//                 ptrees[rep] = ntrees[rep] / sum_over_reps(ntrees[rep])

//------------------------------------------------------------------------------------
// Multiple-region, multiple-replicate likelihood,
// with the background mutation rates gamma-distributed over regions.

GammaRegionPostLike::GammaRegionPostLike(const ForceSummary& thisforces,
                                         const long thisNregion, const long thisNreplicate,
                                         const long thisNParam, DoubleVec2d paramscalars) :
    RegionPostLike(thisforces, thisNregion, thisNreplicate, thisNParam,
                   paramscalars), m_sumG_C(thisNregion, 0.0)
{
    // Note: The matrix m_sumG_C is used in place of m_sumG_ProbGP_over_ProbGPo.

    const RegionGammaInfo *pRegionGammaInfo = registry.GetRegionGammaInfo();
    if (m_growthstart != m_growthend && NULL != pRegionGammaInfo)
    {
        // Growth and alpha are being estimated.
        string msg = "Attempted to use a gamma distribution to allow the ";
        msg += "background mutation rate to vary among genomic regions, ";
        msg += "while estimating population growth/shrinkage rates.  ";
        msg += "Currently, the gamma distribution cannot be used ";
        msg += "if growth rates are being inferred.";
        throw implementation_error(msg);
    }
    for (unsigned long reg = 0; reg < m_paramscalars.size(); reg++)
    {
        m_paramscalars[reg].push_back(1.0);
        m_lparamscalars[reg].push_back(0.0);
    }
    m_pGammaRegionPostLike = this; // for use by the base class, for speed
}

//------------------------------------------------------------------------------------
// Store log(PointProb(G|P)) for each tree G of each replicate of each region.
// We store these during calculation of the log-likelihood,
// so we can re-use them in the gradient calculations.

void GammaRegionPostLike::Store_Current_lnPoint(const double& value)
{
    *m_lnPoint_genealogy_it = value;
    m_lnPoint_genealogy_it++;

    if (m_lnPoint_genealogy_it == m_lnPoint_rep_it->end())
    {
        m_lnPoint_rep_it++;
        if (m_lnPoint_rep_it == m_lnPoint_reg_it->end())
        {
            m_lnPoint_reg_it++;
            if (m_lnPoint_reg_it == m_lnPoint.end())
            {
                m_lnPoint_reg_it = m_lnPoint.begin();
            }
            m_lnPoint_rep_it = m_lnPoint_reg_it->begin();
        }
        m_lnPoint_genealogy_it = m_lnPoint_rep_it->begin();
    }
}

//------------------------------------------------------------------------------------
// Store log(WaitProb(G|P)) for each tree G of each replicate of each region.
// We store these during calculation of the log-likelihood,
// so we can re-use them in the gradient calculations.

void GammaRegionPostLike::Store_Current_lnWait(const double& value)
{
    *m_lnWait_genealogy_it = value;
    m_lnWait_genealogy_it++;

    if (m_lnWait_genealogy_it == m_lnWait_rep_it->end())
    {
        m_lnWait_rep_it++;
        if (m_lnWait_rep_it == m_lnWait_reg_it->end())
        {
            m_lnWait_reg_it++;
            if (m_lnWait_reg_it == m_lnWait.end())
            {
                m_lnWait_reg_it = m_lnWait.begin();
            }
            m_lnWait_rep_it = m_lnWait_reg_it->begin();
        }
        m_lnWait_genealogy_it = m_lnWait_rep_it->begin();
    }
}

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

void GammaRegionPostLike::Setup(const vector<vector<TreeCollector*> >& treedata, const DoubleVec2d& logGeyerWeights)
{
    if (!(m_lnProbGPo.empty() && m_C.empty() && m_lnPoint.empty() && m_lnWait.empty() &&
          m_K_alpha_minus_nevents.empty() && m_K_alpha_minus_nevents_plus_1.empty() &&
          m_nevents_G.empty() && m_pg0.empty()))
    {
        string msg = "Internal logic error:  GammaRegionPostLike::Setup() ";
        msg += "has been called more than once.";
        throw implementation_error(msg);
    }

    m_data = treedata;
    SetParam0();

    m_pg0.resize(m_nRegions);

    for (unsigned long reg = 0; reg < m_nRegions; reg++)
    {
        m_pg0[reg].resize(m_nReplicate);
        DoubleVec2d numreps_zerovectors;
        for (unsigned long rep = 0; rep < m_nReplicate; rep++)
        {
            DoubleVec1d numUniqueTrees_zeroes(m_data[reg][rep]->treeSummaries.size(), 0.0);
            numreps_zerovectors.push_back(numUniqueTrees_zeroes);
            m_pg0[reg][rep].resize(m_data[reg][rep]->treeSummaries.size());
        }
        m_lnProbGPo.push_back(numreps_zerovectors);
        m_C.push_back(numreps_zerovectors);
        m_lnPoint.push_back(numreps_zerovectors);
        m_lnWait.push_back(numreps_zerovectors);
        m_K_alpha_minus_nevents.push_back(numreps_zerovectors);
        m_K_alpha_minus_nevents_plus_1.push_back(numreps_zerovectors);
        m_nevents_G.push_back(numreps_zerovectors);
    }

    // Note:  m_sumG_C (dimension: regions) was created in the constructor.

    // Set the lnPoint and lnWait iterators, in preparation for the
    // calls to Calc_lnProbGP() in the next block.
    m_lnPoint_reg_it = m_lnPoint.begin();
    m_lnPoint_rep_it = m_lnPoint_reg_it->begin();
    m_lnPoint_genealogy_it = m_lnPoint_rep_it->begin();
    m_lnWait_reg_it = m_lnWait.begin();
    m_lnWait_rep_it = m_lnWait_reg_it->begin();
    m_lnWait_genealogy_it = m_lnWait_rep_it->begin();

    double totalNumTreesThisRegion(0.0);

    for (unsigned long reg = 0; reg < m_nRegions; reg++)
    {
        totalNumTreesThisRegion = 0.0;
        for (unsigned long rep = 0; rep < m_nReplicate; rep++)
        {
            totalNumTreesThisRegion +=
                static_cast<double>(m_data[reg][rep]->TreeCount());
            for (unsigned long G = 0;
                 G < m_data[reg][rep]->treeSummaries.size(); G++)
            {
                m_lnProbGPo[reg][rep][G] = Calc_lnProbGP(m_param0[reg][rep],
                                                         m_lparam0[reg][rep],
                                                         m_data[reg][rep]->treeSummaries[G]);

                // Store the total number of events from tree G.
                Summary const *pSummary = m_data[reg][rep]->treeSummaries[G]->GetCoalSummary();
                if (pSummary)
                {
                    // Add the total number of coalescences to m_events_G.
                    const vector<double>& total_ncoal = pSummary->GetShortPoint();
                    m_nevents_G[reg][rep][G] =
                        accumulate(total_ncoal.begin(), total_ncoal.end(), 0.0);
                }
                else
                {
                    // No coalescence force found?!?
                    string msg = "Internal error:  GammaRegionPostLike::Setup() ";
                    msg += "could not find a CoalSummary object.";
                    throw implementation_error(msg);
                }
                pSummary = m_data[reg][rep]->treeSummaries[G]->GetMigSummary();
                if (pSummary)
                {
                    // Add the total number of immigrations to m_events_G.
                    const vector<double>& total_nmig = pSummary->GetShortPoint();
                    m_nevents_G[reg][rep][G] +=
                        accumulate(total_nmig.begin(), total_nmig.end(), 0.0);
                }
                pSummary = m_data[reg][rep]->treeSummaries[G]->GetRecSummary();
                if (pSummary)
                {
                    // Add the total number of recombinations to m_events_G.
                    const vector<double>& total_nrec = pSummary->GetShortPoint();
                    m_nevents_G[reg][rep][G] +=
                        accumulate(total_nrec.begin(), total_nrec.end(), 0.0);
                }

                // BUGBUG WARNING:  If an event type other than coalescence,
                // migration, or recombination is ever added to lamarc,
                // it must be factored into the total number of events above.
                // (Growth is not an event type; it gets applied to coalescent events.)

                // now do the likelihood under each other replicate
                m_pg0[reg][rep][G].resize(m_nReplicate);
                for (unsigned long trrep = 0; trrep < m_nReplicate; ++trrep)
                {
                    m_pg0[reg][rep][G][trrep] =
                        ((trrep == rep) ? m_lnProbGPo[reg][rep][G] :
                         Calc_lnProbGP(m_param0[reg][trrep],m_lparam0[reg][trrep],
                                       m_data[reg][rep]->treeSummaries[G]));
                }
            }
        }
        m_totalNumTrees[reg] = totalNumTreesThisRegion;
    }
    for(unsigned long reg = 0; reg < m_nRegions; ++reg)
    {
        TransformProbG0With(logGeyerWeights[reg], reg);
    }
}

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

bool GammaRegionPostLike::Calculate(const DoubleVec1d& param, const DoubleVec1d& lparam, double& lnL)
{
    // Set the lnPoint and lnWait iterators.
    m_lnPoint_reg_it = m_lnPoint.begin();
    m_lnPoint_rep_it = m_lnPoint_reg_it->begin();
    m_lnPoint_genealogy_it = m_lnPoint_rep_it->begin();
    m_lnWait_reg_it = m_lnWait.begin();
    m_lnWait_rep_it = m_lnWait_reg_it->begin();
    m_lnWait_genealogy_it = m_lnWait_rep_it->begin();

    bool usingGeyer = (m_nReplicate > 1 ? true : false);
    double alpha = param[param.size() - 1];
    if (alpha <= 0.0)
    {
        string msg = "GammaRegionPostLike::Calculate():  Must have alpha > 0.  ";
        msg += "Encountered alpha = " + ToString(alpha) + ".";
        throw implementation_error(msg);
    }
    double logalpha = lparam[lparam.size() - 1]; // avoid redundant calls to log()
    double total_lnL(0.0);
    double C(0.0); // used to calculate lnL

    for (unsigned long reg = 0; reg < m_nRegions; reg++)
    {
        // Scale the parameter vector, e.g., to account for inherent
        // differences between nuclear DNA and Y-chromosome DNA and mtDNA.
        // Note:  The scaled parameter vector does NOT contain alpha, by design.
        DoubleVec1d scaledparam(m_paramscalars[reg].size()),
            scaledlparam(m_lparamscalars[reg].size());
        transform(param.begin(), param.end()--, m_paramscalars[reg].begin(),
                  scaledparam.begin(), multiplies<double>());
        transform(lparam.begin(), lparam.end()--, m_lparamscalars[reg].begin(),
                  scaledlparam.begin(), plus<double>());
        m_sumG_C[reg] = 0.0;

        for (unsigned long rep = 0; rep < m_nReplicate; rep++)
        {
            for (unsigned long G = 0; // "G" means "genealogy tree"
                 G < m_data[reg][rep]->treeSummaries.size(); G++)
            {
                // Calc_lnProbGP() updates m_lnPoint and m_lnWait.
                // We use these separately; we don't need
                // the return value of log(Prob(G|P)) here.
                PostLike::Calc_lnProbGP(scaledparam, scaledlparam,
                                        m_data[reg][rep]->treeSummaries[G]);
                // Compute log(C), then exp() it, to avoid under/overflow.
                C = m_lnPoint[reg][rep][G];
                C -= m_lnProbGPo[reg][rep][G]; // may include Geyer weights
                C += 0.5 * (alpha + m_nevents_G[reg][rep][G]) * logalpha;
                C += 0.5 * (alpha - m_nevents_G[reg][rep][G])
                    * log(-m_lnWait[reg][rep][G]); // -m_lnWait > 0
                // Calculate K(alpha-nevents, 2*sqrt(-m_lnWait*alpha))
                // and K(alpha-nevents+1, 2*sqrt(-m_lnWait*alpha)).
                // Store these for use by the gradient calculations.
                m_K_alpha_minus_nevents[reg][rep][G] =
                    BesselK(alpha - m_nevents_G[reg][rep][G],
                            2.0*sqrt(-m_lnWait[reg][rep][G]*alpha),
                            m_K_alpha_minus_nevents_plus_1[reg][rep][G]);
                // BesselK(v,z) >= 0 for all v,z.  Approaches 0 as z gets large.
                if (m_K_alpha_minus_nevents[reg][rep][G] > 0.0)
                {
                    C += log(m_K_alpha_minus_nevents[reg][rep][G]);
                    m_C[reg][rep][G] = SafeProductWithExp(1.0, C);
                }
                else
                    m_C[reg][rep][G] = 0.0; // because m_C is proportional to BesselK(v,z)
                m_sumG_C[reg] += m_C[reg][rep][G] * m_data[reg][rep]->treeSummaries[G]->GetNCopies();
            }
        }
        if (0.0 == m_sumG_C[reg])
        {
#ifdef TEST
            cerr << "GammaRegionPostLike::Calculate():  sum over reps and trees "
                 << "of region " << reg << " is zero; this must be a positive "
                 << "number.  Returning false and lnL = -DBL_BIG....";
#endif // TEST
            m_last_lnL = lnL = -DBL_BIG;
            return false;
        }
        if (!usingGeyer)
            total_lnL += log(m_sumG_C[reg]/m_totalNumTrees[reg]);
        else
            total_lnL += log(m_sumG_C[reg]);
    }

    total_lnL += (LOG2 - log_gamma(alpha))*m_nRegions;
    m_last_lnL = lnL = total_lnL;
    return true;
}

//____________________________________________________________________________________
