/*
 *  (C) Copyright Nick Thompson 2018.
 *  Use, modification and distribution are 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 <vector>
#include <array>
#include <forward_list>
#include <algorithm>
#include <random>
#include <boost/core/lightweight_test.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/math/constants/constants.hpp>
#include <boost/math/statistics/univariate_statistics.hpp>
#include <boost/multiprecision/cpp_bin_float.hpp>
#include <boost/multiprecision/cpp_complex.hpp>

using boost::multiprecision::cpp_bin_float_50;
using boost::multiprecision::cpp_complex_50;

/*
 * Test checklist:
 * 1) Does it work with multiprecision?
 * 2) Does it work with .cbegin()/.cend() if the data is not altered?
 * 3) Does it work with ublas and std::array? (Checking Eigen and Armadillo will make the CI system really unhappy.)
 * 4) Does it work with std::forward_list if a forward iterator is all that is required?
 * 5) Does it work with complex data if complex data is sensible?
 */

 // To stress test, set global_seed = 0, global_size = huge.
 static const constexpr size_t global_seed = 0;
 static const constexpr size_t global_size = 128;

template<class T>
std::vector<T> generate_random_vector(size_t size, size_t seed)
{
    if (seed == 0)
    {
        std::random_device rd;
        seed = rd();
    }
    std::vector<T> v(size);

    std::mt19937 gen(seed);

    if constexpr (std::is_floating_point<T>::value)
    {
        std::normal_distribution<T> dis(0, 1);
        for (size_t i = 0; i < v.size(); ++i)
        {
         v[i] = dis(gen);
        }
        return v;
    }
    else if constexpr (std::is_integral<T>::value)
    {
        // Rescaling by larger than 2 is UB!
        std::uniform_int_distribution<T> dis(std::numeric_limits<T>::lowest()/2, (std::numeric_limits<T>::max)()/2);
        for (size_t i = 0; i < v.size(); ++i)
        {
         v[i] = dis(gen);
        }
        return v;
    }
    else if constexpr (boost::is_complex<T>::value)
    {
        std::normal_distribution<typename T::value_type> dis(0, 1);
        for (size_t i = 0; i < v.size(); ++i)
        {
            v[i] = {dis(gen), dis(gen)};
        }
        return v;
    }
    else if constexpr (boost::multiprecision::number_category<T>::value == boost::multiprecision::number_kind_complex)
    {
        std::normal_distribution<long double> dis(0, 1);
        for (size_t i = 0; i < v.size(); ++i)
        {
            v[i] = {dis(gen), dis(gen)};
        }
        return v;
    }
    else if constexpr (boost::multiprecision::number_category<T>::value == boost::multiprecision::number_kind_floating_point)
    {
        std::normal_distribution<long double> dis(0, 1);
        for (size_t i = 0; i < v.size(); ++i)
        {
            v[i] = dis(gen);
        }
        return v;
    }
    else
    {
        BOOST_ASSERT_MSG(false, "Could not identify type for random vector generation.");
        return v;
    }
}

template<class Z>
void test_integer_mean()
{
    double tol = 100*std::numeric_limits<double>::epsilon();
    std::vector<Z> v{1,2,3,4,5};
    double mu = boost::math::statistics::mean(v);
    BOOST_TEST(abs(mu - 3) < tol);

    // Work with std::array?
    std::array<Z, 5> w{1,2,3,4,5};
    mu = boost::math::statistics::mean(w);
    BOOST_TEST(abs(mu - 3) < tol);

    v = generate_random_vector<Z>(global_size, global_seed);
    Z scale = 2;

    double m1 = scale*boost::math::statistics::mean(v);
    for (auto & x : v)
    {
        x *= scale;
    }
    double m2 = boost::math::statistics::mean(v);
    BOOST_TEST(abs(m1 - m2) < tol*abs(m1));
}

template<class RandomAccessContainer>
auto naive_mean(RandomAccessContainer const & v)
{
    typename RandomAccessContainer::value_type sum = 0;
    for (auto & x : v)
    {
        sum += x;
    }
    return sum/v.size();
}

template<class Real>
void test_mean()
{
    Real tol = std::numeric_limits<Real>::epsilon();
    std::vector<Real> v{1,2,3,4,5};
    Real mu = boost::math::statistics::mean(v.begin(), v.end());
    BOOST_TEST(abs(mu - 3) < tol);

    // Does range call work?
    mu = boost::math::statistics::mean(v);
    BOOST_TEST(abs(mu - 3) < tol);

    // Can we successfully average only part of the vector?
    mu = boost::math::statistics::mean(v.begin(), v.begin() + 3);
    BOOST_TEST(abs(mu - 2) < tol);

    // Does it work when we const qualify?
    mu = boost::math::statistics::mean(v.cbegin(), v.cend());
    BOOST_TEST(abs(mu - 3) < tol);

    // Does it work for std::array?
    std::array<Real, 7> u{1,2,3,4,5,6,7};
    mu = boost::math::statistics::mean(u.begin(), u.end());
    BOOST_TEST(abs(mu - 4) < 10*tol);

    // Does it work for a forward iterator?
    std::forward_list<Real> l{1,2,3,4,5,6,7};
    mu = boost::math::statistics::mean(l.begin(), l.end());
    BOOST_TEST(abs(mu - 4) < tol);

    // Does it work with ublas vectors?
    boost::numeric::ublas::vector<Real> w(7);
    for (size_t i = 0; i < w.size(); ++i)
    {
        w[i] = i+1;
    }
    mu = boost::math::statistics::mean(w.cbegin(), w.cend());
    BOOST_TEST(abs(mu - 4) < tol);

    v = generate_random_vector<Real>(global_size, global_seed);
    Real scale = 2;
    Real m1 = scale*boost::math::statistics::mean(v);
    for (auto & x : v)
    {
        x *= scale;
    }
    Real m2 = boost::math::statistics::mean(v);
    BOOST_TEST(abs(m1 - m2) < tol*abs(m1));

    // Stress test:
    for (size_t i = 1; i < 30; ++i)
    {
        v = generate_random_vector<Real>(i, 12803);
        auto naive_ = naive_mean(v);
        auto higham_ = boost::math::statistics::mean(v);
        if (abs(higham_ - naive_) >= 100*tol*abs(naive_))
        {
            std::cout << std::hexfloat;
            std::cout << "Terms = " << v.size() << "\n";
            std::cout << "higham = " << higham_ << "\n";
            std::cout << "naive_ = " << naive_ << "\n";
        }
        BOOST_TEST(abs(higham_ - naive_) < 100*tol*abs(naive_));
    }

}

template<class Complex>
void test_complex_mean()
{
    typedef typename Complex::value_type Real;
    Real tol = std::numeric_limits<Real>::epsilon();
    std::vector<Complex> v{{0,1},{0,2},{0,3},{0,4},{0,5}};
    auto mu = boost::math::statistics::mean(v.begin(), v.end());
    BOOST_TEST(abs(mu.imag() - 3) < tol);
    BOOST_TEST(abs(mu.real()) < tol);

    // Does range work?
    mu = boost::math::statistics::mean(v);
    BOOST_TEST(abs(mu.imag() - 3) < tol);
    BOOST_TEST(abs(mu.real()) < tol);
}

template<class Real>
void test_variance()
{
    Real tol = std::numeric_limits<Real>::epsilon();
    std::vector<Real> v{1,1,1,1,1,1};
    Real sigma_sq = boost::math::statistics::variance(v.begin(), v.end());
    BOOST_TEST(abs(sigma_sq) < tol);

    sigma_sq = boost::math::statistics::variance(v);
    BOOST_TEST(abs(sigma_sq) < tol);

    Real s_sq = boost::math::statistics::sample_variance(v);
    BOOST_TEST(abs(s_sq) < tol);

    std::vector<Real> u{1};
    sigma_sq = boost::math::statistics::variance(u.cbegin(), u.cend());
    BOOST_TEST(abs(sigma_sq) < tol);

    std::array<Real, 8> w{0,1,0,1,0,1,0,1};
    sigma_sq = boost::math::statistics::variance(w.begin(), w.end());
    BOOST_TEST(abs(sigma_sq - 1.0/4.0) < tol);

    sigma_sq = boost::math::statistics::variance(w);
    BOOST_TEST(abs(sigma_sq - 1.0/4.0) < tol);

    std::forward_list<Real> l{0,1,0,1,0,1,0,1};
    sigma_sq = boost::math::statistics::variance(l.begin(), l.end());
    BOOST_TEST(abs(sigma_sq - 1.0/4.0) < tol);

    v = generate_random_vector<Real>(global_size, global_seed);
    Real scale = 2;
    Real m1 = scale*scale*boost::math::statistics::variance(v);
    for (auto & x : v)
    {
        x *= scale;
    }
    Real m2 = boost::math::statistics::variance(v);
    BOOST_TEST(abs(m1 - m2) < tol*abs(m1));

    // Wikipedia example for a variance of N sided die:
    // https://en.wikipedia.org/wiki/Variance
    for (size_t j = 16; j < 2048; j *= 2)
    {
        v.resize(1024);
        Real n = v.size();
        for (size_t i = 0; i < v.size(); ++i)
        {
            v[i] = i + 1;
        }

        sigma_sq = boost::math::statistics::variance(v);

        BOOST_TEST(abs(sigma_sq - (n*n-1)/Real(12)) <= tol*sigma_sq);
    }

}

template<class Z>
void test_integer_variance()
{
    double tol = std::numeric_limits<double>::epsilon();
    std::vector<Z> v{1,1,1,1,1,1};
    double sigma_sq = boost::math::statistics::variance(v);
    BOOST_TEST(abs(sigma_sq) < tol);

    std::forward_list<Z> l{0,1,0,1,0,1,0,1};
    sigma_sq = boost::math::statistics::variance(l.begin(), l.end());
    BOOST_TEST(abs(sigma_sq - 1.0/4.0) < tol);

    v = generate_random_vector<Z>(global_size, global_seed);
    Z scale = 2;
    double m1 = scale*scale*boost::math::statistics::variance(v);
    for (auto & x : v)
    {
        x *= scale;
    }
    double m2 = boost::math::statistics::variance(v);
    BOOST_TEST(abs(m1 - m2) < tol*abs(m1));
}

template<class Z>
void test_integer_skewness()
{
    double tol = std::numeric_limits<double>::epsilon();
    std::vector<Z> v{1,1,1};
    double skew = boost::math::statistics::skewness(v);
    BOOST_TEST(abs(skew) < tol);

    // Dataset is symmetric about the mean:
    v = {1,2,3,4,5};
    skew = boost::math::statistics::skewness(v);
    BOOST_TEST(abs(skew) < tol);

    v = {0,0,0,0,5};
    // mu = 1, sigma^2 = 4, sigma = 2, skew = 3/2
    skew = boost::math::statistics::skewness(v);
    BOOST_TEST(abs(skew - 3.0/2.0) < tol);

    std::forward_list<Z> v2{0,0,0,0,5};
    skew = boost::math::statistics::skewness(v);
    BOOST_TEST(abs(skew - 3.0/2.0) < tol);

    v = generate_random_vector<Z>(global_size, global_seed);
    Z scale = 2;
    double m1 = boost::math::statistics::skewness(v);
    for (auto & x : v)
    {
        x *= scale;
    }
    double m2 = boost::math::statistics::skewness(v);
    BOOST_TEST(abs(m1 - m2) < tol*abs(m1));

}

template<class Real>
void test_skewness()
{
    Real tol = std::numeric_limits<Real>::epsilon();
    std::vector<Real> v{1,1,1};
    Real skew = boost::math::statistics::skewness(v);
    BOOST_TEST(abs(skew) < tol);

    // Dataset is symmetric about the mean:
    v = {1,2,3,4,5};
    skew = boost::math::statistics::skewness(v);
    BOOST_TEST(abs(skew) < tol);

    v = {0,0,0,0,5};
    // mu = 1, sigma^2 = 4, sigma = 2, skew = 3/2
    skew = boost::math::statistics::skewness(v);
    BOOST_TEST(abs(skew - Real(3)/Real(2)) < tol);

    std::array<Real, 5> w1{0,0,0,0,5};
    skew = boost::math::statistics::skewness(w1);
    BOOST_TEST(abs(skew - Real(3)/Real(2)) < tol);

    std::forward_list<Real> w2{0,0,0,0,5};
    skew = boost::math::statistics::skewness(w2);
    BOOST_TEST(abs(skew - Real(3)/Real(2)) < tol);

    v = generate_random_vector<Real>(global_size, global_seed);
    Real scale = 2;
    Real m1 = boost::math::statistics::skewness(v);
    for (auto & x : v)
    {
        x *= scale;
    }
    Real m2 = boost::math::statistics::skewness(v);
    BOOST_TEST(abs(m1 - m2) < tol*abs(m1));
}

template<class Real>
void test_kurtosis()
{
    Real tol = std::numeric_limits<Real>::epsilon();
    std::vector<Real> v{1,1,1};
    Real kurt = boost::math::statistics::kurtosis(v);
    BOOST_TEST(abs(kurt) < tol);

    v = {1,2,3,4,5};
    // mu =1, sigma^2 = 2, kurtosis = 17/10
    kurt = boost::math::statistics::kurtosis(v);
    BOOST_TEST(abs(kurt - Real(17)/Real(10)) < 10*tol);

    v = {0,0,0,0,5};
    // mu = 1, sigma^2 = 4, sigma = 2, skew = 3/2, kurtosis = 13/4
    kurt = boost::math::statistics::kurtosis(v);
    BOOST_TEST(abs(kurt - Real(13)/Real(4)) < tol);

    std::array<Real, 5> v1{0,0,0,0,5};
    kurt = boost::math::statistics::kurtosis(v1);
    BOOST_TEST(abs(kurt - Real(13)/Real(4)) < tol);

    std::forward_list<Real> v2{0,0,0,0,5};
    kurt = boost::math::statistics::kurtosis(v2);
    BOOST_TEST(abs(kurt - Real(13)/Real(4)) < tol);

    std::vector<Real> v3(10000);
    std::mt19937 gen(42);
    std::normal_distribution<long double> dis(0, 1);
    for (size_t i = 0; i < v3.size(); ++i) {
        v3[i] = dis(gen);
    }
    kurt = boost::math::statistics::kurtosis(v3);
    BOOST_TEST(abs(kurt - 3) < 0.1);

    std::uniform_real_distribution<long double> udis(-1, 3);
    for (size_t i = 0; i < v3.size(); ++i) {
        v3[i] = udis(gen);
    }
    auto excess_kurtosis = boost::math::statistics::excess_kurtosis(v3);
    BOOST_TEST(abs(excess_kurtosis + 6.0/5.0) < 0.2);

    v = generate_random_vector<Real>(global_size, global_seed);
    Real scale = 2;
    Real m1 = boost::math::statistics::kurtosis(v);
    for (auto & x : v)
    {
        x *= scale;
    }
    Real m2 = boost::math::statistics::kurtosis(v);
    BOOST_TEST(abs(m1 - m2) < tol*abs(m1));

    // This test only passes when there are a large number of samples.
    // Otherwise, the distribution doesn't generate enough outliers to give,
    // or generates too many, giving pretty wildly different values of kurtosis on different runs.
    // However, by kicking up the samples to 1,000,000, I got very close to 6 for the excess kurtosis on every run.
    // The CI system, however, would die on a million long doubles.
    //v3.resize(1000000);
    //std::exponential_distribution<long double> edis(0.1);
    //for (size_t i = 0; i < v3.size(); ++i) {
    //    v3[i] = edis(gen);
    //}
    //excess_kurtosis = boost::math::statistics::kurtosis(v3) - 3;
    //BOOST_TEST(abs(excess_kurtosis - 6.0) < 0.2);
}

template<class Z>
void test_integer_kurtosis()
{
    double tol = std::numeric_limits<double>::epsilon();
    std::vector<Z> v{1,1,1};
    double kurt = boost::math::statistics::kurtosis(v);
    BOOST_TEST(abs(kurt) < tol);

    v = {1,2,3,4,5};
    // mu =1, sigma^2 = 2, kurtosis = 17/10
    kurt = boost::math::statistics::kurtosis(v);
    BOOST_TEST(abs(kurt - 17.0/10.0) < 10*tol);

    v = {0,0,0,0,5};
    // mu = 1, sigma^2 = 4, sigma = 2, skew = 3/2, kurtosis = 13/4
    kurt = boost::math::statistics::kurtosis(v);
    BOOST_TEST(abs(kurt - 13.0/4.0) < tol);

    v = generate_random_vector<Z>(global_size, global_seed);
    Z scale = 2;
    double m1 = boost::math::statistics::kurtosis(v);
    for (auto & x : v)
    {
        x *= scale;
    }
    double m2 = boost::math::statistics::kurtosis(v);
    BOOST_TEST(abs(m1 - m2) < tol*abs(m1));
}

template<class Real>
void test_first_four_moments()
{
    Real tol = 10*std::numeric_limits<Real>::epsilon();
    std::vector<Real> v{1,1,1};
    auto [M1_1, M2_1, M3_1, M4_1] = boost::math::statistics::first_four_moments(v);
    BOOST_TEST(abs(M1_1 - 1) < tol);
    BOOST_TEST(abs(M2_1) < tol);
    BOOST_TEST(abs(M3_1) < tol);
    BOOST_TEST(abs(M4_1) < tol);

    v = {1, 2, 3, 4, 5};
    auto [M1_2, M2_2, M3_2, M4_2] = boost::math::statistics::first_four_moments(v);
    BOOST_TEST(abs(M1_2 - 3) < tol);
    BOOST_TEST(abs(M2_2 - 2) < tol);
    BOOST_TEST(abs(M3_2) < tol);
    BOOST_TEST(abs(M4_2 - Real(34)/Real(5)) < tol);
}

template<class Real>
void test_median()
{
    std::mt19937 g(12);
    std::vector<Real> v{1,2,3,4,5,6,7};

    Real m = boost::math::statistics::median(v.begin(), v.end());
    BOOST_TEST_EQ(m, 4);

    std::shuffle(v.begin(), v.end(), g);
    // Does range call work?
    m = boost::math::statistics::median(v);
    BOOST_TEST_EQ(m, 4);

    v = {1,2,3,3,4,5};
    m = boost::math::statistics::median(v.begin(), v.end());
    BOOST_TEST_EQ(m, 3);
    std::shuffle(v.begin(), v.end(), g);
    m = boost::math::statistics::median(v.begin(), v.end());
    BOOST_TEST_EQ(m, 3);

    v = {1};
    m = boost::math::statistics::median(v.begin(), v.end());
    BOOST_TEST_EQ(m, 1);

    v = {1,1};
    m = boost::math::statistics::median(v.begin(), v.end());
    BOOST_TEST_EQ(m, 1);

    v = {2,4};
    m = boost::math::statistics::median(v.begin(), v.end());
    BOOST_TEST_EQ(m, 3);

    v = {1,1,1};
    m = boost::math::statistics::median(v.begin(), v.end());
    BOOST_TEST_EQ(m, 1);

    v = {1,2,3};
    m = boost::math::statistics::median(v.begin(), v.end());
    BOOST_TEST_EQ(m, 2);
    std::shuffle(v.begin(), v.end(), g);
    m = boost::math::statistics::median(v.begin(), v.end());
    BOOST_TEST_EQ(m, 2);

    // Does it work with std::array?
    std::array<Real, 3> w{1,2,3};
    m = boost::math::statistics::median(w);
    BOOST_TEST_EQ(m, 2);

    // Does it work with ublas?
    boost::numeric::ublas::vector<Real> w1(3);
    w1[0] = 1;
    w1[1] = 2;
    w1[2] = 3;
    m = boost::math::statistics::median(w);
    BOOST_TEST_EQ(m, 2);
}

template<class Real>
void test_median_absolute_deviation()
{
    std::vector<Real> v{-1, 2, -3, 4, -5, 6, -7};

    Real m = boost::math::statistics::median_absolute_deviation(v.begin(), v.end(), 0);
    BOOST_TEST_EQ(m, 4);

    std::mt19937 g(12);
    std::shuffle(v.begin(), v.end(), g);
    m = boost::math::statistics::median_absolute_deviation(v, 0);
    BOOST_TEST_EQ(m, 4);

    v = {1, -2, -3, 3, -4, -5};
    m = boost::math::statistics::median_absolute_deviation(v.begin(), v.end(), 0);
    BOOST_TEST_EQ(m, 3);
    std::shuffle(v.begin(), v.end(), g);
    m = boost::math::statistics::median_absolute_deviation(v.begin(), v.end(), 0);
    BOOST_TEST_EQ(m, 3);

    v = {-1};
    m = boost::math::statistics::median_absolute_deviation(v.begin(), v.end(), 0);
    BOOST_TEST_EQ(m, 1);

    v = {-1, 1};
    m = boost::math::statistics::median_absolute_deviation(v.begin(), v.end(), 0);
    BOOST_TEST_EQ(m, 1);
    // The median is zero, so coincides with the default:
    m = boost::math::statistics::median_absolute_deviation(v.begin(), v.end());
    BOOST_TEST_EQ(m, 1);

    m = boost::math::statistics::median_absolute_deviation(v);
    BOOST_TEST_EQ(m, 1);

    v = {2, -4};
    m = boost::math::statistics::median_absolute_deviation(v.begin(), v.end(), 0);
    BOOST_TEST_EQ(m, 3);

    v = {1, -1, 1};
    m = boost::math::statistics::median_absolute_deviation(v.begin(), v.end(), 0);
    BOOST_TEST_EQ(m, 1);

    v = {1, 2, -3};
    m = boost::math::statistics::median_absolute_deviation(v.begin(), v.end(), 0);
    BOOST_TEST_EQ(m, 2);
    std::shuffle(v.begin(), v.end(), g);
    m = boost::math::statistics::median_absolute_deviation(v.begin(), v.end(), 0);
    BOOST_TEST_EQ(m, 2);

    std::array<Real, 3> w{1, 2, -3};
    m = boost::math::statistics::median_absolute_deviation(w, 0);
    BOOST_TEST_EQ(m, 2);

    // boost.ublas vector?
    boost::numeric::ublas::vector<Real> u(6);
    u[0] = 1;
    u[1] = 2;
    u[2] = -3;
    u[3] = 1;
    u[4] = 2;
    u[5] = -3;
    m = boost::math::statistics::median_absolute_deviation(u, 0);
    BOOST_TEST_EQ(m, 2);
}

template<class Real>
void test_sample_gini_coefficient()
{
    Real tol = std::numeric_limits<Real>::epsilon();
    std::vector<Real> v{1,0,0};
    Real gini = boost::math::statistics::sample_gini_coefficient(v.begin(), v.end());
    BOOST_TEST(abs(gini - 1) < tol);

    gini = boost::math::statistics::sample_gini_coefficient(v);
    BOOST_TEST(abs(gini - 1) < tol);

    v[0] = 1;
    v[1] = 1;
    v[2] = 1;
    gini = boost::math::statistics::sample_gini_coefficient(v.begin(), v.end());
    BOOST_TEST(abs(gini) < tol);

    v[0] = 0;
    v[1] = 0;
    v[2] = 0;
    gini = boost::math::statistics::sample_gini_coefficient(v.begin(), v.end());
    BOOST_TEST(abs(gini) < tol);

    std::array<Real, 3> w{0,0,0};
    gini = boost::math::statistics::sample_gini_coefficient(w);
    BOOST_TEST(abs(gini) < tol);
}

template<class Real>
void test_gini_coefficient()
{
    Real tol = std::numeric_limits<Real>::epsilon();
    std::vector<Real> v{1,0,0};
    Real gini = boost::math::statistics::gini_coefficient(v.begin(), v.end());
    Real expected = Real(2)/Real(3);
    BOOST_TEST(abs(gini - expected) < tol);

    gini = boost::math::statistics::gini_coefficient(v);
    BOOST_TEST(abs(gini - expected) < tol);

    v[0] = 1;
    v[1] = 1;
    v[2] = 1;
    gini = boost::math::statistics::gini_coefficient(v.begin(), v.end());
    BOOST_TEST(abs(gini) < tol);

    v[0] = 0;
    v[1] = 0;
    v[2] = 0;
    gini = boost::math::statistics::gini_coefficient(v.begin(), v.end());
    BOOST_TEST(abs(gini) < tol);

    std::array<Real, 3> w{0,0,0};
    gini = boost::math::statistics::gini_coefficient(w);
    BOOST_TEST(abs(gini) < tol);

    boost::numeric::ublas::vector<Real> w1(3);
    w1[0] = 1;
    w1[1] = 1;
    w1[2] = 1;
    gini = boost::math::statistics::gini_coefficient(w1);
    BOOST_TEST(abs(gini) < tol);

    std::mt19937 gen(18);
    // Gini coefficient for a uniform distribution is (b-a)/(3*(b+a));
    std::uniform_real_distribution<long double> dis(0, 3);
    expected = (dis.b() - dis.a())/(3*(dis.b()+ dis.a()));
    v.resize(1024);
    for(size_t i = 0; i < v.size(); ++i)
    {
        v[i] = dis(gen);
    }
    gini = boost::math::statistics::gini_coefficient(v);
    BOOST_TEST(abs(gini - expected) < 0.01);

}

template<class Z>
void test_integer_gini_coefficient()
{
    double tol = std::numeric_limits<double>::epsilon();
    std::vector<Z> v{1,0,0};
    double gini = boost::math::statistics::gini_coefficient(v.begin(), v.end());
    double expected = 2.0/3.0;
    BOOST_TEST(abs(gini - expected) < tol);

    gini = boost::math::statistics::gini_coefficient(v);
    BOOST_TEST(abs(gini - expected) < tol);

    v[0] = 1;
    v[1] = 1;
    v[2] = 1;
    gini = boost::math::statistics::gini_coefficient(v.begin(), v.end());
    BOOST_TEST(abs(gini) < tol);

    v[0] = 0;
    v[1] = 0;
    v[2] = 0;
    gini = boost::math::statistics::gini_coefficient(v.begin(), v.end());
    BOOST_TEST(abs(gini) < tol);

    std::array<Z, 3> w{0,0,0};
    gini = boost::math::statistics::gini_coefficient(w);
    BOOST_TEST(abs(gini) < tol);

    boost::numeric::ublas::vector<Z> w1(3);
    w1[0] = 1;
    w1[1] = 1;
    w1[2] = 1;
    gini = boost::math::statistics::gini_coefficient(w1);
    BOOST_TEST(abs(gini) < tol);
}

template<typename Real>
void test_interquartile_range()
{
    std::mt19937 gen(486);
    Real iqr;
    // Taken from Wikipedia's example:
    std::vector<Real> v{7, 7, 31, 31, 47, 75, 87, 115, 116, 119, 119, 155, 177};

    // Q1 = 31, Q3 = 119, Q3 - Q1 = 88.
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 88);

    std::shuffle(v.begin(), v.end(), gen);
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 88);

    std::shuffle(v.begin(), v.end(), gen);
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 88);

    std::fill(v.begin(), v.end(), 1);
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 0);

    v = {1,2,3};
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 2);
    std::shuffle(v.begin(), v.end(), gen);
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 2);

    v = {0, 3, 5};
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 5);
    std::shuffle(v.begin(), v.end(), gen);
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 5);

    v = {1,2,3,4};
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 2);
    std::shuffle(v.begin(), v.end(), gen);
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 2);

    v = {1,2,3,4,5};
    // Q1 = 1.5, Q3 = 4.5
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 3);
    std::shuffle(v.begin(), v.end(), gen);
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 3);

    v = {1,2,3,4,5,6};
    // Q1 = 2, Q3 = 5
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 3);
    std::shuffle(v.begin(), v.end(), gen);
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 3);

    v = {1,2,3, 4, 5,6,7};
    // Q1 = 2, Q3 = 6
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 4);
    std::shuffle(v.begin(), v.end(), gen);
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 4);

    v = {1,2,3,4,5,6,7,8};
    // Q1 = 2.5, Q3 = 6.5
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 4);
    std::shuffle(v.begin(), v.end(), gen);
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 4);

    v = {1,2,3,4,5,6,7,8,9};
    // Q1 = 2.5, Q3 = 7.5
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 5);
    std::shuffle(v.begin(), v.end(), gen);
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 5);

    v = {1,2,3,4,5,6,7,8,9,10};
    // Q1 = 3, Q3 = 8
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 5);
    std::shuffle(v.begin(), v.end(), gen);
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 5);

    v = {1,2,3,4,5,6,7,8,9,10,11};
    // Q1 = 3, Q3 = 9
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 6);
    std::shuffle(v.begin(), v.end(), gen);
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 6);

    v = {1,2,3,4,5,6,7,8,9,10,11,12};
    // Q1 = 3.5, Q3 = 9.5
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 6);
    std::shuffle(v.begin(), v.end(), gen);
    iqr = boost::math::statistics::interquartile_range(v);
    BOOST_TEST_EQ(iqr, 6);
}

int main()
{
    test_mean<float>();
    test_mean<double>();
    test_mean<long double>();
    test_mean<cpp_bin_float_50>();

    test_integer_mean<unsigned>();
    test_integer_mean<int>();

    test_complex_mean<std::complex<float>>();
    test_complex_mean<cpp_complex_50>();

    test_variance<float>();
    test_variance<double>();
    test_variance<long double>();
    test_variance<cpp_bin_float_50>();

    test_integer_variance<int>();
    test_integer_variance<unsigned>();

    test_skewness<float>();
    test_skewness<double>();
    test_skewness<long double>();
    test_skewness<cpp_bin_float_50>();

    test_integer_skewness<int>();
    test_integer_skewness<unsigned>();

    test_first_four_moments<float>();
    test_first_four_moments<double>();
    test_first_four_moments<long double>();
    test_first_four_moments<cpp_bin_float_50>();

    test_kurtosis<float>();
    test_kurtosis<double>();
    test_kurtosis<long double>();
    // Kinda expensive:
    //test_kurtosis<cpp_bin_float_50>();

    test_integer_kurtosis<int>();
    test_integer_kurtosis<unsigned>();

    test_median<float>();
    test_median<double>();
    test_median<long double>();
    test_median<cpp_bin_float_50>();
    test_median<int>();

    test_median_absolute_deviation<float>();
    test_median_absolute_deviation<double>();
    test_median_absolute_deviation<long double>();
    test_median_absolute_deviation<cpp_bin_float_50>();

    test_gini_coefficient<float>();
    test_gini_coefficient<double>();
    test_gini_coefficient<long double>();
    test_gini_coefficient<cpp_bin_float_50>();

    test_integer_gini_coefficient<unsigned>();
    test_integer_gini_coefficient<int>();

    test_sample_gini_coefficient<float>();
    test_sample_gini_coefficient<double>();
    test_sample_gini_coefficient<long double>();
    test_sample_gini_coefficient<cpp_bin_float_50>();

    test_interquartile_range<double>();
    test_interquartile_range<cpp_bin_float_50>();
    return boost::report_errors();
}


/*! \file 2d_area_fill.cpp
  \brief Demonstration of area fill below curves.
  \date 2007
  \author Jacob Voytko and Paul A. Bristow
*/

// Copyright Jacob Voytko 2007
// Copyright Paul A. Bristow 2009

// Use, modification and distribution are 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 <boost/svg_plot/svg_2d_plot.hpp>
  using namespace boost::svg; // Needed to get svg colors and others.
  using boost::svg::svg_2d_plot;

#include <map>
  using std::map;
#include <cmath>
  using std::sin;
  using std::cos;
  using std::tan;

double my_sin(double x)
{
  return 50. * sin(x);
}

double my_cos(double x)
{
  return 50. * cos(x);
}

double my_tan(double x)
{
  return 50. * tan(x);
}

int main()
{
  map<double, double> data_sin;
  map<double, double> data_cos;
  map<double, double> data_tan;

  double inter = 3.14159265 / 8.; // 16 points per cycle of 2 pi.

  for(double i = 0; i <= 10.; i+=inter)
  { // Just 10 data points for each function.
    data_sin[i] = my_sin(i);
    data_cos[i] = my_cos(i);
    data_tan[i] = my_tan(i);
  } // for

  svg_2d_plot my_plot;

  // Size/scale settings.
  my_plot.size(700, 500)
         .x_range(-1, 10)
         .y_range(-75, 75);

  // Text settings.
  my_plot.title("Plot of 50 * sin(x), cos(x) and tan(x)")
         .title_font_size(20)
         .x_label("x")
         .y_label("50 * f(x)")
         .x_major_labels_side(bottom)
         .y_major_labels_side(left)
         .x_major_grid_on(true)
         .y_major_grid_on(true)
         .x_major_grid_color(cyan)
         .y_major_grid_color(cyan)
         ;
  // Commands.
  my_plot.plot_window_on(true)
         .x_label_on(true)
         ;

  // Color settings.
  my_plot.background_color(whitesmoke)
         .legend_background_color(lightyellow)
         .legend_border_color(yellow)
         .plot_background_color(ghostwhite)
         .title_color(red)
         ;
  // X axis settings.
  my_plot.x_major_interval(2)
         .x_major_tick_length(14)
         .x_major_tick_width(1)
         .x_minor_tick_length(7)
         .x_minor_tick_width(1)
         .x_num_minor_ticks(3)

  // Y axis settings.
         .y_major_interval(25)
         .y_num_minor_ticks(5);

  svg_2d_plot_series& s_sin = my_plot.plot(data_sin, "sin(x)").line_on(true).area_fill(red);
  std::cout << "s_sin.area_fill() " << s_sin.area_fill() << std::endl; // s_sin.area_fill() RGB(255,0,0)

    svg_2d_plot_series& s_cos = my_plot.plot(data_cos, "cos(x)").line_on(true).area_fill(blue).shape(square);
  std::cout << "s_cos.area_fill() " << s_cos.area_fill() << std::endl; // s_cos.area_fill() RGB(0,0,255)

  svg_2d_plot_series& s_tan = my_plot.plot(data_tan, "tan(x)").shape(cone).line_on(true).area_fill(blank);
  // Note that svg_color(blank) or svg_color(false) returns a non-color blank, so no fill.
  std::cout << "s_tan.area_fill() " << s_tan.area_fill() << std::endl; // s_tan.area_fill() blank

  std::cout << my_plot.title() << std::endl; // "Plot of 50 * sin(x), cos(x) and tan(x)"

  my_plot.write("./2d_area_fill_1.svg");

  my_plot.plot(data_sin, "sin(x)").line_on(true).area_fill(green).shape(square).fill_color(red);
  // Note how this overwrites the previously cos fill and tan line.
  // (It also needs a new title).

  my_plot.title("sin overwriting cos and tan");
  std::cout << my_plot.title() << std::endl; // "sin overwriting cos and tan"

  my_plot.write("./2d_area_fill_2.svg");

   return 0;
} // int main()

/*

Output:

2d_area_fill.cpp
Linking...
Embedding manifest...
Autorun "j:\Cpp\SVG\debug\2d_area_fill.exe"
s_sin.area_fill() RGB(255,0,0)
s_cos.area_fill() RGB(0,0,255)
s_tan.area_fill() blank
Plot of 50 * sin(x), cos(x) and tan(x)

*/


#include <Eigen/Dense>
#include "simple_activation.h"

namespace MyDL{
    using namespace Eigen;

    // 実装がめんどくさいので直接Eigen::MatrixXdを引数にとるよう実装
    MatrixXd softmax(MatrixXd x)
    {
        VectorXd max_coeff_vec, rowwise_sum;
        max_coeff_vec = x.rowwise().maxCoeff();

        x = x.colwise() - max_coeff_vec; // expのオーバーフロー回避 → 各バッチベクトルごとに最大の要素を抽出
        x = x.array().exp();             // expを各要素に実行
        rowwise_sum = x.rowwise().sum(); // バッチベクトルごとに総和を計算

        x.array().colwise() /= rowwise_sum.array(); // 出力の総和が1になるよう調整
        return x;
    }

    MatrixXd sigmoid(MatrixXd x)
    {
        x = x.unaryExpr([](double p) { return 1 / (1 + exp(-p)); });
        return x;
    }
}


#include "k_points_util.h"

#include <boost/functional/hash.hpp>
#include "../array_math.h"

size_t KPointsUtil::get_n_k_points(const double rcut) {
  size_t count = 0;
  const int8_t n_max = floor(rcut);
  for (int8_t i = -n_max; i <= n_max; i++) {
    for (int8_t j = -n_max; j <= n_max; j++) {
      for (int8_t k = -n_max; k <= n_max; k++) {
        if (i * i + j * j + k * k > pow(rcut, 2)) continue;
        count++;
      }
    }
  }
  return count;
}

std::vector<std::array<int8_t, 3>> KPointsUtil::generate_k_points(const double rcut) {
  std::vector<std::array<int8_t, 3>> k_points;
  const int8_t n_max = floor(rcut);
  for (int8_t i = -n_max; i <= n_max; i++) {
    for (int8_t j = -n_max; j <= n_max; j++) {
      for (int8_t k = -n_max; k <= n_max; k++) {
        if (i * i + j * j + k * k > pow(rcut, 2)) continue;
        k_points.push_back(std::array<int8_t, 3>({i, j, k}));
      }
    }
  }
  std::stable_sort(
      k_points.begin(),
      k_points.end(),
      [](const std::array<int8_t, 3>& a, const std::array<int8_t, 3>& b) -> bool {
        return squared_norm(a) < squared_norm(b);
      });
  return k_points;
}

std::vector<std::array<int8_t, 3>> KPointsUtil::get_k_diffs(
    const std::vector<std::array<int8_t, 3>>& k_points) {
  // Generate all possible differences between two different k points.
  std::unordered_set<std::array<int8_t, 3>, boost::hash<std::array<int8_t, 3>>> k_diffs_set;
  std::vector<std::array<int8_t, 3>> k_diffs;
  const size_t n_orbs = k_points.size();
  for (size_t p = 0; p < n_orbs; p++) {
    for (size_t q = 0; q < n_orbs; q++) {
      if (p == q) continue;
      const auto& diff_pq = k_points[q] - k_points[p];
      if (k_diffs_set.count(diff_pq) == 1) continue;
      k_diffs.push_back(diff_pq);
      k_diffs_set.insert(diff_pq);
    }
  }

  // Sort k_diffs into ascending order so that later sorting hci queue will be faster.
  std::stable_sort(
      k_diffs.begin(),
      k_diffs.end(),
      [](const std::array<int8_t, 3>& a, const std::array<int8_t, 3>& b) -> bool {
        return squared_norm(a) < squared_norm(b);
      });

  return k_diffs;
}


/*
Headers for file "utils.cpp".

Copyright (c) 2021 Gabriele Gilardi
*/

#ifndef __UTILS_H_
#define __UTILS_H_

#include <random>
#include <Eigen/Dense>

using namespace std;
using namespace Eigen;

ArrayXXd rnd(uniform_real_distribution<double> dist, mt19937_64& generator,
             int nr=1, int nc=1);
ArrayXXd rnd(normal_distribution<double> dist, mt19937_64& generator,
             int nr=1, int nc=1);
ArrayXi cumsum(ArrayXi X);
ArrayXi cumprod(ArrayXi X);
double stdev(ArrayXd X, int ddof=0);
double rmse(ArrayXd X, ArrayXd Y);
double accuracy(ArrayXd A, ArrayXd B, double tol=1.e-5);
double calc_corr(ArrayXd X, ArrayXd Y);
ArrayXd normalize(ArrayXd X, double mu=0.0, double sigma=1.0);
ArrayXd scale(ArrayXd X, double Xmin, double Xmax, double a=-1.0, double b=1.0);
ArrayXi shuffle(int nel, mt19937_64& gen);
VectorXd exact_sol(MatrixXd A, VectorXd b);

#endif


#include <cradle/geometry/grid_points.hpp>
#include <cradle/geometry/regular_grid.hpp>

#include <boost/assign/std/vector.hpp>

#include <cradle/test.hpp>

using namespace cradle;
using namespace boost::assign;

TEST_CASE("regular_grid_test")
{
    regular_grid<1, float> default_constructed;

    vector2d p0 = make_vector<double>(0, 0);
    vector2d spacing = make_vector<double>(1, 0.5);
    vector2u n_points = make_vector<unsigned>(2, 3);

    regular_grid<2, double> grid(p0, spacing, n_points);
    REQUIRE(grid.p0 == p0);
    REQUIRE(grid.spacing == spacing);
    REQUIRE(grid.n_points == n_points);

    std::vector<vector2d> correct_points;
    correct_points += make_vector<double>(0, 0), make_vector<double>(1, 0),
        make_vector<double>(0, 0.5), make_vector<double>(1, 0.5),
        make_vector<double>(0, 1), make_vector<double>(1, 1);
    CRADLE_CHECK_RANGES_ALMOST_EQUAL(
        make_grid_point_list(grid), correct_points);
}

TEST_CASE("grid_bounding_box_test")
{
    vector2d p0 = make_vector<double>(-1, 0);
    vector2d spacing = make_vector<double>(1, 0.5);
    vector2u n_points = make_vector<unsigned>(2, 3);

    regular_grid<2, double> grid(p0, spacing, n_points);

    REQUIRE(
        bounding_box(grid)
        == box2d(make_vector<double>(-1, 0), make_vector<double>(1, 1)));
}

vector3d
get_point_at_index(regular_grid<3, double> const& grid, size_t index)
{
    size_t x_index = index % grid.n_points[0];
    size_t y_index = (index / grid.n_points[0]) % grid.n_points[1];
    size_t z_index = (index / grid.n_points[0]) / grid.n_points[1];
    return make_vector(
        grid.p0[0] + x_index * grid.spacing[0],
        grid.p0[1] + y_index * grid.spacing[1],
        grid.p0[2] + z_index * grid.spacing[2]);
}


#include <fmt/core.h>
#include <yaml-cpp/yaml.h>

#include <Eigen/Dense>
#include <iostream>
#include <random>

#include "Metropolis.hpp"
#include "NumpySaver.hpp"
#include "Timer.hpp"

using namespace Eigen;
using std::cout, std::endl;

using Array1d = Array<double, 1, 1>;

constexpr double pow2(double x) { return x * x; };
constexpr double pow3(double x) { return x * x * x; };

double pdf_1d(const Array1d& x, double alpha) {
  return std::exp(-2 * alpha * x[0] * x[0]);
}

double energy_1d(const Array1d& x, double alpha) {
  return alpha + x[0] * x[0] * (1. / 2. - 2 * alpha * alpha);
}

/**
 * @brief
 *
 * @param x = {x0, y0, x1, y1}
 * @param alpha
 * @param lambda
 * @return double
 */
double pdf_wavefunction_2d(const Array4d& x, double alpha, double lambda) {
  double r = std::sqrt(pow2(x[0] - x[2]) + pow2(x[1] - x[3]));
  return std::exp(-pow2(x[0]) - pow2(x[1]) - pow2(x[2]) - pow2(x[3]) +
                  2. * lambda * r / (1. + alpha * r));
}

constexpr double derivative_factor(double r, double x, double x_other,
                                   double alpha, double lambda) {
  using std::pow;
  return -1. + lambda / pow2(1 + alpha * r) / r -
         lambda * pow2(x - x_other) / pow3((1. + alpha * r) * r) *
             (1. + 3. * alpha * r) +
         pow2(-x + lambda * (x - x_other) / pow2(1. + alpha * r) / r);
}

double energy_2d(const Array4d& x, double alpha, double lambda) {
  double r = std::sqrt(pow2(x[0] - x[2]) + pow2(x[1] - x[3]));

  double dx1 = derivative_factor(r, x[0], x[2], alpha, lambda);
  double dx2 = derivative_factor(r, x[2], x[0], alpha, lambda);
  double dy1 = derivative_factor(r, x[1], x[3], alpha, lambda);
  double dy2 = derivative_factor(r, x[3], x[1], alpha, lambda);

  return (-dx1 + pow2(x[0]) - dx2 + pow2(x[2]) - dy1 + pow2(x[1]) - dy2 +
          pow2(x[3])) /
             2. +
         lambda / r;
}

std::tuple<double, double> golden_search_simple(std::function<double(double)> f,
                                                double xmin, double xmax,
                                                double tol) {
  constexpr double golden_ratio = 1.618033988749895;
  double x1, x2;
  if (xmin >= xmax)
    throw std::invalid_argument("xmin must be smaller than xmax");

  while (xmax - xmin > tol) {
    x1 = xmax - (xmax - xmin) / golden_ratio;
    x2 = xmin + (xmax - xmin) / golden_ratio;

    if (f(x1) < f(x2))
      xmax = x2;
    else
      xmin = x1;
  }

  return {xmin, xmax};
}

std::tuple<double, double> golden_search(std::function<double(double)> f,
                                         double xmin, double xmax, double tol) {
  constexpr double golden_ratio = 1.618033988749895;
  double x1 = xmax - (xmax - xmin) / golden_ratio;
  double x2 = xmin + (xmax - xmin) / golden_ratio;
  double f1 = f(x1);
  double f2 = f(x2);

  if (xmin >= xmax)
    throw std::invalid_argument("xmin must be smaller than xmax");

  while (xmax - xmin > tol) {
    if (f1 < f2) {
      xmax = x2;
      x2 = x1;
      f2 = f1;
      x1 = xmax - (xmax - xmin) / golden_ratio;
      f1 = f(x1);
    } else {
      xmin = x1;
      x1 = x2;
      f1 = f2;
      x2 = xmin + (xmax - xmin) / golden_ratio;
      f2 = f(x2);
    }
  }

  return {xmin, xmax};
}

std::tuple<double, double> adaptive_golden_search(
    std::function<std::tuple<double, double>(double, Index sample)> f,
    double xmin, double xmax, double tol, Index start_sample = 1000) {
  constexpr double golden_ratio = 1.618033988749895;
  Index sample = start_sample;

  double x1 = xmax - (xmax - xmin) / golden_ratio;
  double x2 = xmin + (xmax - xmin) / golden_ratio;
  auto [f1, f1_err] = f(x1, sample);
  auto [f2, f2_err] = f(x2, sample);

  std::tuple<double, double> buf;

  if (xmin >= xmax)
    throw std::invalid_argument("xmin must be smaller than xmax");

  while (xmax - xmin > tol) {
    if (f1 < f2) {
      xmax = x2;
      x2 = x1;
      f2 = f1;
      x1 = xmax - (xmax - xmin) / golden_ratio;
      std::tie(f1, f1_err) = f(x1, sample);
    } else {
      xmin = x1;
      x1 = x2;
      f1 = f2;
      x2 = xmin + (xmax - xmin) / golden_ratio;
      std::tie(f2, f2_err) = f(x2, sample);
    }
    if (std::abs(f1 - f2) < f1_err + f2_err) {
      sample += (Index)pow2((f1_err + f2_err) / std::abs(f1 - f2));
    }
  }

  return {xmin, xmax};
}

void test_1D_metropolis() {
  Array1d argstart;
  argstart << 0;

  std::uniform_real_distribution<> step(-2, 2);
  std::normal_distribution<> step_gauss(0, 1);

  using namespace std::placeholders;
  auto pdf_normal = std::bind(pdf_1d, _1, 1. / 4.);

  MetropolisAlgorithm<1> malg_uniform(pdf_normal, argstart, step);
  MetropolisAlgorithm<1, std::normal_distribution<> > malg_normal(
      pdf_normal, argstart, step_gauss);

  auto test_1 = malg_uniform.get_sample(100000);
  auto test_2 = malg_normal.get_sample(100000);

  auto [mean, std] =
      Timer::measure_time([&]() { malg_uniform.get_sample(100000); }, .1);
  cout << "Generating 100000 events with uniform step took " << mean << "±"
       << std << "ms\n";

  auto [mean_n, std_n] =
      Timer::measure_time([&]() { malg_normal.get_sample(100000); }, .1);
  cout << "Generating 100000 events with normal step took " << mean_n << "±"
       << std_n << "ms\n";
  NumpySaver("build/output/test_normal_distribution.npy") << test_1 << test_2;
}

double optimal_alpha_2d(double lambda, Index sample = 100000) {
  using namespace std::placeholders;

  Array4d argstart;
  argstart << 0, 1, 0, -1;
  std::normal_distribution<> step_gauss(0, 1);

  std::function<double(double)> f = [&](double alpha) {
    auto pdf = std::bind(pdf_wavefunction_2d, _1, alpha, lambda);
    auto energy = std::bind(energy_2d, _1, alpha, lambda);
    MetropolisAlgorithm<4, std::normal_distribution<> > malg(pdf, argstart,
                                                             step_gauss);
    auto [mean, std, var] = malg.average(energy, sample);
    return mean;
  };

  auto [xmin, xmax] = golden_search(f, .1, 1, 1e-6);
  return (xmin + xmax) / 2;
}

void test_2D_metropolis(double lambda = 1) {
  Array4d argstart;
  argstart << 0, 1, 0, -1;

  std::uniform_real_distribution<> step(-2, 2);
  std::normal_distribution<> step_gauss(0, 1);

  using namespace std::placeholders;
  double alpha = optimal_alpha_2d(lambda);
  auto pdf_2d = std::bind(pdf_wavefunction_2d, _1, alpha, lambda);

  MetropolisAlgorithm<4, std::normal_distribution<> > malg_normal(
      pdf_2d, argstart, step_gauss);

  auto test_1 = malg_normal.get_sample(100000);

  auto [mean, std] =
      Timer::measure_time([&]() { malg_normal.get_sample(100000); }, .1);
  cout << "Generating 100000 2D events with uniform step took " << mean << "±"
       << std << "ms\n";

  NumpySaver("build/output/test_2D_distribution.npy")
      << test_1.col(0) << test_1.col(1) << test_1.col(2) << test_1.col(3);
}

void test_1D_energy(Index n = 300) {
  using namespace std::placeholders;

  Array1d argstart;
  argstart << 0;
  std::normal_distribution<> step_gauss(0, 1);

  ArrayXd alphas = ArrayXd::LinSpaced(n, 0.1, 1);
  ArrayXd means(n);
  ArrayXd stds(n);
  ArrayXd vars(n);

  for (Index i = 0; i < n; i++) {
    auto pdf_normal = std::bind(pdf_1d, _1, alphas[i]);
    auto energy = std::bind(energy_1d, _1, alphas[i]);
    MetropolisAlgorithm<1, std::normal_distribution<> > malg_normal(
        pdf_normal, argstart, step_gauss);
    auto [mean, std, var] = malg_normal.average(energy, 100000);
    means[i] = mean;
    stds[i] = std;
    vars[i] = var;
  }

  NumpySaver("build/output/test_1d_energy.npy")
      << alphas << means << stds << vars;
}

void test_2D_energy(Index n = 300, double lambda = 1) {
  using namespace std::placeholders;

  Array4d argstart;
  argstart << 0, 1, 0, -1;
  std::normal_distribution<> step_gauss(0, 1);

  ArrayXd alphas = ArrayXd::LinSpaced(n, 0.1, 1);
  ArrayXd means(n);
  ArrayXd stds(n);
  ArrayXd vars(n);

  for (Index i = 0; i < n; i++) {
    auto pdf = std::bind(pdf_wavefunction_2d, _1, alphas[i], lambda);
    auto energy = std::bind(energy_2d, _1, alphas[i], lambda);
    MetropolisAlgorithm<4, std::normal_distribution<> > malg(pdf, argstart,
                                                             step_gauss);
    auto [mean, std, var] = malg.average(energy, 100000);
    means[i] = mean;
    stds[i] = std;
    vars[i] = var;
  }

  NumpySaver(fmt::format("build/output/test_2d_energy_{}.npy", lambda))
      << alphas << means << stds << vars;
}

void find_1d_alpha(Index sample = 1000000) {
  using namespace std::placeholders;

  Array1d argstart;
  argstart << 0;
  std::normal_distribution<> step_gauss(0, 1);

  std::function<double(double)> f = [&](double alpha) {
    auto pdf = std::bind(pdf_1d, _1, alpha);
    auto energy = std::bind(energy_1d, _1, alpha);
    MetropolisAlgorithm<1, std::normal_distribution<> > malg(pdf, argstart,
                                                             step_gauss);
    auto [mean, std, var] = malg.average(energy, sample);
    return mean;
  };

  auto [xmin, xmax] = golden_search(f, .1, 1, 1e-6);
  cout << "Optimal α for 1D case is between " << xmin << " and " << xmax << " ("
       << f(xmin) << " < E <" << f(xmax) << ")" << endl;
}

void find_2d_alpha(double lambda = 1, Index sample = 1000000) {
  using namespace std::placeholders;

  Array4d argstart;
  argstart << 0, 1, 0, -1;
  std::normal_distribution<> step_gauss(0, 1);

  std::function<double(double)> f = [&](double alpha) {
    auto pdf = std::bind(pdf_wavefunction_2d, _1, alpha, lambda);
    auto energy = std::bind(energy_2d, _1, alpha, lambda);
    MetropolisAlgorithm<4, std::normal_distribution<> > malg(pdf, argstart,
                                                             step_gauss);
    auto [mean, std, var] = malg.average(energy, sample);
    return mean;
  };

  auto [xmin, xmax] = golden_search(f, .1, 1, 1e-6);
  cout << "Optimal α for 2D case (λ=" << lambda << ") is between " << xmin
       << " and " << xmax << " (" << f(xmin) << " < E <" << f(xmax) << ")"
       << endl;
}

void find_2d_alpha_adaptive(double lambda = 1) {
  using namespace std::placeholders;

  Array4d argstart;
  argstart << 0, 1, 0, -1;
  std::normal_distribution<> step_gauss(0, 1);

  std::function<std::tuple<double, double>(double, Index)> f =
      [&](double alpha, double sample) {
        auto pdf = std::bind(pdf_wavefunction_2d, _1, alpha, lambda);
        auto energy = std::bind(energy_2d, _1, alpha, lambda);
        MetropolisAlgorithm<4, std::normal_distribution<> > malg(pdf, argstart,
                                                                 step_gauss);
        auto [mean, std, var] = malg.average(energy, sample);
        return std::tuple<double, double>({mean, std});
      };

  auto [xmin, xmax] = adaptive_golden_search(f, .1, 1, 1e-6);
  cout << "Optimal (adaptive) α for 2D case (λ=" << lambda << ") is between "
       << xmin << " and " << xmax << endl;
}

void energy_function_test() {
  cout << "E_2d(1, 0, 2, 0|λ=1,α=1)=" << energy_2d({1, 0, 2, 0}, 1, 1) << endl;
  cout << "E_2d(1.1, 3.1, -5.6, -5|λ=2.1,α=1.1)="
       << energy_2d({1.1, 3.1, -5.6, -5}, 1.1, 2.1) << endl;
}

int main(int argc, char const* argv[]) {
  energy_function_test();
  test_1D_metropolis();
  test_2D_metropolis();
  test_1D_energy();
  test_2D_energy(300, 0);
  test_2D_energy(300, 1);
  test_2D_energy(300, 2);
  test_2D_energy(300, 8);
  find_1d_alpha();
  find_2d_alpha(0);
  find_2d_alpha(1);
  find_2d_alpha(2);
  find_2d_alpha(8);
  return 0;
}


#include "parameters.h"

#include <NTL/RR.h>

namespace {
    using namespace NTL;
}

namespace gnfs {
    long param_d(const ZZ& n) {
        RR ln = log(conv<RR>(n));
        RR base = (3 * ln) / log(ln);
        return conv<long>(pow(base, RR(1.0/3)));
    }

    long param_B(const ZZ& n) {
        RR ln = log(conv<RR>(n));
        RR base = 8.0 / 9 * ln * log(ln) * log(ln);
        return conv<long>(exp(pow(base, RR(1.0/3))));
    }

    std::pair<ZZ, ZZX> param_mf(const ZZ& n, long d) {
        ZZ m = conv<ZZ>(pow(conv<RR>(n), RR(1.0/d)));

        // Write n in base m
        ZZX f;
        ZZ cur = n;
        long i = 0;
        while (cur != 0) {
            SetCoeff(f, i, cur % m);
            cur /= m;
            i++;
        }

        return std::make_pair(m, f);
    }
}


/*
 * ex_main.cpp
 *
 *  Created on: 2017. 6. 6.
 *      Author: cho
 */

#include <random>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/matrix_proxy.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/triangular.hpp>
#include <boost/numeric/ublas/io.hpp>
//#include "io.hpp"

using namespace std;
namespace ublas = boost::numeric::ublas;

/// number of data
const unsigned int N = 3;

int main () {
	// random number generator
	random_device rd;      // obtain seed
	mt19937_64 gen(rd());  // mersenne twister engine

	// vector
	cout << "============== vector" << endl;
	{
		ublas::vector<double> t (N); // time vector, sec
		for (unsigned i = 0; i < t.size(); ++i) t(i) = i;
		cout << "t("<<N<<") = " << t << endl;

		uniform_int_distribution<int> dis_y( 0, 10 );
		ublas::vector<double> y (N); // measured value
		for (unsigned i = 0; i < y.size(); ++i) y(i) = dis_y(gen);
		cout << "y("<<N<<") = " << y << endl;

		for (unsigned i = 0; i < N; ++i) {
			ublas::unit_vector<double> u(N, i);
			cout << "unit_vector("<<N<<","<<i<<") = " << u << endl;
		}

		ublas::zero_vector<double> z(N);
		cout << "zero_vector("<<N<<") = " << z << endl;

		ublas::scalar_vector<double> s(N, 3);
		cout << "scalar_vector("<<N<<",3) = " << s << endl;

		ublas::scalar_vector<double> one(N, 1);
		cout << "one("<<N<<",1) = " << one << endl;
	}

	// range
	cout << "============== range, slice" << endl;
	{
		ublas::range r1(0, N);
		for (unsigned i = 0; i < r1.size(); ++ i) {
			cout << "range(0,N) r("<<i<<") = " << r1(i) << endl;
		}

		ublas::range r2(3, N);
		for (unsigned i = 0; i < r2.size(); ++ i) {
			cout << "range(3,N) r("<<i<<") = " << r2(i) << endl;
		}

		ublas::slice s2(0, 2, N);
		for (unsigned i = 0; i < s2.size(); ++ i) {
			cout << "slice(0,2,N) s2("<<i<<") = " << s2(i) << endl;
		}

		ublas::slice s3(1, 3, N);
		for (unsigned i = 0; i < s3.size(); ++ i) {
			cout << "slice(1,3,N) s("<<i<<") = " << s3(i) << endl;
		}
	}

	// matrix
	cout << "============== matrix" << endl;
	{
		ublas::matrix<double> m;
		cout << "m()          = " << m << endl;

		m.resize( N, N );
		cout << "m.resize(N,N)= " << m << endl;

		m.clear();
		cout << "m.clear()    = " << m << endl;

		for (unsigned i = 0; i < m.size1 (); ++ i)
			for (unsigned j = 0; j < m.size2 (); ++ j)
				m (i, j) = i*10 + j;
		cout << "matrix       = " << m << endl;

		cout << "transpose    = " << ublas::trans(m) << endl;
		cout << "column(m, 1) = " << ublas::column (m, 1) << endl;
		cout << "column(m, 2) = " << ublas::column (m, 2) << endl;
		cout << "row(m, 1)    = " << ublas::row (m, 1) << endl;
		cout << "row(m, 2)    = " << ublas::row (m, 2) << endl;
		cout << "project(m, range(0,1), range(0,N))     = "
				<< ublas::project(m, ublas::range(0,1), ublas::range(0,N)) << endl;
		cout << "project(m, range(N-1,N), range(N-1,N)) = "
				<< ublas::project(m, ublas::range(N-1,N), ublas::range(N-1,N)) << endl;
		cout << "project(m, slice(1,2,2), slice(1,2,2)) = "
				<< ublas::project(m, ublas::slice(0,2,2), ublas::slice(0,2,2)) << endl;

		auto m_temp = m;
		for ( unsigned i = 0; i < m_temp.size2(); ++i ) {
			ublas::column( m_temp, i) = ublas::scalar_vector<double>(m_temp.size1(),1);
			cout << "m_temp       = " << m_temp << endl;
		}

		m_temp = m;
		for ( unsigned i = 0; i < m_temp.size1(); ++i ) {
			ublas::row( m_temp, i ) = ublas::scalar_vector<double>(m_temp.size2(),1);
			cout << "m_temp       = " << m_temp << endl;
		}

		ublas::identity_matrix<double> mi(N);
		cout << "identity(N)      = " << mi << endl;

		ublas::zero_matrix<double> zero(N);
		cout << "zero(N)          = " << zero << endl;

		ublas::scalar_matrix<double> ones(1, 1, 1);
		cout << "sizeof( scalar_matrix(1, 1, 1) ) = " << sizeof(ones) << endl;
		ublas::scalar_matrix<double> twos(1000, 2000000, 2);
		cout << "sizeof( scalar_matrix(1000, 2000000, 2) ) = " << sizeof(twos) << endl;

		ublas::matrix<double> ones1(1, 1, 1);
		cout << "sizeof( matrix(1, 1, 1) ) = " << sizeof(ones1) << endl;
		ublas::matrix<double> twos1(10000, 20000, 2);
		cout << "sizeof( matrix(10000, 20000, 2) ) = " << sizeof(twos1) << endl;
	}

	// matrix
	cout << "============== triangular_matrix" << endl;
	{
		ublas::triangular_matrix<double, ublas::lower> l(N,N);
		for ( unsigned i = 0; i < l.size1(); ++i )
			for ( unsigned j = 0; j <= i; ++j )
				l(i,j) = 10*i + j;
		cout << "l(N, N)           = " << l << endl;

		ublas::triangular_matrix<double, ublas::unit_lower> ul(N,N);
		for ( unsigned i = 0; i < ul.size1(); ++i )
			for ( unsigned j = 0; j < i; ++j )
				ul(i,j) = 10*i + j;
		cout << "ul(N, N)          = " << ul << endl;

		ublas::triangular_matrix<double, ublas::upper> u(N,N);
		for ( unsigned i = 0; i < u.size1(); ++i )
			for ( unsigned j = i; j < u.size2(); ++j )
				u(i,j) = 10*i + j+1;
		cout << "u(N, N)           = " << u << endl;

		ublas::triangular_matrix<double, ublas::unit_upper> uu(N,N);
		for ( unsigned i = 0; i < uu.size1(); ++i )
			for ( unsigned j = i+1; j < uu.size2(); ++j )
				uu(i,j) = 10*i + j+1;
		cout << "uu(N, N)          = " << uu << endl;
	}
}


/*
    This file is part of control-lib.

    Copyright (c) 2020, 2021, 2022 Bernardo Fichera <bernardo.fichera@gmail.com>

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

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

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

#include "control_lib/tools/math.hpp"

#include <Eigen/Dense>

namespace control_lib {
    namespace tools {
        inline Eigen::Vector3d eulerError(const Eigen::Vector3d& curr, const Eigen::Vector3d& ref)
        {
            Eigen::Vector3d error = curr - ref;

            for (size_t i = 0; i < 3; i++) {
                if (error(i) > M_PI)
                    error(i) -= 2 * M_PI;
                else if (error(i) < -M_PI)
                    error(i) += 2 * M_PI;
            }

            return error;
        }

        inline Eigen::Vector3d rotationError(const Eigen::Vector3d& curr, const Eigen::Vector3d& ref)
        {
            Eigen::Matrix3d R_current = Eigen::AngleAxisd(curr.norm(), curr.normalized()).toRotationMatrix(),
                            R_desired = Eigen::AngleAxisd(ref.norm(), ref.normalized()).toRotationMatrix();

            Eigen::AngleAxisd aa = Eigen::AngleAxisd(R_current.transpose() * R_desired);

            return aa.axis() * aa.angle();
        }

        inline Eigen::Vector4d quaternionError(const Eigen::Vector4d& curr, const Eigen::Vector4d& ref)
        {
            Eigen::Quaterniond q_current = Eigen::Quaterniond(curr), q_desired = Eigen::Quaterniond(ref);
            return (q_current.inverse() * q_desired).coeffs();
        }

        inline Eigen::MatrixXd kronecker(const Eigen::MatrixXd& A, const Eigen::MatrixXd& B)
        {
            Eigen::MatrixXd C(A.rows() * B.rows(), A.cols() * B.cols());

            for (size_t i = 0; i < A.rows(); i++) {
                for (size_t j = 0; j < A.cols(); j++)
                    C.block(i * B.rows(), j * B.cols(), B.rows(), B.cols()) = A(i, j) * B;
            }

            return C;
        }

        Eigen::MatrixXd solveVectorized(const Eigen::MatrixXd& A, const Eigen::MatrixXd& W)
        {
            size_t dim = A.rows();

            return (kronecker(Eigen::MatrixXd::Identity(dim, dim), A) + kronecker(A, Eigen::MatrixXd::Identity(dim, dim)))
                .colPivHouseholderQr() // selfadjointView<Eigen::Upper>().llt()
                .solve(W.reshaped())
                .reshaped(dim, dim);
        }

        Eigen::MatrixXd bartelsStewart(const Eigen::MatrixXd& A, const Eigen::MatrixXd& W)
        {
            Eigen::RealSchur<Eigen::MatrixXd> schur(A);

            size_t dim = A.rows(), block_dim = (dim % 2) ? 1 : 2;

            Eigen::MatrixXd U = schur.matrixU(), T = schur.matrixT(), C = U.transpose() * W * U, Y = Eigen::MatrixXd::Zero(dim, dim);

            for (size_t i = dim / block_dim; i < 0; i--) {
                Eigen::MatrixXd C_11 = C.block(0, 0, (i - 1) * block_dim, (i - 1) * block_dim),
                                C_12 = C.block(0, (i - 1) * block_dim, (i - 1) * block_dim, block_dim),
                                C_21 = C.block((i - 1) * block_dim, 0, block_dim, (i - 1) * block_dim),
                                C_22 = C.block((i - 1) * block_dim, (i - 1) * block_dim, block_dim, block_dim),
                                R_11 = T.block(0, 0, (i - 1) * block_dim, (i - 1) * block_dim),
                                R_12 = T.block(0, (i - 1) * block_dim, (i - 1) * block_dim, block_dim),
                                R_22 = T.block((i - 1) * block_dim, (i - 1) * block_dim, block_dim, block_dim),
                                Z_12((i - 1) * block_dim, block_dim),
                                Z_21(block_dim, (i - 1) * block_dim),
                                Z_22 = solveVectorized(R_22, C_22),
                                Cbar_12 = C_12 - R_12 * Z_22,
                                Cbar_21 = C_21.transpose() - R_12 * Z_22.transpose();

                for (size_t j = 1 - 1; j < 0; j--) {
                    Eigen::MatrixXd Rcurr = R_11.block((j - 1) * block_dim, (j - 1) * block_dim, block_dim, block_dim);
                    Z_12.block((j - 1) * block_dim, block_dim, 0, block_dim) = solveVectorized(Rcurr, Cbar_12.block((j - 1) * block_dim, block_dim, 0, block_dim));
                    Z_21.block(0, block_dim, (j - 1) * block_dim, block_dim) = solveVectorized(Rcurr, Cbar_21.block((j - 1) * block_dim, block_dim, 0, block_dim));
                }

                Y.block((i - 1) * block_dim, (i - 1) * block_dim, block_dim, block_dim) = Z_22;
                Y.block(0, (i - 1) * block_dim, (i - 1) * block_dim, block_dim) = Z_12;
                Y.block((i - 1) * block_dim, 0, block_dim, (i - 1) * block_dim) = Z_21;

                C = C_11 - R_12 * Z_21 - Z_12 * R_12.transpose();
            }

            return U.transpose() * Y * U;
        }
    } // namespace tools
} // namespace control_lib


#include <cmath>
#include <ctime>
#include <cmath>
#include <bitset>
#include <string>
#include <fstream>
#include <cstdlib>
#include <cassert>
#include <utility>
#include <iostream>

#include <openssl/sha.h>

#pragma once

/* Arbitrary precision integers */
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/chrono/chrono.hpp>
#include <boost/multiprecision/random.hpp>
#include <boost/random/random_device.hpp>

using namespace boost::multiprecision;
using namespace boost::random;

typedef boost::multiprecision::cpp_int bigint;
typedef independent_bits_engine<mt19937, 256, bigint> generator_type;

random_device dev;
generator_type gen(dev);

// http://stackoverflow.com/a/10632725
std::string sha256(const std::string str)
{
    unsigned char hash[SHA256_DIGEST_LENGTH];
    SHA256_CTX sha256;
    SHA256_Init(&sha256);
    SHA256_Update(&sha256, str.c_str(), str.size());
    SHA256_Final(hash, &sha256);
    std::stringstream ss;
    for(int i = 0; i < SHA256_DIGEST_LENGTH; i++)
    {
        ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
    }
    return ss.str();
}

std::string sha256_file(const std::string file_name) {
	std::ifstream ifs(file_name);
	std::string content( (std::istreambuf_iterator<char>(ifs) ),
						(std::istreambuf_iterator<char>()    ) );

	return sha256(content);
}

inline cpp_int modulo(cpp_int a, cpp_int b) {
	cpp_int m = a%b;
	return m>=0 ? m:m+b;
}

/* This is ugly, bad practice, yet extremely convenient.
	Boost is not allowing me to make an array
	cpp_int x[3]. So I just had to emulate an array
	by using the macro "correct_val" to select the wanted variable.
	To my knowledge, there is no better alternative.
*/
#define correct_val(x0,x1,x2,i) (((i)%3)?(((i)&1)?x1:x2):x0)

cpp_int modInverse(cpp_int a, cpp_int b) {

	cpp_int x0, x1, x2;
	cpp_int y0, y1, y2;
	cpp_int quotient  = abs(a / b);
	cpp_int remainder = modulo(a, b);

	x0 = 0;
	y0 = 1;
	x1 = 1;
	y1 = -quotient;

	cpp_int i = 2;
	for (; (b % (a%b)) != 0; i++) {
		a = b;
		b = remainder;
		quotient = abs(a / b);
		remainder = modulo(a, b);
		correct_val(x0,x1,x2,i) = (-quotient * correct_val(x0,x1,x2,i-1)) + correct_val(x0,x1,x2,i-2);
		correct_val(y0,y1,y2,i) = (-quotient * correct_val(y0,y1,y2,i-1)) + correct_val(y0,y1,y2,i-2);
	}

	return correct_val(x0,x1,x2,i-1);
}

typedef struct point {
	cpp_int x,y;

	inline bool operator==(const point& rhs){
		return (x == rhs.x && y == rhs.y);
	}

	inline bool operator!=(const point& rhs){
		return !(*this == rhs);
	}

} Point;

std::ostream& operator<<(std::ostream& out, const point& obj) {
   	return out << std::hex
   				<< obj.x << " " << obj.y;
}

std::istream& operator>>(std::istream& in, point& obj){
	std::string x, y;
	in >> x >> y;
	obj.x = bigint("0x" + x);
	obj.y = bigint("0x" + y);
	return in;
}

std::string string_to_binary(const std::string& input)
{
    std::ostringstream oss;
    for(auto c : input) {
        oss << std::bitset<8>(c);
    }
    return oss.str();
}

class EllipticCurve {

/* To do: change to private */
private:
/* p, a, b parameters of the curve y^2 = x^3 + ax + b */
	const cpp_int p; /* Modulus of the group */
	const cpp_int a;
	const cpp_int b;

	/* Generator */
	const Point G;

	/* Order of G */
	const cpp_int n;

	/* Cofactor */
	const cpp_int h;

	Point PointAdd(Point p1, Point p2){

		if( p1 == p2 ){
			return PointDouble(p1);
		}

		cpp_int xp = p1.x, yp = p1.y;
		cpp_int xq = p2.x, yq = p2.y;

		if( xp == xq ){
			return {0,0};
		}

		if( p1 == Point{0,0} ){
			return p2;
		}

		if( p2 == Point{0,0} ){
			return p1;
		}

		cpp_int inv = modInverse(xq-xp, p);
		cpp_int lambda = (yq-yp)*inv;
		cpp_int xr = modulo((pow(lambda, 2) - xp - xq), p);
		cpp_int yr = modulo((lambda * (xp - xr) - yp), p);

		if(p1.y == p2.y)
			std::cout << p1 << " + " << p2 << " = " << Point{xr,yr} << std::endl;
		return {xr,yr};
	}

	Point PointDouble(Point p1){

		cpp_int xp = p1.x, yp = p1.y;

		cpp_int inv = modInverse(2*yp, p);
		cpp_int lambda = (3*static_cast<cpp_int>(pow(xp,2))+a)*inv;
		cpp_int xr = modulo((static_cast<cpp_int>(pow(lambda, 2)) - 2*xp),p);
		cpp_int yr = modulo((lambda * (xp - xr) - yp),p);
		return {xr,yr};
	}

	Point PointMultiplication(cpp_int k, Point P) {

		if( k == 0 ) {
			return {0,0};
		}
		if( k == 1 ){ /* Base case for the recursion */
			return P;
		}
		if( !(k&1) ) { /* k is even */
			return PointDouble(PointMultiplication(k/2, P));
		}
		else { /* k is odd */
			return PointAdd(P, PointDouble(PointMultiplication((k-1)/2, P)));
		}
	}

	// wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm
	Point Signature(std::string msg_hash, cpp_int secret_key, Point public_point) {

		cpp_int r = 0;
		cpp_int s = 0;
		cpp_int z = bigint(string_to_binary(msg_hash));

		do {
			cpp_int k = getSecretKey(); /* This is just a random value, less than the order of the curve. */
			Point p1 = PointMultiplication(k, G);
			r = modulo(p1.x, n);
			s = modulo(modInverse(k,n) * (z + r * secret_key), n) ;
		} while(r == 0 || s == 0);

		return {r,s};
	}

	bool VerifySignature(std::string msg_hash, Point signature, Point public_key) {
		/* Checking parameters */
		if( public_key == Point{0,0}){
			return false;
		}
		if( !pointIsInEllipticCurve(public_key)){
			return false;
		}
		if( !(PointMultiplication(n, public_key) == Point{0,0})) {
			return false;
		}

		cpp_int r = signature.x;
		cpp_int s = signature.y;
		cpp_int z = bigint(string_to_binary(msg_hash));

		if( !(r > 0 && r < n && s > 0 && s < n) ) {
			return false;
		}

		cpp_int w  = modInverse(s, n);
		cpp_int u1 = modulo(z * w, n);
		cpp_int u2 = modulo(r * w, n);

		Point curve_point = PointAdd(PointMultiplication(u1, G),
									 PointMultiplication(u2, public_key));

		if( ! pointIsInEllipticCurve(curve_point)){
			return false;
		}

		return (r == curve_point.x);
	}

public:

	EllipticCurve(cpp_int p, cpp_int a, cpp_int b, Point generator, cpp_int n, cpp_int h):
	p(p), a(a), b(b), G(generator), n(n), h(h)
	{

	}

	cpp_int getSecretKey(){
		return gen()%(n-1) + 1;
	}

	Point getPublicValue(cpp_int secretKey) {
		return PointMultiplication(secretKey, G);
	}

	Point computeSharedSecret(cpp_int secretKey, Point publicPoint) {
		return PointMultiplication(secretKey, publicPoint);
	}

	bool pointIsInEllipticCurve(Point pt) {
		return modulo(pow(pt.y, 2), p) == modulo(pow(pt.x,3) + a*pt.x + b, p);
	}

	void Sign(const std::string fileName, cpp_int secretKey, Point publicPoint, bool printFileHash = false) {

		std::ofstream ofs(fileName + ".sig");
		std::string fileHash = sha256_file(fileName);
		if( printFileHash ){
			std::cout << "File hash: " << fileHash << std::endl;
		}

		Point signature = Signature(fileHash, secretKey, publicPoint);
		ofs << signature;
	}

	bool Validate(const std::string fileName, const std::string sigFileName, Point publicKey) {

		Point sig;
		std::string fileHash = sha256_file(fileName);
		std::ifstream ifs(sigFileName);

		ifs >> sig;
		return VerifySignature(fileHash, sig, publicKey);
	}
};


#include <iostream>
#include <Eigen/Dense>
#include <Eigen/LU>
#include <Eigen/Core>

using namespace std;
using namespace Eigen;

int main()
{
  srand((unsigned int) time(0));
  Matrix3d m = Matrix3d::Random();
  cout << "Here is the matrix m:" << endl << m << endl;
  Matrix3d inverse;
  bool invertible;
  double determinant;
  m.computeInverseAndDetWithCheck(inverse,determinant, invertible);
  cout << "Its determinant is " << determinant << endl;
  if (invertible) {
    cout << "It is invertible, and its inverse is:" << endl << inverse << endl;
  }
  else {
    cout << "It is not invertible." << endl;
  }

  return 0;
}


/** MIT License

Copyright (c) 2018 Benjamin Bercovici and Jay McMahon

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

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

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

/**
@file   main.cpp
@Author Benjamin Bercovici (bebe0705@colorado.edu)
@date   July, 2017
@brief  main.cpp Implementation of the dcm_transform example
*/

#include <RigidBodyKinematics.hpp>
#include <armadillo>
#include <iostream>

int main() {

	// Transforming a set of 313 euler angles sequence into a Direction Cosine Matrix
	arma::vec angles_313 = {0.1, 0.2, 0.3};
	arma::mat dcm = RBK::euler313_to_dcm(angles_313);

	// Converting the angles to degrees and recomputing the dcm
	arma::vec angles_313_deg = 180 / arma::datum::pi * angles_313;
	arma::mat dcm_deg = RBK::euler313d_to_dcm(angles_313_deg);

	// Ensuring that the two are the same
	std::cout << "Are the DCMs equal? ";
	if (arma::approx_equal(dcm, dcm_deg, "absdiff", 1e-6)) {
		std::cout << "Yes "  << std::endl;
	}
	else {
		std::cout << "No "  << std::endl;
	}

	// The dcm is then converted to a quaternion
	arma::vec quat = RBK::dcm_to_quat(dcm);

	// Is our quaternion of unit norm?
	std::cout << "Quaternion norm: " << arma::norm(quat) << std::endl;

	// The quaternion is then converted to a set of Modified Rodrigues Parameters
	arma::vec mrp = RBK::quat_to_mrp(quat);

	// The mrp is converted back to a set of 321 euler angles
	arma::vec angles_321 = RBK::mrp_to_euler321(mrp);

	// These angles are different from the 313 euler angles
	std::cout << "Are the 321 and 313 sequences the same? " << std::endl;
	std::cout << "321: " << angles_321.t();
	std::cout << "313: " << angles_313.t();

	// But these two sequences should be equivalent
	std::cout << "Are the 321 and 313 sequences equivalent? " ;

	if (arma::approx_equal(angles_313, RBK::mrp_to_euler313(RBK::euler321_to_mrp(angles_321)), "absdiff", 1e-6)) {
		std::cout << " Yes "  << std::endl;
	}
	else {
		std::cout << " No "  << std::endl;
	}

	return 0;

}


#include <cmath>
#include <string>
#include <vector>

#include <Eigen/Dense>
#include <gtest/gtest.h>

#include <isce3/math/polyfunc.h>

using namespace isce3::math;

struct PolyfuncTest : public ::testing::Test {

    void SetUp() override
    {
        // initialze the members
        y_vals = get_y_vals(x_vals, pn_coef_3rd);

        // calculate std and mean of the input x_vals
        Eigen::Map<const Eigen::ArrayXd> x_vals_m(x_vals.data(), x_vals.size());
        x_mean = x_vals_m.mean();
        x_std = cal_std(x_vals_m);

        // centralize/scale the input and get the new centralized coeff "coef_c"
        Eigen::ArrayXd x_vals_c(x_vals_m);
        x_vals_c -= x_vals_c.mean();
        x_vals_c /= cal_std(x_vals_c);
        double mean_c, std_c;
        std::tie(coef_c, mean_c, std_c) = polyfit(x_vals_c, y_vals, 3);
    }

    // list of functions
    Eigen::ArrayXd get_y_vals(
            const std::vector<double>& x, const std::vector<double>& coef3rd)
    {
        // c0 + c1 * x + c2 * x^2 + c3 * x^3
        Eigen::Map<const Eigen::ArrayXd> x_m(x.data(), x.size());
        Eigen::Map<const Eigen::ArrayXd> coef_m(coef3rd.data(), coef3rd.size());
        return coef_m(0) + coef_m(1) * x_m + coef_m(2) * x_m.pow(2) +
               coef_m(3) * x_m.pow(3);
    }

    void validate_coeffs(const Eigen::Ref<const Eigen::ArrayXd>& coef_est,
            const Eigen::Ref<const Eigen::ArrayXd>& coef_true,
            const std::string& message = {})
    {
        ASSERT_EQ(coef_est.size(), coef_true.size())
                << "Size mismtach between estimated and true coeffs " + message;
        EXPECT_NEAR((coef_est - coef_true).abs().maxCoeff(), 0.0, abs_tol)
                << "Value mismatch between estimated and true coeffs " +
                           message;
    }

    double cal_std(const Eigen::Ref<const Eigen::ArrayXd>& x)
    {
        return std::sqrt((x - x.mean()).abs2().mean());
    }

    // list of members
    const double abs_tol {1e-7};
    // 3rd order poly nomial coeffs in ascending order
    std::vector<double> pn_coef_3rd {1, 2, -3, 4};
    // 2ed polynomial which is derivative of 3rd order one above in ascending
    // order
    std::vector<double> pn_coef_2ed {2, -6, 12};
    // input x data with size >= 4!
    std::vector<double> x_vals {-4.5, -3, -0.2, 0.5, 3.4, 6, 6.5};
    // polynomial evaluated y values from x values via 3rd polynomial
    Eigen::ArrayXd y_vals;
    // centralized/scaled 3rd coeff
    Eigen::ArrayXd coef_c;
    // mean/std of x_vals
    double x_mean;
    double x_std;
};

TEST_F(PolyfuncTest, PolyFitting)
{
    // memory map for type conversion
    Eigen::Map<const Eigen::ArrayXd> x_vals_m(x_vals.data(), x_vals.size());
    Eigen::Map<const Eigen::ArrayXd> pn_coef_3rd_m(
            pn_coef_3rd.data(), pn_coef_3rd.size());

    // without any scaling and centering of the input in polyfit
    auto [coef, mean, std] = polyfit(x_vals_m, y_vals, 3);
    EXPECT_NEAR(mean, 0.0, abs_tol) << "MEAN must be zero w/o centralizing!";
    EXPECT_NEAR(std, 1.0, abs_tol)
            << "STD must be unity w/o centralizing and scaling!";
    validate_coeffs(coef, pn_coef_3rd_m,
            std::string("without centralization and scaling"));

    // turn on scaling and centering for the original input in polyfit
    std::tie(coef, mean, std) = polyfit(x_vals_m, y_vals, 3, true);
    EXPECT_NEAR(mean, x_mean, abs_tol) << "Wrong MEAN  w/ centralizing!";
    EXPECT_NEAR(std, x_std, abs_tol)
            << "Wrong STD  w/ centralizing and scaling!";
    validate_coeffs(
            coef, coef_c, std::string("with centralization and scaling"));

    // test exceptions
    // bad deg for polynomial
    EXPECT_THROW(polyfit(x_vals_m, y_vals, 10), isce3::except::InvalidArgument)
            << "Must throw ISCE3 InvalidArgument for degree of polynomial "
               "being too large!";
    // test size mismtach between two vectors
    EXPECT_THROW(
            polyfit(x_vals_m, y_vals.head(4), 3), isce3::except::LengthError)
            << "Must throw ISCE3 LengthError due to size mismtach of two input "
               "vectors!";
}

TEST_F(PolyfuncTest, PolyDerivative)
{
    // memory map for type conversion
    Eigen::Map<const Eigen::ArrayXd> pn_coef_3rd_m(
            pn_coef_3rd.data(), pn_coef_3rd.size());
    Eigen::Map<const Eigen::ArrayXd> pn_coef_2ed_m(
            pn_coef_2ed.data(), pn_coef_2ed.size());

    // w/o scaling (std == 1.0)
    auto coef_der = polyder(pn_coef_3rd_m);
    validate_coeffs(coef_der, pn_coef_2ed_m,
            std::string("for derivative of coeff w/o scaling"));

    // test exceptions
    // bad std value
    EXPECT_THROW(polyder(pn_coef_3rd_m, -0.0), isce3::except::InvalidArgument)
            << "Must throw ISCE3 InvalidArgument for non-positive STD value!";
}

TEST_F(PolyfuncTest, PolyValue)
{
    // memory mapping
    Eigen::Map<const Eigen::ArrayXd> pn_coef_3rd_m(
            pn_coef_3rd.data(), pn_coef_3rd.size());

    // check evaluated values for all input values w/ and w/o centering
    for (std::size_t idx = 0; idx < x_vals.size(); ++idx) {
        // w/o centralization and scaling
        EXPECT_NEAR(polyval(pn_coef_3rd_m, x_vals[idx]), y_vals(idx), abs_tol)
                << "Eval of polynomial (w/o centering) is wrong for input "
                << x_vals[idx];
        // w/ centralization and scaling
        EXPECT_NEAR(polyval(coef_c, x_vals[idx], x_mean, x_std), y_vals(idx),
                abs_tol)
                << "Eval of polynomial (w/ centering) is wrong for input "
                << x_vals[idx];
    }

    // test overloaded polyval with input array
    Eigen::Map<const Eigen::ArrayXd> x_vals_m(x_vals.data(), x_vals.size());
    // w/o centralization and scaling
    auto y = polyval(pn_coef_3rd_m, x_vals_m);
    // w/ centralization and scaling
    auto y_c = polyval(coef_c, x_vals_m, x_mean, x_std);
    for (std::size_t idx = 0; idx < x_vals.size(); ++idx) {
        EXPECT_NEAR(y(idx), y_vals(idx), abs_tol)
                << "Eval of polynomial (w/o centering) for an array is wrong @ "
                   "index "
                << idx << " and for input " << x_vals[idx];
        EXPECT_NEAR(y_c(idx), y_vals(idx), abs_tol)
                << "Eval of polynomial (w/ centering) for an array is wrong @ "
                   "index "
                << idx << " for input " << x_vals[idx];
    }
}

TEST_F(PolyfuncTest, PolyFitObject)
{
    // memory map
    Eigen::Map<const Eigen::ArrayXd> x_vals_m(x_vals.data(), x_vals.size());

    // create Poly1d object  w/ centering and scaling
    auto pf_obj = polyfitObj(x_vals_m, y_vals, 3, true);
    EXPECT_EQ(pf_obj.order, 3) << "Wtrong order of the Poly1d object!";
    EXPECT_NEAR(pf_obj.mean, x_mean, abs_tol)
            << "Wrong MEAN of the Poly1d object!";
    EXPECT_NEAR(pf_obj.norm, x_std, abs_tol)
            << "Wrong STD of the Poly1d object!";
    Eigen::Map<Eigen::ArrayXd> pf_coef(
            pf_obj.coeffs.data(), pf_obj.coeffs.size());
    validate_coeffs(pf_coef, coef_c, std::string("for Poly1d object"));
}

int main(int argc, char** argv)
{
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}


#include <stan/math/fwd/scal.hpp>
#include <gtest/gtest.h>
#include <boost/math/special_functions/digamma.hpp>
#include <test/unit/math/fwd/scal/fun/nan_util.hpp>

TEST(AgradFwdRisingFactorial, Fvar) {
  using stan::math::fvar;
  using stan::math::rising_factorial;
  using boost::math::digamma;

  fvar<double> a(4.0, 1.0);
  fvar<double> x = rising_factorial(a, 1);
  EXPECT_FLOAT_EQ(4.0, x.val_);
  EXPECT_FLOAT_EQ(1.0, x.d_);

  //finite diff
  double eps = 1e-6;
  EXPECT_FLOAT_EQ((stan::math::rising_factorial(4.0 + eps, 1.0)
                  - stan::math::rising_factorial(4.0 - eps, 1.0))
                  / (2 * eps), x.d_);

  fvar<double> c(-3.0, 2.0);

  EXPECT_THROW(rising_factorial(c, 2), std::domain_error);
  EXPECT_THROW(rising_factorial(c, c), std::domain_error);

  x = rising_factorial(a,a);
  EXPECT_FLOAT_EQ(840.0, x.val_);
  EXPECT_FLOAT_EQ(840.0 * (2 * digamma(8) - digamma(4)), x.d_);

  x = rising_factorial(5, a);
  EXPECT_FLOAT_EQ(1680.0, x.val_);
  EXPECT_FLOAT_EQ(1680.0 * digamma(9), x.d_);

  //finite diff
  EXPECT_FLOAT_EQ((stan::math::rising_factorial(5.0, 4.0 + eps)
                  - stan::math::rising_factorial(5.0, 4.0 - eps))
                  / (2 * eps), x.d_);
}

TEST(AgradFwdRisingFactorial, FvarFvarDouble) {
  using stan::math::fvar;
  using stan::math::rising_factorial;
  using boost::math::digamma;

  fvar<fvar<double> > x;
  x.val_.val_ = 4.0;
  x.val_.d_ = 1.0;
  fvar<fvar<double> > y;
  y.val_.val_ = 4.0;
  y.d_.val_ = 1.0;

  fvar<fvar<double> > a = rising_factorial(x,y);

  EXPECT_FLOAT_EQ((840.0), a.val_.val_);
  EXPECT_FLOAT_EQ(840. * (digamma(8) - digamma(4)), a.val_.d_);
  EXPECT_FLOAT_EQ(840 * digamma(8), a.d_.val_);
  EXPECT_FLOAT_EQ(1397.8143, a.d_.d_);
}

struct rising_factorial_fun {
  template <typename T0, typename T1>
  inline
  typename boost::math::tools::promote_args<T0,T1>::type
  operator()(const T0 arg1,
             const T1 arg2) const {
    return rising_factorial(arg1,arg2);
  }
};

TEST(AgradFwdRisingFactorial, nan) {
  rising_factorial_fun rising_factorial_;
  test_nan_fwd(rising_factorial_,3.0,5.0,false);
}


//---------------------------------------------------------------------------//
// Copyright (c) 2021 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2021 Nikita Kaskov <nbering@nil.foundation>
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_MATH_EXPRESSION_PARSER_DEF_HPP
#define CRYPTO3_MATH_EXPRESSION_PARSER_DEF_HPP

#include <cmath>
#include <iostream>
#include <limits>
#include <string>

#include <boost/math/constants/constants.hpp>
#include <boost/spirit/home/x3.hpp>

#include <nil/crypto3/math/expressions/ast.hpp>
#include <nil/crypto3/math/expressions/ast_adapted.hpp>
#include <nil/crypto3/math/expressions/math.hpp>
#include <nil/crypto3/math/expressions/parser.hpp>

namespace nil {
    namespace crypto3 {
        namespace math {
            namespace expressions {
                namespace detail {

                    namespace x3 = boost::spirit::x3;

                    namespace parser {

                        // LOOKUP

                        struct constant_ : x3::symbols<double> {
                            constant_() {
                                // clang-format off
                                add
                                    ("e"      , boost::math::constants::e<double>())
                                    ("epsilon", std::numeric_limits<double>::epsilon())
                                    ("phi"    , boost::math::constants::phi<double>())
                                    ("pi"     , boost::math::constants::pi<double>())
                                    ;
                                // clang-format on
                            }
                        } constant;

                        struct ufunc_ : x3::symbols<double (*)(double)> {
                            ufunc_() {
                                // clang-format off
                                add
                                    ("abs"   , static_cast<double (*)(double)>(&std::abs))
                                    ("acos"  , static_cast<double (*)(double)>(&std::acos))
                                    ("acosh" , static_cast<double (*)(double)>(&std::acosh))
                                    ("asin"  , static_cast<double (*)(double)>(&std::asin))
                                    ("asinh" , static_cast<double (*)(double)>(&std::asinh))
                                    ("atan"  , static_cast<double (*)(double)>(&std::atan))
                                    ("atanh" , static_cast<double (*)(double)>(&std::atanh))
                                    ("cbrt"  , static_cast<double (*)(double)>(&std::cbrt))
                                    ("ceil"  , static_cast<double (*)(double)>(&std::ceil))
                                    ("cos"   , static_cast<double (*)(double)>(&std::cos))
                                    ("cosh"  , static_cast<double (*)(double)>(&std::cosh))
                                    ("deg"   , static_cast<double (*)(double)>(&math::deg))
                                    ("erf"   , static_cast<double (*)(double)>(&std::erf))
                                    ("erfc"  , static_cast<double (*)(double)>(&std::erfc))
                                    ("exp"   , static_cast<double (*)(double)>(&std::exp))
                                    ("exp2"  , static_cast<double (*)(double)>(&std::exp2))
                                    ("floor" , static_cast<double (*)(double)>(&std::floor))
                                    ("isinf" , static_cast<double (*)(double)>(&math::isinf))
                                    ("isnan" , static_cast<double (*)(double)>(&math::isnan))
                                    ("log"   , static_cast<double (*)(double)>(&std::log))
                                    ("log2"  , static_cast<double (*)(double)>(&std::log2))
                                    ("log10" , static_cast<double (*)(double)>(&std::log10))
                                    ("rad"   , static_cast<double (*)(double)>(&math::rad))
                                    ("round" , static_cast<double (*)(double)>(&std::round))
                                    ("sgn"   , static_cast<double (*)(double)>(&math::sgn))
                                    ("sin"   , static_cast<double (*)(double)>(&std::sin))
                                    ("sinh"  , static_cast<double (*)(double)>(&std::sinh))
                                    ("sqrt"  , static_cast<double (*)(double)>(&std::sqrt))
                                    ("tan"   , static_cast<double (*)(double)>(&std::tan))
                                    ("tanh"  , static_cast<double (*)(double)>(&std::tanh))
                                    ("tgamma", static_cast<double (*)(double)>(&std::tgamma))
                                    ;
                                // clang-format on
                            }
                        } ufunc;

                        struct bfunc_ : x3::symbols<double (*)(double, double)> {
                            bfunc_() {
                                // clang-format off
                                add
                                    ("atan2", static_cast<double (*)(double, double)>(&std::atan2))
                                    ("max"  , static_cast<double (*)(double, double)>(&std::fmax))
                                    ("min"  , static_cast<double (*)(double, double)>(&std::fmin))
                                    ("pow"  , static_cast<double (*)(double, double)>(&std::pow))
                                    ;
                                // clang-format on
                            }
                        } bfunc;

                        struct unary_op_ : x3::symbols<double (*)(double)> {
                            unary_op_() {
                                // clang-format off
                                add
                                    ("+", static_cast<double (*)(double)>(&math::plus))
                                    ("-", static_cast<double (*)(double)>(&math::minus))
                                    ("!", static_cast<double (*)(double)>(&math::unary_not))
                                    ;
                                // clang-format on
                            }
                        } unary_op;

                        struct additive_op_ : x3::symbols<double (*)(double, double)> {
                            additive_op_() {
                                // clang-format off
                                add
                                    ("+", static_cast<double (*)(double, double)>(&math::plus))
                                    ("-", static_cast<double (*)(double, double)>(&math::minus))
                                    ;
                                // clang-format on
                            }
                        } additive_op;

                        struct multiplicative_op_ : x3::symbols<double (*)(double, double)> {
                            multiplicative_op_() {
                                // clang-format off
                                add
                                    ("*", static_cast<double (*)(double, double)>(&math::multiplies))
                                    ("/", static_cast<double (*)(double, double)>(&math::divides))
                                    ("%", static_cast<double (*)(double, double)>(&std::fmod))
                                    ;
                                // clang-format on
                            }
                        } multiplicative_op;

                        struct logical_op_ : x3::symbols<double (*)(double, double)> {
                            logical_op_() {
                                // clang-format off
                                add
                                    ("&&", static_cast<double (*)(double, double)>(&math::logical_and))
                                    ("||", static_cast<double (*)(double, double)>(&math::logical_or))
                                    ;
                                // clang-format on
                            }
                        } logical_op;

                        struct relational_op_ : x3::symbols<double (*)(double, double)> {
                            relational_op_() {
                                // clang-format off
                                add
                                    ("<" , static_cast<double (*)(double, double)>(&math::less))
                                    ("<=", static_cast<double (*)(double, double)>(&math::less_equals))
                                    (">" , static_cast<double (*)(double, double)>(&math::greater))
                                    (">=", static_cast<double (*)(double, double)>(&math::greater_equals))
                                    ;
                                // clang-format on
                            }
                        } relational_op;

                        struct equality_op_ : x3::symbols<double (*)(double, double)> {
                            equality_op_() {
                                // clang-format off
                                add
                                    ("==", static_cast<double (*)(double, double)>(&math::equals))
                                    ("!=", static_cast<double (*)(double, double)>(&math::not_equals))
                                    ;
                                // clang-format on
                            }
                        } equality_op;

                        struct power_ : x3::symbols<double (*)(double, double)> {
                            power_() {
                                // clang-format off
                                add
                                    ("**", static_cast<double (*)(double, double)>(&std::pow))
                                    ;
                                // clang-format on
                            }
                        } power;

                        // ADL markers

                        struct expression_class;
                        struct logical_class;
                        struct equality_class;
                        struct relational_class;
                        struct additive_class;
                        struct multiplicative_class;
                        struct factor_class;
                        struct primary_class;
                        struct unary_class;
                        struct binary_class;
                        struct variable_class;

                        // clang-format off

                        // Rule declarations

                        auto const expression     = x3::rule<expression_class    , ast::expression>{"expression"};
                        auto const logical        = x3::rule<logical_class       , ast::expression>{"logical"};
                        auto const equality       = x3::rule<equality_class      , ast::expression>{"equality"};
                        auto const relational     = x3::rule<relational_class    , ast::expression>{"relational"};
                        auto const additive       = x3::rule<additive_class      , ast::expression>{"additive"};
                        auto const multiplicative = x3::rule<multiplicative_class, ast::expression>{"multiplicative"};
                        auto const factor         = x3::rule<factor_class        , ast::expression>{"factor"};
                        auto const primary        = x3::rule<primary_class       , ast::operand   >{"primary"};
                        auto const unary          = x3::rule<unary_class         , ast::unary_op  >{"unary"};
                        auto const binary         = x3::rule<binary_class        , ast::binary_op >{"binary"};
                        auto const variable       = x3::rule<variable_class      , std::string    >{"variable"};

                        // Rule defintions

                        auto const expression_def =
                            logical
                            ;

                        auto const logical_def =
                            equality >> *(logical_op > equality)
                            ;

                        auto const equality_def =
                            relational >> *(equality_op > relational)
                            ;

                        auto const relational_def =
                            additive >> *(relational_op > additive)
                            ;

                        auto const additive_def =
                            multiplicative >> *(additive_op > multiplicative)
                            ;

                        auto const multiplicative_def =
                            factor >> *(multiplicative_op > factor)
                            ;

                        auto const factor_def =
                            primary >> *( power > factor )
                            ;

                        auto const unary_def =
                            ufunc > '(' > expression > ')'
                            ;

                        auto const binary_def =
                            bfunc > '(' > expression > ',' > expression > ')'
                            ;

                        auto const variable_def =
                            x3::raw[x3::lexeme[x3::alpha >> *(x3::alnum | '_')]]
                            ;

                        auto const primary_def =
                              x3::double_
                            | ('(' > expression > ')')
                            | (unary_op > primary)
                            | binary
                            | unary
                            | constant
                            | variable
                            ;

                        BOOST_SPIRIT_DEFINE(
                            expression,
                            logical,
                            equality,
                            relational,
                            additive,
                            multiplicative,
                            factor,
                            primary,
                            unary,
                            binary,
                            variable
                        )

                        // clang-format on

                        struct expression_class {
                            template <typename Iterator, typename Exception, typename Context>
                            x3::error_handler_result on_error(Iterator &, Iterator const &last,
                                                              Exception const &x, Context const &) {
                                std::cout << "Expected " << x.which() << " at \""
                                          << std::string{x.where(), last} << "\"" << std::endl;
                                return x3::error_handler_result::fail;
                            }
                        };

                        using expression_type = x3::rule<expression_class, ast::expression>;

                        BOOST_SPIRIT_DECLARE(expression_type)

                    } // namespace parser

                    parser::expression_type grammar() { return parser::expression; }

                }    // namespace detail
            }    // namespace expressions
        }    // namespace math
    }        // namespace crypto3
}    // namespace nil

#endif    // CRYPTO3_MATH_EXPRESSION_PARSER_DEF_HPP


#include "problemes.h"
#include "arithmetique.h"

#include <fstream>

#include <boost/math/constants/constants.hpp>

typedef unsigned long long nombre;
typedef std::vector<nombre> vecteur;

typedef boost::rational<nombre> fraction;

namespace {
    bool infini(nombre n, nombre k) {
        k /= arithmetique::PGCD(n, k);
        while (k % 2 == 0)
            k /= 2;

        while (k % 5 == 0)
            k /= 5;

        return k == 1;
    }

    bool terminating(nombre n) {
        long double k0 = n / boost::math::constants::e<long double>();
        nombre k_max = static_cast<nombre>(std::ceil(k0));
        nombre k_min = static_cast<nombre>(std::floor(k0));

        long double p_max = puissance::puissance(n / std::ceil(k0), k_max);
        long double p_min = puissance::puissance(n / std::floor(k0), k_min);

        if (p_max > p_min)
            return infini(n, k_max);
        else
            return infini(n, k_min);
    }
}

ENREGISTRER_PROBLEME(183, "Maximum product of parts") {
    // Let N be a positive integer and let N be split into k equal parts, r = N/k, so that N = r + r + ... + r.
    // Let P be the product of these parts, P = r × r × ... × r = rk.
    //
    // For example, if 11 is split into five equal parts, 11 = 2.2 + 2.2 + 2.2 + 2.2 + 2.2,
    // then P = 2.2**5 = 51.53632.
    //
    // Let M(N) = Pmax for a given value of N.
    //
    // It turns out that the maximum for N = 11 is found by splitting eleven into four equal parts
    // which leads to Pmax = (11/4)4; that is, M(11) = 14641/256 = 57.19140625, which is a terminating decimal.
    //
    // However, for N = 8 the maximum is achieved by splitting it into three equal parts, so M(8) = 512/27,
    // which is a non-terminating decimal.
    //
    // Let D(N) = N if M(N) is a non-terminating decimal and D(N) = -N if M(N) is a terminating decimal.
    //
    // For example, ΣD(N) for 5 ≤ N ≤ 100 is 2438.
    //
    // Find ΣD(N) for 5 ≤ N ≤ 10000.
    nombre limite = 10000;
    // std::cout << std::boolalpha;
    // std::cout << "terminating(8) = " << terminating(8) << std::endl;
    // std::cout << "terminating(11) = " << terminating(11) << std::endl;

    nombre resultat_positif = 0;
    nombre resultat_negatif = 0;
    for (nombre n = 5; n < limite + 1; ++n) {
        if (terminating(n))
            resultat_negatif += n;
        else
            resultat_positif += n;
    }

    return std::to_string(resultat_positif - resultat_negatif);
}