// $Id: event.cpp,v 1.66 2018/01/03 21:32:58 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 */


#include <algorithm>
#include <cassert>
#include <functional>                   // for divides<> and less<> used in ThrowIfSubPopSizeTiny

#include "local_build.h"
#include "dynatracer.h"                 // Defines some debugging macros.

#include "arranger.h"
#include "branch.h"
#include "epoch.h"                      // for EpochEvent
#include "event.h"
#include "fc_status.h"                  // for DoEvent()
#include "forcesummary.h"
#include "mathx.h"
#include "range.h"                      // For Link-related typedefs and constants.
#include "registry.h"
#include "shared_ptr.hpp"
#include "stringx.h"
#include "timemanager.h"                // for use in Picktime for coals and disease
#include "tree.h"
#include "vectorx.h"                    // for Contains()

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

using namespace std;

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

typedef boost::shared_ptr<RBranch> RBranch_ptr;

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

Event::Event(const ForceSummary& fs)
    : m_pOwningArranger(NULL),
      m_allparams(fs.GetStartParameters())
{
    // intentionally blank
} // Event ctor

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

bool Event::Done() const
{
    // As far as this event knows, we are Done if there are no more active lineages.
    if (m_pOwningArranger->ActiveSize() == 0) return true;
    else return false;
} // Done

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

Event::Event(const Event& src)
    : m_pOwningArranger(src.m_pOwningArranger),
      m_maxEvents(src.m_maxEvents),
      m_pindex(src.m_pindex),
      m_thetas(src.m_thetas),
      m_growths(src.m_growths),
      m_allparams(src.m_allparams)
{
    // intentionally blank
} // Event copy ctor

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

void Event::InstallParameters(const ForceParameters& starts)
{
    // MCHECK--we don't need these cached separately!
    m_thetas = starts.GetRegionalThetas();
    m_growths = starts.GetGrowthRates();
    m_allparams = starts;
} // Event::InstallParameters

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

XPartitionEvent::XPartitionEvent(const ForceSummary& fs)
    : Event(fs),
      m_nxparts(registry.GetDataPack().GetNCrossPartitions())
{
    // deliberately blank
} // XPartitionEvent ctor

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

void XPartitionEvent::ThrowIfSubPopSizeTiny(double eventT) const
{
    DoubleVec1d popsizes(m_pOwningArranger->GetTree()->XpartThetasAtT(eventT, m_allparams));
    transform(popsizes.begin(), popsizes.end(), popsizes.begin(), bind2nd(divides<double>(),defaults::minMuRate));
    if (find_if(popsizes.begin(), popsizes.end(), bind2nd(less<double>(), 1.0)) != popsizes.end())
    {
        string estring("XPartitionEvent::ThrowIfSubPopSizeTiny:  ");
        estring += "Tried to have a coal event at " + ToString(eventT);
        tinypopulation_error e(estring);
        throw e;
    }
} // XPartitionEvent::ThrowIfSubPopSizeTiny

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

CoalEvent::CoalEvent(const ForceSummary& fs, bool isactive)
    : XPartitionEvent(fs),
      m_chosenxpart(FLAGLONG),
      m_isactive(isactive)
{
    // Set up base class fields deferred to the subclass.
    m_maxEvents = fs.GetMaxEvents(force_COAL);
    m_pindex = FLAGLONG;                // We use cross partitions, not partitions.
} // CoalEvent constructor

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

void CoalEvent::InstallParameters(const ForceParameters& starts)
{
    Event::InstallParameters(starts);
} // InstallParameters

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

void CoalEvent::DoEvent(double eventT, FC_Status& fcstatus)
{
    ThrowIfSubPopSizeTiny(eventT);
    // Pick two active branches from the population at random.
    double randomweight = m_pOwningArranger->randomSource->Float();
    Branch_ptr branch1 = m_pOwningArranger->m_activelist.RemoveBranch(force_COAL, m_chosenxpart, randomweight);

    randomweight = m_pOwningArranger->randomSource->Float();
    Branch_ptr branch2;
    if (m_isactive)
    {
        branch2 = m_pOwningArranger->m_activelist.RemoveBranch(force_COAL, m_chosenxpart, randomweight);
    }
    else
    {
        branch2 = m_pOwningArranger->m_inactivelist.RemoveBranch(force_COAL, m_chosenxpart, randomweight);
    }

    rangeset fcsites;

#if FINAL_COALESCENCE_ON
    rangeset decrsites(Intersection(branch1->GetLiveSites(), branch2->GetLiveSites()));
    fcstatus.Decrement_FC_Counts(decrsites);
    fcsites = fcstatus.Coalesced_Sites();
#endif

    assert(branch1->HasSamePartitionsAs(branch2));

    if (m_isactive)
    {
        Branch_ptr newbranch = m_pOwningArranger->GetTree()->CoalesceActive(eventT, branch1, branch2, fcsites);
        m_pOwningArranger->m_activelist.Append(newbranch);
    }
    else
    {
        Branch_ptr newbranch = m_pOwningArranger->GetTree()->CoalesceInactive(eventT, branch1, branch2, fcsites);
        m_pOwningArranger->m_inactivelist.Collate(newbranch);
    }

} // CoalEvent::DoEvent

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

ActiveCoal::ActiveCoal(const ForceSummary& fs)
    : CoalEvent(fs, true),
      m_invTheta()
{
    // intentionally blank
} // ActiveCoal constructor

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

Event* ActiveCoal::Clone() const
{
    ActiveCoal* event = new ActiveCoal(*this);
    return event;
} // ActiveCoal::Clone

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

void ActiveCoal::InstallParameters(const ForceParameters& starts)
{
    CoalEvent::InstallParameters(starts);

    m_invTheta = starts.GetRegionalThetas();

    // store 1/Theta
    transform(m_invTheta.begin(), m_invTheta.end(),
              m_invTheta.begin(),
              bind1st(divides<double>(),1.0));
} // InstallParameters

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

double ActiveCoal::PickTime(double starttime, double maxtime)
{
    double result = m_pOwningArranger->m_tree->GetTimeManager()->
        TimeOfActiveCoal(starttime, m_pOwningArranger->m_xactives, m_allparams, m_chosenxpart, maxtime);

    if (result < maxtime) return result;
    else return FLAGDOUBLE;
} // ActiveCoal::PickTime

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

InactiveCoal::InactiveCoal(const ForceSummary& fs)
    : CoalEvent(fs, false),
      m_inv2Theta()
{
    // intentionally blank
} // InactiveCoal copy constructor

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

Event* InactiveCoal::Clone() const
{
    InactiveCoal* event = new InactiveCoal(*this);
    return event;
} // InactiveCoal::Clone

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

void InactiveCoal::InstallParameters(const ForceParameters& starts)
{
    CoalEvent::InstallParameters(starts);

    // We store 2/Theta for speed
    m_inv2Theta = starts.GetRegionalThetas();

    // store 2/Theta
    transform(m_inv2Theta.begin(), m_inv2Theta.end(),
              m_inv2Theta.begin(),
              bind1st(divides<double>(),2.0));
} // InstallParameters

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

double InactiveCoal::PickTime(double starttime, double maxtime)
{
    double result =
        m_pOwningArranger->m_tree->GetTimeManager()->TimeOfInactiveCoal(starttime, m_pOwningArranger->m_xactives,
                                                                        m_pOwningArranger->m_xinactives,
                                                                        m_allparams, m_chosenxpart, maxtime);
    if (result < maxtime) return result;
    else return FLAGDOUBLE;
} // InactiveCoal::PickTime

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

PartitionEvent::PartitionEvent(const ForceSummary& fs)
    : Event(fs)
{
    // intentionally blank
} // PartitionEvent ctor

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

MigEvent::MigEvent(const ForceSummary& fs)
    : PartitionEvent(fs),
      m_rescaledMigRates(),
      m_immigrationRates(),
      m_frompop(FLAGLONG),
      m_topop(FLAGLONG)
{
    m_nparts = registry.GetDataPack().GetNPartitionsByForceType(force_MIG);
    m_forcetype = force_MIG;
    m_maxEvents = fs.GetMaxEvents(force_MIG);
    m_pindex = fs.GetPartIndex(force_MIG);
} // MigEvent ctor

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

Event* MigEvent::Clone() const
{
    MigEvent* event = new MigEvent(*this);
    return event;
} // MigEvent::Clone

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

void MigEvent::InstallParameters(const ForceParameters& starts)
{
    Event::InstallParameters(starts);

    // pre-compute migration rate tables
    ComputeCumulativeRates(starts);
} // InstallParameters

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

void MigEvent::ComputeCumulativeRates(const ForceParameters& starts)
{
    vector<vector<double> > migRates = starts.GetRegional2dRates(force_MIG);
    vector<double> indRate;

    m_rescaledMigRates.clear();
    m_immigrationRates.clear();

    double totalMig;
    long i, j;

    // Computes cumulative migration rates in "m_rescaledMigRates"
    // and total immigration into a population in "m_immigrationRates".

    for (i = 0; i < m_nparts; ++i)
    {
        totalMig = 0.0;
        indRate.clear();
        for (j = 0; j < m_nparts; ++j)
        {
            totalMig += migRates[i][j];
            indRate.push_back(totalMig);            // cumulative rate
        }

        // Normalize cumulative rates to total.
        if (totalMig > 0)
        {
            for (j = 0; j < m_nparts; ++j)
            {
                indRate[j] /= totalMig;
            }
        }

        m_rescaledMigRates.push_back(indRate);      // cumulative rates
        m_immigrationRates.push_back(totalMig);     // total immigration
    }
} // ComputeCumulativeRates

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

double MigEvent::PickTime(double starttime, double maxtime)
{
    // Computes timestamp of event by computing "delta t" and adding starttime.

    // Note:  The expectation value of this "delta t" is 1/(M*k), where M and k
    // are the immigration rate and number of active lineages of the population
    // which yields the smallest "delta t."
    map<double, long> times;
    double newtime;
    long i;

    for (i = 0; i < m_nparts; ++i)
    {
        if (m_pOwningArranger->m_pactives[m_pindex][i] > 0)
        {
            newtime = -log (m_pOwningArranger->randomSource->Float()) /
                (m_immigrationRates[i] * m_pOwningArranger->m_pactives[m_pindex][i]);
            times.insert(make_pair(newtime, i));
        }
    }

    if (times.empty())
    {
        // No event is possible.
        m_frompop = FLAGLONG;
        m_topop = FLAGLONG;
        return FLAGDOUBLE;
    }
    else
    {
        // The first map entry is the smallest, and thus chosen, time.
        map<double, long>::const_iterator mapit = times.begin();
        m_frompop = (*mapit).second;
        double randomweight = m_pOwningArranger->randomSource->Float();
        for (m_topop = 0; m_rescaledMigRates[m_frompop][m_topop] < randomweight; ++m_topop)
        {};
        double result = (*mapit).first + starttime;
        if (result < maxtime) return result;
        else return FLAGDOUBLE;
    }

} // MigEvent::PickTime

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

void MigEvent::DoEvent(double eventT, FC_Status&)
{
    assert(m_frompop >= 0 && m_topop >= 0);

    // Pick an active branch from the population at random.
    double randomweight = m_pOwningArranger->randomSource->Float();
    Branch_ptr active = m_pOwningArranger->m_activelist.RemovePartitionBranch(force_MIG, m_frompop, randomweight);

    Branch_ptr newbranch = m_pOwningArranger->GetTree()->Migrate(eventT, m_topop, m_maxEvents, active);
    m_pOwningArranger->m_activelist.Append(newbranch);

} // MigEvent::DoEvent

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

DivMigEvent::DivMigEvent(const ForceSummary& fs)
    : PartitionEvent(fs),
      m_rescaledMigRates(),
      m_immigrationRates(),
      m_frompop(FLAGLONG),
      m_topop(FLAGLONG)
{
    m_nparts = registry.GetDataPack().GetNPartitionsByForceType(force_DIVMIG);
    m_forcetype = force_DIVMIG;
    m_maxEvents = fs.GetMaxEvents(force_DIVMIG);
    m_pindex = fs.GetPartIndex(force_DIVMIG);
    m_epochptr = fs.GetEpochs();
    m_currentepoch = 0;

} // DivMigEvent ctor

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

Event* DivMigEvent::Clone() const
{
    DivMigEvent* event = new DivMigEvent(*this);
    return event;
} // DivMigEvent::Clone

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

void DivMigEvent::InstallParameters(const ForceParameters& starts)
{
    Event::InstallParameters(starts);

    // Pre-compute migration rate tables.
    ComputeCumulativeRates();

    // Add 0 as time of first epoch.
    m_epochtimes = m_allparams.GetEpochTimes();
    m_epochtimes.insert(m_epochtimes.begin(), 0.0);

} // InstallParameters

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

void DivMigEvent::ComputeCumulativeRates()
{
    vector<vector<double> > migRates = m_allparams.GetRegional2dRates(force_DIVMIG);
    vector<double> indRate;

    vector<long> pops = (*m_epochptr)[m_currentepoch].PopulationsHere();
    m_rescaledMigRates.clear();
    m_immigrationRates.clear();

    double totalMig;
    long i, j;

    // Computes cumulative migration rates in "m_rescaledMigRates"
    // and total immigration into a population in "m_immigrationRates".
    for (i = 0; i < m_nparts; ++i)
    {
        totalMig = 0.0;
        indRate.clear();
        for (j = 0; j < m_nparts; ++j)
        {
            if (Contains(pops,i) && Contains(pops,j)) totalMig += migRates[i][j];
            indRate.push_back(totalMig);            // cumulative rate
        }

        // Normalize cumulative rates to total.
        if (totalMig > 0)
        {
            for (j = 0; j < m_nparts; ++j)
            {
                indRate[j] /= totalMig;
            }
        }

        m_rescaledMigRates.push_back(indRate);      // cumulative rates
        m_immigrationRates.push_back(totalMig);     // total immigration
    }
} // ComputeCumulativeRates

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

double DivMigEvent::PickTime(double starttime, double maxtime)
{
    // Computes timestamp of event by computing "delta t" and adding starttime.

    // Note:  The expectation value of this "delta t" is 1/(M*k), where M and k are the immigration
    // rate and number of active lineages of the population which yields the smallest "delta t."

    map<double, long> times;
    double newtime;
    long i;

    // Find epoch that contains starttime.
    unsigned long epochno;
    for (epochno = 1; epochno < m_epochtimes.size(); ++epochno)
    {
        // If this never triggers, we get the right answer by fallthrough.
        if (starttime < m_epochtimes[epochno]) break;
    }
    --epochno;  // We found the epoch past the one we wanted.

    vector<long> activepops = (*m_epochptr)[epochno].PopulationsHere();
    if (epochno != static_cast<unsigned long>(m_currentepoch))
    {
        // We are in a new epoch and need new rates.
        m_currentepoch = epochno;
        ComputeCumulativeRates();
    }
    for (i = 0; i < m_nparts; ++i)
    {
        if (m_pOwningArranger->m_pactives[m_pindex][i] > 0 && m_immigrationRates[i] > 0)
        {
            newtime = -log (m_pOwningArranger->randomSource->Float()) /
                (m_immigrationRates[i] * m_pOwningArranger->m_pactives[m_pindex][i]);
            times.insert(make_pair(newtime, i));
        }
    }

    if (times.empty())
    {
        m_frompop = FLAGLONG;           // No event is possible.
        m_topop = FLAGLONG;
        return FLAGDOUBLE;
    }
    else
    {
        // The first map entry is the smallest, and thus chosen, time.
        map<double, long>::const_iterator mapit = times.begin();
        m_frompop = (*mapit).second;
        double randomweight = m_pOwningArranger->randomSource->Float();
        for (m_topop = 0; m_rescaledMigRates[m_frompop][m_topop] < randomweight; ++m_topop)
        {};
        double result = (*mapit).first + starttime;
        if (result < maxtime) return result;
        else return FLAGDOUBLE;
    }
} // DivMigEvent::PickTime

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

void DivMigEvent::DoEvent(double eventT, FC_Status&)
{
    assert(m_frompop >= 0 && m_topop >= 0);

    // Pick an active branch from the population at random.
    double randomweight = m_pOwningArranger->randomSource->Float();
    Branch_ptr active = m_pOwningArranger->m_activelist.RemovePartitionBranch(force_DIVMIG, m_frompop, randomweight);

    Branch_ptr newbranch = m_pOwningArranger->GetTree()->Migrate(eventT, m_topop, m_maxEvents, active);
    m_pOwningArranger->m_activelist.Append(newbranch);

} // DivMigEvent::DoEvent

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

DiseaseEvent::DiseaseEvent(const ForceSummary& fs)
    : PartitionEvent(fs),
      m_startdis(FLAGLONG),
      m_enddis(FLAGLONG)
{
    m_nparts = registry.GetDataPack().GetNPartitionsByForceType(force_DISEASE);
    m_forcetype = force_DISEASE;
    m_maxEvents = fs.GetMaxEvents(force_DISEASE);
    m_pindex = fs.GetPartIndex(force_DISEASE);

} // DiseaseEvent ctor

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

Event* DiseaseEvent::Clone() const
{
    DiseaseEvent* event = new DiseaseEvent(*this);
    return event;

} // DiseaseEvent::Clone

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

double DiseaseEvent::PickTime(double starttime, double maxtime)
{
    // Sets member variables m_startdis and m_enddis as a side effect!
    double result = m_pOwningArranger->m_tree->GetTimeManager()->
        TimeOfTraitMutation(starttime, m_pOwningArranger->m_pactives[m_pindex],
                            m_allparams, m_startdis, m_enddis, maxtime);

    if (result < maxtime) return result;
    else return FLAGDOUBLE;

} // DiseaseEvent::PickTime

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

void DiseaseEvent::DoEvent(double eventT, FC_Status&)
{
    assert(m_startdis >= 0 && m_enddis >= 0);

    // Pick an active branch from the disease category at random.
    double randomweight = m_pOwningArranger->randomSource->Float();
    Branch_ptr active = m_pOwningArranger->m_activelist.RemovePartitionBranch(force_DISEASE, m_startdis, randomweight);

    Branch_ptr newbranch = m_pOwningArranger->GetTree()->DiseaseMutate(eventT, m_enddis, m_maxEvents, active);
    m_pOwningArranger->m_activelist.Append(newbranch);

} // DiseaseEvent::DoEvent

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

void DiseaseEvent::InstallParameters(const ForceParameters& starts)
{
    Event::InstallParameters(starts);
} // DiseaseEvent::InstallParameters

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

ActiveRec::ActiveRec(const ForceSummary& fs)
    : Event(fs),
      m_recrate(defaults::recombinationRate),
      m_onPartitionForces()
{
    m_maxEvents = fs.GetMaxEvents(force_REC);
    m_pindex = FLAGLONG;                // We don't use partitions...yet!

} // ActiveRec ctor

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

Event* ActiveRec::Clone() const
{
    ActiveRec* event = new ActiveRec(*this);
    return event;

} // ActiveRec::Clone

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

void ActiveRec::InstallParameters(const ForceParameters& starts)
{
    Event::InstallParameters(starts);

    assert(starts.GetRecRates().size() == 1); // Only one recombination rate!
    m_recrate = starts.GetRecRates()[0];

} // InstallParameters

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

double ActiveRec::PickTime(double starttime, double maxtime)
{
    // Computes the timestamp of the end of the interval by means of
    // computing "delta t" and adding it to tstart.

    // Note:  The expectation value of "delta t" is 1/(r*nA), where r and nA are the recombination rate
    // and active Link recweights for the population which yields the smallest value of "delta t".

    RecTree * tr = dynamic_cast<RecTree *>(m_pOwningArranger->GetTree());

#if 1
    DebugAssert2(!(tr->GetCurTargetLinkweight() > ZERO && m_pOwningArranger->ActiveSize() == 0),
                 tr->GetCurTargetLinkweight(),
                 m_pOwningArranger->ActiveSize());
#else // Equivalent to DebugAssert2 above, in case it is removed later.
    assert(!(tr->GetCurTargetLinkweight() > ZERO && m_pOwningArranger->ActiveSize() == 0));
#endif

    // Active sites without lineages.
#if 1
    DebugAssert4(tr->GetCurTargetLinkweight() == m_pOwningArranger->m_activelist.AccumulatedCurTargetLinkweight(),
                 tr->GetNewTargetLinkweight(),
                 m_pOwningArranger->m_inactivelist.AccumulatedNewTargetLinkweight(),
                 tr->GetCurTargetLinkweight(),
                 m_pOwningArranger->m_activelist.AccumulatedCurTargetLinkweight());
#else // Equivalent to DebugAssert4 above, in case it is removed later.
    assert(tr->GetCurTargetLinkweight() == m_pOwningArranger->m_activelist.AccumulatedCurTargetLinkweight());
#endif

    if (tr->GetCurTargetLinkweight() > ZERO)
    {
        double retval = -log(m_pOwningArranger->randomSource->Float()) / (m_recrate * tr->GetCurTargetLinkweight());
        double result = retval + starttime;
        if (result < maxtime) return result;
    }

    return FLAGDOUBLE;

} // ActiveRec::PickTime

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

void ActiveRec::DoEvent(double eventT, FC_Status& fcstatus)
{
    RecTree * tr = dynamic_cast<RecTree *>(m_pOwningArranger->GetTree());

    // Pick an active branch and a recombination point (Littlelink) at random.
#ifdef RUN_BIGLINKS
    // 0.0 < randomweight < <Linkweight_of_all_targetable_links_on_all_branches> (but arbitrarily close to ends).
    Linkweight randomweight = m_pOwningArranger->randomSource->Float() * tr->GetCurTargetLinkweight();
#else
    // 0 <= randomweight < GetCurTargetLinkweight (ie, random proportion of all targetable links over all branches).
    Linkweight randomweight = m_pOwningArranger->randomSource->Long(tr->GetCurTargetLinkweight());
#endif

    Branch_ptr active;
    Branchiter brit;
    for (brit = m_pOwningArranger->m_activelist.BeginBranch(); ; ++brit)
    {
        active = *brit;
        Linkweight recweight = active->GetRangePtr()->GetCurTargetLinkweight();
        if (recweight > randomweight)
        {
            // Due to decrement below, what remains in "randomweight" is random weight threshold
            // compared to total weight of all targetable links on the CHOSEN branch only.
            break;                      // Found it!
        }
        randomweight -= recweight;
    }

    // Get (and test) a pointer to the selected branch's RecRange.
    RecRange * recrange_ptr(dynamic_cast<RecRange *>(active->GetRangePtr()));
    assert(recrange_ptr);

    // Find potential recombination points.
    // Currently targetable Links on the selected branch.
    linkrangeset curTargetLinks(recrange_ptr->GetCurTargetLinks());

    // Assert that the selected branch does potentially contain a recombination point.
    assert( ! curTargetLinks.empty() );

#ifdef RUN_BIGLINKS
    //
    // Initially just a Littlelink index; later converted to index marking Littlelink in middle of chosen Biglink.
    long int recpoint(FLAGLONG);        // Initialize to "Recpoint Not Found" indication so we can test later.
    BiglinkVectormap biglink_vectormap(RecRange::GetBiglinkVectormap());
    //
    // Scan across each LINKRANGEPAIR (representing a set of Biglinks) in the LINKRANGESET of targetable Biglinks.
    linkrangeset::const_iterator rit;
    for (rit = curTargetLinks.begin(); rit != curTargetLinks.end(); ++rit)
    {
        unsigned long int limit(biglink_vectormap.size());
        unsigned long int lo_idx(rit->first);    // Index of (included) lower Biglink in Map.
        unsigned long int hi_idx(rit->second);   // Index of (excluded) upper Biglink in Map.
        //
#if 1
        // Since "lo_idx" and "hi_idx" are UNSIGNED, "foo < limit" is also equivalent "foo >= 0".
        DebugAssert3((lo_idx < limit) && (hi_idx <= limit), lo_idx, hi_idx, limit);
#else // Equivalent to DebugAssert3 above, in case it is removed later.
        assert((lo_idx < limit) && (hi_idx <= limit));
#endif
        //
        // Now scan across the Biglinks included in the LINKRANGEPAIR denoted by iterator "rit".
        // Note that this FOR loop will work for weight summation (finding the recombination point)
        // whether the targetable links happen to be contiguous or not.  In THIS function, they
        // happen always to be contiguous.
        bool done(false);
        for (unsigned long int idx = lo_idx ; idx < hi_idx ; ++idx)
        {
            BiglinkMapitem biglink = biglink_vectormap[idx];
            long int s1 = biglink.GetBiglinkLowerLittlelink();   // (Included) lower Littlelink index.
            long int s2 = biglink.GetBiglinkUpperLittlelink();   // (Excluded) upper Littlelink index.
            if ((s1 + static_cast<long int>(randomweight)) < s2) // Found it!
            {
                recpoint = (s1 + s2) / 2; // Compute the midpoint of this Biglink.
                done = true;
                break;
            }
            // Decrement "randomweight" by weight of current Biglink.  This is because we are scanning
            // across TARGETABLE Biglinks only, not across ALL Biglinks.  The weight in our comparison
            // threshold is a random proportion of the total weight of targetable Biglinks only.
            randomweight -= biglink.GetBiglinkWeight();
        }
        if (done) break;
    }
    //
#else // RUN_BIGLINKS
    //
    // Littlelink version is simple (compared to equiv code in InactiveRec::DoEvent)
    // because the targetable links are assumed always to be contiguous.
    long int recpoint = curTargetLinks.begin()->first;
    recpoint += randomweight;
    //
#endif // RUN_BIGLINKS

    m_pOwningArranger->m_activelist.Remove(brit);

    // Pick a partition at random if we have a local partition force (i.e. Disease) on and active at this region.
    FPartMap fparts;
    if (!m_onPartitionForces.empty())
    {
        for (vector<force_type>::iterator spart = m_onPartitionForces.begin();
             spart != m_onPartitionForces.end(); ++spart)
        {
            if (IsLocalPartForce(*spart))
            {
                DoubleVec1d allSizes = m_pOwningArranger->GetTree()->PartitionThetasAtT(eventT,*spart,m_allparams);
                long randomPart = ChooseRandomFromWeights(allSizes);
                fparts.insert(make_pair(*spart, randomPart));
            }
        }
    }

    rangeset fcsites;
#if FINAL_COALESCENCE_ON
    fcsites = fcstatus.Coalesced_Sites();
#endif

    branchpair newbranches = tr->RecombineActive(eventT, m_maxEvents, fparts, active, recpoint, fcsites, true);

    m_pOwningArranger->m_activelist.Append(newbranches.first);
    m_pOwningArranger->m_activelist.Append(newbranches.second);
    assert(newbranches.first->PartitionsConsistentWith(active));
    assert(newbranches.second->PartitionsConsistentWith(active));

} // ActiveRec::DoEvent

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

InactiveRec::InactiveRec(const ForceSummary& fs)
    : Event(fs),
      m_recrate(defaults::recombinationRate)
{
    m_maxEvents = fs.GetMaxEvents(force_REC);
    m_pindex = FLAGLONG;                // We don't use partitions...yet!

} // InactiveRec ctor

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

Event* InactiveRec::Clone() const
{
    InactiveRec* event = new InactiveRec(*this);
    return event;

} // InactiveRec::Clone

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

void InactiveRec::InstallParameters(const ForceParameters& starts)
{
    Event::InstallParameters(starts);

    assert(starts.GetRecRates().size() == 1); // Only one recombination rate!
    m_recrate = starts.GetRecRates()[0];

} // InstallParameters

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

double InactiveRec::PickTime(double starttime, double maxtime)
{
    // Computes the timestamp of the end of the interval by means of computing "delta t" and adding it to tstart.

    // Note:  The expectation value of "delta t" is 1/(r*nO), where r and nO are the recombination
    // rate and active Link weight for the population which yields the smallest value of "delta t".

    RecTree* tr = dynamic_cast<RecTree*>(m_pOwningArranger->GetTree());
    Linkweight recweight = tr->GetNewTargetLinkweight();

#if 1
    DebugAssert4(tr->GetNewTargetLinkweight() == m_pOwningArranger->m_inactivelist.AccumulatedNewTargetLinkweight(),
                 tr->GetNewTargetLinkweight(),
                 m_pOwningArranger->m_inactivelist.AccumulatedNewTargetLinkweight(),
                 tr->GetCurTargetLinkweight(),
                 m_pOwningArranger->m_inactivelist.AccumulatedCurTargetLinkweight());
#else // Equivalent to DebugAssert4 above, in case it is removed later.
    assert(recweight == m_pOwningArranger->m_inactivelist.AccumulatedNewTargetLinkweight());
#endif

    if (recweight > ZERO)
    {
        double retval = -log(m_pOwningArranger->randomSource->Float()) / (m_recrate * recweight);
        double result = retval + starttime;
        if (result < maxtime) return result;
    }

    return FLAGDOUBLE;

} // InactiveRec::PickTime

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

void InactiveRec::DoEvent(double eventT, FC_Status& fcstatus)
{
    RecTree* tr = dynamic_cast<RecTree*>(m_pOwningArranger->GetTree());

    // Pick a partition at random if we have a local partition force (i.e. Disease) on and active at this region.
    FPartMap fparts;
    if (!m_onPartitionForces.empty())
    {
        for (vector<force_type>::iterator spart = m_onPartitionForces.begin();
             spart != m_onPartitionForces.end(); ++spart)
        {
            if (IsLocalPartForce(*spart))
            {
                DoubleVec1d allSizes = m_pOwningArranger->GetTree()->PartitionThetasAtT(eventT,*spart,m_allparams);
                long randomPart = ChooseRandomFromWeights(allSizes);
                fparts.insert(make_pair(*spart, randomPart));
            }
        }
    }

    // Assert that the set of possible branches does potentially contain a recombination point.
#if 1
    DebugAssert4(tr->GetNewTargetLinkweight() == m_pOwningArranger->m_inactivelist.AccumulatedNewTargetLinkweight(),
                 tr->GetNewTargetLinkweight(),
                 m_pOwningArranger->m_inactivelist.AccumulatedNewTargetLinkweight(),
                 tr->GetCurTargetLinkweight(),
                 m_pOwningArranger->m_inactivelist.AccumulatedCurTargetLinkweight());
#else // Equivalent to DebugAssert4 above, in case it is removed later.
    assert(tr->GetNewTargetLinkweight() == m_pOwningArranger->m_inactivelist.AccumulatedNewTargetLinkweight());
#endif

#ifdef RUN_BIGLINKS
    //
    Branch_ptr branch;                  // Pick an open branch at random.
    // 0.0 < randomweight < <Linkweight_of_all_targetable_links_on_all_branches> (but arbitrarily close to ends).
    Linkweight randomweight = m_pOwningArranger->randomSource->Float() * tr->GetNewTargetLinkweight();
    //
#else // RUN_BIGLINKS
    //
    Branch_ptr branch;                  // Pick an open branch at random.
    // 0 <= randomweight < GetNewTargetLinkweight (ie, random proportion of targetable links over all branches).
    Linkweight randomweight = m_pOwningArranger->randomSource->Long(tr->GetNewTargetLinkweight());
    //
#endif // RUN_BIGLINKS

    Branchiter brit;
    for (brit = m_pOwningArranger->m_inactivelist.BeginBranch(); ; ++brit)
    {
        assert (brit != m_pOwningArranger->m_inactivelist.EndBranch());
        branch = *brit;

        Linkweight recweight = branch->GetRangePtr()->GetNewTargetLinkweight();
        if (recweight > randomweight)
        {
            // Due to decrement below, what remains in "randomweight" is random weight threshold
            // compared to total weight of all targetable links on the chosen branch only.
            break;                      // Found it!
        }
        randomweight -= recweight;
    }

    // At this point "randomweight" contains the accumulated weight (for Littlelinks: link count, counting from zero)
    // along the selected branch to the desired recombination point on the branch (var "branch") selected just above.
    // Assert that the selected branch does potentially contain a recombination point.
    assert(branch != Branch::NONBRANCH);

    m_pOwningArranger->m_inactivelist.Remove(brit);

    // Find the recombination point (Littlelink in the RecRange on the selected branch).
    long int recpoint(FLAGLONG);        // Initialize to "Recpoint Not Found" indication so we can test later.

    // Newly targetable Links on the selected branch.
    linkrangeset newTargetLinks = branch->GetRangePtr()->GetNewTargetLinks();

    // Assert that the selected branch does potentially contain a recombination point.
    assert(! newTargetLinks.empty());

#ifdef RUN_BIGLINKS
    BiglinkVectormap biglink_vectormap(RecRange::GetBiglinkVectormap());
#endif

    // Scan across each LINKRANGEPAIR (representing a set of Links) in the LINKRANGESET of targetable Links.  Note
    // that "Links" means "Biglinks" or "Littlelinks" depending on appropriate model (which one is "#ifdef"ed in).
    linkrangeset::const_iterator rit;
    for (rit = newTargetLinks.begin(); rit != newTargetLinks.end(); ++rit)
    {
#ifdef RUN_BIGLINKS
        //
        unsigned long int limit(biglink_vectormap.size());
        unsigned long int lo_idx(rit->first);    // Index of (included) lower Biglink in Map.
        unsigned long int hi_idx(rit->second);   // Index of (excluded) upper Biglink in Map.
        //
#if 1
        // Since "lo_idx" and "hi_idx" are UNSIGNED, "foo < limit" is also equivalent "foo >= 0".
        DebugAssert3((lo_idx < limit) && (hi_idx <= limit), lo_idx, hi_idx, limit);
#else // Equivalent to DebugAssert3 above, in case it is removed later.
        assert((lo_idx < limit) && (hi_idx <= limit));
#endif
        //
        // Now scan across the Biglinks included in the LINKRANGEPAIR denoted by iterator "rit".
        // Note that this FOR loop will work for weight summation (finding the recombination point)
        // whether the targetable links happen to be contiguous or not.  In THIS function, they
        // explicitly might NOT be contiguous.
        bool done(false);
        for (unsigned long int idx = lo_idx ; idx < hi_idx ; ++idx)
        {
            BiglinkMapitem biglink = biglink_vectormap[idx];
            long int s1 = biglink.GetBiglinkLowerLittlelink();   // (Included) lower Littlelink index.
            long int s2 = biglink.GetBiglinkUpperLittlelink();   // (Excluded) upper Littlelink index.
            if ((s1 + static_cast<long int>(randomweight)) < s2) // Found it!
            {
                recpoint = (s1 + s2) / 2; // Compute the midpoint of this Biglink.
                done = true;
                break;
            }
            // Decrement "randomweight" by weight of current Biglink.  This is because we are scanning
            // across TARGETABLE Biglinks only, not across ALL Biglinks.  The weight in our comparison
            // threshold is a random proportion of the total weight of targetable Biglinks only.
            randomweight -= biglink.GetBiglinkWeight();
        }
        if (done) break;
        //
#else   // RUN_BIGLINKS
        //
        // Littlelink version is more complicated (compared to equiv code in ActiveRec::DoEvent)
        // because the targetable links CANNOT be assumed always to be contiguous.  Thus we must
        // use the same "updating RANDOMWEIGHT" hack for both Biglinks and Littlelinks.
        long int s1 = rit->first;       // (Included) lower Littlelink index.
        long int s2 = rit->second;      // (Excluded) upper Littlelink index.
        recpoint = s1 + randomweight;
        if (recpoint < s2)              // Found it!
        {
            break;
        }
        // Decrement "randomweight" by weight of current Link.
        // NOTE: We will have to change this handling of Link weights (doubles) as Littlelink counts
        // (long ints) when we add capability to represent variable recombination probability with location.
        randomweight -= (s2 - s1);
        //
#endif  //  RUN_BIGLINKS
    }

    // Assert that the selected branch actually contains a recombination point.
    assert(recpoint != FLAGLONG);

#if FINAL_COALESCENCE_ON
    branchpair branches = tr->RecombineInactive(eventT, m_maxEvents, fparts, branch, recpoint, fcstatus.Coalesced_Sites());
#else
    rangeset emptyset;
    branchpair branches = tr->RecombineInactive(eventT, m_maxEvents, fparts, branch, recpoint, emptyset);
#endif

    m_pOwningArranger->m_inactivelist.Collate(branches.first);
    m_pOwningArranger->m_activelist.Append(branches.second);
    assert(branches.first->PartitionsConsistentWith(branch));
    assert(branches.second->PartitionsConsistentWith(branch));

} // InactiveRec::DoEvent

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

EpochEvent::EpochEvent(const ForceSummary& fs)
    : Event(fs),
      m_epochptr(fs.GetEpochs())
{
    m_maxEvents = fs.GetMaxEvents(force_DIVERGENCE);
    m_pindex = FLAGLONG;
}

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

double EpochEvent::PickTime(double starttime, double maxtime)
{
    unsigned long epochno;

    // We never pick the first epoch as it starts at time 0.
    for (epochno = 1; epochno < m_epochtimes.size(); ++epochno)
    {
        if (starttime < m_epochtimes[epochno])
        {
            if (m_epochtimes[epochno] <= maxtime) return m_epochtimes[epochno];
            else return FLAGDOUBLE;
        }
    }
    return FLAGDOUBLE;

} // EpochEvent::PickTime

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

void EpochEvent::DoEvent(double eventT, FC_Status&)
{
    // Find the appropriate Epoch.
    unsigned long i;
    long epno = FLAGLONG;
    for (i = 1; i < m_epochtimes.size(); ++i)
    {
        if (m_epochtimes[i] == eventT) epno = i;
    }

    assert(epno != FLAGLONG);           // Failed to find the epoch?
    const Epoch& epoch = (*m_epochptr)[epno];

    Branchiter branch;
    vector<Branchiter> removebranches;

    // Make a collection of branches to be replaced.
    for (branch = m_pOwningArranger->m_activelist.BeginBranch();
         branch != m_pOwningArranger->m_activelist.EndBranch();
         ++branch)
    {
        // if branch is in epoch.m_departing
        if (find(epoch.Departing().begin(), epoch.Departing().end(),
                 (*branch)->GetPartition(force_DIVMIG)) != epoch.Departing().end())
        {
            removebranches.push_back(branch);
        }
    }

    // Remove all of them, replacing with EBranches.
    for (i = 0; i < removebranches.size(); ++i)
    {
        // Obtain a Branch_ptr to the offending branch (avoids iterator invalidation!).
        Branch_ptr badbranch = *(removebranches[i]);

        // Remove it from activelist.
        m_pOwningArranger->m_activelist.Remove(removebranches[i]);

        // Attach an EBranch to it indicating m_epochptr[epno].arriving
        // (this call puts the EBranch into the TimeList and updates tree counts).
        Branch_ptr newbranch = m_pOwningArranger->GetTree()->
            TransitionEpoch(eventT, epoch.Arriving(), m_maxEvents, badbranch);

        // put EBranch in activelist
        m_pOwningArranger->m_activelist.Append(newbranch);
    }

} // EpochEvent::DoEvent

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

void EpochEvent::InstallParameters(const ForceParameters& starts)
{
    Event::InstallParameters(starts);
    m_epochtimes = starts.GetEpochTimes();
    m_epochtimes.insert(m_epochtimes.begin(), 0.0);

} // InstallParameters

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

Event* EpochEvent::Clone() const
{
    EpochEvent* event = new EpochEvent(*this);
    return event;

} // EpochEvent::Clone

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

string ToString(const Event &e)
{
    switch (e.Type())
    {
        case activeCoalEvent:
            return "activeCoalEvent";
            break;
        case inactiveCoalEvent:
            return "inactiveCoalEvent";
            break;
        case migEvent:
            return "migEvent";
            break;
        case diseaseEvent:
            return "diseaseEvent";
            break;
        case activeRecEvent:
            return "activeRecEvent";
            break;
        case inactiveRecEvent:
            return "inactiveRecEvent";
            break;
        case epochEvent:
            return "epochEvent";
            break;
    }

    assert(false);                      // Unhandled case.
    return "";
}

//____________________________________________________________________________________
