#include "estimator.h"
#include <boost/math/special_functions/digamma.hpp>

namespace statiskit
{
    PoissonDistributionMLEstimation::PoissonDistributionMLEstimation() : ActiveEstimation< PoissonDistribution, DiscreteUnivariateDistributionEstimation >()
    {}

    PoissonDistributionMLEstimation::PoissonDistributionMLEstimation(PoissonDistribution const * estimated, UnivariateData const * data) : ActiveEstimation< PoissonDistribution, DiscreteUnivariateDistributionEstimation >(estimated, data)
    {}

    PoissonDistributionMLEstimation::PoissonDistributionMLEstimation(const PoissonDistributionMLEstimation& estimation) : ActiveEstimation< PoissonDistribution, DiscreteUnivariateDistributionEstimation >(estimation)
    {}

    PoissonDistributionMLEstimation::~PoissonDistributionMLEstimation()
    {}

    PoissonDistributionMLEstimation::Estimator::Estimator()
    {}

    PoissonDistributionMLEstimation::Estimator::Estimator(const Estimator& estimator)
    {}

    PoissonDistributionMLEstimation::Estimator::~Estimator()
    {}

    std::unique_ptr< UnivariateDistributionEstimation > PoissonDistributionMLEstimation::Estimator::operator() (const UnivariateData& data, const bool& lazy) const
    {
        if(data.get_sample_space()->get_outcome() != DISCRETE)
        { throw statiskit::sample_space_error(DISCRETE); }
        std::unique_ptr< UnivariateDistributionEstimation > estimation;
        UnivariateMeanEstimation::Estimator estimator = UnivariateMeanEstimation::Estimator();
        std::unique_ptr< UnivariateLocationEstimation > _estimation = estimator(data);
        double mean = _estimation->get_location();
        if(boost::math::isfinite(mean))
        {
            PoissonDistribution* poisson = new PoissonDistribution(mean);
            if(lazy)
            { estimation = std::make_unique< LazyEstimation< PoissonDistribution, DiscreteUnivariateDistributionEstimation > >(poisson); }
            else
            { estimation = std::make_unique< PoissonDistributionMLEstimation >(poisson, &data); }
        }
        return estimation;
    }

    std::unique_ptr< UnivariateDistributionEstimation::Estimator > PoissonDistributionMLEstimation::Estimator::copy() const
    { return std::make_unique< Estimator >(*this); }

    BinomialDistributionMLEstimation::BinomialDistributionMLEstimation() : OptimizationEstimation<unsigned int, BinomialDistribution, DiscreteUnivariateDistributionEstimation >()
    {}

    BinomialDistributionMLEstimation::BinomialDistributionMLEstimation(BinomialDistribution const * estimated, UnivariateData const * data) : OptimizationEstimation<unsigned int, BinomialDistribution, DiscreteUnivariateDistributionEstimation >(estimated, data)
    {}

    BinomialDistributionMLEstimation::BinomialDistributionMLEstimation(const BinomialDistributionMLEstimation& estimation) : OptimizationEstimation<unsigned int, BinomialDistribution, DiscreteUnivariateDistributionEstimation >(estimation)
    {}

    BinomialDistributionMLEstimation::~BinomialDistributionMLEstimation()
    {}

    BinomialDistributionMLEstimation::Estimator::Estimator() : OptimizationEstimation<unsigned int, BinomialDistribution, DiscreteUnivariateDistributionEstimation >::Estimator()
    { _force = false; }

    BinomialDistributionMLEstimation::Estimator::Estimator(const Estimator& estimator) : OptimizationEstimation<unsigned int, BinomialDistribution, DiscreteUnivariateDistributionEstimation >::Estimator(estimator)
    { _force = estimator._force; }

    BinomialDistributionMLEstimation::Estimator::~Estimator()
    {}

    std::unique_ptr< UnivariateDistributionEstimation > BinomialDistributionMLEstimation::Estimator::operator() (const UnivariateData& data, const bool& lazy) const
    {
        if(data.get_sample_space()->get_outcome() != DISCRETE)
        { throw statiskit::sample_space_error(DISCRETE); }
        std::unique_ptr< UnivariateDistributionEstimation > estimation;
        UnivariateMeanEstimation::Estimator mean_estimator = UnivariateMeanEstimation::Estimator();
        std::unique_ptr< UnivariateLocationEstimation > mean_estimation = mean_estimator(data);
        double mean = mean_estimation->get_location();
        UnivariateVarianceEstimation::Estimator variance_estimator = UnivariateVarianceEstimation::Estimator(false);
        std::unique_ptr< UnivariateDispersionEstimation > variance_estimation = variance_estimator(data, mean);
        double variance = variance_estimation->get_dispersion();
        if(variance > mean && !_force)
        { throw overdispersion_error(); }
        unsigned int kappa = std::max<int>(round(pow(mean, 2)/(mean - variance)), static_cast< DiscreteElementaryEvent* >(data.compute_maximum().get())->get_value());
        BinomialDistribution* binomial = new BinomialDistribution(kappa, mean/double(kappa));
        if(!lazy)
        {
            estimation = std::make_unique< BinomialDistributionMLEstimation >(binomial, &data);
            static_cast< BinomialDistributionMLEstimation* >(estimation.get())->_iterations.push_back(kappa);
        }
        else
        { estimation = std::make_unique< LazyEstimation< BinomialDistribution, DiscreteUnivariateDistributionEstimation > >(binomial); }
        double curr, prev = binomial->loglikelihood(data);
        unsigned int its = 1;
        --kappa;
        if(kappa > mean)
        {
            if(!lazy)
            { static_cast< BinomialDistributionMLEstimation* >(estimation.get())->_iterations.push_back(kappa); }
            binomial->set_kappa(kappa);
            binomial->set_pi(mean/double(kappa));
            curr = binomial->loglikelihood(data);
        }
        else
        { curr = prev; }
        if(curr > prev) {
            if (mean/(kappa-1) >= 0.0 && mean/(kappa-1) <= 1.0) {
                do
                {
                    prev = curr;
                    --kappa;
                    if(!lazy)
                    { static_cast< BinomialDistributionMLEstimation* >(estimation.get())->_iterations.push_back(kappa); }
                    binomial->set_kappa(kappa);
                    binomial->set_pi(mean/double(kappa));
                    curr = binomial->loglikelihood(data);
                    ++its;
                } while(run(its, __impl::reldiff(prev, curr)) && curr > prev);
            }
            if(curr < prev)
            {
                ++kappa;
                if(!lazy)
                { static_cast< BinomialDistributionMLEstimation* >(estimation.get())->_iterations.push_back(kappa); }
                binomial->set_kappa(kappa);
                binomial->set_pi(mean/double(kappa));
            }
        }
        else
        {
            curr = prev;
            do
            {
                prev = curr;
                ++kappa;
                if(!lazy)
                { static_cast< BinomialDistributionMLEstimation* >(estimation.get())->_iterations.push_back(kappa); }
                binomial->set_kappa(kappa);
                binomial->set_pi(mean/double(kappa));
                curr = binomial->loglikelihood(data);
                ++its;
            } while(run(its, __impl::reldiff(prev, curr)) && curr > prev);
            if(curr < prev)
            {
                --kappa;
                if(!lazy)
                { static_cast< BinomialDistributionMLEstimation* >(estimation.get())->_iterations.push_back(kappa); }
                binomial->set_kappa(kappa);
                binomial->set_pi(mean/double(kappa));
            }
        }
        return estimation;
    }

    bool BinomialDistributionMLEstimation::Estimator::get_force() const
    { return _force; }

    void BinomialDistributionMLEstimation::Estimator::set_force(const bool& force)
    { _force = force; }

    std::unique_ptr< UnivariateDistributionEstimation::Estimator > BinomialDistributionMLEstimation::Estimator::copy() const
    { return std::make_unique< Estimator >(*this); }

    BinomialDistributionMMEstimation::BinomialDistributionMMEstimation() : ActiveEstimation< BinomialDistribution, DiscreteUnivariateDistributionEstimation >()
    {}

    BinomialDistributionMMEstimation::BinomialDistributionMMEstimation(BinomialDistribution const * estimated, UnivariateData const * data) : ActiveEstimation< BinomialDistribution, DiscreteUnivariateDistributionEstimation >(estimated, data)
    {}

    BinomialDistributionMMEstimation::BinomialDistributionMMEstimation(const BinomialDistributionMMEstimation& estimation) : ActiveEstimation< BinomialDistribution, DiscreteUnivariateDistributionEstimation >(estimation)
    {}

    BinomialDistributionMMEstimation::~BinomialDistributionMMEstimation()
    {}

    BinomialDistributionMMEstimation::Estimator::Estimator()
    {
        _location = new UnivariateMeanEstimation::Estimator();
        _dispersion = new UnivariateVarianceEstimation::Estimator(false);
    }

    BinomialDistributionMMEstimation::Estimator::Estimator(const Estimator& estimator)
    {
        _location = estimator._location->copy().release();
        _dispersion = estimator._dispersion->copy().release();
    }

    BinomialDistributionMMEstimation::Estimator::~Estimator()
    {
        delete _location;
        delete _dispersion;
    }

    std::unique_ptr< UnivariateDistributionEstimation > BinomialDistributionMMEstimation::Estimator::operator() (const UnivariateData& data, const bool& lazy) const
    {
        if(data.get_sample_space()->get_outcome() != DISCRETE)
        { throw statiskit::sample_space_error(DISCRETE); }
        std::unique_ptr< UnivariateDistributionEstimation > estimation;
        std::unique_ptr< UnivariateLocationEstimation > mean_estimation = (*_location)(data);
        double mean = mean_estimation->get_location();
        std::unique_ptr< UnivariateDispersionEstimation > variance_estimation = (*_dispersion)(data, mean);
        double variance = variance_estimation->get_dispersion();
        if(boost::math::isfinite(mean) && boost::math::isfinite(variance) && mean > variance)
        {
            unsigned int kappa = std::max<int>(round(pow(mean, 2)/(mean - variance)), static_cast< DiscreteElementaryEvent* >(data.compute_maximum().get())->get_value());
            BinomialDistribution* binomial = new BinomialDistribution(kappa, mean/double(kappa));
            if(lazy)
            { estimation = std::make_unique< LazyEstimation< BinomialDistribution, DiscreteUnivariateDistributionEstimation > >(binomial); }
            else
            { estimation = std::make_unique< BinomialDistributionMMEstimation >(binomial, &data); }
        }
        else
        { throw overdispersion_error(); }
        return estimation;
    }

    std::unique_ptr< UnivariateDistributionEstimation::Estimator > BinomialDistributionMMEstimation::Estimator::copy() const
    { return std::make_unique< Estimator >(*this); }

    UnivariateLocationEstimation::Estimator* BinomialDistributionMMEstimation::Estimator::get_location()
    { return _location; }

    void BinomialDistributionMMEstimation::Estimator::set_location(const UnivariateLocationEstimation::Estimator& location)
    { _location = location.copy().release(); }

    UnivariateDispersionEstimation::Estimator* BinomialDistributionMMEstimation::Estimator::get_dispersion()
    { return _dispersion; }

    void BinomialDistributionMMEstimation::Estimator::set_dispersion(const UnivariateDispersionEstimation::Estimator& dispersion)
    { _dispersion = dispersion.copy().release(); }

    LogarithmicDistributionMLEstimation::LogarithmicDistributionMLEstimation() : OptimizationEstimation<double, LogarithmicDistribution, DiscreteUnivariateDistributionEstimation >()
    {}

    LogarithmicDistributionMLEstimation::LogarithmicDistributionMLEstimation(LogarithmicDistribution const * estimated, UnivariateData const * data) : OptimizationEstimation<double, LogarithmicDistribution, DiscreteUnivariateDistributionEstimation >(estimated, data)
    {}

    LogarithmicDistributionMLEstimation::LogarithmicDistributionMLEstimation(const LogarithmicDistributionMLEstimation& estimation) : OptimizationEstimation<double, LogarithmicDistribution, DiscreteUnivariateDistributionEstimation >(estimation)
    {}

    LogarithmicDistributionMLEstimation::~LogarithmicDistributionMLEstimation()
    {}

    LogarithmicDistributionMLEstimation::Estimator::Estimator()
    {}

    LogarithmicDistributionMLEstimation::Estimator::Estimator(const Estimator& estimator)
    {}

    LogarithmicDistributionMLEstimation::Estimator::~Estimator()
    {}

    std::unique_ptr< UnivariateDistributionEstimation > LogarithmicDistributionMLEstimation::Estimator::operator() (const UnivariateData& data, const bool& lazy) const
    {
        if(data.get_sample_space()->get_outcome() != DISCRETE)
        { throw statiskit::sample_space_error(DISCRETE); }
        std::unique_ptr< UnivariateDistributionEstimation > estimation;
        UnivariateMeanEstimation::Estimator mean_estimator = UnivariateMeanEstimation::Estimator();
        std::unique_ptr< UnivariateLocationEstimation > mean_estimation = mean_estimator(data);
        double mean = mean_estimation->get_location();
        double theta = 1 + 2 * (mean - 1);
        if(theta <= 1)
        { throw parameter_error("data", " has a mean inferior or equal to 1"); }
        theta = 1 - 1 / theta;
        LogarithmicDistribution* logarithmic = new LogarithmicDistribution(theta);
        if(!lazy)
        {
            estimation = std::make_unique< LogarithmicDistributionMLEstimation >(logarithmic, &data);
            static_cast< LogarithmicDistributionMLEstimation* >(estimation.get())->_iterations.push_back(theta);
        }
        else
        { estimation = std::make_unique< LazyEstimation< LogarithmicDistribution, DiscreteUnivariateDistributionEstimation > >(logarithmic); }
        double prev, curr = logarithmic->loglikelihood(data);
        unsigned int its = 0;
        do
        {
            prev = curr;
            theta = mean * log(1 - theta) / (mean * log(1 - theta) - 1);
            if(theta > 0. && theta < 1.)
            {
                if(!lazy)
                { static_cast< LogarithmicDistributionMLEstimation* >(estimation.get())->_iterations.push_back(theta); }
                logarithmic->set_theta(theta);
                curr = logarithmic->loglikelihood(data);
                ++its;
            }
        } while(run(its, __impl::reldiff(prev, curr)) && curr > prev);
        return estimation;
    }

    std::unique_ptr< UnivariateDistributionEstimation::Estimator > LogarithmicDistributionMLEstimation::Estimator::copy() const
    { return std::make_unique< Estimator >(*this); }

    GeometricDistributionMLEstimation::GeometricDistributionMLEstimation() : ActiveEstimation<GeometricDistribution, DiscreteUnivariateDistributionEstimation >()
    {}

    GeometricDistributionMLEstimation::GeometricDistributionMLEstimation(GeometricDistribution const * estimated, UnivariateData const * data) : ActiveEstimation<GeometricDistribution, DiscreteUnivariateDistributionEstimation >(estimated, data)
    {}

    GeometricDistributionMLEstimation::GeometricDistributionMLEstimation(const GeometricDistributionMLEstimation& estimation) : ActiveEstimation<GeometricDistribution, DiscreteUnivariateDistributionEstimation >(estimation)
    {}

    GeometricDistributionMLEstimation::~GeometricDistributionMLEstimation()
    {}

    GeometricDistributionMLEstimation::Estimator::Estimator()
    {}

    GeometricDistributionMLEstimation::Estimator::Estimator(const Estimator& estimator)
    {}

    GeometricDistributionMLEstimation::Estimator::~Estimator()
    {}

    std::unique_ptr< UnivariateDistributionEstimation > GeometricDistributionMLEstimation::Estimator::operator() (const UnivariateData& data, const bool& lazy) const
    {
        if(data.get_sample_space()->get_outcome() != DISCRETE)
        { throw statiskit::sample_space_error(DISCRETE); }
        UnivariateMeanEstimation::Estimator mean_estimator = UnivariateMeanEstimation::Estimator();
        std::unique_ptr< UnivariateLocationEstimation > mean_estimation = mean_estimator(data);
        double mean = mean_estimation->get_location();
        GeometricDistribution* geometric = new GeometricDistribution(1 - 1 / mean);
        std::unique_ptr< UnivariateDistributionEstimation > estimation;
        if(lazy)
        { estimation = std::make_unique< LazyEstimation< GeometricDistribution, DiscreteUnivariateDistributionEstimation > >(geometric); }
        else
        { estimation = std::make_unique< GeometricDistributionMLEstimation >(geometric, &data); }
        return estimation;
    }

    std::unique_ptr< UnivariateDistributionEstimation::Estimator > GeometricDistributionMLEstimation::Estimator::copy() const
    { return std::make_unique< Estimator >(*this); }

    NegativeBinomialDistributionMLEstimation::NegativeBinomialDistributionMLEstimation() : OptimizationEstimation<double, NegativeBinomialDistribution, DiscreteUnivariateDistributionEstimation >()
    {}

    NegativeBinomialDistributionMLEstimation::NegativeBinomialDistributionMLEstimation(NegativeBinomialDistribution const * estimated, UnivariateData const * data) : OptimizationEstimation<double, NegativeBinomialDistribution, DiscreteUnivariateDistributionEstimation >(estimated, data)
    {}

    NegativeBinomialDistributionMLEstimation::NegativeBinomialDistributionMLEstimation(const NegativeBinomialDistributionMLEstimation& estimation) : OptimizationEstimation<double, NegativeBinomialDistribution, DiscreteUnivariateDistributionEstimation >(estimation)
    {}

    NegativeBinomialDistributionMLEstimation::~NegativeBinomialDistributionMLEstimation()
    {}

    NegativeBinomialDistributionMLEstimation::Estimator::Estimator() : OptimizationEstimation<double, NegativeBinomialDistribution, DiscreteUnivariateDistributionEstimation >::Estimator()
    { _force = false; }

    NegativeBinomialDistributionMLEstimation::Estimator::Estimator(const Estimator& estimator) : OptimizationEstimation<double, NegativeBinomialDistribution, DiscreteUnivariateDistributionEstimation >::Estimator(estimator)
    { _force = estimator._force; }

    NegativeBinomialDistributionMLEstimation::Estimator::~Estimator()
    {}

    std::unique_ptr< UnivariateDistributionEstimation > NegativeBinomialDistributionMLEstimation::Estimator::operator() (const UnivariateData& data, const bool& lazy) const
    {
        if(data.get_sample_space()->get_outcome() != DISCRETE)
        { throw statiskit::sample_space_error(DISCRETE); }
        std::unique_ptr< UnivariateDistributionEstimation > estimation;
        UnivariateMeanEstimation::Estimator mean_estimator = UnivariateMeanEstimation::Estimator();
        std::unique_ptr< UnivariateLocationEstimation > mean_estimation = mean_estimator(data);
        double mean = mean_estimation->get_location();
        UnivariateVarianceEstimation::Estimator variance_estimator = UnivariateVarianceEstimation::Estimator(false);
        std::unique_ptr< UnivariateDispersionEstimation > variance_estimation = variance_estimator(data, mean);
        double variance = variance_estimation->get_dispersion();
        if(variance < mean && !_force)
        { throw underdispersion_error(); }
        double total = data.compute_total(), kappa;
        if(variance > mean)
        { kappa = pow(mean, 2)/(variance - mean); }
        else
        { kappa = 1.; }
        NegativeBinomialDistribution* negative_binomial = new NegativeBinomialDistribution(kappa, mean / (mean + kappa));
        if(!lazy)
        {
            estimation = std::make_unique< NegativeBinomialDistributionMLEstimation >(negative_binomial, &data);
            static_cast< NegativeBinomialDistributionMLEstimation* >(estimation.get())->_iterations.push_back(kappa);
        }
        else
        { estimation = std::make_unique< LazyEstimation< NegativeBinomialDistribution, DiscreteUnivariateDistributionEstimation > >(negative_binomial); }
        double prev, curr = negative_binomial->loglikelihood(data);
        unsigned int its = 1;
        do
        {
            prev = curr;
            double alpha = 0;
            std::unique_ptr< UnivariateData::Generator > generator = data.generator();
            while(generator->is_valid())
            {
                const UnivariateEvent* event = generator->event();
                if(event && event->get_event() == ELEMENTARY)
                {
                    for(int nu = 0, max_nu = static_cast< const DiscreteElementaryEvent* >(event)->get_value(); nu < max_nu; ++nu)
                    { alpha += nu / (nu + kappa); }
                }
                ++(*generator);
            }
            alpha /= -total;
            alpha += mean;
            kappa = alpha / log(1 + mean/kappa);
            if(kappa > 0.)
            {
                if(!lazy)
                { static_cast< NegativeBinomialDistributionMLEstimation* >(estimation.get())->_iterations.push_back(kappa); }
                negative_binomial->set_kappa(kappa);
                negative_binomial->set_pi(mean / (mean + kappa));
                curr = negative_binomial->loglikelihood(data);
                ++its;
            }
        } while(run(its, __impl::reldiff(prev, curr)) && curr > prev);
        return estimation;
    }

    bool NegativeBinomialDistributionMLEstimation::Estimator::get_force() const
    { return _force; }

    void NegativeBinomialDistributionMLEstimation::Estimator::set_force(const bool& force)
    { _force = force; }

    std::unique_ptr< UnivariateDistributionEstimation::Estimator > NegativeBinomialDistributionMLEstimation::Estimator::copy() const
    { return std::make_unique< Estimator >(*this); }

    NegativeBinomialDistributionMMEstimation::NegativeBinomialDistributionMMEstimation() : ActiveEstimation< NegativeBinomialDistribution, DiscreteUnivariateDistributionEstimation >()
    {}

    NegativeBinomialDistributionMMEstimation::NegativeBinomialDistributionMMEstimation(NegativeBinomialDistribution const * estimated, UnivariateData const * data) : ActiveEstimation< NegativeBinomialDistribution, DiscreteUnivariateDistributionEstimation >(estimated, data)
    {}

    NegativeBinomialDistributionMMEstimation::NegativeBinomialDistributionMMEstimation(const NegativeBinomialDistributionMMEstimation& estimation) : ActiveEstimation< NegativeBinomialDistribution, DiscreteUnivariateDistributionEstimation >(estimation)
    {}

    NegativeBinomialDistributionMMEstimation::~NegativeBinomialDistributionMMEstimation()
    {}

    NegativeBinomialDistributionMMEstimation::Estimator::Estimator()
    {
        _location = new UnivariateMeanEstimation::Estimator();
        _dispersion = new UnivariateVarianceEstimation::Estimator(false);
    }

    NegativeBinomialDistributionMMEstimation::Estimator::Estimator(const Estimator& estimator)
    {
        _location = estimator._location->copy().release();
        _dispersion = estimator._dispersion->copy().release();
    }

    NegativeBinomialDistributionMMEstimation::Estimator::~Estimator()
    {
        delete _location;
        delete _dispersion;
    }

    std::unique_ptr< UnivariateDistributionEstimation > NegativeBinomialDistributionMMEstimation::Estimator::operator() (const UnivariateData& data, const bool& lazy) const
    {
        if(data.get_sample_space()->get_outcome() != DISCRETE)
        { throw statiskit::sample_space_error(DISCRETE); }
        std::unique_ptr< UnivariateDistributionEstimation > estimation;
        std::unique_ptr< UnivariateLocationEstimation > mean_estimation = (*_location)(data);
        double mean = mean_estimation->get_location();
        std::unique_ptr< UnivariateDispersionEstimation > variance_estimation = (*_dispersion)(data, mean);
        double variance = variance_estimation->get_dispersion();
        if(boost::math::isfinite(mean) && boost::math::isfinite(variance) && variance > mean)
        {
            NegativeBinomialDistribution* negbinomial = new NegativeBinomialDistribution(pow(mean, 2)/(variance - mean), 1. - mean/variance);
            if(lazy)
            { estimation = std::make_unique< LazyEstimation< NegativeBinomialDistribution, DiscreteUnivariateDistributionEstimation > >(negbinomial); }
            else
            { estimation = std::make_unique< NegativeBinomialDistributionMMEstimation >(negbinomial, &data); }
        }
        else
        { throw underdispersion_error(); }
        return estimation;
    }

    std::unique_ptr< UnivariateDistributionEstimation::Estimator > NegativeBinomialDistributionMMEstimation::Estimator::copy() const
    { return std::make_unique< Estimator >(*this); }

    UnivariateLocationEstimation::Estimator* NegativeBinomialDistributionMMEstimation::Estimator::get_location()
    { return _location; }

    void NegativeBinomialDistributionMMEstimation::Estimator::set_location(const UnivariateLocationEstimation::Estimator& mean)
    { _location = mean.copy().release(); }

    UnivariateDispersionEstimation::Estimator* NegativeBinomialDistributionMMEstimation::Estimator::get_dispersion()
    { return _dispersion; }

    void NegativeBinomialDistributionMMEstimation::Estimator::set_dispersion(const UnivariateDispersionEstimation::Estimator& dispersion)
    { _dispersion = dispersion.copy().release(); }

    NormalDistributionMLEstimation::NormalDistributionMLEstimation() : ActiveEstimation< NormalDistribution, ContinuousUnivariateDistributionEstimation >()
    {}

    NormalDistributionMLEstimation::NormalDistributionMLEstimation(NormalDistribution const * estimated, UnivariateData const * data) : ActiveEstimation< NormalDistribution, ContinuousUnivariateDistributionEstimation >(estimated, data)
    {}

    NormalDistributionMLEstimation::NormalDistributionMLEstimation(const NormalDistributionMLEstimation& estimation) : ActiveEstimation< NormalDistribution, ContinuousUnivariateDistributionEstimation >(estimation)
    {}

    NormalDistributionMLEstimation::~NormalDistributionMLEstimation()
    {}

    NormalDistributionMLEstimation::Estimator::Estimator()
    {}

    NormalDistributionMLEstimation::Estimator::~Estimator()
    {}

    std::unique_ptr< UnivariateDistributionEstimation > NormalDistributionMLEstimation::Estimator::operator() (const UnivariateData& data, const bool& lazy) const
    {
        if(data.get_sample_space()->get_outcome() != CONTINUOUS)
        { throw statiskit::sample_space_error(CONTINUOUS); }
        std::unique_ptr< UnivariateDistributionEstimation > estimation;
        UnivariateMeanEstimation::Estimator mean_estimator = UnivariateMeanEstimation::Estimator();
        std::unique_ptr< UnivariateLocationEstimation > mean_estimation = mean_estimator(data);
        double mean = mean_estimation->get_location();
        UnivariateVarianceEstimation::Estimator variance_estimator = UnivariateVarianceEstimation::Estimator(false);
        std::unique_ptr< UnivariateDispersionEstimation > variance_estimation = variance_estimator(data, mean);
        double std_err = sqrt(variance_estimation->get_dispersion());
        if(boost::math::isfinite(mean) && boost::math::isfinite(std_err))
        {
            NormalDistribution* normal = new NormalDistribution(mean, std_err);
            if(lazy)
            { estimation = std::make_unique< LazyEstimation< NormalDistribution, ContinuousUnivariateDistributionEstimation > >(normal); }
            else
            { estimation = std::make_unique< NormalDistributionMLEstimation >(normal, &data); }
        }
        return estimation;
    }

    std::unique_ptr< UnivariateDistributionEstimation::Estimator > NormalDistributionMLEstimation::Estimator::copy() const
    { return std::make_unique< Estimator >(*this); }

    UnivariateHistogramDistributionEstimation::UnivariateHistogramDistributionEstimation() : ActiveEstimation< UnivariateHistogramDistribution, ContinuousUnivariateDistributionEstimation >()
    {}

    UnivariateHistogramDistributionEstimation::UnivariateHistogramDistributionEstimation(UnivariateHistogramDistribution const * estimated, UnivariateData const * data) : ActiveEstimation< UnivariateHistogramDistribution, ContinuousUnivariateDistributionEstimation >(estimated, data)
    {}

    UnivariateHistogramDistributionEstimation::UnivariateHistogramDistributionEstimation(const UnivariateHistogramDistributionEstimation& estimation) : ActiveEstimation< UnivariateHistogramDistribution, ContinuousUnivariateDistributionEstimation >(estimation)
    {}

    UnivariateHistogramDistributionEstimation::~UnivariateHistogramDistributionEstimation()
    {}

    UnivariateHistogramDistributionEstimation::Estimator::Estimator()
    { _nb_bins = 0; }

    UnivariateHistogramDistributionEstimation::Estimator::Estimator(const Estimator& estimator)
    { _nb_bins = estimator._nb_bins; }

    UnivariateHistogramDistributionEstimation::Estimator::~Estimator()
    {}

    std::unique_ptr< UnivariateDistributionEstimation > UnivariateHistogramDistributionEstimation::Estimator::operator() (const UnivariateData& data, const bool& lazy) const
    {
        if(data.get_sample_space()->get_outcome() != CONTINUOUS)
        { throw statiskit::sample_space_error(CONTINUOUS); }
        std::unique_ptr< UnivariateDistributionEstimation > estimation;
        auto bins = std::set< double >();
        double total = 0., min = std::numeric_limits< double >::infinity(), max = -1 * std::numeric_limits< double >::infinity();
        std::unique_ptr< UnivariateData::Generator > generator = data.generator();
        double nb_bins = 0;
        while(generator->is_valid())
        {
            auto event = generator->event();
            if(event && event->get_event() == ELEMENTARY)
            {
                auto cevent = static_cast< const ContinuousElementaryEvent* >(event);
                min = std::min(min, cevent->get_value());
                max = std::max(max, cevent->get_value());
                total += generator->weight();
                nb_bins += 1;
            }
            ++(*generator);
        }
        if(_nb_bins != 0)
        { nb_bins = _nb_bins; }
        bins.insert(min - .5 / total * (max - min));
        for(Index index = 1; index < nb_bins; ++index)
        { bins.insert(*(bins.rbegin()) + 1. / nb_bins * (max-min)); }
        bins.insert(max + .5 / nb_bins * (max - min));
        if(bins.size() > 1)
        {
            auto lengths = std::vector< double >(bins.size()-1, 0.);
            std::set< double >::iterator itl = bins.begin(), itr, it_end = bins.end();
            itr = itl;
            ++itr;
            while(itr != it_end)
            {
                lengths[distance(bins.begin(), itl)] = *itr - *itl;
                ++itl;
                ++itr;
            }
            auto densities = std::vector< double >(bins.size()-1, 0.);
            std::set< double >::iterator it;
            generator = data.generator();
            while(generator->is_valid())
            {
                auto event = generator->event();
                if(event)
                {
                    if(event->get_event() == ELEMENTARY)
                    {
                        it = bins.upper_bound(static_cast< const ContinuousElementaryEvent* >(event)->get_value());
                        if(it == bins.end())
                        { densities.back() += generator->weight() / (lengths.back() * total); }
                        else if(it == bins.begin())
                        { densities.front() += generator->weight() / (lengths.front() * total); }
                        else
                        { densities[distance(bins.begin(), it) - 1] += generator->weight() /(lengths[distance(bins.begin(), it) - 1] * total); }
                    }
                }
                ++(*generator);
            }
            UnivariateHistogramDistribution* histogram = new UnivariateHistogramDistribution(bins, densities);
            if(lazy)
            { estimation = std::make_unique< LazyEstimation< UnivariateHistogramDistribution, ContinuousUnivariateDistributionEstimation > >(histogram); }
            else
            { estimation = std::make_unique< UnivariateHistogramDistributionEstimation >(histogram, &data); }
        }
        else
        { throw sample_size_error(1); }
        return estimation;
    }

    std::unique_ptr< UnivariateDistributionEstimation::Estimator > UnivariateHistogramDistributionEstimation::Estimator::copy() const
    { return std::make_unique< Estimator >(*this); }

    const unsigned int& UnivariateHistogramDistributionEstimation::Estimator::get_nb_bins() const
    { return _nb_bins; }

    void UnivariateHistogramDistributionEstimation::Estimator::set_nb_bins(const unsigned int& nb_bins)
    { _nb_bins = nb_bins; }

    RegularUnivariateHistogramDistributionSlopeHeuristicSelection::RegularUnivariateHistogramDistributionSlopeHeuristicSelection(const UnivariateData* data) : SlopeHeuristicSelection< ContinuousUnivariateDistributionEstimation >(data)
    {}

    RegularUnivariateHistogramDistributionSlopeHeuristicSelection::RegularUnivariateHistogramDistributionSlopeHeuristicSelection(const RegularUnivariateHistogramDistributionSlopeHeuristicSelection& selection) : SlopeHeuristicSelection< ContinuousUnivariateDistributionEstimation >(selection)
    {}

    RegularUnivariateHistogramDistributionSlopeHeuristicSelection::~RegularUnivariateHistogramDistributionSlopeHeuristicSelection()
    {}

    RegularUnivariateHistogramDistributionSlopeHeuristicSelection::Estimator::Estimator()
    { _maxbins = 100; }

    RegularUnivariateHistogramDistributionSlopeHeuristicSelection::Estimator::Estimator(const Estimator& estimator)
    { _maxbins = estimator._maxbins; }

    RegularUnivariateHistogramDistributionSlopeHeuristicSelection::Estimator::~Estimator()
    {}

    std::unique_ptr< UnivariateDistributionEstimation > RegularUnivariateHistogramDistributionSlopeHeuristicSelection::Estimator::operator() (const UnivariateData& data, const bool& lazy) const
    {
        if(data.get_sample_space()->get_outcome() != CONTINUOUS)
        { throw statiskit::sample_space_error(CONTINUOUS); }
        RegularUnivariateHistogramDistributionSlopeHeuristicSelection* cache;
        if(lazy)
        { cache = new RegularUnivariateHistogramDistributionSlopeHeuristicSelection(nullptr); }
        else
        { cache = new RegularUnivariateHistogramDistributionSlopeHeuristicSelection(&data); }
        std::set< double > bins = std::set< double >();
        UnivariateHistogramDistributionEstimation::Estimator estimator = UnivariateHistogramDistributionEstimation::Estimator();
        for(Index nb_bins = _maxbins; nb_bins > 0; --nb_bins)
        {
            estimator.set_nb_bins(nb_bins);
            try
            {
                std::unique_ptr< UnivariateDistributionEstimation > estimation = estimator(data, true);
                UnivariateHistogramDistribution* estimated = const_cast< UnivariateHistogramDistribution* >(static_cast< const UnivariateHistogramDistribution* >(estimation->get_estimated()));
                cache->add(estimated->get_nb_parameters(), estimated->loglikelihood(data), static_cast< UnivariateHistogramDistribution* >(estimated->copy().release()));
            }
            catch(const std::exception& error)
            {}
        }
        cache->finalize();
        std::unique_ptr< UnivariateDistributionEstimation > estimation;
        if(lazy)
        {
            estimation = std::make_unique< LazyEstimation< UnivariateHistogramDistribution, ContinuousUnivariateDistributionEstimation > >(static_cast< UnivariateHistogramDistribution* >(cache->get_estimated()->copy().release()));
            delete cache;
        }
        else
        { estimation.reset(cache); }
        return estimation;
    }

    std::unique_ptr< UnivariateDistributionEstimation::Estimator > RegularUnivariateHistogramDistributionSlopeHeuristicSelection::Estimator::copy() const
    { return std::make_unique< Estimator >(*this); }

    const unsigned int& RegularUnivariateHistogramDistributionSlopeHeuristicSelection::Estimator::get_maxbins() const
    { return _maxbins; }

    void RegularUnivariateHistogramDistributionSlopeHeuristicSelection::Estimator::set_maxbins(const unsigned int& maxbins)
    {
        if(maxbins == 0)
        { throw statiskit::lower_bound_error("maxbins", 0, 0, true); }
        _maxbins = maxbins;
    }

    IrregularUnivariateHistogramDistributionSlopeHeuristicSelection::IrregularUnivariateHistogramDistributionSlopeHeuristicSelection(const UnivariateData* data) : SlopeHeuristicSelection< ContinuousUnivariateDistributionEstimation >(data)
    {}

    IrregularUnivariateHistogramDistributionSlopeHeuristicSelection::IrregularUnivariateHistogramDistributionSlopeHeuristicSelection(const IrregularUnivariateHistogramDistributionSlopeHeuristicSelection& selection) : SlopeHeuristicSelection< ContinuousUnivariateDistributionEstimation >(selection)
    {}

    IrregularUnivariateHistogramDistributionSlopeHeuristicSelection::~IrregularUnivariateHistogramDistributionSlopeHeuristicSelection()
    {}

    IrregularUnivariateHistogramDistributionSlopeHeuristicSelection::Estimator::Estimator()
    {
        _maxbins = 100;
        _constant = 1.;
    }

    IrregularUnivariateHistogramDistributionSlopeHeuristicSelection::Estimator::Estimator(const Estimator& estimator)
    {
        _maxbins = estimator._maxbins;
        _constant = estimator._constant;
    }

    IrregularUnivariateHistogramDistributionSlopeHeuristicSelection::Estimator::~Estimator()
    {}

    std::unique_ptr< UnivariateDistributionEstimation > IrregularUnivariateHistogramDistributionSlopeHeuristicSelection::Estimator::operator() (const UnivariateData& data, const bool& lazy) const
    {
        if(data.get_sample_space()->get_outcome() != CONTINUOUS)
        { throw statiskit::sample_space_error(CONTINUOUS); }
        IrregularUnivariateHistogramDistributionSlopeHeuristicSelection* cache;
        if(lazy)
        { cache = new IrregularUnivariateHistogramDistributionSlopeHeuristicSelection(nullptr); }
        else
        { cache = new IrregularUnivariateHistogramDistributionSlopeHeuristicSelection(&data); }
        std::set< double > bins = std::set< double >();
        unsigned int elements = 0;
        double total = 0., min = std::numeric_limits< double >::infinity(), max = -1 * std::numeric_limits< double >::infinity();
        std::unique_ptr< UnivariateData::Generator > generator = data.generator();
        while(generator->is_valid())
        {
            const UnivariateEvent* event = generator->event();
            if(event && event->get_event() == ELEMENTARY)
            {
                const ContinuousElementaryEvent* cevent = static_cast< const ContinuousElementaryEvent* >(event);
                min = std::min(min, cevent->get_value());
                max = std::max(max, cevent->get_value());
                total += generator->weight();
            }
            ++(*generator);
        }
        bins.insert(min - .5 / _maxbins * (max - min));
        for(Index index = 1; index < _maxbins; ++index)
        { bins.insert(*(bins.rbegin()) + 1. / _maxbins * (max-min)); }
        bins.insert(max + .5 / _maxbins * (max - min));
        if(bins.size() > 1)
        {
            std::vector< double > lengths = std::vector< double >(bins.size()-1, 0.);
            std::set< double >::iterator itl = bins.begin(), itr, it_end = bins.end();
            itr = itl;
            ++itr;
            while(itr != it_end)
            {
                lengths[distance(bins.begin(), itl)] = *itr - *itl;
                ++itl;
                ++itr;
            }
            std::vector< double > densities = std::vector< double >(bins.size()-1, 0.);
            std::set< double >::iterator it;
            generator = data.generator();
            while(generator->is_valid())
            {
                const UnivariateEvent* event = generator->event();
                if(event)
                {
                    if(event->get_event() == ELEMENTARY)
                    {
                        it = bins.upper_bound(static_cast< const ContinuousElementaryEvent* >(event)->get_value());
                        if(it == bins.end())
                        { densities.back() += generator->weight() /(lengths.back() * total); }
                        else if(it == bins.begin())
                        { densities.front() +=  generator->weight() /(lengths.front() * total); }
                        else
                        { densities[distance(bins.begin(), it) - 1] += generator->weight() /(lengths[distance(bins.begin(), it) - 1] * total); }
                    }
                }
                ++(*generator);
            }
            std::vector< double > entropies = std::vector< double >(densities.size()-1, std::numeric_limits< double >::quiet_NaN());
            for(Index index = 0, max_index = densities.size()-1; index < max_index; ++index)
            {
                entropies[index] = 0;
                if(densities[index] > 0.)
                { entropies[index] += lengths[index] * densities[index] * log(densities[index]); }
                if(densities[index + 1] > 0.)
                { entropies[index] += lengths[index + 1] * densities[index + 1] * log(densities[index + 1]); }
                double p = (lengths[index] * densities[index] + lengths[index + 1] * densities[index+1]) / (lengths[index] + lengths[index + 1]);
                if(p > 0.)
                { entropies[index] -= (lengths[index] + lengths[index + 1]) * p * log(p); }
                else
                { entropies[index] = std::numeric_limits< double >::infinity(); }
            }
            double score = 0.;
            while(bins.size() > 2)
            {
                std::vector< double >::iterator it = std::min_element(entropies.begin(), entropies.end()), itr;
                if(*it > 0)
                {
                    score -= *it;
                    // if(bins.size() < _maxbins)
                    // {
                        UnivariateHistogramDistribution* current = new UnivariateHistogramDistribution(bins, densities);
                        double penshape = bins.size()-1;
                        penshape = penshape * (1 + _constant * log(_maxbins) - _constant * log(penshape));
                        cache->add(penshape, score, current);
                    // }
                }
                itr = it;
                ++itr;
                densities[distance(entropies.begin(), itr)] = lengths[distance(entropies.begin(), it)] * densities[distance(entropies.begin(), it)] + lengths[distance(entropies.begin(), itr)] * densities[distance(entropies.begin(), itr)];
                densities[distance(entropies.begin(), itr)] /= lengths[distance(entropies.begin(), it)] + lengths[distance(entropies.begin(), itr)];
                lengths[distance(entropies.begin(), itr)] = lengths[distance(entropies.begin(), it)] + lengths[distance(entropies.begin(), itr)];
                std::vector< double >::iterator itd = densities.begin();
                advance(itd, distance(entropies.begin(), it));
                densities.erase(itd);
                std::vector< double >::iterator itl = lengths.begin();
                advance(itl, distance(entropies.begin(), it));
                lengths.erase(itl);
                std::set< double >::iterator itb = bins.begin();
                advance(itb, distance(entropies.begin(), itr));
                bins.erase(itb);
                // TODO optimize
                entropies = std::vector< double >(densities.size()-1, std::numeric_limits< double >::quiet_NaN());
                for(Index index = 0, max_index = densities.size()-1; index < max_index; ++index)
                {
                    entropies[index] = 0;
                    if(densities[index] > 0.)
                    { entropies[index] += lengths[index] * densities[index] * log(densities[index]); }
                    if(densities[index + 1] > 0.)
                    { entropies[index] += lengths[index + 1] * densities[index + 1] * log(densities[index + 1]); }
                    double p = (lengths[index] * densities[index] + lengths[index + 1] * densities[index+1]) / (lengths[index] + lengths[index + 1]);
                    if(p > 0.)
                    { entropies[index] -= (lengths[index] + lengths[index + 1]) * p * log(p); }
                    else
                    { entropies[index] = std::numeric_limits< double >::infinity(); }
                }
            }
            cache->finalize();
        }
        else
        { throw sample_size_error(1); }
        std::unique_ptr< UnivariateDistributionEstimation > estimation;
        if(lazy)
        {
            estimation = std::make_unique< LazyEstimation< UnivariateHistogramDistribution, ContinuousUnivariateDistributionEstimation > >(static_cast<  UnivariateHistogramDistribution* >(cache->get_estimated()->copy().release()));
            delete cache;
        }
        else
        { estimation.reset(cache); }
        return estimation;
    }

    std::unique_ptr< UnivariateDistributionEstimation::Estimator > IrregularUnivariateHistogramDistributionSlopeHeuristicSelection::Estimator::copy() const
    { return std::make_unique< Estimator >(*this); }

    const unsigned int& IrregularUnivariateHistogramDistributionSlopeHeuristicSelection::Estimator::get_maxbins() const
    { return _maxbins; }

    void IrregularUnivariateHistogramDistributionSlopeHeuristicSelection::Estimator::set_maxbins(const unsigned int& maxbins)
    {
        if(maxbins == 0)
        { throw statiskit::lower_bound_error("maxbins", 0, 0, true); }
        _maxbins = maxbins;
    }

    const double& IrregularUnivariateHistogramDistributionSlopeHeuristicSelection::Estimator::get_constant() const
    { return _constant; }

    void IrregularUnivariateHistogramDistributionSlopeHeuristicSelection::Estimator::set_constant(const double& constant)
    {
        if(constant <= 0.)
        { throw statiskit::lower_bound_error("constant", constant, 0.0, true); }
        _constant = constant;
    }

    SingularDistributionEstimation::~SingularDistributionEstimation()
    {}

    SingularDistributionEstimation::Estimator::~Estimator()
    {}

    MultinomialSingularDistributionEstimation::MultinomialSingularDistributionEstimation(MultinomialSingularDistribution const * estimated, MultivariateData const * data) : ActiveEstimation< MultinomialSingularDistribution, SingularDistributionEstimation >(estimated, data)
    {}

    MultinomialSingularDistributionEstimation::MultinomialSingularDistributionEstimation(const MultinomialSingularDistributionEstimation& estimation) : ActiveEstimation< MultinomialSingularDistribution, SingularDistributionEstimation >(estimation)
    {}

    MultinomialSingularDistributionEstimation::~MultinomialSingularDistributionEstimation()
    {}

    MultinomialSingularDistributionEstimation::Estimator::Estimator()
    {}

    MultinomialSingularDistributionEstimation::Estimator::Estimator(const Estimator& estimator)
    {}

    MultinomialSingularDistributionEstimation::Estimator::~Estimator()
    {}

    std::unique_ptr< SingularDistributionEstimation > MultinomialSingularDistributionEstimation::Estimator::operator() (const MultivariateData& data, const bool& lazy) const
    {
        std::unique_ptr< SingularDistributionEstimation > estimation;
        std::unique_ptr< MultivariateData::Generator > generator = data.generator();
        Eigen::VectorXd pi = Eigen::VectorXd::Zero(generator->event()->size());
        while(generator->is_valid())
        {
            const MultivariateEvent* mevent = generator->event();
            for(Index component = 0, max_component = mevent->size(); component < max_component; ++component)
            {
                const UnivariateEvent* uevent = mevent->get(component);
                if(uevent && uevent->get_outcome() == DISCRETE && uevent->get_event() == ELEMENTARY)
                { pi[component] += generator->weight() * static_cast< const DiscreteElementaryEvent* >(uevent)->get_value(); }
            }
            ++(*generator);
        }
        MultinomialSingularDistribution* estimated = new MultinomialSingularDistribution(pi);
        if(lazy)
        { estimation = std::make_unique< LazyEstimation< MultinomialSingularDistribution, SingularDistributionEstimation > >(estimated); }
        else
        { estimation = std::make_unique< MultinomialSingularDistributionEstimation >(estimated, &data); }
        return estimation;
    }

    DirichletMultinomialSingularDistributionEstimation::DirichletMultinomialSingularDistributionEstimation(DirichletMultinomialSingularDistribution const * estimated, MultivariateData const * data) : OptimizationEstimation<Eigen::VectorXd, DirichletMultinomialSingularDistribution, SingularDistributionEstimation >(estimated, data)
    {}

    DirichletMultinomialSingularDistributionEstimation::DirichletMultinomialSingularDistributionEstimation(const DirichletMultinomialSingularDistributionEstimation& estimation) : OptimizationEstimation<Eigen::VectorXd, DirichletMultinomialSingularDistribution, SingularDistributionEstimation >(estimation)
    {}

    DirichletMultinomialSingularDistributionEstimation::~DirichletMultinomialSingularDistributionEstimation()
    {}

    DirichletMultinomialSingularDistributionEstimation::Estimator::Estimator() : PolymorphicCopy<SingularDistributionEstimation::Estimator, Estimator, OptimizationEstimation<Eigen::VectorXd, DirichletMultinomialSingularDistribution, SingularDistributionEstimation >::Estimator >()
    {}

    DirichletMultinomialSingularDistributionEstimation::Estimator::Estimator(const Estimator& estimator) : PolymorphicCopy<SingularDistributionEstimation::Estimator, Estimator, OptimizationEstimation<Eigen::VectorXd, DirichletMultinomialSingularDistribution, SingularDistributionEstimation >::Estimator >(estimator)
    {}

    DirichletMultinomialSingularDistributionEstimation::Estimator::~Estimator()
    {}

    std::unique_ptr< SingularDistributionEstimation > DirichletMultinomialSingularDistributionEstimation::Estimator::operator() (const MultivariateData& data, const bool& lazy) const
    {
        std::unique_ptr< SingularDistributionEstimation > estimation;
        double total = data.compute_total();
        Eigen::VectorXd prev, curr = Eigen::VectorXd::Ones(data.get_sample_space()->size());
        DirichletMultinomialSingularDistribution* estimated = new DirichletMultinomialSingularDistribution(curr);
        if(lazy)
        { estimation = std::make_unique< LazyEstimation< DirichletMultinomialSingularDistribution, SingularDistributionEstimation > >(estimated); }
        else
        { estimation = std::make_unique< DirichletMultinomialSingularDistributionEstimation >(estimated, &data); }
        unsigned int its = 0;
        do
        {
            prev = curr;
            Eigen::VectorXd temp = Eigen::VectorXd::Zero(data.get_sample_space()->size());
            for(Index component = 0, max_component = data.get_sample_space()->size(); component < max_component; ++component)
            {
                std::unique_ptr< MultivariateData::Generator > generator = data.generator();
                while(generator->is_valid())
                {
                    const MultivariateEvent* mevent = generator->event();
                    if(mevent)
                    {
                        const UnivariateEvent* uevent = mevent->get(component);
                        if(uevent && uevent->get_outcome() == DISCRETE && uevent->get_event() == ELEMENTARY)
                        { temp[component] += generator->weight() * boost::math::digamma(static_cast< const DiscreteElementaryEvent* >(uevent)->get_value() + prev[component]); }
                    }
                    ++(*generator);
                }
                temp[component] -= total * boost::math::digamma(prev[component]);
            }
            std::pair< double, double > sums = std::make_pair(0., curr.sum());
            std::unique_ptr< MultivariateData::Generator > generator = data.generator();
            while(generator->is_valid())
            {
                const MultivariateEvent* event = generator->event();
                if(event)
                {
                    int value = 0;
                    for(Index component = 0, max_component = data.get_sample_space()->size(); component < max_component; ++component)
                    {
                        const UnivariateEvent* uevent = event->get(component);
                        if(uevent && uevent->get_outcome() == DISCRETE && uevent->get_event() == ELEMENTARY)
                        { value += static_cast< const DiscreteElementaryEvent* >(uevent)->get_value(); }
                    }
                    sums.first += generator->weight() * boost::math::digamma(value + sums.second);
                }
                ++(*generator);
            }
            sums.first -= total * boost::math::digamma(sums.second);
            temp /= sums.first;
            if(temp.minCoeff() >= 0.)
            {
                curr = prev.cwiseProduct(temp);
                if(!lazy)
                { static_cast< DirichletMultinomialSingularDistributionEstimation* >(estimation.get())->_iterations.push_back(curr); }
            }
            ++its;
        } while(run(its, __impl::reldiff(prev, curr)));
        estimated->set_alpha(curr);
        return estimation;
    }

    SplittingDistributionEstimation::SplittingDistributionEstimation(SplittingDistribution const * estimated, MultivariateData const * data) : ActiveEstimation< SplittingDistribution, DiscreteMultivariateDistributionEstimation >(estimated, data)
    {
        _sum = nullptr;
        _singular = nullptr;
    }

    SplittingDistributionEstimation::SplittingDistributionEstimation(const SplittingDistributionEstimation& estimation) : ActiveEstimation< SplittingDistribution, DiscreteMultivariateDistributionEstimation >(estimation)
    {
        _sum = estimation._sum;
        _singular = estimation._singular;
    }

    SplittingDistributionEstimation::~SplittingDistributionEstimation()
    {
        if(_sum)
        { delete _sum; }
        if(_singular)
        { delete _singular; }
    }

    const DiscreteUnivariateDistributionEstimation* SplittingDistributionEstimation::get_sum() const
    { return _sum; }

    const SingularDistributionEstimation* SplittingDistributionEstimation::get_singular() const
    { return _singular; }

    SplittingDistributionEstimation::Estimator::Estimator()
    {
        _sum = nullptr;
        _singular = nullptr;
    }

    SplittingDistributionEstimation::Estimator::Estimator(const Estimator& estimator)
    {
        if(estimator._sum)
        { _sum = static_cast< DiscreteUnivariateDistributionEstimation::Estimator* >((estimator._sum->copy()).release()); }
        else
        { _sum = nullptr; }
        if(estimator._singular)
        { _singular = estimator._singular->copy().release(); }
        else
        { _singular = nullptr; }
    }

    SplittingDistributionEstimation::Estimator::~Estimator()
    {
        if(_sum)
        {
            delete _sum;
            _sum = nullptr;
        }
        if(_singular)
        {
            delete _singular;
            _singular = nullptr;
        }
    }

    std::unique_ptr< MultivariateDistributionEstimation > SplittingDistributionEstimation::Estimator::operator() (const MultivariateData& data, const bool& lazy) const
    {
        UnivariateDataFrame* sum_data = new UnivariateDataFrame(get_NN());
        std::unique_ptr< MultivariateData::Generator > generator = data.generator();
        while(generator->is_valid())
        {
            int value = 0;
            const MultivariateEvent* mevent = generator->event();
            for(Index component = 0, max_component = mevent->size(); component < max_component; ++component)
            {
                const UnivariateEvent* uevent = mevent->get(component);
                if(uevent && uevent->get_outcome() == DISCRETE && uevent->get_event() == ELEMENTARY)
                { value += static_cast< const DiscreteElementaryEvent* >(uevent)->get_value(); }
            }
            DiscreteElementaryEvent* sum_event = new DiscreteElementaryEvent(value);
            sum_data->add_event(sum_event);
            ++(*generator);
        }
        WeightedUnivariateData weighted_sum_data = WeightedUnivariateData(sum_data);
        Index index = 0;
        generator = data.generator();
        while(generator->is_valid())
        {
            weighted_sum_data.set_weight(index, generator->weight());
            ++index;
            ++(*generator);
        }
        DiscreteUnivariateDistributionEstimation* sum = static_cast< DiscreteUnivariateDistributionEstimation* >(((*_sum)(weighted_sum_data, lazy)).release());
        delete sum_data;
        SingularDistributionEstimation* singular = (*_singular)(data, lazy).release();
        SplittingDistribution* estimated = new SplittingDistribution(*(static_cast< const DiscreteUnivariateDistribution* >(sum->get_estimated())), *(singular->get_estimated()));
        std::unique_ptr< MultivariateDistributionEstimation > estimation;
        if(lazy)
        {
            estimation = std::make_unique< LazyEstimation< SplittingDistribution, DiscreteMultivariateDistributionEstimation > >(estimated);
            if(sum)
            { delete sum; }
            if(singular)
            { delete singular; }
        }
        else
        {
            estimation = std::make_unique< SplittingDistributionEstimation >(estimated, &data);
            static_cast< SplittingDistributionEstimation* >(estimation.get())->_sum = sum;
            static_cast< SplittingDistributionEstimation* >(estimation.get())->_singular = singular;
        }
        return estimation;
    }

    const DiscreteUnivariateDistributionEstimation::Estimator* SplittingDistributionEstimation::Estimator::get_sum() const
    { return _sum; }

    void  SplittingDistributionEstimation::Estimator::set_sum(const DiscreteUnivariateDistributionEstimation::Estimator& sum)
    {
        if(_sum)
        { delete _sum; }
        _sum = static_cast< DiscreteUnivariateDistributionEstimation::Estimator* >(sum.copy().release());
    }

    const SingularDistributionEstimation::Estimator* SplittingDistributionEstimation::Estimator::get_singular() const
    { return _singular; }

    void SplittingDistributionEstimation::Estimator::set_singular(const SingularDistributionEstimation::Estimator& singular)
    {
        if(_singular)
        { delete _singular; }
        _singular = static_cast< SingularDistributionEstimation::Estimator* >(singular.copy().release());
    }

    std::unordered_set< uintptr_t > SplittingDistributionEstimation::Estimator::children() const
    {
        std::unordered_set< uintptr_t > ch;
        ch.insert(compute_identifier(*_sum));
        __impl::merge(ch, compute_children(*_sum));
        ch.insert(compute_identifier(*_singular));
        __impl::merge(ch, compute_children(*_singular));
        return ch;
    }

    NegativeMultinomialDistributionEstimation::NegativeMultinomialDistributionEstimation() : OptimizationEstimation<double, SplittingDistribution, DiscreteMultivariateDistributionEstimation >()
    {}

    NegativeMultinomialDistributionEstimation::NegativeMultinomialDistributionEstimation(SplittingDistribution const * estimated, MultivariateData const * data) : OptimizationEstimation<double, SplittingDistribution, DiscreteMultivariateDistributionEstimation >(estimated, data)
    {}

    NegativeMultinomialDistributionEstimation::NegativeMultinomialDistributionEstimation(const NegativeMultinomialDistributionEstimation& estimation) : OptimizationEstimation<double, SplittingDistribution, DiscreteMultivariateDistributionEstimation >(estimation)
    {}

    NegativeMultinomialDistributionEstimation::~NegativeMultinomialDistributionEstimation()
    {}

    NegativeMultinomialDistributionEstimation::WZ99Estimator::WZ99Estimator() : OptimizationEstimation<double, SplittingDistribution, DiscreteMultivariateDistributionEstimation >::Estimator()
    {}

    NegativeMultinomialDistributionEstimation::WZ99Estimator::WZ99Estimator(const WZ99Estimator& estimator) : OptimizationEstimation<double, SplittingDistribution, DiscreteMultivariateDistributionEstimation >::Estimator(estimator)
    {}

    NegativeMultinomialDistributionEstimation::WZ99Estimator::~WZ99Estimator()
    {}

    std::unique_ptr< MultivariateDistributionEstimation > NegativeMultinomialDistributionEstimation::WZ99Estimator::operator() (const MultivariateData& data, const bool& lazy) const
    {
        const MultivariateSampleSpace* sample_space = data.get_sample_space();
        for(Index index = 0, max_index = sample_space->size(); index < max_index; ++index)
        {
            if(sample_space->get(index)->get_outcome() != DISCRETE)
            { throw statiskit::sample_space_error(DISCRETE); }
        }
        std::unique_ptr< MultivariateDistributionEstimation > estimation;
        MultivariateMeanEstimation::Estimator mean_estimator = MultivariateMeanEstimation::Estimator();
        std::unique_ptr< MultivariateLocationEstimation > mean_estimation = mean_estimator(data);
        Eigen::VectorXd mean = mean_estimation->get_location();
        MultivariateVarianceEstimation::Estimator covariance_estimator = MultivariateVarianceEstimation::Estimator();
        std::unique_ptr< MultivariateDispersionEstimation > covariance_estimation = covariance_estimator(data, mean);
        Eigen::MatrixXd covariance = covariance_estimation->get_dispersion();
        double total = data.compute_total(), kappa;
        double _location = mean.sum(), variance = 0.;
        for(Index i = 0, max_i = sample_space->size(); i < max_i; ++i)
        {
            for(Index j = 0; j <= i; ++j)
            { variance += covariance(i, j); }
        }
        if(variance > _location)
        { kappa = pow(_location, 2)/(variance - _location); }
        else
        { kappa = 1.; }
        double q =  _location / (_location + kappa);
        NegativeBinomialDistribution negative_binomial = NegativeBinomialDistribution(kappa, 1. - q);
        SplittingDistribution* negative_multinomial = new SplittingDistribution(negative_binomial, MultinomialSingularDistribution(mean * q / kappa));
        if(!lazy)
        {
            estimation = std::make_unique< NegativeMultinomialDistributionEstimation >(negative_multinomial, &data);
            static_cast< NegativeMultinomialDistributionEstimation* >(estimation.get())->_iterations.push_back(kappa);
        }
        else
        { estimation = std::make_unique< LazyEstimation< SplittingDistribution, DiscreteMultivariateDistributionEstimation > >(negative_multinomial); }
        double prev, curr = kappa; //negative_multinomial->loglikelihood(data);
        unsigned int its = 1;
        double chisq = 0.;
        std::unique_ptr< MultivariateData::Generator > generator = data.generator();
        while(generator->is_valid())
        {
            const MultivariateEvent* event = generator->event();
            if(event)
            {
                for(Index index = 0, max_index = event->size(); index < max_index; ++index)
                {
                    const UnivariateEvent* uevent = event->get(index);
                    if(uevent && uevent->get_event() == ELEMENTARY)
                    { chisq += generator->weight() / total * pow(static_cast< const DiscreteElementaryEvent* >(uevent)->get_value() - mean(index), 2.) / mean(index); }
                }
            }
            ++(*generator);
        }
        do
        {
            prev = curr;
            double _chisq = 0;
            generator = data.generator();
            while(generator->is_valid())
            {
                const MultivariateEvent* event = generator->event();
                if(event)
                {
                    for(Index index = 0, max_index = event->size(); index < max_index; ++index)
                    {
                        const UnivariateEvent* uevent = event->get(index);
                        if(uevent && uevent->get_event() == ELEMENTARY)
                        { _chisq += generator->weight() / total * pow(static_cast< const DiscreteElementaryEvent* >(uevent)->get_value() - mean(index), 2.) / (mean(index) * (1 + mean(index) / kappa)); }
                    }
                }
                ++(*generator);
            }
            kappa *= chisq / _chisq;
            if(kappa > 0.)
            {
                if(!lazy)
                { static_cast< NegativeMultinomialDistributionEstimation* >(estimation.get())->_iterations.push_back(kappa); }
                negative_binomial.set_kappa(kappa);
                q =  _location / (_location + kappa);
                negative_binomial.set_pi(1. - q);
                negative_multinomial->set_sum(negative_binomial);
                static_cast< MultinomialSingularDistribution* >(negative_multinomial->get_singular())->set_pi(mean * q / kappa);
                // curr = negative_multinomial->loglikelihood(data);
                curr = kappa;
                ++its;
            }
        } while(run(its, __impl::reldiff(prev, curr)));
        return estimation;
    }

    std::unique_ptr< MultivariateDistributionEstimation::Estimator > NegativeMultinomialDistributionEstimation::WZ99Estimator::copy() const
    { return std::make_unique< NegativeMultinomialDistributionEstimation::WZ99Estimator >(*this); }
}


#ifndef CONFIG_H // This pattern is to prevent symbols to be loaded multiple times
#define CONFIG_H // from multiple imports

/***** Defines ***********************************************************************************/

#define MG_FLOAT // ---------- Use floats for all Eigen operations
#define LEN 8192 // ---------- Maximum length of text string
#define _USE_MATH_DEFINES // - M_PI , etc.
#define GL_GLEXT_PROTOTYPES // Important for all of your programs

//~ #define MG_DUBBL // Use doubles for all Eigen operations

/***** Includes *****/

/*** Standard ***/
#include <string>
#include <iostream>

/*** Eigen ***/
#include <Eigen/Core> // ---- The living heart of Eigen
#include <Eigen/Dense> // --- Cross Product , etc.
#include <Eigen/Geometry> //- Quaternion , etc

/*** OGL ***/
#include <GL/gl.h>
#include <GL/glut.h>

/***** Shortcuts and Aliases *****/

/*** Standard ***/
using std::string;
using std::cout;
using std::cerr;
using std::endl;

/*** Eigen ***/
#ifdef MG_FLOAT
	using vec2e = Eigen::Vector2f;
	using vec2r = Eigen::RowVector2f;
	using vec3e = Eigen::Vector3f;
	using vec3r = Eigen::RowVector3f;
	using vec4e = Eigen::Vector4f;
	using matXe = Eigen::MatrixXf;
	using typeF = float;
	#define random rand_float
#endif
#ifdef MG_DUBBL
	using vec2e = Eigen::Vector2d;
	using vec3e = Eigen::Vector3d;
	using matXe = Eigen::MatrixXd;
	using typeF = double;
	#define random rand_dbbl
#endif
using vec2i = Eigen::Vector2i;
using matXi = Eigen::MatrixXi;

#endif


//==================================================================================================
/*!
  @file

  Copyright 2016 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)
*/
//==================================================================================================
#include <boost/simd/pack.hpp>
#include <boost/simd/function/ctz.hpp>
#include <boost/simd/function/max.hpp>
#include <boost/simd/meta/cardinal_of.hpp>
#include <simd_test.hpp>
#include <boost/simd/constant/inf.hpp>
#include <boost/simd/constant/minf.hpp>
#include <boost/simd/constant/mone.hpp>
#include <boost/simd/constant/nan.hpp>
#include <boost/simd/constant/one.hpp>
#include <boost/simd/constant/zero.hpp>
#include <boost/simd/constant/nbmantissabits.hpp>
#include <boost/simd/constant/signmask.hpp>

template <typename T, std::size_t N, typename Env>
void test(Env& runtime)
{
  namespace bs = boost::simd;
  namespace bd = boost::dispatch;
  using p_t = bs::pack<T, N>;
  using iT =  bd::as_integer_t<T>;
  using i_t=  bs::pack<iT, N>;

  T a1[N];
  iT b[N];

  a1[0] = T(1);
  b[0]  = bs::ctz(a1[0]);

  for(std::size_t i = 1; i < N; ++i)
  {
    a1[i] = T(bs::max(T(a1[i-1]*3), T(1)));
    b[i]  = bs::ctz(a1[i]);
  }

  p_t aa1(&a1[0], &a1[0]+N);
  i_t bb(&b[0], &b[0]+N);
  STF_EQUAL(bs::ctz(aa1), bb);
}

STF_CASE_TPL("Check ctz on pack" , STF_NUMERIC_TYPES)
{
  namespace bs = boost::simd;
  using p_t = bs::pack<T>;
  static const std::size_t N = bs::cardinal_of<p_t>::value;
  test<T, N>(runtime);
  test<T, N/2>(runtime);
  test<T, N*2>(runtime);
}

STF_CASE_TPL (" ctz real",  STF_IEEE_TYPES)
{
  namespace bs = boost::simd;
  namespace bd = boost::dispatch;
  using bs::ctz;
  using r_t = decltype(ctz(T()));
  // return type conformity test
  STF_EXPR_IS(ctz(T()), bd::as_integer_t<T>);

  // specific values tests
#ifndef BOOST_SIMD_NO_INVALIDS
  STF_EQUAL(ctz(bs::Inf<T>()), r_t(bs::Nbmantissabits<T>()));
  STF_EQUAL(ctz(bs::Minf<T>()), r_t(bs::Nbmantissabits<T>()));
#endif
  STF_EQUAL(ctz(bs::Zero<T>()), r_t(sizeof(T)*8));
  STF_EQUAL(ctz(bs::Signmask<T>()), r_t(sizeof(T)*8-1));
} // end of test for real_

STF_CASE_TPL (" ctz signed_int",  STF_SIGNED_INTEGRAL_TYPES)
{
  namespace bs = boost::simd;
  using bs::ctz;
  using bs::Nbmantissabits;
  using bs::Signmask;
  using p_t = bs::pack<T>;

  using r_t = decltype(ctz(p_t()));

  for(std::size_t j=0; j< (sizeof(T)*CHAR_BIT-1); j++)
  {
  namespace bs = boost::simd;
    // Test 01111 ... 10000b
    T value = ~T(0) & ~((T(1)<<j)-1);
    STF_EQUAL(ctz( p_t(value) ), r_t(j));
    STF_EQUAL(ctz( p_t(-value) ), r_t(j));
  }

  STF_EQUAL(ctz(Signmask<p_t>()) , r_t(sizeof(T)*CHAR_BIT-1) );
}

STF_CASE_TPL(" ctz unsigned_integer", STF_UNSIGNED_INTEGRAL_TYPES )
{
  namespace bs = boost::simd;
  namespace bd = boost::dispatch;
  using bs::ctz;
  using p_t = bs::pack<T>;

  using r_t = decltype(ctz(p_t()));

  // return type conformity test
  STF_EXPR_IS(ctz(p_t()), bd::as_integer_t<p_t>);

  // specific values tests
  for(std::size_t j=0; j< sizeof(T)*CHAR_BIT; j++)
  {
    // Test 1111 ... 10000b
    T value = ~T(0) & ~((T(1)<<j)-1);
    STF_EQUAL(ctz( p_t(value) ), r_t(j));
  }
 }


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#include <boost/simd/bitwise/include/functions/twopower.hpp>
#include <boost/simd/sdk/simd/io.hpp>
#include <boost/dispatch/meta/as_integer.hpp>
#include <nt2/sdk/unit/tests.hpp>
#include <nt2/sdk/unit/module.hpp>
#include <nt2/sdk/unit/tests/type_expr.hpp>
#include <boost/simd/include/constants/one.hpp>
#include <boost/simd/include/constants/zero.hpp>

NT2_TEST_CASE_TPL ( twopower_ui,  BOOST_SIMD_UNSIGNED_TYPES)
{

  using boost::simd::twopower;
  using boost::simd::tag::twopower_;
  typedef typename boost::dispatch::meta::call<twopower_(T)>::type r_t;
  typedef typename boost::common_type<T>::type wished_r_t;

  // return type conformity test
  NT2_TEST_TYPE_IS( r_t, wished_r_t );

  // specific values tests
  NT2_TEST_EQUAL(twopower(boost::simd::Zero<T>()), boost::simd::One<r_t>());

  for(unsigned int i=0; i < sizeof(T)*8 ; i++)
 {
   NT2_TEST_EQUAL(twopower(T(i)), T(1) << i);
 }
} // end of test for unsigned_int_

NT2_TEST_CASE_TPL ( twopower_si,  BOOST_SIMD_INTEGRAL_SIGNED_TYPES)
{

  using boost::simd::twopower;
  using boost::simd::tag::twopower_;
  typedef typename boost::dispatch::meta::call<twopower_(T)>::type r_t;
  typedef T wished_r_t;

  // return type conformity test
  NT2_TEST_TYPE_IS( r_t, wished_r_t );

  // specific values tests
  NT2_TEST_EQUAL(twopower(boost::simd::Zero<T>()), boost::simd::One<r_t>());
  NT2_TEST_EQUAL(twopower(boost::simd::Mone<T>()), boost::simd::Zero<r_t>());

  for(unsigned int i=0; i < sizeof(T)*8-1 ; i++)
  {
    NT2_TEST_EQUAL(twopower(T(i)), T(1) << i);
  }

} // end of test for unsigned_int_


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

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2012 Mateusz Loskot, London, UK.

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, 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)

#include <sstream>

#include <algorithms/test_distance.hpp>

#include <boost/mpl/if.hpp>
#include <boost/array.hpp>

#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/adapted/c_array.hpp>
#include <boost/geometry/geometries/adapted/boost_tuple.hpp>

#include <test_common/test_point.hpp>
#include <test_geometries/custom_segment.hpp>
#include <test_geometries/wrapped_boost_array.hpp>

BOOST_GEOMETRY_REGISTER_C_ARRAY_CS(cs::cartesian)
BOOST_GEOMETRY_REGISTER_BOOST_TUPLE_CS(cs::cartesian)

// Register boost array as a linestring
namespace boost { namespace geometry { namespace traits
{
template <typename Point, std::size_t PointCount>
struct tag< boost::array<Point, PointCount> >
{
    typedef linestring_tag type;
};

}}}

template <typename P>
void test_distance_point()
{
    namespace services = bg::strategy::distance::services;
    typedef typename bg::default_distance_result<P>::type return_type;

    // Basic, trivial test

    P p1;
    bg::set<0>(p1, 1);
    bg::set<1>(p1, 1);

    P p2;
    bg::set<0>(p2, 2);
    bg::set<1>(p2, 2);

    return_type d = bg::distance(p1, p2);
    BOOST_CHECK_CLOSE(d, return_type(1.4142135), 0.001);

    // Test specifying strategy manually
    typename services::default_strategy<bg::point_tag, P>::type strategy;

    d = bg::distance(p1, p2, strategy);
    BOOST_CHECK_CLOSE(d, return_type(1.4142135), 0.001);

    {
        // Test custom strategy
        BOOST_CONCEPT_ASSERT( (bg::concept::PointDistanceStrategy<taxicab_distance, P, P>) );

        typedef typename services::return_type<taxicab_distance, P, P>::type cab_return_type;
        BOOST_MPL_ASSERT((boost::is_same<cab_return_type, typename bg::coordinate_type<P>::type>));

        taxicab_distance tcd;
        cab_return_type d = bg::distance(p1, p2, tcd);

        BOOST_CHECK( bg::math::abs(d - cab_return_type(2)) <= cab_return_type(0.01) );
    }

    {
        // test comparability

        typedef typename services::default_strategy<bg::point_tag, P>::type strategy_type;
        typedef typename services::comparable_type<strategy_type>::type comparable_strategy_type;

        strategy_type strategy;
        comparable_strategy_type comparable_strategy = services::get_comparable<strategy_type>::apply(strategy);
        return_type comparable = services::result_from_distance<comparable_strategy_type, P, P>::apply(comparable_strategy, 3);

        BOOST_CHECK_CLOSE(comparable, return_type(9), 0.001);
    }
}

template <typename P>
void test_distance_segment()
{
    typedef typename bg::default_distance_result<P>::type return_type;
    typedef typename bg::coordinate_type<P>::type coordinate_type;

    P s1; bg::set<0>(s1, 1); bg::set<1>(s1, 1);
    P s2; bg::set<0>(s2, 4); bg::set<1>(s2, 4);

    // Check points left, right, projected-left, projected-right, on segment
    P p1; bg::set<0>(p1, 0); bg::set<1>(p1, 1);
    P p2; bg::set<0>(p2, 1); bg::set<1>(p2, 0);
    P p3; bg::set<0>(p3, 3); bg::set<1>(p3, 1);
    P p4; bg::set<0>(p4, 1); bg::set<1>(p4, 3);
    P p5; bg::set<0>(p5, 3); bg::set<1>(p5, 3);

    bg::model::referring_segment<P const> const seg(s1, s2);

    return_type d1 = bg::distance(p1, seg);
    return_type d2 = bg::distance(p2, seg);
    return_type d3 = bg::distance(p3, seg);
    return_type d4 = bg::distance(p4, seg);
    return_type d5 = bg::distance(p5, seg);

    BOOST_CHECK_CLOSE(d1, return_type(1), 0.001);
    BOOST_CHECK_CLOSE(d2, return_type(1), 0.001);
    BOOST_CHECK_CLOSE(d3, return_type(1.4142135), 0.001);
    BOOST_CHECK_CLOSE(d4, return_type(1.4142135), 0.001);
    BOOST_CHECK_CLOSE(d5, return_type(0), 0.001);

    // Reverse case: segment/point instead of point/segment
    return_type dr1 = bg::distance(seg, p1);
    return_type dr2 = bg::distance(seg, p2);

    BOOST_CHECK_CLOSE(dr1, d1, 0.001);
    BOOST_CHECK_CLOSE(dr2, d2, 0.001);

    // Test specifying strategy manually:
    // 1) point-point-distance
    typename bg::strategy::distance::services::default_strategy<bg::point_tag, P>::type pp_strategy;
    d1 = bg::distance(p1, seg, pp_strategy);
    BOOST_CHECK_CLOSE(d1, return_type(1), 0.001);

    // 2) point-segment-distance
    typename bg::strategy::distance::services::default_strategy<bg::segment_tag, P>::type ps_strategy;
    d1 = bg::distance(p1, seg, ps_strategy);
    BOOST_CHECK_CLOSE(d1, return_type(1), 0.001);

    // 3) custom point strategy
    taxicab_distance tcd;
    d1 = bg::distance(p1, seg, tcd);
    BOOST_CHECK_CLOSE(d1, return_type(1), 0.001);
}

template <typename Point, typename Geometry, typename T>
void test_distance_linear(std::string const& wkt_point, std::string const& wkt_geometry, T const& expected)
{
    Point p;
    bg::read_wkt(wkt_point, p);

    Geometry g;
    bg::read_wkt(wkt_geometry, g);

    typedef typename bg::default_distance_result<Point>::type return_type;
    return_type d = bg::distance(p, g);

    // For point-to-linestring (or point-to-polygon), both a point-strategy and a point-segment-strategy can be specified.
    // Test this.
    return_type ds1 = bg::distance(p, g, bg::strategy::distance::pythagoras<>());
    return_type ds2 = bg::distance(p, g, bg::strategy::distance::projected_point<>());

    BOOST_CHECK_CLOSE(d, return_type(expected), 0.001);
    BOOST_CHECK_CLOSE(ds1, return_type(expected), 0.001);
    BOOST_CHECK_CLOSE(ds2, return_type(expected), 0.001);
}

template <typename P>
void test_distance_array_as_linestring()
{
    typedef typename bg::default_distance_result<P>::type return_type;

    // Normal array does not have
    boost::array<P, 2> points;
    bg::set<0>(points[0], 1);
    bg::set<1>(points[0], 1);
    bg::set<0>(points[1], 3);
    bg::set<1>(points[1], 3);

    P p;
    bg::set<0>(p, 2);
    bg::set<1>(p, 1);

    return_type d = bg::distance(p, points);
    BOOST_CHECK_CLOSE(d, return_type(0.70710678), 0.001);

    bg::set<0>(p, 5); bg::set<1>(p, 5);
    d = bg::distance(p, points);
    BOOST_CHECK_CLOSE(d, return_type(2.828427), 0.001);
}

template <typename P>
void test_all()
{
    test_distance_point<P>();
    test_distance_segment<P>();
    test_distance_array_as_linestring<P>();

    test_geometry<P, bg::model::segment<P> >("POINT(1 3)", "LINESTRING(1 1,4 4)", sqrt(2.0));
    test_geometry<P, bg::model::segment<P> >("POINT(3 1)", "LINESTRING(1 1,4 4)", sqrt(2.0));

    test_geometry<P, P>("POINT(1 1)", "POINT(2 2)", sqrt(2.0));
    test_geometry<P, P>("POINT(0 0)", "POINT(0 3)", 3.0);
    test_geometry<P, P>("POINT(0 0)", "POINT(4 0)", 4.0);
    test_geometry<P, P>("POINT(0 3)", "POINT(4 0)", 5.0);
    test_geometry<P, bg::model::linestring<P> >("POINT(1 3)", "LINESTRING(1 1,4 4)", sqrt(2.0));
    test_geometry<P, bg::model::linestring<P> >("POINT(3 1)", "LINESTRING(1 1,4 4)", sqrt(2.0));
    test_geometry<P, bg::model::linestring<P> >("POINT(50 50)", "LINESTRING(50 40, 40 50)", sqrt(50.0));
    test_geometry<P, bg::model::linestring<P> >("POINT(50 50)", "LINESTRING(50 40, 40 50, 0 90)", sqrt(50.0));
    test_geometry<bg::model::linestring<P>, P>("LINESTRING(1 1,4 4)", "POINT(1 3)", sqrt(2.0));
    test_geometry<bg::model::linestring<P>, P>("LINESTRING(50 40, 40 50)", "POINT(50 50)", sqrt(50.0));
    test_geometry<bg::model::linestring<P>, P>("LINESTRING(50 40, 40 50, 0 90)", "POINT(50 50)", sqrt(50.0));

    // Rings
    test_geometry<P, bg::model::ring<P> >("POINT(1 3)", "POLYGON((1 1,4 4,5 0,1 1))", sqrt(2.0));
    test_geometry<P, bg::model::ring<P> >("POINT(3 1)", "POLYGON((1 1,4 4,5 0,1 1))", 0.0);
    // other way round
    test_geometry<bg::model::ring<P>, P>("POLYGON((1 1,4 4,5 0,1 1))", "POINT(3 1)", 0.0);
    // open ring
    test_geometry<P, bg::model::ring<P, true, false> >("POINT(1 3)", "POLYGON((4 4,5 0,1 1))", sqrt(2.0));

    // Polygons
    test_geometry<P, bg::model::polygon<P> >("POINT(1 3)", "POLYGON((1 1,4 4,5 0,1 1))", sqrt(2.0));
    test_geometry<P, bg::model::polygon<P> >("POINT(3 1)", "POLYGON((1 1,4 4,5 0,1 1))", 0.0);
    // other way round
    test_geometry<bg::model::polygon<P>, P>("POLYGON((1 1,4 4,5 0,1 1))", "POINT(3 1)", 0.0);
    // open polygon
    test_geometry<P, bg::model::polygon<P, true, false> >("POINT(1 3)", "POLYGON((4 4,5 0,1 1))", sqrt(2.0));

    // Polygons with holes
    std::string donut = "POLYGON ((0 0,1 9,8 1,0 0),(1 1,4 1,1 4,1 1))";
    test_geometry<P, bg::model::polygon<P> >("POINT(2 2)", donut, 0.5 * sqrt(2.0));
    test_geometry<P, bg::model::polygon<P> >("POINT(3 3)", donut, 0.0);
    // other way round
    test_geometry<bg::model::polygon<P>, P>(donut, "POINT(2 2)", 0.5 * sqrt(2.0));
    // open
    test_geometry<P, bg::model::polygon<P, true, false> >("POINT(2 2)", "POLYGON ((0 0,1 9,8 1),(1 1,4 1,1 4))", 0.5 * sqrt(2.0));

    // Should (currently) give compiler assertion
    // test_geometry<bg::model::polygon<P>, bg::model::polygon<P> >(donut, donut, 0.5 * sqrt(2.0));

    // DOES NOT COMPILE - cannot do read_wkt (because boost::array is not variably sized)
    // test_geometry<P, boost::array<P, 2> >("POINT(3 1)", "LINESTRING(1 1,4 4)", sqrt(2.0));

    test_geometry<P, test::wrapped_boost_array<P, 2> >("POINT(3 1)", "LINESTRING(1 1,4 4)", sqrt(2.0));

    test_distance_linear<P, bg::model::linestring<P> >("POINT(3 1)", "LINESTRING(1 1,4 4)", sqrt(2.0));

}

template <typename P>
void test_empty_input()
{
    P p;
    bg::model::linestring<P> line_empty;
    bg::model::polygon<P> poly_empty;
    bg::model::ring<P> ring_empty;

    test_empty_input(p, line_empty);
    test_empty_input(p, poly_empty);
    test_empty_input(p, ring_empty);
}

void test_large_integers()
{
    typedef bg::model::point<int, 2, bg::cs::cartesian> int_point_type;
    typedef bg::model::point<double, 2, bg::cs::cartesian> double_point_type;

    // point-point
    {
        std::string const a = "POINT(2544000 528000)";
        std::string const b = "POINT(2768040 528000)";
        int_point_type ia, ib;
        double_point_type da, db;
        bg::read_wkt(a, ia);
        bg::read_wkt(b, ib);
        bg::read_wkt(a, da);
        bg::read_wkt(b, db);

        BOOST_AUTO(idist, bg::distance(ia, ib));
        BOOST_AUTO(ddist, bg::distance(da, db));

        BOOST_CHECK_MESSAGE(std::abs(idist - ddist) < 0.1,
                "within<a double> different from within<an int>");
    }
    // Point-segment
    {
        std::string const a = "POINT(2600000 529000)";
        std::string const b = "LINESTRING(2544000 528000, 2768040 528000)";
        int_point_type ia;
        double_point_type da;
        bg::model::segment<int_point_type> ib;
        bg::model::segment<double_point_type> db;
        bg::read_wkt(a, ia);
        bg::read_wkt(b, ib);
        bg::read_wkt(a, da);
        bg::read_wkt(b, db);

        BOOST_AUTO(idist, bg::distance(ia, ib));
        BOOST_AUTO(ddist, bg::distance(da, db));

        BOOST_CHECK_MESSAGE(std::abs(idist - ddist) < 0.1,
                "within<a double> different from within<an int>");
    }
}

int test_main(int, char* [])
{
#ifdef TEST_ARRAY
    //test_all<int[2]>();
    //test_all<float[2]>();
    //test_all<double[2]>();
    //test_all<test::test_point>(); // located here because of 3D
#endif

    test_large_integers();

    test_all<bg::model::d2::point_xy<int> >();
    test_all<boost::tuple<float, float> >();
    test_all<bg::model::d2::point_xy<float> >();
    test_all<bg::model::d2::point_xy<double> >();

#ifdef HAVE_TTMATH
    test_all<bg::model::d2::point_xy<ttmath_big> >();
#endif

    test_empty_input<bg::model::d2::point_xy<int> >();

    return 0;
}


// BLAS level 2
// symmetric matrices

//#define BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS
//#define BOOST_NO_FUNCTION_TEMPLATE_ORDERING

#include <stddef.h>
#include <iostream>
#include <boost/numeric/bindings/atlas/cblas1.hpp>
#include <boost/numeric/bindings/atlas/cblas2.hpp>
#include <boost/numeric/bindings/traits/ublas_vector.hpp>
#include <boost/numeric/bindings/traits/ublas_symmetric.hpp>
#include "utils.h"

namespace ublas = boost::numeric::ublas;
namespace atlas = boost::numeric::bindings::atlas;
namespace traits = boost::numeric::bindings::traits;

using std::cout;
using std::cin;
using std::endl;

typedef double real_t;
typedef ublas::vector<real_t> vct_t;

typedef ublas::symmetric_matrix<
  real_t, ublas::upper, ublas::column_major
> ucsymm_t;
typedef ublas::symmetric_matrix<
  real_t, ublas::lower, ublas::column_major
> lcsymm_t;
typedef ublas::symmetric_matrix<
  real_t, ublas::upper, ublas::row_major
> ursymm_t;
typedef ublas::symmetric_matrix<
  real_t, ublas::lower, ublas::row_major
> lrsymm_t;

typedef ublas::matrix<real_t, ublas::column_major> cm_t;
typedef ublas::matrix<real_t, ublas::row_major> rm_t;
typedef ublas::symmetric_adaptor<cm_t, ublas::upper> ucsa_t;
typedef ublas::symmetric_adaptor<cm_t, ublas::lower> lcsa_t;
typedef ublas::symmetric_adaptor<rm_t, ublas::upper> ursa_t;
typedef ublas::symmetric_adaptor<rm_t, ublas::lower> lrsa_t;

int main (int argc, char **argv) {
  size_t n = 0;
  if (argc > 1) {
    n = atoi(argv [1]);
  }

  cout << endl;
  if (n <= 0) {
    cout << "n -> ";
    cin >> n;
  }
  cout << endl;

  vct_t vx (n), vy (n);
  atlas::set (1., vx);
  print_v (vx, "vx");
  vy (1) = 1.;
  print_v (vy, "vy");

  // symmetric matrix

  ucsymm_t ucs (n, n);
  lcsymm_t lcs (n, n);
  ursymm_t urs (n, n);
  lrsymm_t lrs (n, n);

  init_symm (ucs, 'u');
  init_symm (lcs, 'l');
  init_symm (urs, 'u');
  init_symm (lrs, 'l');

  print_m (ucs, "ucs");
  cout << endl;
  print_m (lcs, "lcs");
  cout << endl;
  print_m (urs, "urs");
  cout << endl;
  print_m (lrs, "lrs");
  cout << endl;

  // m += x x^T
  atlas::spr (vx, ucs);
  print_m (ucs, "ucs += x x^T");
  cout << endl;
  atlas::spr (1, vx, lcs);
  print_m (lcs, "lcs += x x^T");
  cout << endl;
  atlas::spr (1.0f, vx, urs);
  print_m (urs, "urs += x x^T");
  cout << endl;
  atlas::spr (vx, lrs);
  print_m (lrs, "lrs += x x^T");
  cout << endl;

  // m += x y^T + y x^T
  atlas::spr2 (1, vx, vy, ucs);
  print_m (ucs, "ucs += x y^T + y x^T");
  cout << endl;
  atlas::spr2 (vx, vy, lcs);
  print_m (lcs, "lcs += x y^T + y x^T");
  cout << endl;
  atlas::spr2 (vx, vy, urs);
  print_m (urs, "urs += x y^T + y x^T");
  cout << endl;
  atlas::spr2 (1., vx, vy, lrs);
  print_m (lrs, "lrs += x y^T + y x^T");
  cout << endl;

  // symmetric adaptor

  cm_t cmu (n, n);
  cm_t cml (n, n);
  rm_t rmu (n, n);
  rm_t rml (n, n);

  ucsa_t ucsa (cmu);
  lcsa_t lcsa (cml);
  ursa_t ursa (rmu);
  lrsa_t lrsa (rml);

  init_symm (ucsa, 'u');
  init_symm (lcsa, 'l');
  init_symm (ursa, 'u');
  init_symm (lrsa, 'l');

  print_m (ucsa, "ucsa");
  cout << endl;
  print_m (lcsa, "lcsa");
  cout << endl;
  print_m (ursa, "ursa");
  cout << endl;
  print_m (lrsa, "lrsa");
  cout << endl;

  // m += x x^T
  atlas::syr (vx, ucsa);
  print_m (ucsa, "ucsa += x x^T");
  cout << endl;
  atlas::syr (1, vx, lcsa);
  print_m (lcsa, "lcsa += x x^T");
  cout << endl;
  atlas::syr (1.0f, vx, ursa);
  print_m (ursa, "ursa += x x^T");
  cout << endl;
  atlas::syr (vx, lrsa);
  print_m (lrsa, "lrsa += x x^T");
  cout << endl;

  // m += x y^T + y x^T
  atlas::syr2 (1, vx, vy, ucsa);
  print_m (ucsa, "ucsa += x y^T + y x^T");
  cout << endl;
  atlas::syr2 (vx, vy, lcsa);
  print_m (lcsa, "lcsa += x y^T + y x^T");
  cout << endl;
  atlas::syr2 (vx, vy, ursa);
  print_m (ursa, "ursa += x y^T + y x^T");
  cout << endl;
  atlas::syr2 (1., vx, vy, lrsa);
  print_m (lrsa, "lrsa += x y^T + y x^T");
  cout << endl;

}


#include <string>
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <stdio.h>
#include <cstdio>
#include <ctime>
#include <chrono>
#include <vector>
#include <climits>
#include <utility>                          // for std::pair
#include "algo.hh"

//includes from boost librabry
#include <boost/config.hpp>
#include <boost/utility.hpp>                // for boost::tie
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/property_map/property_map.hpp>

#define OUTPUT 0

using namespace std;
using namespace boost;

template<class F, class T>
void dijkstra(const vector<F> &graph, int root, vector<T> &dist);

/* Reads data from a gph-file
 * and computes the longest shortest path
 * to the vertex 1.
 * The boost-graph library is used for
 * the shortest path computation.
 *
 */
pair<int,int> algo1(char* filename){

  ifstream    file(filename);
  string      line;

  if(!file){
    cout << "Could not open file" << endl;
    exit(EXIT_FAILURE);
  }

  //property weighted graph
  typedef property <edge_weight_t, int> EdgeWeightProperty;
  //typedef for graph type: undirected, weighted graph
  typedef adjacency_list <listS, vecS, undirectedS, no_property, EdgeWeightProperty> BoostGraph;
  //typedef to describe vertecies in boost library
  typedef graph_traits <BoostGraph>::vertex_descriptor vertex_descriptor;
  //typedef for edges in the graph
  typedef pair<int, int> VPair;

  //read number of vertices and edges
  getline(file, line, ' ');
  const int numV = stoi(line);
  getline(file, line, '\n');
  const int numE = stoi(line);
#if OUTPUT
  fprintf(stdout, "number of vertices: %i | number of edges: %i\n",  numV, numE);
  fprintf(stdout, "Edges \t\t Weight\n");
  fprintf(stdout, "---- \t\t ----\n");
#endif

  VPair* edges;          //edges as pair of vertices
  int*  weights;         //weights of an edge

  try{
    //dynamic memory allocation
    edges   = new VPair [numE];
    weights = new int [numE];

  } catch (bad_alloc& ba) {
    fprintf(stderr, "Too many edges. try a smaller graph");
  }

  int edgecount = 0;

  //loop over all lines in the file
  while( getline(file, line) ){

    stringstream linestream(line);
    string       vertex1, vertex2, weight;

    try{
      getline(linestream, vertex1, ' ');
      getline(linestream, vertex2, ' ');
      getline(linestream, weight, '\n');

      edges[edgecount] = VPair(stoi(vertex1), stoi(vertex2));    //store edges as pairs of vertices
      weights[edgecount] = stoi(weight);                         //store corresponding weights

      #if OUTPUT
      fprintf(stdout, "(%i,%i) \t\t %i \n", edges[edgecount].first, edges[edgecount].second, weights[edgecount]);
      #endif

    } catch (invalid_argument& ia){
      //when data is not a digit,
      //std::stoi throws an invalid argument exception
    } catch ( ... ){}

    edgecount++;
  }//while

  file.close();

  //create graph containig edges and weights
  BoostGraph g (edges, edges + numE, weights, numV);

  //setup for shortest path solver
  property_map<BoostGraph, edge_weight_t>::type weightmap = get(edge_weight, g);
  vector<vertex_descriptor> pre(num_vertices(g));
  //vector to store distances to source vertex
  vector<int> dist(num_vertices(g));
  //define source vertex to which shortest path shall be
  //computed from all other vertecies in graph g
  vertex_descriptor source = vertex(edges[0].first, g);

  //use dijkstra algorithm, since all edges are positive
  dijkstra_shortest_paths(g, source,
                          predecessor_map(make_iterator_property_map(pre.begin(), get(vertex_index, g))).
                          distance_map(make_iterator_property_map(dist.begin(), get(vertex_index, g))));

  delete[] edges;
  delete[] weights;

  int maxdist = 0;
  int vertex  = 0;

  //iterate over all vertecies and update
  //maximum disntance, if needed
  graph_traits < BoostGraph >::vertex_iterator vi, vend;
  tie(vi, vend) = vertices(g);
  vi++;
  for (; vi != vend; ++vi) {

    if(dist[*vi] > maxdist){
      maxdist = dist[*vi];
      vertex  = *vi;
    }
  }

  return VPair(vertex, maxdist);

}

/* Reads data from a gph-file
 * and computes the longest shortest path
 * to vertex with index 1.
 * This method uses a self-written
 * Dijkstra-algorithm.
 *
 */
pair<int,int> algo2(char*filename){

  ifstream    file(filename);
  string      line;

  if(!file){
    cout << "Could not open file" << endl;
    exit(EXIT_FAILURE);
  }

  typedef int vertex_;
  typedef int weight_;

  /* Edge
   * pair of ints containing the weight and
   * the vertex pointed to. This is useful
   * for the adjacency list
   */
  typedef pair<vertex_, weight_> Edge;

  /* Graph
   * An adjacency list representing the graph.
   * graph[i] returns a list with Edge elements.
   */
  typedef vector< vector<Edge> > Graph;

  //read number of vertices and edges
   getline(file, line, ' ');
   const int numV = stoi(line);
   getline(file, line, '\n');
   const int numE = stoi(line);

 #if OUTPUT
   fprintf(stdout, "number of vertices: %i | number of edges: %i\n",  numV, numE);
   fprintf(stdout, "Edges \t\t Weight\n");
   fprintf(stdout, "---- \t\t ----\n");
 #endif

   Graph graph(numV);

   int  edgecount = 0;
   //loop over all lines in the file
   while( getline(file, line) ){

     stringstream linestream(line);
     string       vertex1, vertex2, weight;

     try{
       getline(linestream, vertex1, ' ');
       getline(linestream, vertex2, ' ');
       getline(linestream, weight, '\n');

       //add both directions, since undirected graph
       graph[stoi(vertex1)-1].push_back(Edge(stoi(vertex2)-1, stoi(weight)));
       graph[stoi(vertex2)-1].push_back(Edge(stoi(vertex1)-1, stoi(weight)));

     } catch (invalid_argument& ia){
       //when data is not a digit,
       //std::stoi throws an invalid argument exception
     } catch ( ... ){}

     edgecount++;
   }//while

   file.close();

   int maxdist = 0;
   int vertex  = 0;

   //initialize distance
   vector<weight_> dist(numV, INT_MAX);

   dijkstra<vector<Edge>, weight_>(graph, 0,  dist);

   for(uint i = 0; i < dist.size(); ++i){
     if(dist[i] > maxdist){
       maxdist = dist[i];
       vertex  = i+1;
     }
   }

   return pair<int,int>(vertex, maxdist);

}

template<class F, class T>
void dijkstra(const vector<F> &graph, int root, vector<T> &dist) {

  typedef pair<int,int> Edge;

  /* A set helps insertion and insert/erase/find operations in logarithmic time.
   * This set maintains Edge(distance,vertex number) sorted on basis of distance
   */
  set< Edge > pq;
  set< Edge > ::iterator it;

  int u,v,wt;

  dist[root] = 0;
  pq.insert(Edge(root,0));

  while(pq.size() != 0){
    it = pq.begin();
    u = it->first;
    pq.erase(it);

    for(vector<Edge>::const_iterator i = graph[u].begin(); i != graph[u].end(); i++){
      v  = i->first;
      wt = i->second;

      if(dist[v] > dist[u] + wt){
        if(dist[v] != INT_MAX){
          pq.erase(Edge(v,dist[v]));
        }
        dist[v] = dist[u] + wt;
        pq.insert(Edge(v,dist[v]));
      }
    }
  }//while

} //dijkstra