#pragma once

#include "conditionals.hpp"

#include <algorithm>
#include <vector>
#include "conditionals.hpp"
#include "datatype.hpp"
#include "qmatrix.hpp"
#if defined(POLGSS)
#include "partition.hpp"
#endif
#include "asrv.hpp"
#include "libhmsbeagle/beagle.h"
#include <boost/format.hpp>
#include <boost/math/distributions/gamma.hpp>
#include <Eigen/Dense>

namespace lorad {

    class Likelihood;

    class Model {

        friend class Likelihood;

        public:
            typedef std::vector<ASRV::SharedPtr>      asrv_vect_t;
            typedef std::vector<QMatrix::SharedPtr>   qmat_vect_t;
            typedef std::vector<unsigned>             subset_sizes_t;
            typedef std::vector<DataType>             subset_datatype_t;
            typedef std::vector<double>               subset_relrate_vect_t;
            typedef std::vector<QMatrix::SharedPtr>   state_freq_params_t;
            typedef std::vector<QMatrix::SharedPtr>   exchangeability_params_t;
            typedef std::vector<QMatrix::SharedPtr>   omega_params_t;
#if defined(HOLDER_ETAL_PRIOR)
            typedef std::vector<ASRV::SharedPtr>      shape_params_t;
#else
            typedef std::vector<ASRV::SharedPtr>      ratevar_params_t;
#endif
            typedef std::vector<ASRV::SharedPtr>      pinvar_params_t;
            typedef boost::shared_ptr<Model>          SharedPtr;

                                        Model();
                                        ~Model();

            void                        activate();
            void                        inactivate();

            std::string                 describeModel();

            void                        setSubsetDataTypes(const subset_datatype_t & datatype_vect);

#if defined(HOLDER_ETAL_PRIOR)
            void                        setSubsetShape(ASRV::shape_ptr_t shape, unsigned subset, bool fixed);
#else
            void                        setSubsetRateVar(ASRV::ratevar_ptr_t ratevar, unsigned subset, bool fixed);
#endif
            void                        setSubsetPinvar(ASRV::pinvar_ptr_t pinvar, unsigned subset, bool fixed);
            void                        setSubsetExchangeabilities(QMatrix::freq_xchg_ptr_t exchangeabilities, unsigned subset, bool fixed);
            void                        setSubsetStateFreqs(QMatrix::freq_xchg_ptr_t state_frequencies, unsigned subset, bool fixed);
            void                        setSubsetOmega(QMatrix::omega_ptr_t omega, unsigned subset, bool fixed);

            void                        setSubsetRelRates(subset_relrate_vect_t & relrates, bool fixed);
            subset_relrate_vect_t &     getSubsetRelRates();
            bool                        isFixedSubsetRelRates() const;
            double                      calcNormalizingConstantForSubsetRelRates() const;

#if defined(POLGSS)
            void                        setSubsetStateFreqRefDistParams(QMatrix::freq_xchg_ptr_t freq_refdist_params, unsigned subset);
            void                        setSubsetExchangeabilitiesRefDistParams(QMatrix::freq_xchg_ptr_t xchg_refdist_params, unsigned subset);
            void                        setSubsetPinvarRefDistParams(ASRV::pinvar_refdist_ptr_t pinvar_refdist_params, unsigned subset);
#if defined(HOLDER_ETAL_PRIOR)
            void                        setSubsetShapeRefDistParams(ASRV::shape_refdist_ptr_t shape_refdist_params, unsigned subset);
            void                        setEdgeLenRefDistParams(std::vector<double> & edgelen_refdist);
            std::vector<double>         getEdgeLenRefDistParamsVect() const;
#else
            void                        setSubsetRateVarRefDistParams(ASRV::ratevar_refdist_ptr_t ratevar_refdist_params, unsigned subset);
            void                        setTreeLengthRefDistParams(std::vector<double> & treelen_refdist);
            std::vector<double>         getTreeLengthRefDistParamsVect() const;
            void                        setEdgeProportionsRefDistParams(std::vector<double> & edgeprops_refdist);
            std::vector<double>         getEdgeProportionsRefDistParamsVect() const;
#endif
            void                        sampleParams();
            std::string                 saveReferenceDistributions(Partition::SharedPtr partition);
            void                        setSubsetRelRatesRefDistParams(std::vector<double> refdist_params);
            std::vector<double>         getSubsetRelRatesRefDistParamsVect();
            std::string                 calcBetaRefDist(std::string title, std::string subset_name, std::vector<double> & vect);
            std::string                 calcGammaRefDist(std::string title, std::string subset_name, std::vector<double> & vect);
            std::string                 calcDirichletRefDist(std::string title, std::string subset_name, std::vector< QMatrix::freq_xchg_t > & vect, bool relrates = false);
#endif

            void                        setTreeIndex(unsigned i, bool fixed);
            unsigned                    getTreeIndex() const;
            bool                        isFixedTree() const;

            void                        setTopologyPriorOptions(bool allow_polytomies, bool resclass, double C);
            bool                        isResolutionClassTopologyPrior() const;
            double                      getTopologyPriorC() const;
            bool                        isAllowPolytomies() const;

            unsigned                    getNumSubsets() const;
            unsigned                    getNumSites() const;
            unsigned                    getSubsetNumSites(unsigned subset) const;
            const QMatrix &             getQMatrix(unsigned subset) const;
            const ASRV &                getASRV(unsigned subset) const;

            void                        setSubsetIsInvarModel(bool is_invar, unsigned subset);
            bool                        getSubsetIsInvarModel(unsigned subset) const;

            void                        setSubsetSizes(const subset_sizes_t nsites_vect);
            subset_sizes_t &            getSubsetSizes();

            void                        setSubsetNumPatterns(const subset_sizes_t npatterns_vect);
            unsigned                    getSubsetNumPatterns(unsigned subset) const;

            void                        setSubsetNumCateg(unsigned ncateg, unsigned subset);
            unsigned                    getSubsetNumCateg(unsigned subset) const;

            state_freq_params_t &       getStateFreqParams();
//TODO: why is this commented out?
//#if defined(POLGSS)
//            void                        setStateFreqRefDistParams(std::vector<double> refdist_params);
//            std::vector<double>         getStateFreqRefDistParams();
//#endif

            exchangeability_params_t &  getExchangeabilityParams();
//#if defined(POLGSS)
//            void                        setExchangeabilityRefDistParams(std::vector<double> refdist_params);
//            std::vector<double>         getExchangeabilityRefDistParams();
//#endif

            omega_params_t &            getOmegaParams();
//#if defined(POLGSS)
//            void                        setOmegaRefDistParams(std::vector<double> refdist_params);
//            std::vector<double>         getOmegaRefDistParams();
//#endif

#if defined(HOLDER_ETAL_PRIOR)
            shape_params_t &            getShapeParams();
//#if defined(POLGSS)
//            void                        setShapeRefDistParams(std::vector<double> refdist_params);
//            std::vector<double>         getShapeRefDistParams();
//#endif
#else
            ratevar_params_t &          getRateVarParams();
//#if defined(POLGSS)
//            void                        setRateVarRefDistParams(std::vector<double> refdist_params);
//            std::vector<double>         getRateVarRefDistParams();
//#endif
#endif

            pinvar_params_t &           getPinvarParams();
//#if defined(POLGSS)
//            void                        setPinvarRefDistParams(std::vector<double> refdist_params);
//            std::vector<double>         getPinvarRefDistParams();
//#endif

            int                         setBeagleEigenDecomposition(int beagle_instance, unsigned subset, unsigned instance_subset);
            int                         setBeagleStateFrequencies(int beagle_instance, unsigned subset, unsigned instance_subset);
            int                         setBeagleAmongSiteRateVariationRates(int beagle_instance, unsigned subset, unsigned instance_subset);
            int                         setBeagleAmongSiteRateVariationProbs(int beagle_instance, unsigned subset, unsigned instance_subset);

            std::string                 paramNamesAsString(std::string sep) const;
            std::string                 paramValuesAsString(std::string sep) const;

            void                        saveParamNames(std::vector<std::string> & param_name_vect) const;
            double                      logRatioTransform(std::vector<double> & param_vect) const;
            double                      logRatioUntransform(std::vector<double> & param_vect) const;
            double                      logTransformParameters(std::vector<double> & param_vect) const;
            double                      setParametersFromLogTransformed(Eigen::VectorXd & param_vect, unsigned first, unsigned nparams);

        private:

            void                        clear();

            unsigned                    _num_subsets;
            unsigned                    _num_sites;
            subset_sizes_t              _subset_sizes;
            subset_sizes_t              _subset_npatterns;
            subset_datatype_t           _subset_datatypes;
            qmat_vect_t                 _qmatrix;
            asrv_vect_t                 _asrv;

            unsigned                    _tree_index;
            bool                        _tree_fixed;

            bool                        _allow_polytomies;
            bool                        _resolution_class_prior;
            double                      _topo_prior_C;

            bool                        _subset_relrates_fixed;
            subset_relrate_vect_t       _subset_relrates;

            state_freq_params_t         _state_freq_params;
            exchangeability_params_t    _exchangeability_params;
            omega_params_t              _omega_params;
#if defined(HOLDER_ETAL_PRIOR)
            shape_params_t              _shape_params;
#else
            ratevar_params_t            _ratevar_params;
#endif
            pinvar_params_t             _pinvar_params;

#if defined(POLGSS)
            std::vector<double>         _state_freq_refdist_params;
            std::vector<double>         _exchangeability_refdist_params;
            std::vector<double>         _omega_refdist_params;
            std::vector<double>         _pinvar_refdist_params;
            std::vector<double>         _subset_relrates_refdist_params;
            std::vector<double>         _treelen_refdist_params;
#if defined(HOLDER_ETAL_PRIOR)
            std::vector<double>         _shape_refdist_params;
            std::vector<double>         _edgelen_refdist_params;
#else
            std::vector<double>         _ratevar_refdist_params;
            std::vector<double>         _edgeprops_refdist_params;
#endif

            std::vector< QMatrix::freq_xchg_t>                      _sampled_subset_relrates;
            std::map<unsigned, std::vector<QMatrix::freq_xchg_t> >  _sampled_exchangeabilities;
            std::map<unsigned, std::vector<QMatrix::freq_xchg_t> >  _sampled_state_freqs;
            std::map<unsigned, std::vector<double> >                _sampled_omegas;
            std::map<unsigned, std::vector<double> >                _sampled_pinvars;
#if defined(HOLDER_ETAL_PRIOR)
            std::map<unsigned, std::vector<double> >                _sampled_shapes;
#else
            std::map<unsigned, std::vector<double> >                _sampled_ratevars;
#endif
#endif
        };

    inline Model::Model() {
        clear();
    }

    inline Model::~Model() {
    }

    inline void Model::clear() {
#if defined(POLGSS)
        _state_freq_refdist_params.clear();
        _exchangeability_refdist_params.clear();
        _omega_refdist_params.clear();
        _pinvar_refdist_params.clear();
        _subset_relrates_refdist_params.clear();
        _treelen_refdist_params.clear();
#if defined(HOLDER_ETAL_PRIOR)
        _shape_refdist_params.clear();
        _edgelen_refdist_params.clear();
#else
        _ratevar_refdist_params.clear();
        _edgeprops_refdist_params.clear();
#endif
#endif
        _num_subsets = 0;
        _num_sites = 0;
        _tree_index = 0;
        _tree_fixed = false;
        _allow_polytomies = true;
        _resolution_class_prior = true;
        _topo_prior_C = 1.0;
        _subset_relrates_fixed = false;
        _subset_relrates.clear();
        _subset_sizes.clear();
        _subset_npatterns.clear();
        _subset_datatypes.clear();
        _qmatrix.clear();
        _asrv.clear();
    }

    inline std::string Model::describeModel() {
        // Creates summary such as following and returns as a string:
        //
        // Partition information:
        //
        //          data subset           1           2           3
        //    -----------------------------------------------------
        //           num. sites          20          20          20
        //        num. patterns           7           5          17
        //          num. states           4           4           4
        //      rate categories           4           1           4
        //
        // Parameter linkage:
        //
        //          data subset           1           2           3
        //    -----------------------------------------------------
        //          state freqs           1           1           1
        //    exchangeabilities           1           1           2
        //        rate variance           1           2           3
        //               pinvar           1           2           -

        // Start with empty parameter vectors
        _state_freq_params.clear();
        _exchangeability_params.clear();
        _omega_params.clear();
#if defined(HOLDER_ETAL_PRIOR)
        _shape_params.clear();
#else
        _ratevar_params.clear();
#endif
        _pinvar_params.clear();

        // Sets used to determine which parameters are linked across subsets
        std::set<double *> freqset;
        std::set<double *> xchgset;
        std::set<double *> omegaset;
#if defined(HOLDER_ETAL_PRIOR)
        std::set<double *> shapeset;
#else
        std::set<double *> ratevarset;
#endif
        std::set<double *> pinvarset;
        std::set<double *> relrateset;

        // Vectors of pointers to distinct parameters
        std::vector<double *> unique_freq;
        std::vector<double *> unique_xchg;
        std::vector<double *> unique_omega;
#if defined(HOLDER_ETAL_PRIOR)
        std::vector<double *> unique_shape;
#else
        std::vector<double *> unique_ratevar;
#endif
        std::vector<double *> unique_pinvar;
        std::vector<double *> unique_relrate;

        // Map for storing strings that will contain the information for each row
        std::map<std::string, std::string> ss = {
            {"subset",    ""},
            {"dashes",    ""},
            {"freqs",     ""},
            {"xchg",      ""},
            {"omega",     ""},
            {"ratevar",   ""},
            {"pinvar",    ""},
            {"ncateg",    ""},
            {"nsites",    ""},
            {"npatterns", ""},
            {"nstates",   ""}
        };

        // Ensure that the subset relative rates are fixed if there is only one
        // subset; otherwise the subset relative rates will be added to the list
        // of free parameters that are updated, which makes no sense in this case
        if (_num_subsets == 1)
            _subset_relrates_fixed = true;

        // Loop through subsets, building up rows as we go
        for (unsigned i = 0; i < _num_subsets; i++) {
#if defined(HOLDER_ETAL_PRIOR)
            // Ensure that for subsets in which the number of rate categories is 1 that
            // the gamma shape is fixed; otherwise the gamma shape will
            // be added to the list of free parameters that are updated, which makes
            // no sense in this case
            if (_asrv[i]->getNumCateg() == 1) {
                _asrv[i]->fixShape(true);
            }
#else
            // Ensure that for subsets in which the number of rate categories is 1 that
            // the gamma rate variance is fixed; otherwise the gamma rate variance will
            // be added to the list of free parameters that are updated, which makes
            // no sense in this case
            if (_asrv[i]->getNumCateg() == 1) {
                _asrv[i]->fixRateVar(true);
            }
#endif

            unsigned index;
            ss["subset"] += boost::str(boost::format("%12d") % (i+1));
            ss["dashes"] += "------------";

            // Determine whether state freqs are unique for this subset
            QMatrix::freq_xchg_ptr_t pfreq = _qmatrix[i]->getStateFreqsSharedPtr();
            QMatrix::freq_xchg_t & freq = *pfreq;
            double * freq_addr = &freq[0];
            auto f = freqset.insert(freq_addr);
            if (f.second) {
                unique_freq.push_back(freq_addr);
                if (!_qmatrix[i]->isFixedStateFreqs())
                    _state_freq_params.push_back(_qmatrix[i]);
                index = (unsigned)unique_freq.size();
            }
            else {
                auto iter = std::find(unique_freq.begin(), unique_freq.end(), freq_addr);
                index = (unsigned)std::distance(unique_freq.begin(), iter) + 1;
            }
            ss["freqs"] += boost::str(boost::format("%12d") % index);

            // Determine whether exchangeabilities are unique for this subset
            if (_subset_datatypes[i].isNucleotide()) {
                QMatrix::freq_xchg_ptr_t pxchg = _qmatrix[i]->getExchangeabilitiesSharedPtr();
                QMatrix::freq_xchg_t & xchg = *pxchg;
                double * xchg_addr = &xchg[0];
                auto x = xchgset.insert(xchg_addr);
                if (x.second) {
                    unique_xchg.push_back(xchg_addr);
                    if (!_qmatrix[i]->isFixedExchangeabilities())
                        _exchangeability_params.push_back(_qmatrix[i]);
                    index = (unsigned)unique_xchg.size();
                }
                else {
                    auto iter = std::find(unique_xchg.begin(), unique_xchg.end(), xchg_addr);
                    index = (unsigned)std::distance(unique_xchg.begin(), iter) + 1;
                }
                ss["xchg"] += boost::str(boost::format("%12d") % index);
            }
            else {
                ss["xchg"] += boost::str(boost::format("%12s") % "-");
            }

            // Determine whether omega is unique for this subset
            if (_subset_datatypes[i].isCodon()) {
                QMatrix::omega_ptr_t pomega = _qmatrix[i]->getOmegaSharedPtr();
                QMatrix::omega_t omegavalue = *pomega;
                double * omega_addr = &omegavalue;
                auto o = omegaset.insert(omega_addr);
                if (o.second) {
                    unique_omega.push_back(omega_addr);
                    if (!_qmatrix[i]->isFixedOmega())
                        _omega_params.push_back(_qmatrix[i]);
                    index = (unsigned)unique_omega.size();
                }
                else {
                    auto iter = std::find(unique_omega.begin(), unique_omega.end(), omega_addr);
                    index = (unsigned)std::distance(unique_omega.begin(), iter) + 1;
                }
                ss["omega"] += boost::str(boost::format("%12d") % index);
            }
            else {
                ss["omega"] += boost::str(boost::format("%12s") % "-");
            }

#if defined(HOLDER_ETAL_PRIOR)
            // Determine whether gamma shape is unique for this subset
            ASRV::shape_ptr_t pshape = _asrv[i]->getShapeSharedPtr();
            double & shape = *pshape;
            double * shape_addr = &shape;
            auto r = shapeset.insert(shape_addr);
            if (r.second) {
                unique_shape.push_back(shape_addr);
                if (!_asrv[i]->isFixedShape())
                    _shape_params.push_back(_asrv[i]);
                index = (unsigned)unique_shape.size();
            }
            else {
                auto iter = std::find(unique_shape.begin(), unique_shape.end(), shape_addr);
                index = (unsigned)std::distance(unique_shape.begin(), iter) + 1;
            }
            ss["shape"] += boost::str(boost::format("%12d") % index);
#else
            // Determine whether rate variance is unique for this subset
            ASRV::ratevar_ptr_t pratevar = _asrv[i]->getRateVarSharedPtr();
            double & ratevar = *pratevar;
            double * ratevar_addr = &ratevar;
            auto r = ratevarset.insert(ratevar_addr);
            if (r.second) {
                unique_ratevar.push_back(ratevar_addr);
                if (!_asrv[i]->isFixedRateVar())
                    _ratevar_params.push_back(_asrv[i]);
                index = (unsigned)unique_ratevar.size();
            }
            else {
                auto iter = std::find(unique_ratevar.begin(), unique_ratevar.end(), ratevar_addr);
                index = (unsigned)std::distance(unique_ratevar.begin(), iter) + 1;
            }
            ss["ratevar"] += boost::str(boost::format("%12d") % index);
#endif

            // Determine whether pinvar is unique for this subset
            if (_asrv[i]->getIsInvarModel()) {
                ASRV::pinvar_ptr_t ppinvar = _asrv[i]->getPinvarSharedPtr();
                double & pinvar = *ppinvar;
                double * pinvar_addr = &pinvar;
                auto r = pinvarset.insert(pinvar_addr);
                if (r.second) {
                    unique_pinvar.push_back(pinvar_addr);
                    if (!_asrv[i]->isFixedPinvar())
                        _pinvar_params.push_back(_asrv[i]);
                    index = (unsigned)unique_pinvar.size();
                }
                else {
                    auto iter = std::find(unique_pinvar.begin(), unique_pinvar.end(), pinvar_addr);
                    index = (unsigned)std::distance(unique_pinvar.begin(), iter) + 1;
                }
                ss["pinvar"] += boost::str(boost::format("%12d") % index);
            }
            else {
                ss["pinvar"] += boost::str(boost::format("%12s") % "-");
            }

            // Save number of rate categories for this subset
            ss["ncateg"] += boost::str(boost::format("%12d") % _asrv[i]->getNumCateg());

            // Save number of sites for this subset
            ss["nsites"] += boost::str(boost::format("%12d") % _subset_sizes[i]);

            // Save number of patterns for this subset
            ss["npatterns"] += boost::str(boost::format("%12d") % _subset_npatterns[i]);

            // Save number of states for this subset
            if (_subset_datatypes.size() == _num_subsets)
                ss["nstates"] += boost::str(boost::format("%12d") % _subset_datatypes[i].getNumStates());
            else
                ss["nstates"] += boost::str(boost::format("%12s") % "?");

        }
        std::string s = "Partition information:\n\n";

        s += boost::str(boost::format("%20s%s\n") % "data subset" % ss["subset"]);
        s += boost::str(boost::format("%20s%s\n") % "-----------------" % ss["dashes"]);
        s += boost::str(boost::format("%20s%s\n") % "num. sites" % ss["nsites"]);
        s += boost::str(boost::format("%20s%s\n") % "num. patterns" % ss["npatterns"]);
        s += boost::str(boost::format("%20s%s\n") % "num. states" % ss["nstates"]);
        s += boost::str(boost::format("%20s%s\n") % "rate categories" % ss["ncateg"]);

        s += "\nParameter linkage:\n\n";

        s += boost::str(boost::format("%20s%s\n") % "data subset" % ss["subset"]);
        s += boost::str(boost::format("%20s%s\n") % "-----------------" % ss["dashes"]);
        s += boost::str(boost::format("%20s%s\n") % "state freqs" % ss["freqs"]);
        s += boost::str(boost::format("%20s%s\n") % "exchangeabilities" % ss["xchg"]);
        s += boost::str(boost::format("%20s%s\n") % "omega" % ss["omega"]);
        s += boost::str(boost::format("%20s%s\n") % "rate variance" % ss["ratevar"]);
        s += boost::str(boost::format("%20s%s\n") % "pinvar" % ss["pinvar"]);

        s += "\nParameter values for each subset:\n";

        s += "\n  relative rate:\n";
        for (unsigned i = 0; i < _num_subsets; i++) {
            s += boost::str(boost::format("  %12d: %g\n") % (i+1) % _subset_relrates[i]);
        }

        s += "\n  state freqs:\n";
        for (unsigned i = 0; i < _num_subsets; i++) {
            QMatrix::freq_xchg_t & freqs = *(_qmatrix[i]->getStateFreqsSharedPtr());
            std::vector<std::string> freqs_as_strings(freqs.size());
            std::transform(freqs.begin(), freqs.end(), freqs_as_strings.begin(), [](double freq) {return boost::str(boost::format("%g") % freq);});
            std::string tmp = boost::algorithm::join(freqs_as_strings, ",");
            s += boost::str(boost::format("  %12d: (%s)\n") % (i+1) % tmp);
        }

        s += "\n  exchangeabilities:\n";
        for (unsigned i = 0; i < _num_subsets; i++) {
            if (_subset_datatypes[i].isNucleotide()) {
                QMatrix::freq_xchg_t & xchg = *(_qmatrix[i]->getExchangeabilitiesSharedPtr());
                std::vector<std::string> xchg_as_strings(xchg.size());
                std::transform(xchg.begin(), xchg.end(), xchg_as_strings.begin(), [](double x) {return boost::str(boost::format("%g") % x);});
                std::string tmp = boost::algorithm::join(xchg_as_strings, ",");
                s += boost::str(boost::format("  %12d: (%s)\n") % (i+1) % tmp);
            }
            else {
                s += boost::str(boost::format("  %12d: -\n") % (i+1));
            }
        }

        s += "\n  omega:\n";
        for (unsigned i = 0; i < _num_subsets; i++) {
            if (_subset_datatypes[i].isCodon()) {
                double omega = *(_qmatrix[i]->getOmegaSharedPtr());
                s += boost::str(boost::format("  %12d: %g\n") % (i+1) % omega);
            }
            else {
                s += boost::str(boost::format("  %12d: -\n") % (i+1));
            }
        }

#if defined(HOLDER_ETAL_PRIOR)
        s += "\n  gamma shape:\n";
        for (unsigned i = 0; i < _num_subsets; i++) {
            if (_asrv[i]->getNumCateg() > 1) {
                double shape = *(_asrv[i]->getShapeSharedPtr());
                s += boost::str(boost::format("  %12d: %g\n") % (i+1) % shape);
            }
            else
                s += boost::str(boost::format("  %12d: -\n") % (i+1));
        }
#else
        s += "\n  rate variance:\n";
        for (unsigned i = 0; i < _num_subsets; i++) {
            if (_asrv[i]->getNumCateg() > 1) {
                double ratevar = *(_asrv[i]->getRateVarSharedPtr());
                s += boost::str(boost::format("  %12d: %g\n") % (i+1) % ratevar);
            }
            else
                s += boost::str(boost::format("  %12d: -\n") % (i+1));
        }
#endif

        s += "\n  pinvar:\n";
        for (unsigned i = 0; i < _num_subsets; i++) {
            double pinvar = *(_asrv[i]->getPinvarSharedPtr());
            bool is_invar_model = _asrv[i]->getIsInvarModel();
            if (is_invar_model)
                s += boost::str(boost::format("  %12d: %g\n") % (i+1) % pinvar);
            else
                s += boost::str(boost::format("  %12d: -\n") % (i+1));
        }

        return s;
    }

    inline unsigned Model::getSubsetNumPatterns(unsigned subset) const {
        assert(subset < _num_subsets);
        return _subset_npatterns[subset];
    }

    inline unsigned Model::getSubsetNumSites(unsigned subset) const {
        assert(subset < _num_subsets);
        return _subset_sizes[subset];
    }

    inline unsigned Model::getNumSites() const {
        return _num_sites;
    }

    inline unsigned Model::getNumSubsets() const {
        return _num_subsets;
    }

    inline unsigned Model::getSubsetNumCateg(unsigned subset) const {
        assert(subset < _num_subsets);
        assert(_asrv.size() == _num_subsets);
        assert(_asrv[subset]);
        return _asrv[subset]->getNumCateg();
    }

    inline bool Model::getSubsetIsInvarModel(unsigned subset) const {
        assert(subset < _num_subsets);
        assert(_asrv.size() == _num_subsets);
        assert(_asrv[subset]);
        return _asrv[subset]->getIsInvarModel();
    }

    inline const QMatrix & Model::getQMatrix(unsigned subset) const {
        assert(subset < _num_subsets);
        return *(_qmatrix[subset]);
    }

    inline const ASRV & Model::getASRV(unsigned subset) const {
        assert(subset < _num_subsets);
        return *(_asrv[subset]);
    }

    inline Model::state_freq_params_t & Model::getStateFreqParams() {
        return _state_freq_params;
    }

    inline Model::exchangeability_params_t & Model::getExchangeabilityParams() {
        return _exchangeability_params;
    }

    inline Model::omega_params_t & Model::getOmegaParams() {
        return _omega_params;
    }

#if defined(HOLDER_ETAL_PRIOR)
    inline Model::shape_params_t & Model::getShapeParams() {
        return _shape_params;
    }
#else
    inline Model::ratevar_params_t & Model::getRateVarParams() {
        return _ratevar_params;
    }
#endif

    inline Model::pinvar_params_t & Model::getPinvarParams() {
        return _pinvar_params;
    }

    inline double Model::calcNormalizingConstantForSubsetRelRates() const {
        // normalize _relrates so that expected relative rate across subsets equals 1.0
        double normalizing_constant = 0.0;
        for (unsigned s = 0; s < _num_subsets; s++) {
            normalizing_constant += _subset_sizes[s]*_subset_relrates[s]/_num_sites;
        }
        return normalizing_constant;
    }

    inline Model::subset_sizes_t & Model::getSubsetSizes() {
        return _subset_sizes;
    }

    inline void Model::setSubsetSizes(const subset_sizes_t nsites_vect) {
        assert(nsites_vect.size() == _num_subsets);
        _subset_sizes.resize(_num_subsets);
        std::copy(nsites_vect.begin(), nsites_vect.end(), _subset_sizes.begin());
        _num_sites = std::accumulate(_subset_sizes.begin(), _subset_sizes.end(), 0);
    }

    inline void Model::setSubsetNumPatterns(const subset_sizes_t npatterns_vect) {
        assert(npatterns_vect.size() == _num_subsets);
        _subset_npatterns.resize(_num_subsets);
        std::copy(npatterns_vect.begin(), npatterns_vect.end(), _subset_npatterns.begin());
    }

    inline void Model::setSubsetDataTypes(const subset_datatype_t & datatype_vect) {
        _num_subsets = (unsigned)datatype_vect.size();

        _qmatrix.clear();
        _qmatrix.resize(_num_subsets);

        _asrv.clear();
        _asrv.resize(_num_subsets);

        _subset_datatypes.resize(_num_subsets);
        std::copy(datatype_vect.begin(), datatype_vect.end(), _subset_datatypes.begin());

		_subset_relrates.assign(_num_subsets, 1.0);

        for (unsigned s = 0; s < _num_subsets; s++) {
            _asrv[s].reset(new ASRV());
            if (_subset_datatypes[s].isNucleotide())
                _qmatrix[s].reset(new QMatrixNucleotide());
            else if (_subset_datatypes[s].isCodon()) {
                GeneticCode::SharedPtr gcptr = _subset_datatypes[s].getGeneticCode();
                _qmatrix[s].reset(new QMatrixCodon(gcptr));
                }
            else
                throw XLorad(boost::format("Only nucleotide or codon data allowed in this version, you specified data type \"%s\" for subset %d") % _subset_datatypes[s].getDataTypeAsString() % (s+1));
        }
    }

    inline void Model::setSubsetNumCateg(unsigned ncateg, unsigned subset) {
        assert(subset < _num_subsets);
        if (ncateg < 1) {
            throw XLorad(boost::str(boost::format("number of categories used for among-site rate variation must be greater than zero but the value %d was supplied") % ncateg));
        }
        _asrv[subset]->setNumCateg(ncateg);
    }

#if defined(HOLDER_ETAL_PRIOR)
    inline void Model::setSubsetShape(ASRV::shape_ptr_t shape, unsigned subset, bool fixed) {
        assert(subset < _num_subsets);
        assert(shape);
        if (*shape < 0.0)
            throw XLorad(boost::str(boost::format("gamma shape must be greater than or equal to zero but the value %.5f was supplied") % *shape));
        _asrv[subset]->setShapeSharedPtr(shape);
        _asrv[subset]->fixShape(fixed);
    }
#else
    inline void Model::setSubsetRateVar(ASRV::ratevar_ptr_t ratevar, unsigned subset, bool fixed) {
        assert(subset < _num_subsets);
        assert(ratevar);
        if (*ratevar < 0.0)
            throw XLorad(boost::str(boost::format("rate variance must be greater than or equal to zero but the value %.5f was supplied") % *ratevar));
        _asrv[subset]->setRateVarSharedPtr(ratevar);
        _asrv[subset]->fixRateVar(fixed);
    }
#endif

    inline void Model::setSubsetPinvar(ASRV::pinvar_ptr_t pinvar, unsigned subset, bool fixed) {
        assert(subset < _num_subsets);
        assert(pinvar);
        if (*pinvar < 0.0)
            throw XLorad(boost::str(boost::format("proportion of invariable sites must be greater than or equal to zero but the value %.5f was supplied") % *pinvar));
        if (*pinvar >= 1.0)
            throw XLorad(boost::str(boost::format("proportion of invariable sites must be less than one but the value %.5f was supplied") % *pinvar));
        _asrv[subset]->setPinvarSharedPtr(pinvar);
        _asrv[subset]->fixPinvar(fixed);
    }

    inline void Model::setSubsetIsInvarModel(bool is_invar, unsigned subset) {
        assert(subset < _num_subsets);
        _asrv[subset]->setIsInvarModel(is_invar);
    }

    inline void Model::setSubsetExchangeabilities(QMatrix::freq_xchg_ptr_t exchangeabilities, unsigned subset, bool fixed) {
        assert(subset < _num_subsets);
        if (!_subset_datatypes[subset].isCodon()) {
            double first_xchg = (*exchangeabilities)[0];
            if (first_xchg == -1)
                _qmatrix[subset]->setEqualExchangeabilities(exchangeabilities);
            else
                _qmatrix[subset]->setExchangeabilitiesSharedPtr(exchangeabilities);
            _qmatrix[subset]->fixExchangeabilities(fixed);
        }
    }

    inline void Model::setSubsetStateFreqs(QMatrix::freq_xchg_ptr_t state_frequencies, unsigned subset, bool fixed) {
        assert(subset < _num_subsets);
        double first_freq = (*state_frequencies)[0];
        if (first_freq == -1)
            _qmatrix[subset]->setEqualStateFreqs(state_frequencies);
        else
            _qmatrix[subset]->setStateFreqsSharedPtr(state_frequencies);
        _qmatrix[subset]->fixStateFreqs(fixed);
    }

#if defined(POLGSS)
    inline void Model::setSubsetStateFreqRefDistParams(QMatrix::freq_xchg_ptr_t freq_refdist_params, unsigned subset) {
        assert(subset < _num_subsets);
        _qmatrix[subset]->setStateFreqRefDistParamsSharedPtr(freq_refdist_params);
    }

    inline void Model::setSubsetExchangeabilitiesRefDistParams(QMatrix::freq_xchg_ptr_t xchg_refdist_params, unsigned subset) {
        assert(subset < _num_subsets);
        _qmatrix[subset]->setExchangeabilityRefDistParamsSharedPtr(xchg_refdist_params);
    }
#if defined(HOLDER_ETAL_PRIOR)
    inline void Model::setSubsetShapeRefDistParams(ASRV::shape_refdist_ptr_t shape_refdist_params, unsigned subset) {
        assert(subset < _num_subsets);
        _asrv[subset]->setShapeRefDistParamsSharedPtr(shape_refdist_params);
    }
#else
    inline void Model::setSubsetRateVarRefDistParams(ASRV::ratevar_refdist_ptr_t ratevar_refdist_params, unsigned subset) {
        assert(subset < _num_subsets);
        _asrv[subset]->setRateVarRefDistParamsSharedPtr(ratevar_refdist_params);
    }
#endif
    inline void Model::setSubsetPinvarRefDistParams(ASRV::pinvar_refdist_ptr_t pinvar_refdist_params, unsigned subset) {
        assert(subset < _num_subsets);
        _asrv[subset]->setPinvarRefDistParamsSharedPtr(pinvar_refdist_params);
    }

#endif

    inline void Model::setSubsetOmega(QMatrix::omega_ptr_t omega, unsigned subset, bool fixed) {
        assert(subset < _num_subsets);
        assert(*omega > 0.0);
        if (_subset_datatypes[subset].isCodon()) {
            _qmatrix[subset]->setOmegaSharedPtr(omega);
            _qmatrix[subset]->fixOmega(fixed);
        }
    }

    inline void Model::activate() {
        for (auto q : _qmatrix)
            q->setActive(true);
    }

    inline void Model::inactivate() {
        for (auto q : _qmatrix)
            q->setActive(false);
    }

    inline int Model::setBeagleEigenDecomposition(int beagle_instance, unsigned subset, unsigned instance_subset) {
        assert(subset < _qmatrix.size());
        const double * pevec = _qmatrix[subset]->getEigenvectors();
        const double * pivec = _qmatrix[subset]->getInverseEigenvectors();
        const double * pival = _qmatrix[subset]->getEigenvalues();
        int code = beagleSetEigenDecomposition(
            beagle_instance,    // Instance number (input)
            instance_subset,    // Index of eigen-decomposition buffer (input)
            pevec,              // Flattened matrix (stateCount x stateCount) of eigen-vectors (input)
            pivec,              // Flattened matrix (stateCount x stateCount) of inverse-eigen- vectors (input)
            pival);             // Vector of eigenvalues

        return code;
    }

    inline int Model::setBeagleStateFrequencies(int beagle_instance, unsigned subset, unsigned instance_subset) {
        assert(subset < _qmatrix.size());
        const double * pfreq = _qmatrix[subset]->getStateFreqs();
        int code = beagleSetStateFrequencies(
             beagle_instance,   // Instance number (input)
             instance_subset,   // Index of state frequencies buffer (input)
             pfreq);            // State frequencies array (stateCount) (input)

        return code;
    }

    inline int Model::setBeagleAmongSiteRateVariationRates(int beagle_instance, unsigned subset, unsigned instance_subset) {
        assert(subset < _asrv.size());
        const double * prates = _asrv[subset]->getRates();
        int code = beagleSetCategoryRatesWithIndex(
            beagle_instance,    // Instance number (input)
            instance_subset,    // Index of category rates buffer (input)
            prates);            // Array containing categoryCount rate scalers (input)

        return code;
    }

    inline int Model::setBeagleAmongSiteRateVariationProbs(int beagle_instance, unsigned subset, unsigned instance_subset) {
        assert(subset < _asrv.size());
        const double * pprobs = _asrv[subset]->getProbs();
        int code = beagleSetCategoryWeights(
            beagle_instance,    // Instance number (input)
            instance_subset,    // Index of category weights buffer (input)
            pprobs);            // Category weights array (categoryCount) (input)

        return code;
    }

    inline void Model::saveParamNames(std::vector<std::string> & param_name_vect) const {
        // Almost the same as Model::paramNamesAsString, but Model::paramNamesAsString is for output
        // to parameter log files where redundancy does not matter (for example, TL can be output
        // in addition to all edge lengths). Model::saveParamNames, on the other hand, is for output
        // to a file used in marginal likelihood estimation, and thus cannot have columns for redundant parameters
        if (_num_subsets > 1) {
            for (unsigned i = 1; i <= _num_subsets - 1; ++i)
                param_name_vect.push_back(boost::str(boost::format("subsetrate-%d") % i));
        }
        for (unsigned k = 1; k <= _num_subsets; k++) {
            if (_subset_datatypes[k-1].isNucleotide()) {
                if (!_qmatrix[k-1]->isFixedExchangeabilities()) {
                    param_name_vect.push_back(boost::str(boost::format("xchg-%d-1") % k));
                    param_name_vect.push_back(boost::str(boost::format("xchg-%d-2") % k));
                    param_name_vect.push_back(boost::str(boost::format("xchg-%d-3") % k));
                    param_name_vect.push_back(boost::str(boost::format("xchg-%d-4") % k));
                    param_name_vect.push_back(boost::str(boost::format("xchg-%d-5") % k));
                }

                if (!_qmatrix[k-1]->isFixedStateFreqs()) {
                    param_name_vect.push_back(boost::str(boost::format("freq-%d-1") % k));
                    param_name_vect.push_back(boost::str(boost::format("freq-%d-2") % k));
                    param_name_vect.push_back(boost::str(boost::format("freq-%d-3") % k));
                }
            }
            else if (_subset_datatypes[k-1].isCodon()) {
                if (!_qmatrix[k-1]->isFixedOmega()) {
                    param_name_vect.push_back("omega");
                }

                if (!_qmatrix[k-1]->isFixedStateFreqs()) {
                    for (unsigned i = 1; i <= 60; ++i)
                        param_name_vect.push_back(boost::str(boost::format("freq-%d-%d") % k % i));
                }
            }
            if (_asrv[k-1]->getIsInvarModel()) {
                if (!_asrv[k-1]->isFixedPinvar()) {
                    param_name_vect.push_back("pinvar");
                }
            }
#if defined(HOLDER_ETAL_PRIOR)
            if (_asrv[k-1]->getNumCateg() > 1) {
                if (!_asrv[k-1]->isFixedShape()) {
                    param_name_vect.push_back("shape");
                }
            }
#else
            if (_asrv[k-1]->getNumCateg() > 1) {
                if (!_asrv[k-1]->isFixedRateVar()) {
                    param_name_vect.push_back("ratevar");
                }
            }
#endif
        }
    }

    inline std::string Model::paramNamesAsString(std::string sep) const {
        // Almost the same as Model::saveParamNames, but this version is for output to parameter log files
        // where redundancy does not matter (for example, TL can be output in addition to all edge lengths)
        // Model::saveParamNames, on the other hand, is for output to a file used in marginal likelihood
        // estimation, and thus cannot have columns for redundant parameters
        unsigned k;
        std::string s = "";
        if (_num_subsets > 1) {
            for (k = 0; k < _num_subsets; k++) {
                s += boost::str(boost::format("m-%d%s") % k % sep);
            }
        }
        for (k = 0; k < _num_subsets; k++) {
            if (_subset_datatypes[k].isNucleotide()) {
                if (!_qmatrix[k]->isFixedExchangeabilities()) {
                    s += boost::str(boost::format("rAC-%d%srAG-%d%srAT-%d%srCG-%d%srCT-%d%srGT-%d%s") % k % sep % k % sep % k % sep % k % sep % k % sep % k % sep);
                }
                if (!_qmatrix[k]->isFixedStateFreqs()) {
                    s += boost::str(boost::format("piA-%d%spiC-%d%spiG-%d%spiT-%d%s") % k % sep % k % sep % k % sep % k % sep);
                }
            }
            else if (_subset_datatypes[k].isCodon()) {
                if (!_qmatrix[k]->isFixedOmega()) {
                    s += boost::str(boost::format("omega-%d%s") % k % sep);
                }
                if (!_qmatrix[k]->isFixedStateFreqs()) {
                    for (std::string codon : _subset_datatypes[0].getGeneticCode()->_codons)
                        s += boost::str(boost::format("pi%s-%d%s") % codon % k % sep);
                }
            }
            if (_asrv[k]->getIsInvarModel()) {
                if (!_asrv[k]->isFixedPinvar()) {
                    s += boost::str(boost::format("pinvar-%d%s") % k % sep);
                }
            }
#if defined(HOLDER_ETAL_PRIOR)
            if (_asrv[k]->getNumCateg() > 1) {
                if (!_asrv[k]->isFixedShape()) {
                    s += boost::str(boost::format("shape-%d%s") % k % sep);
                }
            }
#else
            if (_asrv[k]->getNumCateg() > 1) {
                if (!_asrv[k]->isFixedRateVar()) {
                    s += boost::str(boost::format("ratevar-%d%s") % k % sep);
                }
            }
#endif
        }
        return s;
    }

    inline std::string Model::paramValuesAsString(std::string sep) const {
        unsigned k;
        std::string s = "";
        if (_num_subsets > 1) {
            for (k = 0; k < _num_subsets; k++) {
                s += boost::str(boost::format("%.5f%s") % _subset_relrates[k] % sep);
            }
        }
        for (k = 0; k < _num_subsets; k++) {
            if (_subset_datatypes[k].isNucleotide()) {
                if (!_qmatrix[k]->isFixedExchangeabilities()) {
                    QMatrix::freq_xchg_t x = *_qmatrix[k]->getExchangeabilitiesSharedPtr();
                    s += boost::str(boost::format("%.5f%s%.5f%s%.5f%s%.5f%s%.5f%s%.5f%s") % x[0] % sep % x[1] % sep % x[2] % sep % x[3] % sep % x[4] % sep % x[5] % sep);
                }
                if (!_qmatrix[k]->isFixedStateFreqs()) {
                    QMatrix::freq_xchg_t f = *_qmatrix[k]->getStateFreqsSharedPtr();
                    s += boost::str(boost::format("%.5f%s%.5f%s%.5f%s%.5f%s") % f[0] % sep % f[1] % sep % f[2] % sep % f[3] % sep);
                }
            }
            else if (_subset_datatypes[k].isCodon()) {
                if (!_qmatrix[k]->isFixedOmega()) {
                    s += boost::str(boost::format("%.5f%s") % _qmatrix[k]->getOmega() % sep);
                }
                if (!_qmatrix[k]->isFixedStateFreqs()) {
                    QMatrix::freq_xchg_t f = *_qmatrix[k]->getStateFreqsSharedPtr();
                    for (unsigned m = 0; m < _subset_datatypes[0].getNumStates(); m++)
                        s += boost::str(boost::format("%.5f%s") % f[m] % sep);
                }
            }
            if (_asrv[k]->getIsInvarModel()) {
                if (!_asrv[k]->isFixedPinvar()) {
                    s += boost::str(boost::format("%.5f%s") % _asrv[k]->getPinvar() % sep);
                }
            }
#if defined(HOLDER_ETAL_PRIOR)
            if (_asrv[k]->getNumCateg() > 1) {
                if (!_asrv[k]->isFixedShape()) {
                    s += boost::str(boost::format("%.5f%s") % _asrv[k]->getShape() % sep);
                }
            }
#else
            if (_asrv[k]->getNumCateg() > 1) {
                if (!_asrv[k]->isFixedRateVar()) {
                    s += boost::str(boost::format("%.5f%s") % _asrv[k]->getRateVar() % sep);
                }
            }
#endif
        }
        return s;
    }

    inline void Model::setSubsetRelRates(subset_relrate_vect_t & relrates, bool fixed) {
        assert(_num_subsets > 0);
        assert(relrates.size() > 0);
        if (relrates[0] == -1)
            _subset_relrates.assign(_num_subsets, 1.0);
        else
            _subset_relrates.assign(relrates.begin(), relrates.end());
        _subset_relrates_fixed = fixed;
    }

    inline Model::subset_relrate_vect_t & Model::getSubsetRelRates() {
        return _subset_relrates;
    }

    inline bool Model::isFixedSubsetRelRates() const {
        return _subset_relrates_fixed;
    }

    inline void Model::setTreeIndex(unsigned i, bool fixed) {
        _tree_index = i;
        _tree_fixed = fixed;
    }

    inline unsigned Model::getTreeIndex() const {
        return _tree_index;
    }

    inline bool Model::isFixedTree() const {
        return _tree_fixed;
    }

    inline void Model::setTopologyPriorOptions(bool allow_polytomies, bool resclass, double C) {
        _allow_polytomies       = allow_polytomies;
        _resolution_class_prior = resclass;
        _topo_prior_C           = C;
    }

    inline bool Model::isAllowPolytomies() const {
        return _allow_polytomies;
    }

    inline bool Model::isResolutionClassTopologyPrior() const {
        return _resolution_class_prior;
    }

    inline double Model::getTopologyPriorC() const {
        return _topo_prior_C;
    }

    // Suppose param_vect = {a, b, c, d} and the sum of elements = 1.
    // Replaces param_vect with {log(b/a), log(c/a), log(d/a)}.
    // phi = b/a + c/a + d/a = (1-a)/a.
    inline double Model::logRatioTransform(std::vector<double> & param_vect) const {
        unsigned sz = (unsigned)param_vect.size();
        assert(sz > 0);
        std::vector<double> result_vect(sz - 1);
        double first = param_vect[0];
        double log_first = log(first);
        double log_jacobian = log_first;
        for (unsigned i = 1; i < sz; ++i) {
            double element = param_vect[i];
            double log_element = log(element);
            log_jacobian += log_element;
            result_vect[i-1] = log_element - log_first;
        }
        param_vect.clear();
        param_vect.resize(sz - 1);
        std::copy(result_vect.begin(), result_vect.end(), param_vect.begin());
        return log_jacobian;
    }

    // Suppose param_vect = {log(b/a), log(c/a), log(d/a)}.
    // If phi = b/a + c/a + d/a = (1-a)/a, then a = 1/(1+phi).
    // Replaces param_vect with {a, b, c, d}.
    inline double Model::logRatioUntransform(std::vector<double> & param_vect) const {
        unsigned sz = (unsigned)param_vect.size();
        assert(sz > 0);
        std::vector<double> result_vect(sz + 1);
        double phi = 0.0;
        result_vect[0] = 1.0;
        for (unsigned i = 1; i < sz + 1; ++i) {
            double r = exp(param_vect[i-1]);
            result_vect[i] = r;
            phi += r;
        }
        double log_jacobian = 0.0;
        for (unsigned i = 0; i < sz + 1; ++i) {
            result_vect[i] /= (1.0 + phi);
            log_jacobian += log(result_vect[i]);
        }
        param_vect.clear();
        param_vect.resize(sz + 1);
        std::copy(result_vect.begin(), result_vect.end(), param_vect.begin());
        return log_jacobian;
    }

    inline double Model::logTransformParameters(std::vector<double> & param_vect) const {
        unsigned k;
        double log_jacobian = 0.0;
        if (_num_subsets > 1) {
            std::vector<double> tmp(_subset_relrates.begin(), _subset_relrates.end());
            log_jacobian += logRatioTransform(tmp);
            param_vect.insert(param_vect.end(), tmp.begin(), tmp.end());
        }
        for (k = 0; k < _num_subsets; k++) {
            if (_subset_datatypes[k].isNucleotide()) {
                if (!_qmatrix[k]->isFixedExchangeabilities()) {
                    QMatrix::freq_xchg_t x = *_qmatrix[k]->getExchangeabilitiesSharedPtr();
                    log_jacobian += logRatioTransform(x);
                    param_vect.insert(param_vect.end(), std::begin(x), std::end(x));
                }

                if (!_qmatrix[k]->isFixedStateFreqs()) {
                    QMatrix::freq_xchg_t f = *_qmatrix[k]->getStateFreqsSharedPtr();
                    log_jacobian += logRatioTransform(f);
                    param_vect.insert(param_vect.end(), std::begin(f), std::end(f));
                }
            }
            else if (_subset_datatypes[k].isCodon()) {
                if (!_qmatrix[k]->isFixedOmega()) {
                    double log_omega = log(_qmatrix[k]->getOmega());
                    log_jacobian += log_omega;
                    param_vect.push_back(log_omega);
                }

                if (!_qmatrix[k]->isFixedStateFreqs()) {
                    QMatrix::freq_xchg_t f = *_qmatrix[k]->getStateFreqsSharedPtr();
                    log_jacobian += logRatioTransform(f);
                    param_vect.insert(param_vect.end(), std::begin(f), std::end(f));
                }
            }
            if (_asrv[k]->getIsInvarModel()) {
                if (!_asrv[k]->isFixedPinvar()) {
                    double pinvar = _asrv[k]->getPinvar();
                    assert(pinvar > 0.0 && pinvar < 1.0);
                    double log_pinvar = log(pinvar);
                    double log_one_minus_pinvar = log(1.0 - pinvar);
                    log_jacobian += log_pinvar;
                    log_jacobian += log_one_minus_pinvar;
                    param_vect.push_back(log_pinvar - log_one_minus_pinvar);
                }
            }
#if defined(HOLDER_ETAL_PRIOR)
            if (_asrv[k]->getNumCateg() > 1) {
                if (!_asrv[k]->isFixedShape()) {
                    double log_shape = log(_asrv[k]->getShape());
                    log_jacobian += log_shape;
                    param_vect.push_back(log_shape);
                }
            }
#else
            if (_asrv[k]->getNumCateg() > 1) {
                if (!_asrv[k]->isFixedRateVar()) {
                    double log_ratevar = log(_asrv[k]->getRateVar());
                    log_jacobian += log_ratevar;
                    param_vect.push_back(log_ratevar);
                }
            }
#endif
        }
        return log_jacobian;
    }

    inline double Model::setParametersFromLogTransformed(Eigen::VectorXd & param_vect, unsigned first, unsigned nparams) {
        unsigned k;
        unsigned cursor = first;
        double log_jacobian = 0.0;
        if (_num_subsets > 1) {
            assert(param_vect.rows() >= cursor + _num_subsets - 1);

            // Copy log-ratio-transformed subset relative rates to temporary vector
            std::vector<double> tmp(_num_subsets-1);
            for (unsigned i = 0; i < _num_subsets - 1; ++i)
                tmp[i] = param_vect(cursor + i);
            log_jacobian += logRatioUntransform(tmp);
            assert(tmp.size() == _num_subsets); // tmp should have increased in size by 1

            // Copy detransformed subset relative rates to model
            std::copy(tmp.begin(), tmp.end(), _subset_relrates.begin());
            cursor += _num_subsets - 1;
        }
        for (k = 0; k < _num_subsets; k++) {
            if (_subset_datatypes[k].isNucleotide()) {
                if (!_qmatrix[k]->isFixedExchangeabilities()) {
                    assert(param_vect.rows() >= cursor + 5);
                    QMatrix::freq_xchg_t x(5);
                    x[0] = param_vect(cursor++);
                    x[1] = param_vect(cursor++);
                    x[2] = param_vect(cursor++);
                    x[3] = param_vect(cursor++);
                    x[4] = param_vect(cursor++);
                    log_jacobian += logRatioUntransform(x);
                    assert(x.size() == 6); // x should have increased in size by 1
                    _qmatrix[k]->setExchangeabilities(x);
                }

                if (!_qmatrix[k]->isFixedStateFreqs()) {
                    assert(param_vect.rows() >= cursor + 3);
                    QMatrix::freq_xchg_t f(3);
                    f[0] = param_vect(cursor++);
                    f[1] = param_vect(cursor++);
                    f[2] = param_vect(cursor++);
                    log_jacobian += logRatioUntransform(f);
                    assert(f.size() == 4); // f should have increased in size by 1
                    _qmatrix[k]->setStateFreqs(f);
                }
            }
            else if (_subset_datatypes[k].isCodon()) {
                if (!_qmatrix[k]->isFixedOmega()) {
                    assert(param_vect.rows() >= cursor + 1);
                    double log_omega = param_vect(cursor++);
                    log_jacobian += log_omega;
                    double omega = exp(log_omega);
                    _qmatrix[k]->setOmega(omega);
                }

                if (!_qmatrix[k]->isFixedStateFreqs()) {
                    assert(param_vect.rows() >= cursor + 60);
                    QMatrix::freq_xchg_t f(60);
                    for (unsigned i = 0; i < 60; ++i)
                        f[i] = param_vect(cursor++);
                    log_jacobian += logRatioUntransform(f);
                    assert(f.size() == 61); // f should have increased in size by 1
                    _qmatrix[k]->setStateFreqs(f);
                }
            }
            if (_asrv[k]->getIsInvarModel()) {
                if (!_asrv[k]->isFixedPinvar()) {
                    assert(param_vect.rows() >= cursor + 1);
                    double logit_pinvar = param_vect(cursor++);
                    double log_pinvar = logit_pinvar - log(1.0 + exp(logit_pinvar));
                    log_jacobian += log_pinvar;
                    double pinvar = exp(log_pinvar);
                    log_jacobian += log(1.0 - pinvar);
                    _asrv[k]->setPinvar(pinvar);
                }
            }
#if defined(HOLDER_ETAL_PRIOR)
            if (_asrv[k]->getNumCateg() > 1) {
                if (!_asrv[k]->isFixedShape()) {
                    assert(param_vect.rows() >= cursor + 1);
                    double log_shape = param_vect(cursor++);
                    log_jacobian += log_shape;
                    double shape = exp(log_shape);
                    _asrv[k]->setShape(shape);
                }
            }
#else
            if (_asrv[k]->getNumCateg() > 1) {
                if (!_asrv[k]->isFixedRateVar()) {
                    assert(param_vect.rows() >= cursor + 1);
                    double log_ratevar = param_vect(cursor++);
                    log_jacobian += log_ratevar;
                    double ratevar = exp(log_ratevar);
                    _asrv[k]->setRateVar(ratevar);
                }
            }
#endif
        }
        return log_jacobian;
    }

#if defined(POLGSS)
#if defined(HOLDER_ETAL_PRIOR)
    inline void Model::setEdgeLenRefDistParams(std::vector<double> & edgelen_refdist_params) {
        _edgelen_refdist_params = edgelen_refdist_params;
    }

    inline std::vector<double> Model::getEdgeLenRefDistParamsVect() const {
        return _edgelen_refdist_params;
    }
#else
    inline void Model::setEdgeProportionsRefDistParams(std::vector<double> & edgeprops_refdist_params) {
        _edgeprops_refdist_params = edgeprops_refdist_params;
    }

    inline std::vector<double> Model::getEdgeProportionsRefDistParamsVect() const {
        return _edgeprops_refdist_params;
    }

    inline void Model::setTreeLengthRefDistParams(std::vector<double> & treelen_refdist_params) {
        _treelen_refdist_params = treelen_refdist_params;
    }

    inline std::vector<double> Model::getTreeLengthRefDistParamsVect() const {
        return _treelen_refdist_params;
    }
#endif

    inline void Model::sampleParams() {
        unsigned k;
        if (_num_subsets > 1) {
            _sampled_subset_relrates.push_back(_subset_relrates);
        }
        for (k = 0; k < _num_subsets; k++) {
            if (_subset_datatypes[k].isNucleotide()) {
                QMatrix::freq_xchg_t & x = *_qmatrix[k]->getExchangeabilitiesSharedPtr();
                _sampled_exchangeabilities[k].push_back(x);

                QMatrix::freq_xchg_t & f = *_qmatrix[k]->getStateFreqsSharedPtr();
                _sampled_state_freqs[k].push_back(f);
            }
            else if (_subset_datatypes[k].isCodon()) {
                _sampled_omegas[k].push_back(_qmatrix[k]->getOmega());

                QMatrix::freq_xchg_t & f = *_qmatrix[k]->getStateFreqsSharedPtr();
                _sampled_state_freqs[k].push_back(f);
            }
            if (_asrv[k]->getIsInvarModel()) {
                _sampled_pinvars[k].push_back(_asrv[k]->getPinvar());
            }
#if defined(HOLDER_ETAL_PRIOR)
            if (_asrv[k]->getNumCateg() > 1) {
                _sampled_shapes[k].push_back(_asrv[k]->getShape());
            }
#else
            if (_asrv[k]->getNumCateg() > 1) {
                _sampled_ratevars[k].push_back(_asrv[k]->getRateVar());
            }
#endif
        }
    }

    inline std::string Model::calcGammaRefDist(std::string title, std::string subset_name, std::vector<double> & vect) {
        //TODO: nearly identical to TreeManip::calcGammaRefDist - make one version that can be used by both Model and TreeManip
        // Compute sums and sums-of-squares for each component
        unsigned n = (unsigned)vect.size();
        double sumv   = 0.0;
        double sumsqv = 0.0;
        for (unsigned i = 0; i < n; i++) {
            double v = vect[i];
            sumv += v;
            sumsqv += v*v;
        }

        // Compute mean and variance
        double mu;
        double s;
        mu = sumv/n;
        s = (sumsqv - mu*mu*n)/(n-1);

        // Compute parameters of reference distribution and save each
        // as an element of the string vector svect
        // s = shape*scale^2
        // mu = shape*scale
        double scale = s/mu;
        double shape = mu/scale;
        std::string refdiststr = boost::str(boost::format("%s = %s:%.3f, %.3f\n") % title % subset_name % shape % scale);

        return refdiststr;
    }

    inline std::string Model::calcBetaRefDist(std::string title, std::string subset_name, std::vector<double> & vect) {
        // mean = a/(a+b)  1-mean = b/(a+b)  var = ab/[(a+b)^2 (a+b+1)]
        // phi = a+b = mean*(1-mean)/var   a = mean*phi   b = (1-mean)*phi

        // Calculate mean and var
        double mean = 0.0;
        double var = 0.0;
        for (auto p : vect) {
            mean += p;
            var += p*p;
        }
        double n = (double)vect.size();
        assert(n > 1.0);
        mean /= n;
        var = (var - n*mean*mean)/(n-1);

        // Calculate a and b
        double phi = mean*(1.0 - mean)/var;
        double a = mean*phi;
        double b = (1.0-mean)*phi;
        std::string refdiststr = boost::str(boost::format("%s = %s:%.3f, %.3f\n") % title % subset_name % a % b);
        return refdiststr;
    }

    inline std::string Model::calcDirichletRefDist(std::string title, std::string subset_name, std::vector< QMatrix::freq_xchg_t > & vect, bool relrates) {
        // Sanity check: also calculate means and variances using Boost accumulator
        // see https://www.nu42.com/2016/12/descriptive-stats-with-cpp-boost.html
        //boost::accumulators::accumulator_set<double, boost::accumulators::stats<boost::accumulators::tag::variance> > acc;

        //TODO: nearly identical to TreeManip::calcGammaRefDist - make one version that can be used by both Model and TreeManip
        // Ming-Hui Chen method of matching component variances
        // mu_i = phi_i/phi is mean of component i (estimate using sample mean)
        // s_i^2 is sample variance of component i
        //
        //       sum_i mu_i^2 (1 - mu_i)^2
        // phi = --------------------------- - 1
        //       sum_i s_i^2 mu_i (1 - mu_i)
        //
        // phi_i = phi mu_i
        unsigned n = (unsigned)vect.size();
        assert(n > 0);
        unsigned k = (unsigned)vect[0].size();

#if defined(POLTMPPARAMS)
        std::ofstream outf(boost::str(boost::format("doof-%s.txt") % title));
        if (title == "exchangerefdist") {
            outf << "rAC-0	rAG-0	rAT-0	rCG-0	rCT-0	rGT-0\n";
        }
        else if (title == "statefreqrefdist") {
            outf << "piA-0	piC-0	piG-0	piT-0\n";
        }
#endif
        // Compute sums and sums-of-squares for each component
        std::vector<double> sums(k, 0.0);
        std::vector<double> sumsq(k, 0.0);
        for (unsigned i = 0; i < n; i++) {
            QMatrix::freq_xchg_t & dir = vect[i];
            //acc(dir[0]);
            for (unsigned j = 0; j < k; j++) {
                double v = dir[j];
#if defined(POLTMPPARAMS)
                outf << boost::format("%.5f\t") % v;
#endif
                sums[j] += v;
                sumsq[j] += v*v;
            }
#if defined(POLTMPPARAMS)
            outf << "\n";
#endif
        }
#if defined(POLTMPPARAMS)
        outf.close();
#endif

        // Compute means and variances for each component
        std::vector<double> mu(k, 0.0);
        std::vector<double> s(k, 0.0);
        double numer = 0.0;
        double denom = 0.0;
        for (unsigned j = 0; j < k; j++) {
            mu[j] = sums[j]/n;
            s[j] = (sumsq[j] - mu[j]*mu[j]*n)/(n-1);
            if (relrates) {
                double p = 1.0*_subset_sizes[j]/_num_sites;
                mu[j] *= p;
                s[j] *= p*p;
            }
            numer += mu[j]*mu[j]*(1.0 - mu[j])*(1.0 - mu[j]);
            denom += s[j]*mu[j]*(1.0 - mu[j]);
        }

        // Compute phi
        double phi = numer/denom - 1.0;
#if defined(POLTMPPARAMS)
        ::om.outputConsole(boost::format("%s:\n") % title;
        ::om.outputConsole("  phi = %g\n") % phi);
        for (unsigned j = 0; j < k; j++) {
            ::om.outputConsole(boost::format("  mu[%d] = %g\n") % j % mu[j]);
        }
        ::om.outputConsole();
#endif

        // Compute parameters of reference distribution and save each
        // as an element of the string vector svect
        std::vector<std::string> svect;
        for (unsigned j = 0; j < k; j++) {
            double c = phi*mu[j];
            std::string stmp = boost::str(boost::format("%.3f") % c);
            svect.push_back(stmp);
        }
        std::string refdiststr = boost::str(boost::format("%s = %s:%s\n") % title % subset_name % boost::algorithm::join(svect, ","));

        return refdiststr;
    }

    inline std::string Model::saveReferenceDistributions(Partition::SharedPtr partition) {
        // Calculate and save reference distribution parameters in a conf file that can be used
        // in a subsequent generalized steppingstone analysis
        unsigned k;
        std::string s;
        if (_num_subsets > 1) {
            s += calcDirichletRefDist("relratesrefdist", "default", _sampled_subset_relrates, true);
        }
        for (k = 0; k < _num_subsets; k++) {
            if (_subset_datatypes[k].isNucleotide()) {
                if (!_qmatrix[k]->isFixedExchangeabilities())
                    s += calcDirichletRefDist("exchangerefdist", partition->getSubsetName(k), _sampled_exchangeabilities[k]);
                if (!_qmatrix[k]->isFixedStateFreqs())
                    s += calcDirichletRefDist("statefreqrefdist", partition->getSubsetName(k), _sampled_state_freqs[k]);
            }
            else if (_subset_datatypes[k].isCodon()) {
                if (!_qmatrix[k]->isFixedOmega())
                    s += calcGammaRefDist("omegarefdist", partition->getSubsetName(k), _sampled_omegas[k]);
                if (!_qmatrix[k]->isFixedStateFreqs())
                    s += calcDirichletRefDist("statefreqrefdist", partition->getSubsetName(k), _sampled_state_freqs[k]);
            }
            if (_asrv[k]->getIsInvarModel()) {
                if (!_asrv[k]->isFixedPinvar())
                    s += calcBetaRefDist("pinvarrefdist", partition->getSubsetName(k), _sampled_pinvars[k]);
            }
#if defined(HOLDER_ETAL_PRIOR)
            if (_asrv[k]->getNumCateg() > 1) {
                if (!_asrv[k]->isFixedShape())
                    s += calcGammaRefDist("shaperefdist", partition->getSubsetName(k), _sampled_shapes[k]);
            }
#else
            if (_asrv[k]->getNumCateg() > 1) {
                if (!_qmatrix[k]->isFixedRateVar())
                    s += calcGammaRefDist("ratevarrefdist", partition->getSubsetName(k), _sampled_ratevars[k]);
            }
#endif
        }

        return s;
    }

//#if defined(POLGSS)
//    inline void Model::setStateFreqRefDistParams(std::vector<double> refdist_params) {
//        _state_freq_refdist_params.resize(refdist_params.size());
//        std::copy(refdist_params.begin(), refdist_params.end(), _state_freq_refdist_params.begin());
//    }

//    inline std::vector<double> Model::getStateFreqRefDistParams() {
//        return _state_freq_refdist_params;
//    }
//#endif

//    inline void Model::setExchangeabilityRefDistParams(std::vector<double> refdist_params) {
//        _exchangeability_refdist_params.resize(refdist_params.size());
//        std::copy(refdist_params.begin(), refdist_params.end(), _exchangeability_refdist_params.begin());
//    }
//
//    inline std::vector<double> Model::getExchangeabilityRefDistParams() {
//        return _exchangeability_refdist_params;
//    }

//    inline void Model::setOmegaRefDistParams(std::vector<double> refdist_params) {
//        _omega_refdist_params.resize(refdist_params.size());
//        std::copy(refdist_params.begin(), refdist_params.end(), _omega_refdist_params.begin());
//    }
//
//    inline std::vector<double> Model::getOmegaRefDistParams() {
//        return _omega_refdist_params;
//    }

//#if defined(HOLDER_ETAL_PRIOR)
//    inline void Model::setShapeRefDistParams(std::vector<double> refdist_params) {
//        _shape_refdist_params.resize(refdist_params.size());
//        std::copy(refdist_params.begin(), refdist_params.end(), _shape_refdist_params.begin());
//    }
//
//    inline std::vector<double> Model::getShapeRefDistParams() {
//        return _shape_refdist_params;
//    }
//#else
//    inline void Model::setRateVarRefDistParams(std::vector<double> refdist_params) {
//        _ratevar_refdist_params.resize(refdist_params.size());
//        std::copy(refdist_params.begin(), refdist_params.end(), _ratevar_refdist_params.begin());
//    }
//
//    inline std::vector<double> Model::getRateVarRefDistParams() {
//        return _ratevar_refdist_params;
//    }
//#endif

//    inline void Model::setPinvarRefDistParams(std::vector<double> refdist_params) {
//        _pinvar_refdist_params.resize(refdist_params.size());
//        std::copy(refdist_params.begin(), refdist_params.end(), _pinvar_refdist_params.begin());
//    }
//
//    inline std::vector<double> Model::getPinvarRefDistParams() {
//        return _pinvar_refdist_params;
//    }

    inline void Model::setSubsetRelRatesRefDistParams(std::vector<double> refdist_params) {
        _subset_relrates_refdist_params.resize(refdist_params.size());
        std::copy(refdist_params.begin(), refdist_params.end(), _subset_relrates_refdist_params.begin());
    }

    inline std::vector<double> Model::getSubsetRelRatesRefDistParamsVect() {
        return _subset_relrates_refdist_params;
    }

#endif
}


// Copyright (c) 2012-2013 The PPCoin developers
// Copyright (c) 2015-2019 The PIVX developers
// Copyright (c) 2019 The Veil developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <boost/assign/list_of.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/foreach.hpp>

#include "chainparams.h"
#include "kernel.h"
#include "policy/policy.h"
#include "script/interpreter.h"
#include "timedata.h"
#include "util.h"
#include "stakeinput.h"
#include "veil/zerocoin/zchain.h"
#include "libzerocoin/bignum.h"
#include <versionbits.h>

using namespace std;

//test hash vs target
bool stakeTargetHit(arith_uint256 hashProofOfStake, int64_t nValueIn, arith_uint256 bnTargetPerCoinDay)
{
    //get the stake weight - weight is equal to coin amount
    arith_uint256 bnTarget = arith_uint256(nValueIn) * bnTargetPerCoinDay;

    //Double check for overflow, give max value if overflow
    if (bnTargetPerCoinDay > bnTarget)
        bnTarget = ~arith_uint256();

    // Now check if proof-of-stake hash meets target protocol
    return hashProofOfStake < bnTarget;
}

bool CheckStake(const CDataStream& ssUniqueID, CAmount nValueIn, const uint64_t nStakeModifier, const uint256& bnTarget,
                unsigned int nTimeBlockFrom, unsigned int& nTimeTx, uint256& hashProofOfStake)
{
    CDataStream ss(SER_GETHASH, 0);
    ss << nStakeModifier << nTimeBlockFrom << ssUniqueID << nTimeTx;
    hashProofOfStake = Hash(ss.begin(), ss.end());
    //LogPrintf("%s: modifier:%d nTimeBlockFrom:%d nTimeTx:%d hash:%s\n", __func__, nStakeModifier, nTimeBlockFrom, nTimeTx, hashProofOfStake.GetHex());

    return stakeTargetHit(UintToArith256(hashProofOfStake), nValueIn, UintToArith256(bnTarget));
}

//Sets nValueIn with the weighted amount given a certain zerocoin denomination
void WeightStake(CAmount& nValueIn, const libzerocoin::CoinDenomination denom)
{
    if (denom == libzerocoin::CoinDenomination::ZQ_ONE_HUNDRED) {
        //10% reduction
        nValueIn = (nValueIn * 90) / 100;
    } else if (denom == libzerocoin::CoinDenomination::ZQ_ONE_THOUSAND) {
        //20% reduction
        nValueIn = (nValueIn * 80) / 100;
    } else if (denom == libzerocoin::CoinDenomination::ZQ_TEN_THOUSAND) {
        //30% reduction
        nValueIn = (nValueIn * 70) / 100;
    }
}

std::set<uint256> setFoundStakes;
bool Stake(CStakeInput* stakeInput, unsigned int nBits, unsigned int nTimeBlockFrom, unsigned int& nTimeTx, const CBlockIndex* pindexBest, uint256& hashProofOfStake, bool fWeightStake)
{
    if (nTimeTx < nTimeBlockFrom)
        return error("Stake() : nTime violation");

    if (nTimeBlockFrom + nStakeMinAge > nTimeTx) // Min age requirement
        return error("Stake() : min age violation - nTimeBlockFrom=%d nStakeMinAge=%d nTimeTx=%d",
                     nTimeBlockFrom, nStakeMinAge, nTimeTx);

    //grab difficulty
    arith_uint256 bnTargetPerCoinDay;
    bnTargetPerCoinDay.SetCompact(nBits);

    //grab stake modifier
    uint64_t nStakeModifier = 0;
    if (!stakeInput->GetModifier(nStakeModifier))
        return error("failed to get kernel stake modifier");

    bool fSuccess = false;
    unsigned int nTryTime = 0;
    int nHeightStart = pindexBest->nHeight;
    //staking too far into future increases chances of orphan
    int64_t nMaxTime = (int)GetAdjustedTime() + MAX_FUTURE_BLOCK_TIME - 40;

    CDataStream ssUniqueID = stakeInput->GetUniqueness();
    CAmount nValueIn = stakeInput->GetValue();

    //Adjust stake weights to larger denoms
    if (fWeightStake)
        WeightStake(nValueIn, stakeInput->GetDenomination());

    int nBestHeight = pindexBest->nHeight;
    uint256 hashBestBlock = pindexBest->GetBlockHash();
    if (!mapStakeHashCounter.count(nBestHeight))
        mapStakeHashCounter[nBestHeight] = 0;

    int i = 0;
    while (true) //iterate the hashing
    {
        //new block came in, move on
        if (chainActive.Height() != nHeightStart)
            break;

        //hash this iteration
        nTryTime = nTimeTx + i;
        if (nTryTime >= nMaxTime - 5)
            break;

        mapStakeHashCounter[nBestHeight]++;
        i++;

        // if stake hash does not meet the target then continue to next iteration
        if (!CheckStake(ssUniqueID, nValueIn, nStakeModifier, ArithToUint256(bnTargetPerCoinDay), nTimeBlockFrom, nTryTime, hashProofOfStake))
            continue;

        if (setFoundStakes.count(hashProofOfStake))
            continue;
        setFoundStakes.emplace(hashProofOfStake);

        fSuccess = true; // if we make it this far then we have successfully created a stake hash
        nTimeTx = nTryTime;
        break;
    }

    mapHashedBlocks.clear();
    mapHashedBlocks[hashBestBlock] = nTryTime; //store a time stamp of when we last hashed on this block
    return fSuccess;
}

// Check kernel hash target and coinstake signature
bool CheckProofOfStake(CBlockIndex* pindexCheck, const CTransactionRef txRef, const uint32_t& nBits, const unsigned int& nTimeBlock, uint256& hashProofOfStake, std::unique_ptr<CStakeInput>& stake)
{
    if (!txRef->IsCoinStake())
        return error("CheckProofOfStake() : called on non-coinstake %s", txRef->GetHash().ToString().c_str());

    //Construct the stakeinput object
    if (txRef->vin.size() != 1 && txRef->vin[0].IsZerocoinSpend())
        return error("%s: Stake is not a zerocoinspend", __func__);

    const CTxIn& txin = txRef->vin[0];

    auto spend = TxInToZerocoinSpend(txin);
    if (!spend)
        return false;
    stake = std::unique_ptr<CStakeInput>(new ZerocoinStake(*spend.get()));
    if (spend->getSpendType() != libzerocoin::SpendType::STAKE)
        return error("%s: spend is using the wrong SpendType (%d)", __func__, (int)spend->getSpendType());

    CBlockIndex* pindex = stake->GetIndexFrom();
    if (!pindex)
        return error("%s: Failed to find the block index", __func__);

    // Read block header
    CBlock blockprev;
    if (!ReadBlockFromDisk(blockprev, pindex->GetBlockPos(), Params().GetConsensus()))
        return error("CheckProofOfStake(): INFO: failed to find block");

    arith_uint256 bnTargetPerCoinDay;
    bnTargetPerCoinDay.SetCompact(nBits);

    uint64_t nStakeModifier = 0;
    if (!stake->GetModifier(nStakeModifier))
        return error("%s failed to get modifier for stake input\n", __func__);

    unsigned int nBlockFromTime = blockprev.nTime;
    unsigned int nTxTime = nTimeBlock;
    CAmount nValue = stake->GetValue();

    // Enforce VIP-1 after it was activated
    if ((int)nTxTime > Params().EnforceWeightReductionTime())
        WeightStake(nValue, stake->GetDenomination());

    if (!CheckStake(stake->GetUniqueness(), nValue, nStakeModifier, ArithToUint256(bnTargetPerCoinDay), nBlockFromTime,
                    nTxTime, hashProofOfStake)) {
        return error("CheckProofOfStake() : INFO: check kernel failed on coinstake %s, hashProof=%s \n",
                     txRef->GetHash().GetHex(), hashProofOfStake.GetHex());
    }

    return true;
}


// Boost.Geometry - gis-projections (based on PROJ4)

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017, 2018.
// Modifications copyright (c) 2017-2018, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 5.0.0

// Original copyright notice:

// Purpose:  Implementation of the krovak (Krovak) projection.
//           Definition: http://www.ihsenergy.com/epsg/guid7.html#1.4.3
// Author:   Thomas Flemming, tf@ttqv.com
// Copyright (c) 2001, Thomas Flemming, tf@ttqv.com

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#ifndef BOOST_GEOMETRY_PROJECTIONS_KROVAK_HPP
#define BOOST_GEOMETRY_PROJECTIONS_KROVAK_HPP

#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>

namespace boost { namespace geometry
{

namespace srs { namespace par4
{
    struct krovak {}; // Krovak

}} //namespace srs::par4

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace krovak
    {
            static double epsilon = 1e-15;
            static double S45 = 0.785398163397448;  /* 45 deg */
            static double S90 = 1.570796326794896;  /* 90 deg */
            static double UQ  = 1.04216856380474;   /* DU(2, 59, 42, 42.69689) */
            static double S0  = 1.37008346281555;   /* Latitude of pseudo standard parallel 78deg 30'00" N */
            /* Not sure at all of the appropriate number for max_iter... */
            static int max_iter = 100;

            template <typename T>
            struct par_krovak
            {
                T alpha;
                T k;
                T n;
                T rho0;
                T ad;
                int czech;
            };

            /**
               NOTES: According to EPSG the full Krovak projection method should have
                      the following parameters.  Within PROJ.4 the azimuth, and pseudo
                      standard parallel are hardcoded in the algorithm and can't be
                      altered from outside.  The others all have defaults to match the
                      common usage with Krovak projection.

              lat_0 = latitude of centre of the projection

              lon_0 = longitude of centre of the projection

              ** = azimuth (true) of the centre line passing through the centre of the projection

              ** = latitude of pseudo standard parallel

              k  = scale factor on the pseudo standard parallel

              x_0 = False Easting of the centre of the projection at the apex of the cone

              y_0 = False Northing of the centre of the projection at the apex of the cone

             **/

            // template class, using CRTP to implement forward/inverse
            template <typename T, typename Parameters>
            struct base_krovak_ellipsoid
                : public base_t_fi<base_krovak_ellipsoid<T, Parameters>, T, Parameters>
            {
                par_krovak<T> m_proj_parm;

                inline base_krovak_ellipsoid(const Parameters& par)
                    : base_t_fi<base_krovak_ellipsoid<T, Parameters>, T, Parameters>(*this, par)
                {}

                // FORWARD(e_forward)  ellipsoid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T& lp_lon, T& lp_lat, T& xy_x, T& xy_y) const
                {
                    T gfi, u, deltav, s, d, eps, rho;

                    gfi = math::pow( (T(1) + this->m_par.e * sin(lp_lat)) / (T(1) - this->m_par.e * sin(lp_lat)), this->m_proj_parm.alpha * this->m_par.e / T(2));

                    u = 2. * (atan(this->m_proj_parm.k * math::pow( tan(lp_lat / T(2) + S45), this->m_proj_parm.alpha) / gfi)-S45);
                    deltav = -lp_lon * this->m_proj_parm.alpha;

                    s = asin(cos(this->m_proj_parm.ad) * sin(u) + sin(this->m_proj_parm.ad) * cos(u) * cos(deltav));
                    d = asin(cos(u) * sin(deltav) / cos(s));

                    eps = this->m_proj_parm.n * d;
                    rho = this->m_proj_parm.rho0 * math::pow(tan(S0 / T(2) + S45) , this->m_proj_parm.n) / math::pow(tan(s / T(2) + S45) , this->m_proj_parm.n);

                    xy_y = rho * cos(eps);
                    xy_x = rho * sin(eps);

                    xy_y *= this->m_proj_parm.czech;
                    xy_x *= this->m_proj_parm.czech;
                }

                // INVERSE(e_inverse)  ellipsoid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T& xy_x, T& xy_y, T& lp_lon, T& lp_lat) const
                {
                    T u, deltav, s, d, eps, rho, fi1, xy0;
                    int i;

                    // TODO: replace with std::swap()
                    xy0 = xy_x;
                    xy_x = xy_y;
                    xy_y = xy0;

                    xy_x *= this->m_proj_parm.czech;
                    xy_y *= this->m_proj_parm.czech;

                    rho = sqrt(xy_x * xy_x + xy_y * xy_y);
                    eps = atan2(xy_y, xy_x);

                    d = eps / sin(S0);
                    s = T(2) * (atan(math::pow(this->m_proj_parm.rho0 / rho, T(1) / this->m_proj_parm.n) * tan(S0 / T(2) + S45)) - S45);

                    u = asin(cos(this->m_proj_parm.ad) * sin(s) - sin(this->m_proj_parm.ad) * cos(s) * cos(d));
                    deltav = asin(cos(s) * sin(d) / cos(u));

                    lp_lon = this->m_par.lam0 - deltav / this->m_proj_parm.alpha;

                    /* ITERATION FOR lp_lat */
                    fi1 = u;

                    for (i = max_iter; i ; --i) {
                        lp_lat = T(2) * ( atan( math::pow( this->m_proj_parm.k, T(-1) / this->m_proj_parm.alpha)  *
                                              math::pow( tan(u / T(2) + S45) , T(1) / this->m_proj_parm.alpha)  *
                                              math::pow( (T(1) + this->m_par.e * sin(fi1)) / (T(1) - this->m_par.e * sin(fi1)) , this->m_par.e / T(2))
                                            )  - S45);

                        if (fabs(fi1 - lp_lat) < epsilon)
                            break;
                        fi1 = lp_lat;
                    }
                    if( i == 0 )
                        BOOST_THROW_EXCEPTION( projection_exception(error_non_convergent) );

                   lp_lon -= this->m_par.lam0;
                }

                static inline std::string get_name()
                {
                    return "krovak_ellipsoid";
                }

            };

            // Krovak
            template <typename Parameters, typename T>
            inline void setup_krovak(Parameters& par, par_krovak<T>& proj_parm)
            {
                T u0, n0, g;

                /* we want Bessel as fixed ellipsoid */
                par.a = 6377397.155;
                par.e = sqrt(par.es = 0.006674372230614);

                /* if latitude of projection center is not set, use 49d30'N */
                if (!pj_param_exists(par.params, "lat_0"))
                    par.phi0 = 0.863937979737193;

                /* if center long is not set use 42d30'E of Ferro - 17d40' for Ferro */
                /* that will correspond to using longitudes relative to greenwich    */
                /* as input and output, instead of lat/long relative to Ferro */
                if (!pj_param_exists(par.params, "lon_0"))
                    par.lam0 = 0.7417649320975901 - 0.308341501185665;

                /* if scale not set default to 0.9999 */
                if (!pj_param_exists(par.params, "k"))
                    par.k0 = 0.9999;

                proj_parm.czech = 1;
                if( !pj_param_exists(par.params, "czech") )
                    proj_parm.czech = -1;

                /* Set up shared parameters between forward and inverse */
                proj_parm.alpha = sqrt(T(1) + (par.es * math::pow(cos(par.phi0), 4)) / (T(1) - par.es));
                u0 = asin(sin(par.phi0) / proj_parm.alpha);
                g = math::pow( (T(1) + par.e * sin(par.phi0)) / (T(1) - par.e * sin(par.phi0)) , proj_parm.alpha * par.e / T(2) );
                proj_parm.k = tan( u0 / 2. + S45) / math::pow(tan(par.phi0 / T(2) + S45) , proj_parm.alpha) * g;
                n0 = sqrt(T(1) - par.es) / (T(1) - par.es * math::pow(sin(par.phi0), 2));
                proj_parm.n = sin(S0);
                proj_parm.rho0 = par.k0 * n0 / tan(S0);
                proj_parm.ad = S90 - UQ;
            }

    }} // namespace detail::krovak
    #endif // doxygen

    /*!
        \brief Krovak projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Pseudocylindrical
         - Ellipsoid
        \par Projection parameters
         - lat_ts: Latitude of true scale (degrees)
         - lat_0: Latitude of origin
         - lon_0: Central meridian
         - k: Scale factor on the pseudo standard parallel
        \par Example
        \image html ex_krovak.gif
    */
    template <typename T, typename Parameters>
    struct krovak_ellipsoid : public detail::krovak::base_krovak_ellipsoid<T, Parameters>
    {
        inline krovak_ellipsoid(const Parameters& par) : detail::krovak::base_krovak_ellipsoid<T, Parameters>(par)
        {
            detail::krovak::setup_krovak(this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::par4::krovak, krovak_ellipsoid, krovak_ellipsoid)

        // Factory entry(s)
        template <typename T, typename Parameters>
        class krovak_entry : public detail::factory_entry<T, Parameters>
        {
            public :
                virtual base_v<T, Parameters>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<krovak_ellipsoid<T, Parameters>, T, Parameters>(par);
                }
        };

        template <typename T, typename Parameters>
        inline void krovak_init(detail::base_factory<T, Parameters>& factory)
        {
            factory.add_to_factory("krovak", new krovak_entry<T, Parameters>);
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_KROVAK_HPP


//==================================================================================================
/**
  Copyright 2017 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_FMASUBADD_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_FMASUBADD_HPP_INCLUDED

#include <boost/simd/detail/overload.hpp>
#include <boost/simd/function/subadd.hpp>
#include <boost/simd/meta/hierarchy/simd.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
  namespace bs = boost::simd;

  BOOST_DISPATCH_OVERLOAD ( fmasubadd_
                          , (typename A0, typename X)
                          , bd::cpu_
                          , bs::pack_<bd::arithmetic_<A0>, X>
                          , bs::pack_<bd::arithmetic_<A0>, X>
                          , bs::pack_<bd::arithmetic_<A0>, X>
                          )
   {
      BOOST_FORCEINLINE A0 operator()( const A0& a0, const A0& a1
                                     , const A0& a2) const BOOST_NOEXCEPT
      {
        return subadd(a0*a1, a2);
      }
   };
} } }

#endif


/* Copyright © 2017 Apple Inc. All rights reserved.
 *
 * Use of this source code is governed by a BSD-3-clause license that can
 * be found in the LICENSE.txt file or at https://opensource.org/licenses/BSD-3-Clause
 */
#ifndef TURI_DML_DATA_ROW_TRANSATION_H_
#define TURI_DML_DATA_ROW_TRANSATION_H_

#include <ml/ml_data/data_storage/ml_data_row_format.hpp>
#include <ml/ml_data/ml_data_entry.hpp>
#include <ml/ml_data/ml_data_column_modes.hpp>
#include <ml/ml_data/data_storage/internal_metadata.hpp>
#include <core/data/flexible_type/flexible_type.hpp>
#include <core/util/code_optimization.hpp>
#include <type_traits>

#include <Eigen/SparseCore>
#include <Eigen/Core>

#include <array>

namespace turi {

typedef Eigen::Matrix<double, Eigen::Dynamic,1>  DenseVector;
typedef Eigen::SparseVector<double> SparseVector;

////////////////////////////////////////////////////////////////////////////////
// Translation routines to the basic ml_data_entry type

/** Translation from one row type to another
 */
std::vector<ml_data_entry> translate_row_to_ml_data_entry(
    const std::shared_ptr<ml_metadata>& metadata,
    const std::vector<ml_data_entry_global_index>& row);

/** Translation routines.
 */
std::vector<ml_data_entry> translate_row_to_ml_data_entry(
    const std::shared_ptr<ml_metadata>& metadata,
    const DenseVector& row);

/** Translates the original sparse row format to the ml_data_entry
 *  vector.
 */
std::vector<ml_data_entry> translate_row_to_ml_data_entry(
    const std::shared_ptr<ml_metadata>& metadata,
    const SparseVector& v);

////////////////////////////////////////////////////////////////////////////////
// translation routines to the original row type

std::vector<flexible_type> translate_row_to_original(
    const std::shared_ptr<ml_metadata>& metadata,
    const std::vector<ml_data_entry>& row);

/** Translates the original sparse row format to the original flexible
 *  types.
 */
std::vector<flexible_type> translate_row_to_original(
    const std::shared_ptr<ml_metadata>& metadata,
    const DenseVector& v);

/** Translates the original sparse row format to the original flexible
 *  types.
 */
std::vector<flexible_type> translate_row_to_original(
    const std::shared_ptr<ml_metadata>& metadata,
    const SparseVector& v);

/** Translate a vector of global indices to the next
 */
std::vector<flexible_type> translate_row_to_original(
    const std::shared_ptr<ml_metadata>& metadata,
    const std::vector<ml_data_entry_global_index>& row);

}

////////////////////////////////////////////////////////////////////////////////

#endif /* _ML_DATA_ROW_FORMAT_H_ */


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_RING_PROPERTIES_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_RING_PROPERTIES_HPP

#include <boost/geometry/algorithms/area.hpp>
#include <boost/geometry/algorithms/within.hpp>
#include <boost/geometry/algorithms/detail/point_on_border.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace overlay
{

template <typename Point>
struct ring_properties
{
    typedef Point point_type;
    typedef typename default_area_result<Point>::type area_type;

    // Filled by "select_rings"
    Point point;
    area_type area;

    // Filled by "update_ring_selection"
    bool reversed;

    // Filled/used by "assign_rings"
    bool discarded;
    ring_identifier parent;
    area_type parent_area;
    std::vector<ring_identifier> children;

    inline ring_properties()
        : area(area_type())
        , reversed(false)
        , discarded(false)
        , parent_area(-1)
    {}

    template <typename RingOrBox>
    inline ring_properties(RingOrBox const& ring_or_box)
        : reversed(false)
        , discarded(false)
        , parent_area(-1)
    {
        this->area = geometry::area(ring_or_box);
        // We should take a point somewhere in the middle of the ring,
        // to avoid taking a point on a (self)tangency,
        // in cases where multiple points come together
        geometry::point_on_border(this->point, ring_or_box, true);
    }

    inline area_type get_area() const
    {
        return reversed ? -area : area;
    }
};

}} // namespace detail::overlay
#endif // DOXYGEN_NO_DETAIL

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_RING_PROPERTIES_HPP


#ifndef UTILS_HPP
#define UTILS_HPP

#include <cstdlib>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/matrix_sparse.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/numeric/ublas/matrix_proxy.hpp>
#include <boost/numeric/ublas/vector_proxy.hpp>
#include <boost/exception/all.hpp>

namespace singularity {
    typedef boost::numeric::ublas::mapped_matrix<double, boost::numeric::ublas::row_major> matrix_t;
    typedef boost::numeric::ublas::vector<double> vector_t;
    typedef boost::numeric::ublas::mapped_vector<double> sparce_vector_t;
    typedef unsigned long int index_t;
    typedef boost::numeric::ublas::matrix_range<matrix_t> matrix_range_t;
    typedef boost::numeric::ublas::vector_range<vector_t> vector_range_t;
    typedef boost::numeric::ublas::range range_t;
    typedef boost::numeric::ublas::mapped_matrix<uint8_t, boost::numeric::ublas::row_major> byte_matrix_t;

    struct parameters_t {
        uint account_amount_threshold = 10000;
        uint transaction_amount_threshold = 100;
        double outlink_weight = 0.7;
        double interlevel_weight = 0.1;
        uint clustering_m = 4;
        double clustering_e = 0.3;
        uint decay_period = 86400; // 24h = 86400 1s-blocks
        double decay_koefficient = 0.9;
        unsigned int num_threads = 1;
        double token_usd_rate = 1;
    };

    namespace matrix_tools
    {
        void normalize_columns(matrix_t &m);
        void normalize_rows(matrix_t &m);
        sparce_vector_t calculate_correction_vector(const matrix_t& o);
        std::shared_ptr<matrix_t> resize(matrix_t& m, matrix_t::size_type size1, matrix_t::size_type size2);
        void prod( vector_t& out, const matrix_t& m, const vector_t& v, unsigned int num_threads);
        void partial_prod( vector_t& out, const matrix_t& m, const vector_t& v, range_t range);
        std::vector<range_t> split_range(range_t range, unsigned int max);
    };

    class runtime_exception: public std::runtime_error
    {
    public:
        runtime_exception(const std::string& __arg) :
        runtime_error(__arg) {
        }
    };
}

#endif /* UTILS_HPP */