// $Id: ui_vars_datamodel.cpp,v 1.42 2018/01/03 21:33:05 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 <cassert>

#include "calculators.h"        // for FrequenciesFromData(const Locus&);
#include "constants.h"          // for model_type
#include "datatype.h"           // for predicate ModelIsIllegalForDataType
#include "defaults.h"
#include "dlmodel.h"            // for ModelTypeAcceptsDataType
#include "errhandling.h"
#include "report_strings.h"
#include "stringx.h"
#include "vectorx.h"            // for DoubleVec1d
#include "ui_constants.h"
#include "ui_regid.h"
#include "ui_vars.h"
#include "ui_vars_datamodel.h"

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

UIVarsSingleDataModel::UIVarsSingleDataModel(UIRegId regionId)
    :
    m_regionIndex(regionId.GetRegion()),
    m_locusIndex(regionId.GetLocus()),
    m_datatype(regionId.GetDataType()),
    doNormalize(defaults::doNormalize),
    autoCorrelation(defaults::autoCorrelation),
    categoryRates(defaults::categoryRates()),
    categoryProbabilities(defaults::categoryProbabilities()),
    ttRatio(defaults::ttratio),
    doCalcFreqsFromData(defaults::calcFreqsFromData),
    baseFrequency_A(defaults::baseFrequencyA),
    baseFrequency_C(defaults::baseFrequencyC),
    baseFrequency_G(defaults::baseFrequencyG),
    baseFrequency_T(defaults::baseFrequencyT),
    gtr_AC(defaults::gtrRateAC),
    gtr_AG(defaults::gtrRateAG),
    gtr_AT(defaults::gtrRateAT),
    gtr_CG(defaults::gtrRateCG),
    gtr_CT(defaults::gtrRateCT),
    gtr_GT(defaults::gtrRateGT),
    alpha(defaults::KS_alpha),
    m_relmurate(defaults::relativeMuRate),
    optimizeAlpha(defaults::optimize_KS_alpha),
    m_perBaseErrorRate(defaults::per_base_error_rate)
{
    m_modelType = DefaultModelForDataType(m_datatype);
}

UIVarsSingleDataModel::UIVarsSingleDataModel(const UIVarsSingleDataModel & clone, UIRegId regionId)
    :
    m_regionIndex           (regionId.GetRegion()),
    m_locusIndex            (regionId.GetLocus()),
    m_datatype              (regionId.GetDataType()),
    m_modelType             (clone.m_modelType),
    doNormalize             (clone.doNormalize),
    autoCorrelation         (clone.autoCorrelation),
    categoryRates           (clone.categoryRates),
    categoryProbabilities   (clone.categoryProbabilities),
    ttRatio                 (clone.ttRatio),
    doCalcFreqsFromData     (clone.doCalcFreqsFromData),
    baseFrequency_A         (clone.baseFrequency_A),
    baseFrequency_C         (clone.baseFrequency_C),
    baseFrequency_G         (clone.baseFrequency_G),
    baseFrequency_T         (clone.baseFrequency_T),
    gtr_AC                  (clone.gtr_AC),
    gtr_AG                  (clone.gtr_AG),
    gtr_AT                  (clone.gtr_AT),
    gtr_CG                  (clone.gtr_CG),
    gtr_CT                  (clone.gtr_CT),
    gtr_GT                  (clone.gtr_GT),
    alpha                   (clone.alpha),
    m_relmurate             (clone.m_relmurate),
    optimizeAlpha           (clone.optimizeAlpha),
    m_perBaseErrorRate      (clone.m_perBaseErrorRate)
{
    if (!ModelTypeAcceptsDataType(m_modelType, m_datatype))
    {
        assert(false); //Let's try to not get in this situation.
        m_modelType = DefaultModelForDataType(m_datatype);
    }
}

UIVarsSingleDataModel::~UIVarsSingleDataModel()
{
}

double
UIVarsSingleDataModel::MakeNextCategoryRate(DoubleVec1d rates)
{
    if(rates.empty())
    {
        assert(false); //How did we have an empty vector to start with?
        return defaults::categoryRate;
    }
    return rates[rates.size()-1] * defaults::categoryRateMultiple;
}

double
UIVarsSingleDataModel::MakeNextCategoryProbability(DoubleVec1d probabilities)
{
    if(probabilities.empty())
    {
        assert(false); //How did we have an empty vector to start with?
        return defaults::categoryProbability;
    }
    return probabilities[probabilities.size()-1];
}

long int UIVarsSingleDataModel::GetNumCategories() const
{
    // well, really it should be categoryProbablities.size()
    // as well. argh
    assert(categoryRates.size() == categoryProbabilities.size());
    return categoryRates.size();
}

double UIVarsSingleDataModel::GetCategoryProbability(long int categoryIndex) const
{
    assert(categoryIndex < GetNumCategories());
    return categoryProbabilities[categoryIndex];
}

double UIVarsSingleDataModel::GetCategoryRate(long int categoryIndex) const
{
    assert(categoryIndex < GetNumCategories());
    return categoryRates[categoryIndex];
}

bool UIVarsSingleDataModel::GetCalcFreqsFromData()  const
{
    if (GetDataModelType() == GTR)
    {
        return false;
    }
    return doCalcFreqsFromData;
}

double UIVarsSingleDataModel::GetFrequencyA() const
{
    if(GetCalcFreqsFromData())
    {
        // not terribly efficient, but maintaining logic to cache
        // the values was very difficult
        return FrequenciesFromData(m_regionIndex,m_locusIndex,GetDataModelType())[0];
    }
    return baseFrequency_A;
}

double UIVarsSingleDataModel::GetFrequencyC() const
{
    if(GetCalcFreqsFromData())
    {
        // not terribly efficient, but maintaining logic to cache
        // the values was very difficult
        return FrequenciesFromData(m_regionIndex,m_locusIndex,GetDataModelType())[1];
    }
    return baseFrequency_C;
}

double UIVarsSingleDataModel::GetFrequencyG() const
{
    if(GetCalcFreqsFromData())
    {
        // not terribly efficient, but maintaining logic to cache
        // the values was very difficult
        return FrequenciesFromData(m_regionIndex,m_locusIndex,GetDataModelType())[2];
    }
    return baseFrequency_G;
}

double UIVarsSingleDataModel::GetFrequencyT() const
{
    if(GetCalcFreqsFromData())
    {
        // not terribly efficient, but maintaining logic to cache
        // the values was very difficult
        return FrequenciesFromData(m_regionIndex,m_locusIndex,GetDataModelType())[3];
    }
    return baseFrequency_T;
}

void UIVarsSingleDataModel::SetDataModelType(model_type mtype)
{
    if (ModelTypeAcceptsDataType(mtype,m_datatype))
    {
        m_modelType = mtype;
    }
    else
    {
        string error = "The model " + ToString(mtype) +
            " may not be used for data type " + ToString(m_datatype) +
            " in region " + ToString(m_regionIndex+1) + ".";
        //EWFIX.P5 LOCUS + ", segment " + ToString(m_locusIndex+1) + ".";
        throw data_error(error);
    }
}

void UIVarsSingleDataModel::SetAutoCorrelation(double x)
{
    if (x < 1)
    {
        throw data_error("The autocorrelation must be one or greater.");
    }
    autoCorrelation = x;
}

void UIVarsSingleDataModel::SetNumCategories(long int numCats)
{
    if(numCats < 1)
    {
        throw data_error("There must be a positive number of categories for all mutational models.");
    }
    if (numCats > defaults::maxNumCategories)
    {
        string err = "The maximum number of categories of mutation rates you may";
        err += " set is " + ToString(defaults::maxNumCategories)
            + ", though the effectiveness of adding "
            + "more categories drops considerably after about 5.";
        throw data_error(err);
    }
    while(static_cast<long int>(categoryRates.size()) < numCats)
    {
        categoryRates.push_back(MakeNextCategoryRate(categoryRates));
    }
    categoryRates.resize(numCats);
    while(static_cast<long int>(categoryProbabilities.size()) < numCats)
    {
        categoryProbabilities.push_back(MakeNextCategoryProbability(categoryProbabilities));
    }
    categoryProbabilities.resize(numCats);
}

void UIVarsSingleDataModel::SetCategoryProbability(double prob, long int categoryIndex)
{
    assert(categoryIndex < GetNumCategories());
    if(prob <= 0)
    {
        throw data_error("You must set a positive probability to go with each rate.");
    }
    categoryProbabilities[categoryIndex] = prob;
}

void UIVarsSingleDataModel::SetCategoryRate(double rate, long int categoryIndex)
{
    assert(categoryIndex < GetNumCategories());
    if(rate < 0 )
    {
        throw data_error("You must set a positive or zero relative rate for each category.");
    }
    categoryRates[categoryIndex] = rate;
}

void UIVarsSingleDataModel::SetTTRatio(double x)
{
    if (x < 0.5)
    {
        throw data_error("The TT Ratio must be greater than 0.5.");
    }
    ttRatio=x;
}

void UIVarsSingleDataModel::SetCalcFreqsFromData(bool set)
{
    if(set == false && GetCalcFreqsFromData() == true)
    {
        // setting calc freqs from true to false, so
        // we want to start with the calculated values
        // in the local variables
        if(m_regionIndex >= 0)     // doesn't make sense to do
            // this for the global model
        {
            DoubleVec1d freqs =
                FrequenciesFromData(m_regionIndex,m_locusIndex,GetDataModelType());
            baseFrequency_A = freqs[0];
            baseFrequency_C = freqs[1];
            baseFrequency_G = freqs[2];
            baseFrequency_T = freqs[3];
        }

    }
    assert(set==false || GetDataModelType() != GTR);    // should be checked in caller
    doCalcFreqsFromData = set;
}

void UIVarsSingleDataModel::SetFrequencyA(double freq)
{
    if (freq<0)
    {
        throw data_error("Base frequencies must be greater than zero");
    }
    SetCalcFreqsFromData(false);
    baseFrequency_A = freq;
}

void UIVarsSingleDataModel::SetFrequencyC(double freq)
{
    if (freq<0)
    {
        throw data_error("Base frequencies must be greater than zero");
    }
    SetCalcFreqsFromData(false);
    baseFrequency_C = freq;
}

void UIVarsSingleDataModel::SetFrequencyG(double freq)
{
    if (freq<0)
    {
        throw data_error("Base frequencies must be greater than zero");
    }
    SetCalcFreqsFromData(false);
    baseFrequency_G = freq;
}

void UIVarsSingleDataModel::SetFrequencyT(double freq)
{
    if (freq<0)
    {
        throw data_error("Base frequencies must be greater than zero");
    }
    SetCalcFreqsFromData(false);
    baseFrequency_T = freq;
}

void UIVarsSingleDataModel::SetGTR_AT(double x)
{
    if (x <= 0)
    {
        throw data_error("GTR rates must be greater than zero");
    }
    gtr_AT = x;
}

void UIVarsSingleDataModel::SetGTR_AC(double x)
{
    if (x <= 0)
    {
        throw data_error("GTR rates must be greater than zero");
    }
    gtr_AC = x;
}
void UIVarsSingleDataModel::SetGTR_AG(double x)
{
    if (x <= 0)
    {
        throw data_error("GTR rates must be greater than zero");
    }
    gtr_AG = x;
}
void UIVarsSingleDataModel::SetGTR_CG(double x)
{
    if (x <= 0)
    {
        throw data_error("GTR rates must be greater than zero");
    }
    gtr_CG = x;
}
void UIVarsSingleDataModel::SetGTR_CT(double x)
{
    if (x <= 0)
    {
        throw data_error("GTR rates must be greater than zero");
    }
    gtr_CT = x;
}
void UIVarsSingleDataModel::SetGTR_GT(double x)
{
    if (x <= 0)
    {
        throw data_error("GTR rates must be greater than zero");
    }
    gtr_GT = x;
}

void UIVarsSingleDataModel::SetRelativeMuRate(double x)
{
    if (x <= 0)
    {
        throw data_error("The relative mutation rate must be greater than zero");
    }
    m_relmurate = x;
}

void UIVarsSingleDataModel::SetAlpha(double x)
{
    if (x < 0 || x > 1)
    {
        throw data_error("The multi-step:single-step ratio for a MixedKS model must be between zero and one.");
    }
    alpha = x;
}

bool UIVarsSingleDataModel::IdenticalCategoryRates() const
{
    DoubleVec1d testRates = categoryRates;
    std::sort(testRates.begin(), testRates.end());
    for (unsigned long int i = 1; i < testRates.size(); i++)
    {
        if (fabs(testRates[i] - testRates[i-1]) < EPSILON)
        {
            return true;
        }
    }
    return false;
}

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

void UIVarsDataModels::checkRegionIdGlobalNotAllowed(UIRegId regionId) const
{
    if (regionId.GetRegion() < 0)
    {
        assert(false);
        throw implementation_error("Region index < 0");
    }
    if (regionId.GetRegion() >= GetConstUIVars().datapackplus.GetNumRegions())
    {
        assert(false);
        throw implementation_error("Region index >= number of regions");
    }
    if (regionId.GetLocus() >= GetConstUIVars().datapackplus.GetNumLoci(regionId.GetRegion()))
    {
        assert(false);
        throw implementation_error("Locus index >= number of regions");
    }
}

void UIVarsDataModels::checkRegionIdGlobalAllowed(UIRegId regionId) const
{
    if (regionId.GetRegion() != uiconst::GLOBAL_ID)
    {
        checkRegionIdGlobalNotAllowed(regionId);
    }
}

UIVarsSingleDataModel & UIVarsDataModels::getRegionModelToAlter(UIRegId regionId)
{
    checkRegionIdGlobalAllowed(regionId);
    if(regionId.GetRegion() == uiconst::GLOBAL_ID)
    {
        switch(regionId.GetDataType())
        {
            case dtype_DNA:
            case dtype_SNP:
                return m_globalModelNucleotide;
            case dtype_msat:
                return m_globalModelMsat;
            case dtype_kallele:
                return m_globalModelKAllele;
        }
    }
    // if we're here, then regionId is not specifying the global
    // data model. However, that region may currently be using
    // the global data model. If that is the case, we have to
    // initialize the data model for regionId to a copy of the
    // global data model and edit from there. That's exactly
    // what SetUseGlobalModel does when setting from true to
    // false. (If setting from false to false, we already have
    // the correct datamodel in individualModels[regionId])
    // ewalkup 8-19-2004
    SetUseGlobalModel(false,regionId);
    return m_individualModels[regionId.GetRegion()][regionId.GetLocus()];
}

bool UIVarsDataModels::GetUseGlobalModel(UIRegId regionId) const
{
    checkRegionIdGlobalNotAllowed(regionId);
    return m_useGlobalModel[regionId.GetRegion()][regionId.GetLocus()];
}

bool UIVarsDataModels::IdenticalCategoryRates(UIRegId regionId) const
{
    checkRegionIdGlobalAllowed(regionId);
    return GetRegionModel(regionId).IdenticalCategoryRates();
}

void UIVarsDataModels::SetUseGlobalModel(bool useGlobal, UIRegId regionId)
{
    checkRegionIdGlobalNotAllowed(regionId);
    if(useGlobal == GetUseGlobalModel(regionId))
    {
        return;
        // we don't want to do anything if we're already
        // set to this value
    }
    m_useGlobalModel[regionId.GetRegion()][regionId.GetLocus()] = useGlobal;
    if(useGlobal == false)
        // we'll be using a local model for this regionId,
        // but we just had the global model.
        // We need a model to use!!
        // So, start with a copy of the global model
        // (One might argue that we should re-use whatever
        // we've got in the existing local data model, but
        // that precludes the user from setting to the
        // global and then tweaking just a little for the
        // current regionId.)   ewalkup 8-19-2004
    {
        //We need to save the relative mu rate from the initial setup.
        UIVarsSingleDataModel newmodel(GetGlobalModel(regionId.GetDataType()), regionId);
        m_individualModels[regionId.GetRegion()][regionId.GetLocus()] = newmodel;
    }
}

UIVarsDataModels::UIVarsDataModels(UIVars * myUIVars, long int nregions, long int maxLoci)
    : UIVarsComponent(myUIVars),
      m_globalModelNucleotide(UIRegId(dtype_DNA)),
      m_globalModelMsat(UIRegId(dtype_msat)),
      m_globalModelKAllele(UIRegId(dtype_kallele)),
      m_useGlobalModel(nregions,deque<bool>(maxLoci,true)),
      m_individualModels(nregions)
{
    //Requires datapackplus to be set up already in the uivars.
    for(size_t index = 0; index < m_individualModels.size(); index++)
    {
        m_individualModels[index] = vector<UIVarsSingleDataModel>();
    }

    for(long int regionIndex = 0; regionIndex < nregions; regionIndex++)
    {
        long int numLoci = GetConstUIVars().datapackplus.GetNumLoci(regionIndex);
        for(long int locusIndex = 0; locusIndex < numLoci; locusIndex++)
        {
            UIRegId regId(regionIndex, locusIndex, GetConstUIVars());
            UIVarsSingleDataModel thisModel(regId);
            m_individualModels[regionIndex].push_back(thisModel);
        }
    }
}

UIVarsDataModels::UIVarsDataModels(UIVars * myUIVars, const UIVarsDataModels& dataModels)
    : UIVarsComponent(myUIVars),
      m_globalModelNucleotide(dataModels.m_globalModelNucleotide),
      m_globalModelMsat(dataModels.m_globalModelMsat),
      m_globalModelKAllele(dataModels.m_globalModelKAllele),
      m_useGlobalModel(dataModels.m_useGlobalModel)
{
    m_individualModels.clear();
    for(size_t index = 0; index < dataModels.m_individualModels.size(); index++)
    {
        m_individualModels.push_back(vector<UIVarsSingleDataModel>());
        for(size_t index2 = 0; index2 < dataModels.m_individualModels[index].size(); index2++)
        {
            m_individualModels[index].push_back(UIVarsSingleDataModel(dataModels.m_individualModels[index][index2]));
        }
    }
}

//We need to call Initialize because some of the information we need is in
// uivars.datapackplus, which isn't set up until after the construction of
// UIVars.  Since we're also constructed there, we have to wait.
void UIVarsDataModels::Initialize()
{
}

UIVarsDataModels::~UIVarsDataModels()
{
}

const UIVarsSingleDataModel & UIVarsDataModels::GetRegionModel(UIRegId regionId) const
{
    checkRegionIdGlobalAllowed(regionId);
    if (regionId.GetRegion() == uiconst::GLOBAL_ID)
    {
        return GetGlobalModel(regionId.GetDataType());
    }
    assert(regionId.GetLocus() != uiconst::GLOBAL_ID);
    //LS NOTE:  If we want to store region-wide default data models instead of
    // only global data models, we would get at them here and GetLocus() would
    // indeed return GLOBAL_ID.  But this system is not yet in place.
    if(GetUseGlobalModel(regionId))
    {
        return GetGlobalModel(regionId.GetDataType());
    }
    return m_individualModels[regionId.GetRegion()][regionId.GetLocus()];
}

const UIVarsSingleDataModel & UIVarsDataModels::GetGlobalModel(data_type datatype) const
{
    switch (datatype)
    {
        case dtype_DNA:
        case dtype_SNP:
            return m_globalModelNucleotide;
        case dtype_msat:
            return m_globalModelMsat;
        case dtype_kallele:
            return m_globalModelKAllele;
    }
    assert(false); //uncaught data type
    return m_globalModelNucleotide;
}

data_type UIVarsDataModels::GetDataType(UIRegId regionId) const
{
    return regionId.GetDataType();
}

model_type  UIVarsDataModels::GetDataModelType(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetDataModelType();
}

bool        UIVarsDataModels::GetNormalization(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetNormalization();
}

double      UIVarsDataModels::GetAutoCorrelation(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetAutoCorrelation();
}

long int    UIVarsDataModels::GetNumCategories(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetNumCategories();
}

double UIVarsDataModels::GetCategoryRate(UIRegId regionId, long int categoryIndex) const
{
    return GetRegionModel(regionId).GetCategoryRate(categoryIndex);
}

DoubleVec1d UIVarsDataModels::GetCategoryRates(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetCategoryRates();
}

DoubleVec1d UIVarsDataModels::GetCategoryProbabilities(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetCategoryProbabilities();
}

double UIVarsDataModels::GetCategoryProbability(UIRegId regionId, long int categoryIndex) const
{
    return GetRegionModel(regionId).GetCategoryProbability(categoryIndex);
}

double      UIVarsDataModels::GetTTRatio(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetTTRatio();
}

bool UIVarsDataModels::GetCalcFreqsFromData(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetCalcFreqsFromData();
}

bool UIVarsDataModels::returnLocalCalcedFreqWithGlobalModel(UIRegId regionId) const
{
    // When a locus is assigned a global data model and that global
    // data model says to use calculated frequencies for nucleotides,
    // we need to get those calculated values on a per-locus basis.
    // EWFIX.P5 LOCUS -- currently we're doing this per region
    if(regionId.GetRegion() != uiconst::GLOBAL_ID)
    {
        if ( GetUseGlobalModel(regionId)         &&
             GetGlobalModel(regionId.GetDataType()).GetCalcFreqsFromData() )
        {
            return true;
        }
    }
    return false;
}

double      UIVarsDataModels::GetFrequencyA(UIRegId regionId) const
{
    if(returnLocalCalcedFreqWithGlobalModel(regionId))
    {
        // not terribly efficient, but maintaining logic to cache
        // the values was very difficult
        return FrequenciesFromData(regionId.GetRegion(),
                                   regionId.GetLocus(),
                                   GetDataModelType(regionId))[0];
    }
    else
    {
        return GetRegionModel(regionId).GetFrequencyA();
    }
}

double      UIVarsDataModels::GetFrequencyC(UIRegId regionId) const
{
    if(returnLocalCalcedFreqWithGlobalModel(regionId))
    {
        // not terribly efficient, but maintaining logic to cache
        // the values was very difficult
        return FrequenciesFromData(regionId.GetRegion(),
                                   regionId.GetLocus(),
                                   GetDataModelType(regionId))[1];
    }
    else
    {
        return GetRegionModel(regionId).GetFrequencyC();
    }
}

double      UIVarsDataModels::GetFrequencyG(UIRegId regionId) const
{
    if(returnLocalCalcedFreqWithGlobalModel(regionId))
    {
        // not terribly efficient, but maintaining logic to cache
        // the values was very difficult
        return FrequenciesFromData(regionId.GetRegion(),
                                   regionId.GetLocus(),
                                   GetDataModelType(regionId))[2];
    }
    else
    {
        return GetRegionModel(regionId).GetFrequencyG();
    }
}

double      UIVarsDataModels::GetFrequencyT(UIRegId regionId) const
{
    if(returnLocalCalcedFreqWithGlobalModel(regionId))
    {
        // not terribly efficient, but maintaining logic to cache
        // the values was very difficult
        return FrequenciesFromData(regionId.GetRegion(),
                                   regionId.GetLocus(),
                                   GetDataModelType(regionId))[3];
    }
    else
    {
        return GetRegionModel(regionId).GetFrequencyT();
    }
}

double      UIVarsDataModels::GetGTR_AC(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetGTR_AC();
}

double      UIVarsDataModels::GetGTR_AG(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetGTR_AG();
}

double      UIVarsDataModels::GetGTR_AT(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetGTR_AT();
}

double      UIVarsDataModels::GetGTR_CG(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetGTR_CG();
}

double      UIVarsDataModels::GetGTR_CT(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetGTR_CT();
}

double      UIVarsDataModels::GetGTR_GT(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetGTR_GT();
}

double UIVarsDataModels::GetAlpha(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetAlpha();
}

double UIVarsDataModels::GetRelativeMuRate(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetRelativeMuRate();
}

DoubleVec2d UIVarsDataModels::GetRelativeMuRates() const
{
    const UIVars& vars(GetConstUIVars());
    long int reg, nregs(vars.datapackplus.GetNumRegions());
    DoubleVec2d relmurates(nregs);
    for(reg = 0; reg < nregs; ++reg)
    {
        long int loc, nloci(vars.datapackplus.GetNumLoci(reg));
        DoubleVec1d regionalmurates(nloci);
        for(loc = 0; loc < nloci; ++loc)
        {
            UIRegId regionId(reg,loc,vars);
            regionalmurates[loc] = GetRegionModel(regionId).GetRelativeMuRate();
        }
        relmurates[reg] = regionalmurates;
    }
    return relmurates;
}

bool UIVarsDataModels::GetOptimizeAlpha(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetOptimizeAlpha();
}

double UIVarsDataModels::GetPerBaseErrorRate(UIRegId regionId) const
{
    return GetRegionModel(regionId).GetPerBaseErrorRate();
}

StringVec1d
UIVarsDataModels::GetDataModelReport(UIRegId regionId) const
{
    StringVec1d report;
    string line;
    // model type identification
    //////////////////////////////////////////////////////
    line =  reportstr::header + ToString(GetDataModelType(regionId));
    report.push_back(line);
    // add items common to all models
    //////////////////////////////////////////////////////
    if(GetNumCategories(regionId) > 1)
    {
        line =  ToString(GetNumCategories(regionId)) + reportstr::categoryHeader;
        line += ToString(GetAutoCorrelation(regionId));
        report.push_back(line);
        DoubleVec1d catRates = GetCategoryRates(regionId);
        DoubleVec1d catProbs = GetCategoryProbabilities(regionId);
        for(long int cat = 0; cat < GetNumCategories(regionId); cat++)
        {
            line =    reportstr::categoryRate
                + " "
                + ToString(catRates[cat])
                + " "
                + reportstr::categoryRelativeProb
                + " "
                + ToString(catProbs[cat]);
            report.push_back(line);
        }
    }
    line = reportstr::relativeMutationRate + " " +
        ToString(GetRelativeMuRate(regionId));
    report.push_back(line);
    // add items common to nuc models
    //////////////////////////////////////////////////////
    if(GetDataModelType(regionId) == F84 || GetDataModelType(regionId) == GTR)
    {
        line =  reportstr::baseFreqs;
        line += ToString(GetFrequencyA(regionId)) + reportstr::baseFreqSeparator;
        line += ToString(GetFrequencyC(regionId)) + reportstr::baseFreqSeparator;
        line += ToString(GetFrequencyG(regionId)) + reportstr::baseFreqSeparator;
        line += ToString(GetFrequencyT(regionId));
        report.push_back(line);
    }
    // add items for F84 models
    //////////////////////////////////////////////////////
    if(GetDataModelType(regionId) == F84)
    {
        line = reportstr::TTratio + ToString(GetTTRatio(regionId));
        report.push_back(line);
        line = reportstr::perBaseErrorRate + ToString(GetPerBaseErrorRate(regionId));
        report.push_back(line);
    }
    // add items for GTR models
    //////////////////////////////////////////////////////
    if(GetDataModelType(regionId) == GTR)
    {
        line = reportstr::GTRRates;
        report.push_back(line);
        line =  reportstr::GTRRatesFromA;
        line += ToString(GetGTR_AC(regionId)) + reportstr::GTRRateSeparator;
        line += ToString(GetGTR_AG(regionId)) + reportstr::GTRRateSeparator;
        line += ToString(GetGTR_AT(regionId));
        report.push_back(line);
        line =  reportstr::GTRRatesFromC;
        line += ToString(GetGTR_CG(regionId)) + reportstr::GTRRateSeparator;
        line += ToString(GetGTR_CT(regionId));
        report.push_back(line);
        line =  reportstr::GTRRatesFromG;
        line += ToString(GetGTR_GT(regionId));
        report.push_back(line);
        line = reportstr::perBaseErrorRate + ToString(GetPerBaseErrorRate(regionId));
        report.push_back(line);
    }
    // add items for Stepwise models
    //////////////////////////////////////////////////////
    if(GetDataModelType(regionId) == Stepwise
       || GetDataModelType(regionId) == KAllele
       || GetDataModelType(regionId) == MixedKS)
    {
        line =  reportstr::numberOfBins + ToString(defaults::bins);
        // erynes BUGBUG -- hard coded for some models, set for
        // others? Need to make this available from
        // UIVarsDataModels
        report.push_back(line);
    }
    // add items for Brownian models
    //////////////////////////////////////////////////////
    if(GetDataModelType(regionId) == Brownian)
    {
        line =  reportstr::brownian;
        report.push_back(line);
    }
    // add items for MixedKS models
    //////////////////////////////////////////////////////
    if(GetDataModelType(regionId) == MixedKS)
    {
        line =  reportstr::alpha + ToString(GetAlpha(regionId));
        report.push_back(line);
        if(GetOptimizeAlpha(regionId))
        {
            line = reportstr::optimizeAlpha;
            report.push_back(line);
        }
    }
    return report;
}

ModelTypeVec1d
UIVarsDataModels::GetLegalDataModels(UIRegId regionId) const
{
    ModelTypeVec1d legalModels = defaults::allDataModels();
    legalModels.erase(
        std::remove_if(legalModels.begin(),legalModels.end(),
                       ModelIsIllegalForDataType(regionId.GetDataType())),
        legalModels.end());
    return legalModels;
}

void UIVarsDataModels::SetDataModelType(model_type x, UIRegId regionId)
{
    getRegionModelToAlter(regionId).SetDataModelType(x);
}

void UIVarsDataModels::SetNormalization(bool x, UIRegId regionId)
{
    getRegionModelToAlter(regionId).SetNormalization(x);
}

void UIVarsDataModels::SetAutoCorrelation(double x, UIRegId regionId)
{
    getRegionModelToAlter(regionId).SetAutoCorrelation(x);
}

void UIVarsDataModels::SetNumCategories(long int numCats, UIRegId regionId)
{
    getRegionModelToAlter(regionId).SetNumCategories(numCats);
}

void UIVarsDataModels::SetCategoryRate(double x, UIRegId regionId, long int categoryIndex)
{
    getRegionModelToAlter(regionId).SetCategoryRate(x,categoryIndex);
}

void UIVarsDataModels::SetCategoryProbability(double x, UIRegId regionId, long int categoryIndex)
{
    getRegionModelToAlter(regionId).SetCategoryProbability(x,categoryIndex);
}

void UIVarsDataModels::SetTTRatio(double x, UIRegId regionId)
{
    if (x == 0.5)
    {
        x = 0.500001;
        GetConstUIVars().GetUI()->AddWarning("Using 0.500001 for the TT Ratio instead of 0.5, since "
                                             "LAMARC's algorithm divides by zero if the TT Ratio is exactly 0.5.");
    }
    getRegionModelToAlter(regionId).SetTTRatio(x);
}

void UIVarsDataModels::SetCalcFreqsFromData(bool x, UIRegId regionId)
{
    if (x==true && GetDataModelType(regionId) == GTR)
    {
        GetConstUIVars().GetUI()->AddWarning("Warning:  You may not calculate base frequencies from the data in the GTR model -- "
                                             "the base frequencies should arise from the same program that gave you the GTR rates.");
        return;
    };
    getRegionModelToAlter(regionId).SetCalcFreqsFromData(x);
}

double UIVarsDataModels::adjustZeroFrequency(double freq)
{
    if (freq == 0)
    {
        GetConstUIVars().GetUI()->AddWarning("Base frequencies must be greater than zero--setting this frequency to 0.00001");
        return defaults::minLegalFrequency;
    }
    return freq;
}

void UIVarsDataModels::SetFrequencyA(double x, UIRegId regionId)
{
    getRegionModelToAlter(regionId).SetFrequencyA(adjustZeroFrequency(x));
}

void UIVarsDataModels::SetFrequencyC(double x, UIRegId regionId)
{
    getRegionModelToAlter(regionId).SetFrequencyC(adjustZeroFrequency(x));
}

void UIVarsDataModels::SetFrequencyG(double x, UIRegId regionId)
{
    getRegionModelToAlter(regionId).SetFrequencyG(adjustZeroFrequency(x));
}

void UIVarsDataModels::SetFrequencyT(double x, UIRegId regionId)
{
    getRegionModelToAlter(regionId).SetFrequencyT(adjustZeroFrequency(x));
}

void UIVarsDataModels::SetGTR_AC(double x, UIRegId regionId)
{
    getRegionModelToAlter(regionId).SetGTR_AC(x);
}

void UIVarsDataModels::SetGTR_AG(double x, UIRegId regionId)
{
    getRegionModelToAlter(regionId).SetGTR_AG(x);
}

void UIVarsDataModels::SetGTR_AT(double x, UIRegId regionId)
{
    getRegionModelToAlter(regionId).SetGTR_AT(x);
}

void UIVarsDataModels::SetGTR_CG(double x, UIRegId regionId)
{
    getRegionModelToAlter(regionId).SetGTR_CG(x);
}

void UIVarsDataModels::SetGTR_CT(double x, UIRegId regionId)
{
    getRegionModelToAlter(regionId).SetGTR_CT(x);
}

void UIVarsDataModels::SetGTR_GT(double x, UIRegId regionId)
{
    getRegionModelToAlter(regionId).SetGTR_GT(x);
}

void UIVarsDataModels::SetAlpha(double x, UIRegId regionId)
{
    getRegionModelToAlter(regionId).SetAlpha(x);
}

void UIVarsDataModels::SetRelativeMuRate(double x, UIRegId regionId)
{
    if (x <= 0)
    {
        throw data_error("The relative mutation rate must be greater than zero.");
    }
    getRegionModelToAlter(regionId).SetRelativeMuRate(x);
    GetUIVars().forces.FillCalculatedStartValues();
}

void UIVarsDataModels::SetOptimizeAlpha(bool x, UIRegId regionId)
{
    getRegionModelToAlter(regionId).SetOptimizeAlpha(x);
}

void UIVarsDataModels::SetPerBaseErrorRate(double x, UIRegId regionId)
{
    if (x < 0)
    {
        throw data_error("The per base error rate must be non-negative");
    }
    if (x > 0.1)                        // EWFIX -- what is the limit
    {
        throw data_error("The per base error rate must less than or equal to 0.1");
    }
    getRegionModelToAlter(regionId).SetPerBaseErrorRate(x);
}

void UIVarsDataModels::SetAllRegionsToGlobalModel()
{
    vector < deque<bool> >::iterator i;
    for(i = m_useGlobalModel.begin(); i != m_useGlobalModel.end(); i++)
    {
        deque<bool>::iterator j;
        for(j = i->begin(); j != i->end(); j++)
        {
            (*j) = true;
        }
    }
}

UIVarsDataModels::ModelIsIllegalForDataType::ModelIsIllegalForDataType(
    data_type dtype)
    :
    m_dtype(dtype)
{
}

UIVarsDataModels::ModelIsIllegalForDataType::~ModelIsIllegalForDataType()
{
}

bool
UIVarsDataModels::ModelIsIllegalForDataType::operator()(model_type mtype) const
{
    return !(ModelTypeAcceptsDataType(mtype,m_dtype));
}

//____________________________________________________________________________________
