#include <gtest/gtest.h>

#include <Eigen/Core>
#include <array>
#include <vector>

#include "Box.h"
#include "Exceptions.h"

TEST(Box, BasicProperties) {
  cpet::Box b({1.3, 2.4, 2});

  ASSERT_EQ(b.type(), "box") << "Incorrectly describes the volume type";
  ASSERT_EQ(b.description(), "Box: 1.300000 2.400000 2.000000")
      << "Incorrect description and or formatting";

  EXPECT_DOUBLE_EQ(b.maxDim(), 2.4) << "Expected maximum dimension " << 2.4;
  EXPECT_NEAR(b.diagonal(), 6.76756973, 0.0000001)
      << "Diagonal off by expected " << 6.76756973 << " by more than tolerance "
      << 0.0000001;

  Eigen::Vector3d point{0, 0, 0};
  EXPECT_TRUE(b.isInside(point)) << "Origin is not within the box";

  point = {1.2, -2, 1};
  EXPECT_TRUE(b.isInside(point))
      << "Point " << point.transpose() << " not within " << b.description();

  point = {-1.3, 2.4, 2.1};
  EXPECT_FALSE(b.isInside(point))
      << "Point " << point.transpose() << " within " << b.description();

  for (int i = 0; i < 100; i++) {
    EXPECT_TRUE(b.isInside(b.randomPoint()));
  }

  constexpr double STEP_SIZE = 0.001;
  const double max_distance = b.diagonal() / STEP_SIZE;
  for (int i = 0; i < 10; i++) {
    EXPECT_TRUE(b.randomDistance(STEP_SIZE) <= max_distance);
  }
}

TEST(Box, Displaced) {
  cpet::Box b({1, 1, 1}, {0, 1, 0});
  ASSERT_TRUE(b.isInside({0, 1, 0}));
  EXPECT_TRUE(b.isInside({0, .5, 0}));
  EXPECT_FALSE(b.isInside({-0.5, -0.5, -0.5}));
  EXPECT_TRUE(b.isInside({0.5, 1.5, 0}));

  for (int i = 0; i < 100; i++) {
    EXPECT_TRUE(b.isInside(b.randomPoint()));
  }
  constexpr double STEP_SIZE = 0.001;
  const double max_distance = b.diagonal() / STEP_SIZE;
  for (int i = 0; i < 10; i++) {
    EXPECT_TRUE(b.randomDistance(STEP_SIZE) <= max_distance);
  }
}

TEST(Box, Partition) {
  const std::array<double, 3> sides = {2, 3, 5};
  const std::array<int, 3> density = {10, 10, 10};
  const cpet::Box b(sides);

  std::vector<Eigen::Vector3d> expected_partition;
  double x = -1 * sides[0];
  while (x <= sides[0]) {
    double y = -1 * sides[1];
    while (y <= sides[1]) {
      double z = -1 * sides[2];
      while (z <= sides[2]) {
        expected_partition.emplace_back(x, y, z);
        z += static_cast<double>(static_cast<float>(sides[2] / density[2]));
      }
      y += (sides[1] / density[1]);
    }
    x += (sides[0] / density[0]);
  }

  const auto result = b.partition(density);
  ASSERT_EQ(result.size(), expected_partition.size());
  for (size_t i = 0; i < result.size(); i++) {
    double difference = (result.at(i) - expected_partition.at(i)).norm();
    EXPECT_NEAR(difference, 0.0, 0.000001);
  }
}

TEST(Box, PartitionDisplaced) {
  const std::array<double, 3> sides = {3, 3, 2};
  const std::array<int, 3> density = {15, 15, 10};
  const Eigen::Vector3d center = {1, 0, 1};
  const cpet::Box b(sides, center);

  std::vector<Eigen::Vector3d> expected_partition;
  expected_partition.reserve(
      static_cast<size_t>(abs(density[0] * density[1] * density[2])));

  double x = -1 * sides[0];
  while (x <= sides[0]) {
    double y = -1 * sides[1];
    while (y <= sides[1]) {
      double z = -1 * sides[2];
      while (z <= sides[2]) {
        expected_partition.emplace_back((x + 1), y, (z + 1));
        z += static_cast<double>(static_cast<float>(sides[2] / density[2]));
      }
      y += (sides[1] / density[1]);
    }
    x += (sides[0] / density[0]);
  }

  const auto result = b.partition(density);
  ASSERT_EQ(result.size(), expected_partition.size());
  for (size_t i = 0; i < result.size(); i++) {
    const double difference = (result.at(i) - expected_partition.at(i)).norm();
    EXPECT_NEAR(difference, 0.0, 0.000001);
  }
}

TEST(Box, InvalidParameters) {
  EXPECT_THROW(cpet::Box({-1.5, 2, 3}), cpet::value_error);
  EXPECT_THROW(cpet::Box({1.5, -2, 3}), cpet::value_error);
  EXPECT_THROW(cpet::Box({1.5, 2, -3}), cpet::value_error);
  EXPECT_THROW(cpet::Box({-1.5, 2, -3}), cpet::value_error);
}


#include "sequence.hpp"
#include "pow2.hpp"
#include "multiply.hpp"
#include <boost/hana/integral_constant.hpp>
#include <array>

using namespace boost::hana::literals;

std::array<int, 5> a{{1, 2, 3, 4, 5}};

template <typename UnderlyingExpr>
struct add_expr
{
    constexpr explicit add_expr(UnderlyingExpr const& expr, unsigned value) :
        expr{expr}, value{value} {};

    auto operator()(size_t index) const {
        return expr(index) + value;
    }

    UnderlyingExpr const& expr;
    unsigned value;
};

template <typename UnderlyingExpr>
auto operator+(multiply_expr<UnderlyingExpr> const& m, unsigned value)
{
    return add_expr{m, value};
}

int main()
{
    auto s = sequence(a);
    auto e = (2 * (s ^ 2_c)) + 1;

    auto x = e(0);
    return x;
}


#pragma once

#include <iostream>
#include <complex>
#include <cmath>
#include <vector>
#include <set>
#include <assert.h>
#include <memory>
#include <iomanip>
#include <fstream>

#include <Eigen/CXX11/Tensor>
#include <Eigen/MPRealSupport>

#include "common.hpp"
#include "kernel.hpp"
#include "piecewise_polynomial.hpp"

#include "irlib/detail/basis_impl.ipp"

namespace irlib {

/**
 * Class representing kernel Ir basis
 */
    class basis {
    public:
        /**
         * Constructor
         * @param s  statistics
         * @param Lambda Lambda
         * @param sv singular values
         * @param u_basis piecewise polynomials representing u_l(x)
         * @param v_basis piecewise polynomials representing v_l(y)
         */
        basis(statistics::statistics_type s,
            double Lambda,
            const std::vector<mpfr::mpreal> &sv,
            const std::vector<piecewise_polynomial<mpfr::mpreal, mpfr::mpreal>> &u_basis,
            const std::vector<piecewise_polynomial<mpfr::mpreal, mpfr::mpreal>> &v_basis,
            const std::vector<std::vector<mpfr::mpreal>> &u_basis_coeff_l,
            const std::vector<std::vector<mpfr::mpreal>> &v_basis_coeff_l
        ) throw(std::runtime_error) {
            statistics_ = s;
            Lambda_ = Lambda;
            sv_ = sv;
            u_basis_ = u_basis;
            v_basis_ = v_basis;
            u_basis_coeff_l_ = u_basis_coeff_l;
            v_basis_coeff_l_ = v_basis_coeff_l;
        }

    private:
        statistics::statistics_type statistics_;
        double Lambda_;
        std::vector<mpfr::mpreal> sv_;
        std::vector<piecewise_polynomial<mpfr::mpreal, mpfr::mpreal>> u_basis_, v_basis_;
        std::vector<std::vector<mpfr::mpreal>> u_basis_coeff_l_;
        std::vector<std::vector<mpfr::mpreal>> v_basis_coeff_l_;

        //mutable mp_prec_t default_prec_bak;

        mp_prec_t save_default_prec() const {
            mp_prec_t default_prec_bak = mpfr::mpreal::get_default_prec();
            mpfr::mpreal::set_default_prec(get_prec());
            return default_prec_bak;
        }

        void restore_default_prec(mp_prec_t prec) const {
            mpfr::mpreal::set_default_prec(prec);
        }

    public:
        /**
         * Compute the values of the basis functions for a given x.
         * @param x    x = 2 * tau/beta - 1  (-1 <= x <= 1)
         * @param val  results
         */
        double sl(int l) const throw(std::runtime_error) {
            assert(l >= 0 && l < dim());
            python_runtime_check(l >= 0 && l < dim(), "Index l is out of range.");
            return static_cast<double>(sv_[l]);
        }

        mpfr::mpreal sl_mp(int l) const throw(std::runtime_error) {
            assert(l >= 0 && l < dim());
            python_runtime_check(l >= 0 && l < dim(), "Index l is out of range.");
            return sv_[l];
        }

        double Lambda() const {
            return Lambda_;
        }

        /**
         * @param l  order of basis function
         * @param x  x on [-1,1]
         * @return   The value of u_l(x)
         */
        double ulx(int l, double x) const throw(std::runtime_error) {
            assert(x >= -1 && x <= 1);
            assert(l >= 0 && l < dim());
            auto bak = save_default_prec();

            //auto val = ulx_mp(l, mpfr::mpreal(x));
            //auto r = static_cast<double>(val);

            auto r = static_cast<double>(ulx_mp(l, mpfr::mpreal(x)));
            restore_default_prec(bak);
            return r;
        }

        double ulx_derivative(int l, double x, int order) const throw(std::runtime_error) {
            assert(x >= -1 && x <= 1);
            assert(l >= 0 && l < dim());
            auto bak = save_default_prec();
            auto r = static_cast<double>(ulx_derivative_mp(l, mpfr::mpreal(x), order));
            restore_default_prec(bak);
            return r;
        }

        /**
         * @param l  order of basis function
         * @param y  y on [-1,1]
         * @return   The value of v_l(y)
         */
        double vly(int l, double y) const throw(std::runtime_error) {
            assert(y >= -1 && y <= 1);
            assert(l >= 0 && l < dim());
            auto bak = save_default_prec();
            auto r = static_cast<double>(vly_mp(l, mpfr::mpreal(y)));
            restore_default_prec(bak);
            return r;
        }

        double vly_derivative(int l, double y, int order) const throw(std::runtime_error) {
            assert(y >= -1 && y <= 1);
            assert(l >= 0 && l < dim());
            auto bak = save_default_prec();
            auto r = static_cast<double>(vly_derivative_mp(l, mpfr::mpreal(y), order));
            restore_default_prec(bak);
            return r;
        }

        /**
         * Direct access to coefficients of u_l(x)
         */
        double coeff_ulx(int l, int section, int p) const {
            assert(l >= 0 && l < dim());
            return static_cast<double>(u_basis_[l].coefficient(section, p));
        }

        std::vector<mpreal> coeff_ulx_leg(int l) const {
            assert(l >= 0 && l < dim());
            return u_basis_coeff_l_[l];
        }

        /**
         * Direct access to coefficients of v_l(y)
         */
        double coeff_vly(int l, int section, int p) const {
            assert(l >= 0 && l < dim());
            return static_cast<double>(v_basis_[l].coefficient(section, p));
        }

        std::vector<mpreal> coeff_vly_leg(int l) const {
          assert(l >= 0 && l < dim());
          return v_basis_coeff_l_[l];
        }

        /**
         * Access to sections
         */
        int num_sections_ulx() const {
            return u_basis_[0].num_sections();
        }

        int num_sections_vly() const {
            return v_basis_[0].num_sections();
        }

        double section_edge_ulx(int i) const {
            return static_cast<double>(u_basis_[0].section_edge(i));
        }

        double section_edge_vly(int i) const {
            return static_cast<double>(v_basis_[0].section_edge(i));
        }

        int num_local_poly_ulx() const {
            return u_basis_[0].order() + 1;
        }

        int num_local_poly_vly() const {
            return v_basis_[0].order() + 1;
        }

        /**
         * This function should not be called outside this library
         * @param l  order of basis function
         * @param x  x on [-1,1]
         * @return   The value of u_l(x)
         */
#ifndef SWIG //DO NOT EXPOSE TO PYTHON
        mpfr::mpreal ulx_mp(int l, const mpfr::mpreal &x) const throw(std::runtime_error) {
            assert(x >= -1 && x <= 1);
            assert(l >= 0 && l < dim());
            python_runtime_check(l >= 0 && l < dim(), "Index l is out of range.");
            python_runtime_check(x >= -1 && x <= 1, "x must be in [-1,1].");

            auto bak = save_default_prec();

            mpfr:mpreal r;
            if (x >= 0) {
                r = u_basis_[l].compute_value(x);
            } else {
                r = u_basis_[l].compute_value(-x) * (l % 2 == 0 ? 1 : -1);
            }

            restore_default_prec(bak);

            return r;
        }

        mpfr::mpreal ulx_derivative_mp(int l, const mpfr::mpreal &x, int order) const throw(std::runtime_error) {
            assert(x >= -1 && x <= 1);
            assert(l >= 0 && l < dim());
            python_runtime_check(l >= 0 && l < dim(), "Index l is out of range.");
            python_runtime_check(x >= -1 && x <= 1, "x must be in [-1,1].");

            auto bak = save_default_prec();

            mpfr::mpreal r;
            if (x >= 0) {
                r = u_basis_[l].derivative(x, order);
            } else {
                r = u_basis_[l].derivative(-x, order) * ((l+order) % 2 == 0 ? 1 : -1);
            }

            restore_default_prec(bak);

            return r;
        }

        /**
         * This function should not be called outside this library
         * @param l  order of basis function
         * @param y  y on [-1,1]
         * @return   The value of v_l(y)
         */
        mpfr::mpreal vly_mp(int l, const mpfr::mpreal &y) const throw(std::runtime_error) {
            assert(y >= -1 && y <= 1);
            assert(l >= 0 && l < dim());
            python_runtime_check(l >= 0 && l < dim(), "Index l is out of range.");
            python_runtime_check(y >= -1 && y <= 1, "y must be in [-1,1].");

            auto bak = save_default_prec();

            mpfr::mpreal r;
            if (y >= 0) {
                r = v_basis_[l].compute_value(y);
            } else {
                r = v_basis_[l].compute_value(-y) * (l % 2 == 0 ? 1 : -1);
            }

            restore_default_prec(bak);

            return r;
        }

        mpfr::mpreal vly_derivative_mp(int l, const mpfr::mpreal &y, int order) const throw(std::runtime_error) {
            assert(y >= -1 && y <= 1);
            assert(l >= 0 && l < dim());
            python_runtime_check(l >= 0 && l < dim(), "Index l is out of range.");
            python_runtime_check(y >= -1 && y <= 1, "y must be in [-1,1].");

            auto bak = save_default_prec();

            mpfr::mpreal r;
            if (y >= 0) {
                r = v_basis_[l].derivative(y, order);
            } else {
                r = v_basis_[l].derivative(-y, order) * ((l+order) % 2 == 0 ? 1 : -1);
            }

            restore_default_prec(bak);

            return r;
        }
#endif

        std::string ulx_str(int l, const std::string& str_x) const throw(std::runtime_error) {
            auto bak = save_default_prec();

            auto prec = u_basis_[l].section_edge(0).get_prec();
            mpfr::mpreal x(str_x, prec);
            auto ulx = ulx_mp(l, x);

            std::ostringstream out;
            out << std::setprecision(mpfr::bits2digits(ulx.get_prec())) << ulx;
            //std::cout << "debug ulx_str " << std::setprecision(20) << str_x << " " << x << " " << ulx << " " << out.str() << std::endl;

            restore_default_prec(bak);

            return out.str();
        }

        std::string vly_str(int l, const std::string& str_y) const throw(std::runtime_error) {
            auto bak = save_default_prec();

            auto prec = v_basis_[l].section_edge(0).get_prec();
            mpfr::mpreal y(str_y, prec);
            auto vly = vly_mp(l, y);

            std::ostringstream out;
            out << std::setprecision(mpfr::bits2digits(vly.get_prec())) << vly;

            restore_default_prec(bak);

            return out.str();
        }

        /**
         * Return a reference to the l-th basis function
         * @param l l-th basis function
         * @return  reference to the l-th basis function
         */
        const piecewise_polynomial<mpfr::mpreal, mpfr::mpreal> &ul(int l) const throw(std::runtime_error) {
            assert(l >= 0 && l < dim());
            python_runtime_check(l >= 0 && l < dim(), "Index l is out of range.");
            return u_basis_[l];
        }

        const piecewise_polynomial<mpfr::mpreal, mpfr::mpreal> &vl(int l) const throw(std::runtime_error) {
            assert(l >= 0 && l < dim());
            python_runtime_check(l >= 0 && l < dim(), "Index l is out of range.");
            return v_basis_[l];
        }

        /**
         * Return number of basis functions
         * @return  number of basis functions
         */
        int dim() const { return u_basis_.size(); }

        mpfr_prec_t get_prec() const {
            return ul(0).section_edge(0).get_prec();
        }

        /// Return statistics
        irlib::statistics::statistics_type get_statistics() const {
            return statistics_;
        }

        std::string get_statistics_str() const {
            return statistics_ == statistics::FERMIONIC ? "F" : "B" ;
        }

        int get_prec_int() const {
            return static_cast<int>(ul(0).section_edge(0).get_prec());
        }

#ifndef SWIG //DO NOT EXPOSE TO PYTHON

        /**
         * Compute transformation matrix to Matsubara freq.
         * The computation may take some time. You may store the result somewhere and do not call this routine frequenctly.
         * @param n_vec  This vector must contain indices of Matsubara freqencies
         * @param Tnl    Results
         */
        void compute_Tnl(
                const std::vector<long> &n_vec,
                Eigen::Tensor<std::complex<double>, 2> &Tnl
        ) const {
            auto bak = save_default_prec();

            auto trans_to_non_negative = [&](long n) {
                if (n >= 0) {
                    return n;
                } else {
                    if (statistics_ == irlib::statistics::FERMIONIC) {
                        return -n - 1;
                    } else {
                        return -n;
                    }
                }
            };

            auto nl = dim();

            std::set<long> none_negative_n;
            for (const auto &n : n_vec) {
                none_negative_n.insert(trans_to_non_negative(n));
            }

            Eigen::Tensor<std::complex<double>, 2> Tnl_tmp;
            compute_transformation_matrix_to_matsubara<mpreal>(
                    std::vector<long>(none_negative_n.begin(), none_negative_n.end()),
                    statistics_, u_basis_, Tnl_tmp
            );

            Tnl = Eigen::Tensor<std::complex<double>, 2>(n_vec.size(), nl);
            for (int i = 0; i < n_vec.size(); ++i) {
                auto index_data = std::distance(
                        none_negative_n.begin(),
                        none_negative_n.find(trans_to_non_negative(n_vec[i]))
                );
                if (n_vec[i] >= 0) {
                    for (int l = 0; l < nl; ++l) {
                        Tnl(i, l) = Tnl_tmp(index_data, l);
                    }
                } else {
                    for (int l = 0; l < nl; ++l) {
                        Tnl(i, l) = std::conj(Tnl_tmp(index_data, l));
                    }
                }
            }

            restore_default_prec(bak);
        }

#endif

        /**
         * Compute transformation matrix to Matsubara freq.
         * The computation may take some time. You may store the result somewhere and do not call this routine frequenctly.
         * @param n_vec  This vector must contain indices of Matsubara freqencies
         * @return Results
         */
        Eigen::Tensor<std::complex<double>, 2>
        compute_Tnl(const std::vector<long> &n_vec) const {
            std::cout << "Warning: compute_Tnl() is not well tested!" << std::endl;
            Eigen::Tensor<std::complex<double>, 2> Tnl;
            compute_Tnl(n_vec, Tnl);
            return Tnl;
        }

        std::complex<double> compute_Tnl_safe(long n, int l) {
            auto bak = save_default_prec();

            auto o = (statistics_ == irlib::statistics::FERMIONIC ? 2*n+1 : 2*n);
            if (o >= 0) {
                auto r = to_dcomplex(
                        compute_Tnl_impl(u_basis_[l], l%2==0, statistics_, mpfr::const_pi() * 0.5 * o,
                                        mpfr::digits2bits(get_prec()),
                                        mpfr::digits2bits(get_prec()))
                );
                restore_default_prec(bak);
                return r;
            } else {
                auto r = to_dcomplex(
                        compute_Tnl_impl(u_basis_[l], l%2==0, statistics_, -mpfr::const_pi() * 0.5 * o,
                                        mpfr::digits2bits(get_prec()),
                                        mpfr::digits2bits(get_prec()))
                );
                restore_default_prec(bak);
                return std::conj(r);
            }
        }

    };

    inline basis compute_basis(statistics::statistics_type s,
                        double Lambda,
                        int max_dim = 1000,
                        double cutoff = 1e-8,
                        const std::string& fp_mode="mp",
                        double r_tol = 1e-8,
                        long prec = 64,
                        int n_local_poly = 10,
                        int num_nodes_gauss_legendre = 24,
                        bool verbose = true
        ) throw(std::runtime_error) {
        std::vector<mpfr::mpreal> sv;
        std::vector<piecewise_polynomial<mpfr::mpreal, mpfr::mpreal>> u_basis;
        std::vector<piecewise_polynomial<mpfr::mpreal, mpfr::mpreal>> v_basis;
        std::vector<std::vector<mpfr::mpreal>> u_basis_coeff_l;
        std::vector<std::vector<mpfr::mpreal>> v_basis_coeff_l;

        // Increase default precision if needed
        auto min_prec = std::max(
                mpfr::digits2bits(std::log10(1/cutoff)+2*std::log10(1/r_tol)),
                long(64)//At least 19 digits
        );
        //min_prec = std::max(min_prec, mpfr::digits2bits(std::log10(1/r_tol))+10);
        min_prec = std::max(min_prec, prec);
        if (min_prec > mpfr::mpreal::get_default_prec()) {
            mpfr::mpreal::set_default_prec(min_prec);
        }
        if (verbose) {
            std::cout << "Using default precision = " << min_prec << " bits." << std::endl;
        }

        if (fp_mode == "mp") {
            if (s == statistics::FERMIONIC) {
                std::tie(sv, u_basis, v_basis, u_basis_coeff_l, v_basis_coeff_l) = generate_ir_basis_functions<mpfr::mpreal>(
                        fermionic_kernel<mpfr::mpreal>(Lambda), max_dim, cutoff, verbose, r_tol, n_local_poly, num_nodes_gauss_legendre);
            } else if (s == statistics::BOSONIC) {
                std::tie(sv, u_basis, v_basis, u_basis_coeff_l, v_basis_coeff_l) = generate_ir_basis_functions<mpfr::mpreal>(
                        bosonic_kernel<mpfr::mpreal>(Lambda), max_dim, cutoff, verbose, r_tol, n_local_poly, num_nodes_gauss_legendre);
            }
        } else {
            throw std::runtime_error("Unknown fp_mode " + fp_mode + ". Only 'mp' is supported.");
        }

        return basis(s, Lambda, sv, u_basis, v_basis, u_basis_coeff_l, v_basis_coeff_l);
    }

    inline void savetxt(const std::string& fname, const basis& b) throw(std::runtime_error) {
        std::ofstream ofs(fname);

        int version = 2;
        ofs << version << std::endl;
        ofs << b.get_statistics() << std::endl;
        ofs << b.Lambda() << std::endl;
        ofs << b.dim() << std::endl;

        ofs << b.sl_mp(0).get_prec() << std::endl;
        for (int l=0; l<b.dim(); ++l) {
            auto sl = b.sl_mp(l);
            ofs << std::setprecision(mpfr::bits2digits(sl.get_prec())) << sl << std::endl;
        }
        for (int l=0; l<b.dim(); ++l) {
            ofs << b.ul(l);
        }
        for (int l=0; l<b.dim(); ++l) {
            ofs << b.vl(l);
        }
        for (int l=0; l<b.dim(); ++l) {
            auto coeffs = b.coeff_ulx_leg(l);
            for (int c=0; c<coeffs.size(); ++c) {
                ofs << coeffs[c] << std::endl;
            }
        }
        for (int l=0; l<b.dim(); ++l) {
            auto coeffs = b.coeff_vly_leg(l);
            for (int c=0; c<coeffs.size(); ++c) {
                ofs << coeffs[c] << std::endl;
            }
        }
    }

    //inline void dump_coeff(std::ofstream& ofs, const std::vector<mpreal>& coeff) {
      //auto num_sections = b.ul(l).num_sections();
      //
    //}

    inline basis loadtxt(const std::string& fname) throw(std::runtime_error) {
        std::ifstream ifs(fname);

        if (!ifs.is_open()) {
            throw std::runtime_error(fname + " cannot be opened!");
        }

        statistics::statistics_type s;
        double Lambda;
        int dim;

        int version;
        ifs >> version;

        if (version == 2) {
            {
                int itmp;
                ifs >> itmp;
                s = static_cast<statistics::statistics_type>(itmp);
            }
            ifs >> Lambda;
            ifs >> dim;

            mpfr_prec_t prec;
            ifs >> prec;
            std::vector<mpfr::mpreal> sv(dim);
            for (int l=0; l<dim; ++l) {
                sv[l].set_prec(prec);
                ifs >> sv[l];
            }

            std::vector<piecewise_polynomial<mpfr::mpreal, mpfr::mpreal>> u_basis(dim), v_basis(dim);

            for (int l=0; l<dim; ++l) {
                ifs >> u_basis[l];
            }

            for (int l=0; l<dim; ++l) {
                ifs >> v_basis[l];
            }

            std::vector<std::vector<mpreal>> u_basis_coeffs_leg(dim);
            for (int l = 0; l < dim; ++l) {
                u_basis_coeffs_leg[l].resize(u_basis[l].num_sections() * (u_basis[l].order()+1));
                for (int c = 0; c < u_basis_coeffs_leg[l].size(); ++c) {
                    ifs >> u_basis_coeffs_leg[l][c];
                }
            }

            std::vector<std::vector<mpreal>> v_basis_coeffs_leg(dim);
            for (int l = 0; l < dim; ++l) {
                v_basis_coeffs_leg[l].resize(v_basis[l].num_sections() * (v_basis[l].order()+1));
                for (int c = 0; c < v_basis_coeffs_leg[l].size(); ++c) {
                    ifs >> v_basis_coeffs_leg[l][c];
                }
            }

            return basis(s, Lambda, sv, u_basis, v_basis, u_basis_coeffs_leg, v_basis_coeffs_leg);
        } else {
            throw std::runtime_error("Version " + std::to_string(version) + " is not supported!");
        }
    }
}


#pragma once

#include <Eigen/Core>
#include <Eigen/Sparse>

#include <utils/eigen_ext.hpp>

namespace ipc::rigid {

/// @brief Compute the total mass, center of mass, and moment of intertia
void compute_mass_properties(
    const Eigen::MatrixXd& vertices,
    const Eigen::MatrixXi& facets,
    double& total_mass,
    VectorMax3d& center_of_mass,
    MatrixMax3d& moment_of_inertia);

/// @brief Compute the 2D total mass, center of mass, and moment of intertia
void compute_mass_properties_2D(
    const Eigen::MatrixXd& vertices,
    const Eigen::MatrixXi& edges,
    double& mass,
    VectorMax3d& center,
    MatrixMax3d& intertia);

/// @brief Compute the 3D total mass, center of mass, and moment of intertia
void compute_mass_properties_3D(
    const Eigen::MatrixXd& vertices,
    const Eigen::MatrixXi& faces,
    double& mass,
    VectorMax3d& center,
    MatrixMax3d& intertia);

/// @brief Construct the sparse mass matrix for the given mesh (V, E).
void construct_mass_matrix(
    const Eigen::MatrixXd& vertices,
    const Eigen::MatrixXi& facets,
    Eigen::SparseMatrix<double>& mass_matrix);

/// @brief Computes the total mass for the given mesh
double compute_total_mass(
    const Eigen::MatrixXd& vertices, const Eigen::MatrixXi& facets);
/// @brief Computes the total mass from the mass matrix
double compute_total_mass(const Eigen::SparseMatrix<double>& mass_matrix);

VectorMax3d compute_center_of_mass(
    const Eigen::MatrixXd& vertices, const Eigen::MatrixXi& facets);
VectorMax3d compute_center_of_mass(
    const Eigen::MatrixXd& vertices,
    const Eigen::SparseMatrix<double>& mass_matrix);

/**
 * @brief Computes the moment of intertia
 *
 * Assumes vertices are given in body space (i.e centered of mass at 0,0).
 */
MatrixMax3d compute_moment_of_inertia(
    const Eigen::MatrixXd& vertices, const Eigen::MatrixXi& facets);
MatrixMax3d compute_moment_of_inertia(
    const Eigen::MatrixXd& vertices,
    const Eigen::SparseMatrix<double>& mass_matrix);

} // namespace ipc::rigid


////////////////////////////////////////////////////////////////////////////////
// ConstStrainDisplacement_cli.cc
////////////////////////////////////////////////////////////////////////////////
/*! @file
//      Generate a constant strain displacement for demonstration purposes.
//      Given a strain tensor, create a linear displacement field with that
//      strain. We choose the displacement field with u = 0 at the bounding box
//      center and with no infinitesimal rigid rotation component.
//
//      The no-rigid-rotation constraint makes integrating the strain simple
//      because it allows us to treat the strain tensor as a Jacobian:
//      J = 1/2 (J + J') + 1/2 (J - J') = strain + irot
//      irot = 0 ==> J = strain
//      This can also be interpreted componentwise: no rotation means, e.g.,
//      1/2 (u_x,y - u_y,x) = 0       \   ==> u_x,y = u_y,x = e_xy = e_yx
//      1/2 (u_x,y + u_y, x) = e_xy   /
//
//      If requested, the fluctuation displacement computed by periodic
//      homogenization can be added in.
*/
//  Author:  Julian Panetta (jpanetta), julian.panetta@gmail.com
//  Company:  New York University
//  Created:  12/04/2014 01:18:22
////////////////////////////////////////////////////////////////////////////////
#include <boost/algorithm/string.hpp>
#include <string>
#include <vector>
#include <MeshFEM/Types.hh>
#include <MeshFEM/SymmetricMatrix.hh>
#include <MeshFEM/FEMMesh.hh>
#include <MeshFEM/MeshIO.hh>
#include <MeshFEM/LinearElasticity.hh>
#include <MeshFEM/Materials.hh>
#include <MeshFEM/PeriodicHomogenization.hh>
#include <MeshFEM/OrthotropicHomogenization.hh>
#include <MeshFEM/MSHFieldWriter.hh>

#include <boost/program_options.hpp>

namespace po = boost::program_options;
using namespace std;
using namespace PeriodicHomogenization;

[[ noreturn ]] void usage(int exitVal, const po::options_description &visible_opts) {
    cout << "Usage: ConstStrainDisplacement_cli [options] in.msh -s 'e_00 e_11 ...' out.msh" << endl;
    cout << visible_opts << endl;
    exit(exitVal);
}

po::variables_map parseCmdLine(int argc, const char *argv[])
{
    po::options_description hidden_opts("Hidden Arguments");
    hidden_opts.add_options()
        ("mesh", po::value<string>(), "input mesh")
        ("outMesh", po::value<string>(), "output mesh")
        ;
    po::positional_options_description p;
    p.add("mesh",    1);
    p.add("outMesh", 1);

    po::options_description visible_opts;
    visible_opts.add_options()("help", "Produce this help message")
        ("material,m", po::value<string>(), "base material")
        ("strain,s", po::value<string>(), "macroscopic strain tensor")
        ("stress,S", po::value<string>(), "macroscopic stress tensor")
        ("degree,d",   po::value<int>()->default_value(2), "degree of finite elements")
        ("nodalLoad,l",                     "compute the effective force on each node.")
        ("addFluctuation,f",                "add fluctuation strains to the displacement")
        ("macroOut", po::value<string>(),   "also output the unit cell deformation")
        ("manualPeriodicVertices", po::value<string>(), "Manually specify identified periodic vertices using a hacky file format (see PeriodicCondition constructor)")
        ("orthotropicCell,O",               "Analyze the orthotropic symmetry base cell only")
        ;

    po::options_description cli_opts;
    cli_opts.add(visible_opts).add(hidden_opts);

    po::variables_map vm;
    try {
        po::store(po::command_line_parser(argc, argv).
                  options(cli_opts).positional(p).run(), vm);
        po::notify(vm);
    }
    catch (std::exception &e) {
        cout << "Error: " << e.what() << endl << endl;
        usage(1, visible_opts);
    }

    bool fail = false;
    if (vm.count("outMesh") == 0) {
        cout << "Error: must specify input and output mesh" << endl;
        fail = true;
    }

    if (vm.count("strain") + vm.count("stress") != 1) {
        cout << "Error: must specify macro strain or stress tensor" << endl;
        fail = true;
    }

    if (fail || vm.count("help"))
        usage(fail, visible_opts);

    return vm;
}

template<size_t _N>
using HMG = LinearElasticity::HomogenousMaterialGetter<Materials::Constant>::template Getter<_N>;

template<size_t _N, size_t _FEMDegree>
void execute(const po::variables_map &args,
             const vector<MeshIO::IOVertex> &inVertices,
             const vector<MeshIO::IOElement> &inElements)
{
    auto &mat = HMG<_N>::material;
    if (args.count("material")) mat.setFromFile(args["material"].as<string>());
    typedef LinearElasticity::Mesh<_N, _FEMDegree, HMG> Mesh;
    typedef LinearElasticity::Simulator<Mesh> Simulator;
    typedef typename Simulator::VField VField;
    Simulator sim(inElements, inVertices);
    const auto &mesh = sim.mesh();
    MSHFieldWriter writer(args["outMesh"].as<string>(), mesh);

    // Parse strain/stress tensor.
    vector<string> probeComponents;
    string probeString = args.count("strain") ? args["strain"].as<string>() : args["stress"].as<string>();
    boost::trim(probeString);
    boost::split(probeComponents, probeString, boost::is_any_of("\t "),
                 boost::token_compress_on);
    if (probeComponents.size() != flatLen(_N))
        throw runtime_error("Invalid strain tensor");

    SymmetricMatrixValue<Real, _N> strain;
    // Actually a stress for the macro stress case!
    for (size_t i = 0; i < probeComponents.size(); ++i)
        strain[i] = stod(probeComponents[i]);

    // Convert stress probe to corresponding strain probe.
    std::vector<VField> w_ij;

    std::unique_ptr<PeriodicCondition<_N>> pc;
    if (args.count("manualPeriodicVertices"))
        pc = Future::make_unique<PeriodicCondition<_N>>(sim.mesh(), args["manualPeriodicVertices"].as<string>());

    auto doCellProblemSolve = [&]() {
        if (args.count("orthotropicCell") == 0)   solveCellProblems(w_ij, sim, 1e-7, false, std::move(pc));
        else PeriodicHomogenization::Orthotropic::solveCellProblems(w_ij, sim, 1e-7);
    };

    auto getHomogenizedTensor = [&]() {
        if (args.count("orthotropicCell") == 0)   return homogenizedElasticityTensorDisplacementForm(w_ij, sim);
        else return PeriodicHomogenization::Orthotropic::homogenizedElasticityTensorDisplacementForm(w_ij, sim);
    };

    if (args.count("stress")) {
        doCellProblemSolve();
        auto Eh = getHomogenizedTensor();
        auto Sh = Eh.inverse();
        strain = Sh.doubleContract(strain);
    }

    auto bbox = mesh.boundingBox();
    VectorND<_N> center = bbox.center();

    if (args.count("macroOut")) {
        if (_N != 2) throw std::runtime_error("macro displacement output currently only supported in 2D");

        std::vector<MeshIO::IOVertex> squareVertices;
        std::vector<MeshIO::IOElement> squareElems;

        VField uMacro(4);
        SymmetricMatrixField<Real, _N> stressMacro(2);

        if (w_ij.size() == 0) doCellProblemSolve();
        auto Eh = getHomogenizedTensor();
        stressMacro(0) = stressMacro(1) = Eh.doubleContract(strain);

        // 2   3
        // 0   1
        size_t i = 0;
        for (Real y : {bbox.minCorner[1], bbox.maxCorner[1]}) {
            for (Real x : {bbox.minCorner[0], bbox.maxCorner[0]}) {
                VectorND<_N> p;
                p.setZero(), p[0] = x, p[1] = y;
                squareVertices.emplace_back(p);
                uMacro(i++) = strain.contract(p - center);
            }
        }

        squareElems.emplace_back(0, 1, 3);
        squareElems.emplace_back(0, 3, 2);

        MSHFieldWriter mwriter(args["macroOut"].as<string>(), squareVertices, squareElems);
        mwriter.addField("u_cstrain", uMacro, DomainType::PER_NODE);
        mwriter.addField("stress", stressMacro, DomainType::PER_ELEMENT);
    }

    VField cstrainDisp(mesh.numNodes());
    for (auto n : mesh.nodes())
        cstrainDisp(n.index()) = strain.contract(n->p - center);

    if (args.count("addFluctuation")) {
        if (w_ij.size() == 0) doCellProblemSolve();
        // Remove rigid translation of fluctuation displacements relative to the
        // base cell (i.e. try to keep the fluctuation-displaced microstructure
        // "within" the base cell):
        // The no-rigid-motion constraint on fluctuation displacements
        // ensures the microstructure's center of mass doesn't move, but
        // this is not what we need. Instead, we need to ensure vertices on
        // periodic boundary do not move off the boundary. We enforce this in an
        // average sense for each cell face by translating so that the
        // corresponding displacement component's average over all vertices on
        // the face is zero.
        for (size_t i = 0; i < w_ij.size(); ++i) {
            VField &w = w_ij[i];
            VectorND<_N> translation(VectorND<_N>::Zero());
            vector<int> numAveraged(_N);

            for (size_t bni = 0; bni < mesh.numBoundaryNodes(); ++bni) {
                auto n = mesh.boundaryNode(bni).volumeNode();
                for (size_t d = 0; d < _N; ++d) {
                    if (std::abs(n->p[d] - bbox.minCorner[d]) < 1e-9) {
                        translation[d] += w(n.index())[d];
                        ++numAveraged[d];
                    }
                }
            }
            for (size_t d = 0; d < _N; ++d)
                translation[d] /= numAveraged[d];
            for (size_t n = 0; n < w.domainSize(); ++n)
                w(n) -= translation;
        }

        for (size_t i = 0; i < w_ij.size(); ++i) {
            VField tmp(w_ij[i]);
            tmp *= (((i < _N) ? 1.0 : 2.0) * strain[i]);
            cstrainDisp += tmp;

            writer.addField("w_ij " + to_string(i), w_ij[i], DomainType::PER_NODE);
            // auto strain = sim.averageStrainField(w_ij[i]);
            // writer.addField("strain w_ij " + to_string(i), strain, DomainType::PER_ELEMENT);

            // ScalarField<Real> comp(strain.domainSize());
            // for (size_t c = 0; c < flatLen(_N); ++c) {
            //     for (size_t ei = 0; ei < strain.domainSize(); ++ei)
            //         comp(ei) = strain(ei)[c];
            //     writer.addField("strain w_ij " + to_string(i) + " comp " + to_string(c), comp, DomainType::PER_ELEMENT);
            // }
        }
    }

    writer.addField("u_cstrain", cstrainDisp, DomainType::PER_NODE);
    if (args.count("nodalLoad"))
        writer.addField("f_cstrain", sim.applyStiffnessMatrix(cstrainDisp), DomainType::PER_NODE);

    writer.addField("stress", sim.averageStressField(cstrainDisp), DomainType::PER_ELEMENT);
}

////////////////////////////////////////////////////////////////////////////////
/*! Program entry point
//  @param[in]  argc    Number of arguments
//  @param[in]  argv    Argument strings
//  @return     status  (0 on success)
*///////////////////////////////////////////////////////////////////////////////
int main(int argc, const char *argv[])
{
    po::variables_map args = parseCmdLine(argc, argv);

    vector<MeshIO::IOVertex>  inVertices;
    vector<MeshIO::IOElement> inElements;
    auto type = load(args["mesh"].as<string>(), inVertices, inElements,
            MeshIO::FMT_GUESS, MeshIO::MESH_GUESS);

    // Infer dimension from mesh type.
    size_t dim;
    if      (type == MeshIO::MESH_TET) dim = 3;
    else if (type == MeshIO::MESH_TRI) dim = 2;
    else    throw std::runtime_error("Mesh must be triangle or tet.");

    // Look up and run appropriate instantiation.
    int deg = args["degree"].as<int>();
    auto exec = (dim == 3) ? ((deg == 2) ? execute<3, 2> : execute<3, 1>)
                           : ((deg == 2) ? execute<2, 2> : execute<2, 1>);
    exec(args, inVertices, inElements);
    return 0;
}


// test_negative_binomial.cpp

// Copyright Paul A. Bristow 2007.
// Copyright John Maddock 2006.

// 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)

// Tests for Negative Binomial Distribution.

// Note that these defines must be placed BEFORE #includes.
#define BOOST_MATH_OVERFLOW_ERROR_POLICY ignore_error
// because several tests overflow & underflow by design.
#define BOOST_MATH_DISCRETE_QUANTILE_POLICY real

#ifdef _MSC_VER
#  pragma warning(disable: 4127) // conditional expression is constant.
#endif

#if !defined(TEST_FLOAT) && !defined(TEST_DOUBLE) && !defined(TEST_LDOUBLE) && !defined(TEST_REAL_CONCEPT)
#  define TEST_FLOAT
#  define TEST_DOUBLE
#  define TEST_LDOUBLE
#  define TEST_REAL_CONCEPT
#endif

#include <boost/math/tools/test.hpp> // for real_concept
#include <boost/math/concepts/real_concept.hpp> // for real_concept
using ::boost::math::concepts::real_concept;

#include <boost/math/distributions/negative_binomial.hpp> // for negative_binomial_distribution
using boost::math::negative_binomial_distribution;

#include <boost/math/special_functions/gamma.hpp>
  using boost::math::lgamma;  // log gamma

#define BOOST_TEST_MAIN
#include <boost/test/unit_test.hpp> // for test_main
#include <boost/test/floating_point_comparison.hpp> // for BOOST_CHECK_CLOSE
#include "table_type.hpp"
#include "test_out_of_range.hpp"

#include <iostream>
using std::cout;
using std::endl;
using std::setprecision;
using std::showpoint;
#include <limits>
using std::numeric_limits;

template <class RealType>
void test_spot( // Test a single spot value against 'known good' values.
               RealType N,    // Number of successes.
               RealType k,    // Number of failures.
               RealType p,    // Probability of success_fraction.
               RealType P,    // CDF probability.
               RealType Q,    // Complement of CDF.
               RealType tol)  // Test tolerance.
{
   boost::math::negative_binomial_distribution<RealType> bn(N, p);
   BOOST_CHECK_EQUAL(N, bn.successes());
   BOOST_CHECK_EQUAL(p, bn.success_fraction());
   BOOST_CHECK_CLOSE(
     cdf(bn, k), P, tol);

  if((P < 0.99) && (Q < 0.99))
  {
    // We can only check this if P is not too close to 1,
    // so that we can guarantee that Q is free of error:
    //
    BOOST_CHECK_CLOSE(
      cdf(complement(bn, k)), Q, tol);
    if(k != 0)
    {
      BOOST_CHECK_CLOSE(
        quantile(bn, P), k, tol);
    }
    else
    {
      // Just check quantile is very small:
      if((std::numeric_limits<RealType>::max_exponent <= std::numeric_limits<double>::max_exponent)
        && (boost::is_floating_point<RealType>::value))
      {
        // Limit where this is checked: if exponent range is very large we may
        // run out of iterations in our root finding algorithm.
        BOOST_CHECK(quantile(bn, P) < boost::math::tools::epsilon<RealType>() * 10);
      }
    }
    if(k != 0)
    {
      BOOST_CHECK_CLOSE(
        quantile(complement(bn, Q)), k, tol);
    }
    else
    {
      // Just check quantile is very small:
      if((std::numeric_limits<RealType>::max_exponent <= std::numeric_limits<double>::max_exponent)
        && (boost::is_floating_point<RealType>::value))
      {
        // Limit where this is checked: if exponent range is very large we may
        // run out of iterations in our root finding algorithm.
        BOOST_CHECK(quantile(complement(bn, Q)) < boost::math::tools::epsilon<RealType>() * 10);
      }
    }
    // estimate success ratio:
    BOOST_CHECK_CLOSE(
      negative_binomial_distribution<RealType>::find_lower_bound_on_p(
      N+k, N, P),
      p, tol);
    // Note we bump up the sample size here, purely for the sake of the test,
    // internally the function has to adjust the sample size so that we get
    // the right upper bound, our test undoes this, so we can verify the result.
    BOOST_CHECK_CLOSE(
      negative_binomial_distribution<RealType>::find_upper_bound_on_p(
      N+k+1, N, Q),
      p, tol);

    if(Q < P)
    {
       //
       // We check two things here, that the upper and lower bounds
       // are the right way around, and that they do actually bracket
       // the naive estimate of p = successes / (sample size)
       //
      BOOST_CHECK(
        negative_binomial_distribution<RealType>::find_lower_bound_on_p(
        N+k, N, Q)
        <=
        negative_binomial_distribution<RealType>::find_upper_bound_on_p(
        N+k, N, Q)
        );
      BOOST_CHECK(
        negative_binomial_distribution<RealType>::find_lower_bound_on_p(
        N+k, N, Q)
        <=
        N / (N+k)
        );
      BOOST_CHECK(
        N / (N+k)
        <=
        negative_binomial_distribution<RealType>::find_upper_bound_on_p(
        N+k, N, Q)
        );
    }
    else
    {
       // As above but when P is small.
      BOOST_CHECK(
        negative_binomial_distribution<RealType>::find_lower_bound_on_p(
        N+k, N, P)
        <=
        negative_binomial_distribution<RealType>::find_upper_bound_on_p(
        N+k, N, P)
        );
      BOOST_CHECK(
        negative_binomial_distribution<RealType>::find_lower_bound_on_p(
        N+k, N, P)
        <=
        N / (N+k)
        );
      BOOST_CHECK(
        N / (N+k)
        <=
        negative_binomial_distribution<RealType>::find_upper_bound_on_p(
        N+k, N, P)
        );
    }

    // Estimate sample size:
    BOOST_CHECK_CLOSE(
      negative_binomial_distribution<RealType>::find_minimum_number_of_trials(
      k, p, P),
      N+k, tol);
    BOOST_CHECK_CLOSE(
      negative_binomial_distribution<RealType>::find_maximum_number_of_trials(
         k, p, Q),
      N+k, tol);

    // Double check consistency of CDF and PDF by computing the finite sum:
    RealType sum = 0;
    for(unsigned i = 0; i <= k; ++i)
    {
      sum += pdf(bn, RealType(i));
    }
    BOOST_CHECK_CLOSE(sum, P, tol);

    // Complement is not possible since sum is to infinity.
  } //
} // test_spot

template <class RealType> // Any floating-point type RealType.
void test_spots(RealType)
{
  // Basic sanity checks, test data is to double precision only
  // so set tolerance to 1000 eps expressed as a percent, or
  // 1000 eps of type double expressed as a percent, whichever
  // is the larger.

  RealType tolerance = (std::max)
    (boost::math::tools::epsilon<RealType>(),
    static_cast<RealType>(std::numeric_limits<double>::epsilon()));
  tolerance *= 100 * 100000.0f;

  cout << "Tolerance = " << tolerance << "%." << endl;

  RealType tol1eps = boost::math::tools::epsilon<RealType>() * 2; // Very tight, suit exact values.
  //RealType tol2eps = boost::math::tools::epsilon<RealType>() * 2; // Tight, suit exact values.
  RealType tol5eps = boost::math::tools::epsilon<RealType>() * 5; // Wider 5 epsilon.
  cout << "Tolerance 5 eps = " << tol5eps << "%." << endl;

  // Sources of spot test values:

  // MathCAD defines pbinom(k, r, p) (at about 64-bit double precision, about 16 decimal digits)
  // returns pr(X , k) when random variable X has the binomial distribution with parameters r and p.
  // 0 <= k
  // r > 0
  // 0 <= p <= 1
  // P = pbinom(30, 500, 0.05) = 0.869147702104609

  // And functions.wolfram.com

  using boost::math::negative_binomial_distribution;
  using  ::boost::math::negative_binomial;
  using  ::boost::math::cdf;
  using  ::boost::math::pdf;

  // Test negative binomial using cdf spot values from MathCAD cdf = pnbinom(k, r, p).
  // These test quantiles and complements as well.

  test_spot(  // pnbinom(1,2,0.5) = 0.5
  static_cast<RealType>(2),   // successes r
  static_cast<RealType>(1),   // Number of failures, k
  static_cast<RealType>(0.5), // Probability of success as fraction, p
  static_cast<RealType>(0.5), // Probability of result (CDF), P
  static_cast<RealType>(0.5),  // complement CCDF Q = 1 - P
  tolerance);

  test_spot( // pbinom(0, 2, 0.25)
  static_cast<RealType>(2),    // successes r
  static_cast<RealType>(0),    // Number of failures, k
  static_cast<RealType>(0.25),
  static_cast<RealType>(0.0625),                    // Probability of result (CDF), P
  static_cast<RealType>(0.9375),                    // Q = 1 - P
  tolerance);

  test_spot(  // pbinom(48,8,0.25)
  static_cast<RealType>(8),     // successes r
  static_cast<RealType>(48),    // Number of failures, k
  static_cast<RealType>(0.25),                    // Probability of success, p
  static_cast<RealType>(9.826582228110670E-1),     // Probability of result (CDF), P
  static_cast<RealType>(1 - 9.826582228110670E-1),   // Q = 1 - P
  tolerance);

  test_spot(  // pbinom(2,5,0.4)
  static_cast<RealType>(5),     // successes r
  static_cast<RealType>(2),     // Number of failures, k
  static_cast<RealType>(0.4),                    // Probability of success, p
  static_cast<RealType>(9.625600000000020E-2),     // Probability of result (CDF), P
  static_cast<RealType>(1 - 9.625600000000020E-2),   // Q = 1 - P
  tolerance);

  test_spot(  // pbinom(10,100,0.9)
  static_cast<RealType>(100),     // successes r
  static_cast<RealType>(10),     // Number of failures, k
  static_cast<RealType>(0.9),                    // Probability of success, p
  static_cast<RealType>(4.535522887695670E-1),     // Probability of result (CDF), P
  static_cast<RealType>(1 - 4.535522887695670E-1),   // Q = 1 - P
  tolerance);

  test_spot(  // pbinom(1,100,0.991)
  static_cast<RealType>(100),     // successes r
  static_cast<RealType>(1),     // Number of failures, k
  static_cast<RealType>(0.991),                    // Probability of success, p
  static_cast<RealType>(7.693413044217000E-1),     // Probability of result (CDF), P
  static_cast<RealType>(1 - 7.693413044217000E-1),   // Q = 1 - P
  tolerance);

  test_spot(  // pbinom(10,100,0.991)
  static_cast<RealType>(100),     // successes r
  static_cast<RealType>(10),     // Number of failures, k
  static_cast<RealType>(0.991),                    // Probability of success, p
  static_cast<RealType>(9.999999940939000E-1),     // Probability of result (CDF), P
  static_cast<RealType>(1 - 9.999999940939000E-1),   // Q = 1 - P
  tolerance);

if(std::numeric_limits<RealType>::is_specialized)
{ // An extreme value test that takes 3 minutes using the real concept type
  // for which numeric_limits<RealType>::is_specialized == false, deliberately
  // and for which there is no Lanczos approximation defined (also deliberately)
  // giving a very slow computation, but with acceptable accuracy.
  // A possible enhancement might be to use a normal approximation for
  // extreme values, but this is not implemented.
  test_spot(  // pbinom(100000,100,0.001)
  static_cast<RealType>(100),     // successes r
  static_cast<RealType>(100000),     // Number of failures, k
  static_cast<RealType>(0.001),                    // Probability of success, p
  static_cast<RealType>(5.173047534260320E-1),     // Probability of result (CDF), P
  static_cast<RealType>(1 - 5.173047534260320E-1),   // Q = 1 - P
  tolerance*1000); // *1000 is OK 0.51730475350664229  versus

  // functions.wolfram.com
  //   for I[0.001](100, 100000+1) gives:
  // Wolfram       0.517304753506834882009032744488738352004003696396461766326713
  // JM nonLanczos 0.51730475350664229 differs at the 13th decimal digit.
  // MathCAD       0.51730475342603199 differs at 10th decimal digit.

  // Error tests:
  check_out_of_range<negative_binomial_distribution<RealType> >(20, 0.5);
  BOOST_MATH_CHECK_THROW(negative_binomial_distribution<RealType>(0, 0.5), std::domain_error);
  BOOST_MATH_CHECK_THROW(negative_binomial_distribution<RealType>(-2, 0.5), std::domain_error);
  BOOST_MATH_CHECK_THROW(negative_binomial_distribution<RealType>(20, -0.5), std::domain_error);
  BOOST_MATH_CHECK_THROW(negative_binomial_distribution<RealType>(20, 1.5), std::domain_error);
}
 // End of single spot tests using RealType

  // Tests on PDF:
  BOOST_CHECK_CLOSE(
  pdf(negative_binomial_distribution<RealType>(static_cast<RealType>(2), static_cast<RealType>(0.5)),
  static_cast<RealType>(0) ),  // k = 0.
  static_cast<RealType>(0.25), // 0
  tolerance);

  BOOST_CHECK_CLOSE(
  pdf(negative_binomial_distribution<RealType>(static_cast<RealType>(4), static_cast<RealType>(0.5)),
  static_cast<RealType>(0)),  // k = 0.
  static_cast<RealType>(0.0625), // exact 1/16
  tolerance);

  BOOST_CHECK_CLOSE(
  pdf(negative_binomial_distribution<RealType>(static_cast<RealType>(20), static_cast<RealType>(0.25)),
  static_cast<RealType>(0)),  // k = 0
  static_cast<RealType>(9.094947017729270E-13), // pbinom(0,20,0.25) = 9.094947017729270E-13
  tolerance);

  BOOST_CHECK_CLOSE(
  pdf(negative_binomial_distribution<RealType>(static_cast<RealType>(20), static_cast<RealType>(0.2)),
  static_cast<RealType>(0)),  // k = 0
  static_cast<RealType>(1.0485760000000003e-014), // MathCAD 1.048576000000000E-14
  tolerance);

  BOOST_CHECK_CLOSE(
  pdf(negative_binomial_distribution<RealType>(static_cast<RealType>(10), static_cast<RealType>(0.1)),
  static_cast<RealType>(0)),  // k = 0.
  static_cast<RealType>(1e-10), // MathCAD says zero, but suffers cancellation error?
  tolerance);

  BOOST_CHECK_CLOSE(
  pdf(negative_binomial_distribution<RealType>(static_cast<RealType>(20), static_cast<RealType>(0.1)),
  static_cast<RealType>(0)),  // k = 0.
  static_cast<RealType>(1e-20), // MathCAD says zero, but suffers cancellation error?
  tolerance);

  BOOST_CHECK_CLOSE( // .
  pdf(negative_binomial_distribution<RealType>(static_cast<RealType>(20), static_cast<RealType>(0.9)),
  static_cast<RealType>(0)),  // k.
  static_cast<RealType>(1.215766545905690E-1), // k=20  p = 0.9
  tolerance);

  // Tests on cdf:
  // MathCAD pbinom k, r, p) == failures, successes, probability.

  BOOST_CHECK_CLOSE(cdf(
    negative_binomial_distribution<RealType>(static_cast<RealType>(2), static_cast<RealType>(0.5)), // successes = 2,prob 0.25
    static_cast<RealType>(0) ), // k = 0
    static_cast<RealType>(0.25), // probability 1/4
    tolerance);

  BOOST_CHECK_CLOSE(cdf(complement(
    negative_binomial_distribution<RealType>(static_cast<RealType>(2), static_cast<RealType>(0.5)), // successes = 2,prob 0.25
    static_cast<RealType>(0) )), // k = 0
    static_cast<RealType>(0.75), // probability 3/4
    tolerance);
  BOOST_CHECK_CLOSE( // k = 1.
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(20), static_cast<RealType>(0.25)),
  static_cast<RealType>(1)),  // k =1.
  static_cast<RealType>(1.455191522836700E-11),
  tolerance);

  BOOST_CHECK_SMALL( // Check within an epsilon with CHECK_SMALL
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(20), static_cast<RealType>(0.25)),
  static_cast<RealType>(1)) -
  static_cast<RealType>(1.455191522836700E-11),
  tolerance );

  // Some exact (probably - judging by trailing zeros) values.
  BOOST_CHECK_CLOSE(
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(0)),  // k.
  static_cast<RealType>(1.525878906250000E-5),
  tolerance);

  BOOST_CHECK_CLOSE(
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(0)),  // k.
  static_cast<RealType>(1.525878906250000E-5),
  tolerance);

  BOOST_CHECK_SMALL(
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(0)) -
  static_cast<RealType>(1.525878906250000E-5),
  tolerance );

  BOOST_CHECK_CLOSE( // k = 1.
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(1)),  // k.
  static_cast<RealType>(1.068115234375010E-4),
  tolerance);

  BOOST_CHECK_CLOSE( // k = 2.
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(2)),  // k.
  static_cast<RealType>(4.158020019531300E-4),
  tolerance);

  BOOST_CHECK_CLOSE( // k = 3.
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(3)),  // k.bristow
  static_cast<RealType>(1.188278198242200E-3),
  tolerance);

  BOOST_CHECK_CLOSE( // k = 4.
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(4)),  // k.
  static_cast<RealType>(2.781510353088410E-3),
  tolerance);

  BOOST_CHECK_CLOSE( // k = 5.
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(5)),  // k.
  static_cast<RealType>(5.649328231811500E-3),
  tolerance);

  BOOST_CHECK_CLOSE( // k = 6.
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(6)),  // k.
  static_cast<RealType>(1.030953228473680E-2),
  tolerance);

  BOOST_CHECK_CLOSE( // k = 7.
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(7)),  // k.
  static_cast<RealType>(1.729983836412430E-2),
  tolerance);

  BOOST_CHECK_CLOSE( // k = 8.
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(8)),  // k = n.
  static_cast<RealType>(2.712995628826370E-2),
  tolerance);

  BOOST_CHECK_CLOSE( //
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(48)),  // k
  static_cast<RealType>(9.826582228110670E-1),
  tolerance);

  BOOST_CHECK_CLOSE( //
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(64)),  // k
  static_cast<RealType>(9.990295004935590E-1),
  tolerance);

  BOOST_CHECK_CLOSE( //
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(5), static_cast<RealType>(0.4)),
  static_cast<RealType>(26)),  // k
  static_cast<RealType>(9.989686246611190E-1),
  tolerance);

  BOOST_CHECK_CLOSE( //
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(5), static_cast<RealType>(0.4)),
  static_cast<RealType>(2)),  // k failures
  static_cast<RealType>(9.625600000000020E-2),
  tolerance);

  BOOST_CHECK_CLOSE( //
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(50), static_cast<RealType>(0.9)),
  static_cast<RealType>(20)),  // k
  static_cast<RealType>(9.999970854144170E-1),
  tolerance);

  BOOST_CHECK_CLOSE( //
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(500), static_cast<RealType>(0.7)),
  static_cast<RealType>(200)),  // k
  static_cast<RealType>(2.172846379930550E-1),
  tolerance* 2);

  BOOST_CHECK_CLOSE( //
  cdf(negative_binomial_distribution<RealType>(static_cast<RealType>(50), static_cast<RealType>(0.7)),
  static_cast<RealType>(20)),  // k
  static_cast<RealType>(4.550203671301790E-1),
  tolerance);

  // Tests of other functions, mean and other moments ...

  negative_binomial_distribution<RealType> dist(static_cast<RealType>(8), static_cast<RealType>(0.25));
  using namespace std; // ADL of std names.
  // mean:
  BOOST_CHECK_CLOSE(
    mean(dist), static_cast<RealType>(8 * (1 - 0.25) /0.25), tol5eps);
  BOOST_CHECK_CLOSE(
    mode(dist), static_cast<RealType>(21), tol1eps);
  // variance:
  BOOST_CHECK_CLOSE(
    variance(dist), static_cast<RealType>(8 * (1 - 0.25) / (0.25 * 0.25)), tol5eps);
  // std deviation:
  BOOST_CHECK_CLOSE(
    standard_deviation(dist), // 9.79795897113271239270
    static_cast<RealType>(9.797958971132712392789136298823565567864L), // using functions.wolfram.com
    //                              9.79795897113271152534  == sqrt(8 * (1 - 0.25) / (0.25 * 0.25)))
    tol5eps * 100);
  BOOST_CHECK_CLOSE(
    skewness(dist), //
    static_cast<RealType>(0.71443450831176036),
    // using http://mathworld.wolfram.com/skewness.html
    tolerance);
  BOOST_CHECK_CLOSE(
    kurtosis_excess(dist), //
    static_cast<RealType>(0.7604166666666666666666666666666666666666L), // using Wikipedia Kurtosis(excess) formula
    tol5eps * 100);
  BOOST_CHECK_CLOSE(
    kurtosis(dist), // true
    static_cast<RealType>(3.76041666666666666666666666666666666666666L), //
    tol5eps * 100);
  // hazard:
  RealType x = static_cast<RealType>(0.125);
  BOOST_CHECK_CLOSE(
  hazard(dist, x)
  , pdf(dist, x) / cdf(complement(dist, x)), tol5eps);
  // cumulative hazard:
  BOOST_CHECK_CLOSE(
  chf(dist, x), -log(cdf(complement(dist, x))), tol5eps);
  // coefficient_of_variation:
  BOOST_CHECK_CLOSE(
  coefficient_of_variation(dist)
  , standard_deviation(dist) / mean(dist), tol5eps);

  // Special cases for PDF:
  BOOST_CHECK_EQUAL(
  pdf(
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0)), //
  static_cast<RealType>(0)),
  static_cast<RealType>(0) );

  BOOST_CHECK_EQUAL(
  pdf(
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0)),
  static_cast<RealType>(0.0001)),
  static_cast<RealType>(0) );

  BOOST_CHECK_EQUAL(
  pdf(
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(1)),
  static_cast<RealType>(0.001)),
  static_cast<RealType>(0) );

  BOOST_CHECK_EQUAL(
  pdf(
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(1)),
  static_cast<RealType>(8)),
  static_cast<RealType>(0) );

  BOOST_CHECK_SMALL(
  pdf(
   negative_binomial_distribution<RealType>(static_cast<RealType>(2), static_cast<RealType>(0.25)),
  static_cast<RealType>(0))-
  static_cast<RealType>(0.0625),
  2 * boost::math::tools::epsilon<RealType>() ); // Expect exact, but not quite.
  // numeric_limits<RealType>::epsilon()); // Not suitable for real concept!

  // Quantile boundary cases checks:
  BOOST_CHECK_EQUAL(
  quantile(  // zero P < cdf(0) so should be exactly zero.
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(0)),
  static_cast<RealType>(0));

  BOOST_CHECK_EQUAL(
  quantile(  // min P < cdf(0) so should be exactly zero.
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(boost::math::tools::min_value<RealType>())),
  static_cast<RealType>(0));

  BOOST_CHECK_CLOSE_FRACTION(
  quantile(  // Small P < cdf(0) so should be near zero.
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(boost::math::tools::epsilon<RealType>())), //
  static_cast<RealType>(0),
    tol5eps);

  BOOST_CHECK_CLOSE(
  quantile(  // Small P < cdf(0) so should be exactly zero.
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(0.0001)),
  static_cast<RealType>(0.95854156929288470),
    tolerance);

  //BOOST_CHECK(  // Fails with overflow for real_concept
  //quantile(  // Small P near 1 so k failures should be big.
  //negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  //static_cast<RealType>(1 - boost::math::tools::epsilon<RealType>())) <=
  //static_cast<RealType>(189.56999032670058)  // 106.462769 for float
  //);

  if(std::numeric_limits<RealType>::has_infinity)
  { // BOOST_CHECK tests for infinity using std::numeric_limits<>::infinity()
    // Note that infinity is not implemented for real_concept, so these tests
    // are only done for types, like built-in float, double.. that have infinity.
    // Note that these assume that  BOOST_MATH_OVERFLOW_ERROR_POLICY is NOT throw_on_error.
    // #define BOOST_MATH_THROW_ON_OVERFLOW_POLICY ==  throw_on_error would throw here.
    // #define BOOST_MAT_DOMAIN_ERROR_POLICY IS defined throw_on_error,
    //  so the throw path of error handling is tested below with BOOST_MATH_CHECK_THROW tests.

    BOOST_CHECK(
    quantile(  // At P == 1 so k failures should be infinite.
    negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
    static_cast<RealType>(1)) ==
    //static_cast<RealType>(boost::math::tools::infinity<RealType>())
    static_cast<RealType>(std::numeric_limits<RealType>::infinity()) );

    BOOST_CHECK_EQUAL(
    quantile(  // At 1 == P  so should be infinite.
    negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
    static_cast<RealType>(1)), //
    std::numeric_limits<RealType>::infinity() );

    BOOST_CHECK_EQUAL(
    quantile(complement(  // Q zero 1 so P == 1 < cdf(0) so should be exactly infinity.
    negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
    static_cast<RealType>(0))),
    std::numeric_limits<RealType>::infinity() );
   } // test for infinity using std::numeric_limits<>::infinity()
  else
  { // real_concept case, so check it throws rather than returning infinity.
    BOOST_CHECK_EQUAL(
    quantile(  // At P == 1 so k failures should be infinite.
    negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
    static_cast<RealType>(1)),
    boost::math::tools::max_value<RealType>() );

    BOOST_CHECK_EQUAL(
    quantile(complement(  // Q zero 1 so P == 1 < cdf(0) so should be exactly infinity.
    negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
    static_cast<RealType>(0))),
    boost::math::tools::max_value<RealType>());
  }
  BOOST_CHECK( // Should work for built-in and real_concept.
  quantile(complement(  // Q very near to 1 so P nearly 1  < so should be large > 384.
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(boost::math::tools::min_value<RealType>())))
   >= static_cast<RealType>(384) );

  BOOST_CHECK_EQUAL(
  quantile(  //  P ==  0 < cdf(0) so should be zero.
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(0)),
  static_cast<RealType>(0));

  // Quantile Complement boundary cases:

  BOOST_CHECK_EQUAL(
  quantile(complement(  // Q = 1 so P = 0 < cdf(0) so should be exactly zero.
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(1))),
  static_cast<RealType>(0)
  );

  BOOST_CHECK_EQUAL(
  quantile(complement(  // Q very near 1 so P == epsilon < cdf(0) so should be exactly zero.
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(1 - boost::math::tools::epsilon<RealType>()))),
  static_cast<RealType>(0)
  );

  // Check that duff arguments throw domain_error:
  BOOST_MATH_CHECK_THROW(
  pdf( // Negative successes!
  negative_binomial_distribution<RealType>(static_cast<RealType>(-1), static_cast<RealType>(0.25)),
  static_cast<RealType>(0)), std::domain_error
  );
  BOOST_MATH_CHECK_THROW(
  pdf( // Negative success_fraction!
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(-0.25)),
  static_cast<RealType>(0)), std::domain_error
  );
  BOOST_MATH_CHECK_THROW(
  pdf( // Success_fraction > 1!
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(1.25)),
  static_cast<RealType>(0)),
  std::domain_error
  );
  BOOST_MATH_CHECK_THROW(
  pdf( // Negative k argument !
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(-1)),
  std::domain_error
  );
  //BOOST_MATH_CHECK_THROW(
  //pdf( // Unlike binomial there is NO limit on k (failures)
  //negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  //static_cast<RealType>(9)), std::domain_error
  //);
  BOOST_MATH_CHECK_THROW(
  cdf(  // Negative k argument !
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(0.25)),
  static_cast<RealType>(-1)),
  std::domain_error
  );
  BOOST_MATH_CHECK_THROW(
  cdf( // Negative success_fraction!
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(-0.25)),
  static_cast<RealType>(0)), std::domain_error
  );
  BOOST_MATH_CHECK_THROW(
  cdf( // Success_fraction > 1!
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(1.25)),
  static_cast<RealType>(0)), std::domain_error
  );
  BOOST_MATH_CHECK_THROW(
  quantile(  // Negative success_fraction!
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(-0.25)),
  static_cast<RealType>(0)), std::domain_error
  );
  BOOST_MATH_CHECK_THROW(
  quantile( // Success_fraction > 1!
  negative_binomial_distribution<RealType>(static_cast<RealType>(8), static_cast<RealType>(1.25)),
  static_cast<RealType>(0)), std::domain_error
  );
  // End of check throwing 'duff' out-of-domain values.

#define T RealType
#include "negative_binomial_quantile.ipp"

  for(unsigned i = 0; i < negative_binomial_quantile_data.size(); ++i)
  {
     using namespace boost::math::policies;
     typedef policy<discrete_quantile<boost::math::policies::real> > P1;
     typedef policy<discrete_quantile<integer_round_down> > P2;
     typedef policy<discrete_quantile<integer_round_up> > P3;
     typedef policy<discrete_quantile<integer_round_outwards> > P4;
     typedef policy<discrete_quantile<integer_round_inwards> > P5;
     typedef policy<discrete_quantile<integer_round_nearest> > P6;
     RealType tol = boost::math::tools::epsilon<RealType>() * 700;
     if(!boost::is_floating_point<RealType>::value)
        tol *= 10;  // no lanczos approximation implies less accuracy
     //
     // Check full real value first:
     //
     negative_binomial_distribution<RealType, P1> p1(negative_binomial_quantile_data[i][0], negative_binomial_quantile_data[i][1]);
     RealType x = quantile(p1, negative_binomial_quantile_data[i][2]);
     BOOST_CHECK_CLOSE_FRACTION(x, negative_binomial_quantile_data[i][3], tol);
     x = quantile(complement(p1, negative_binomial_quantile_data[i][2]));
     BOOST_CHECK_CLOSE_FRACTION(x, negative_binomial_quantile_data[i][4], tol);
     //
     // Now with round down to integer:
     //
     negative_binomial_distribution<RealType, P2> p2(negative_binomial_quantile_data[i][0], negative_binomial_quantile_data[i][1]);
     x = quantile(p2, negative_binomial_quantile_data[i][2]);
     BOOST_CHECK_EQUAL(x, floor(negative_binomial_quantile_data[i][3]));
     x = quantile(complement(p2, negative_binomial_quantile_data[i][2]));
     BOOST_CHECK_EQUAL(x, floor(negative_binomial_quantile_data[i][4]));
     //
     // Now with round up to integer:
     //
     negative_binomial_distribution<RealType, P3> p3(negative_binomial_quantile_data[i][0], negative_binomial_quantile_data[i][1]);
     x = quantile(p3, negative_binomial_quantile_data[i][2]);
     BOOST_CHECK_EQUAL(x, ceil(negative_binomial_quantile_data[i][3]));
     x = quantile(complement(p3, negative_binomial_quantile_data[i][2]));
     BOOST_CHECK_EQUAL(x, ceil(negative_binomial_quantile_data[i][4]));
     //
     // Now with round to integer "outside":
     //
     negative_binomial_distribution<RealType, P4> p4(negative_binomial_quantile_data[i][0], negative_binomial_quantile_data[i][1]);
     x = quantile(p4, negative_binomial_quantile_data[i][2]);
     BOOST_CHECK_EQUAL(x, negative_binomial_quantile_data[i][2] < 0.5f ? floor(negative_binomial_quantile_data[i][3]) : ceil(negative_binomial_quantile_data[i][3]));
     x = quantile(complement(p4, negative_binomial_quantile_data[i][2]));
     BOOST_CHECK_EQUAL(x, negative_binomial_quantile_data[i][2] < 0.5f ? ceil(negative_binomial_quantile_data[i][4]) : floor(negative_binomial_quantile_data[i][4]));
     //
     // Now with round to integer "inside":
     //
     negative_binomial_distribution<RealType, P5> p5(negative_binomial_quantile_data[i][0], negative_binomial_quantile_data[i][1]);
     x = quantile(p5, negative_binomial_quantile_data[i][2]);
     BOOST_CHECK_EQUAL(x, negative_binomial_quantile_data[i][2] < 0.5f ? ceil(negative_binomial_quantile_data[i][3]) : floor(negative_binomial_quantile_data[i][3]));
     x = quantile(complement(p5, negative_binomial_quantile_data[i][2]));
     BOOST_CHECK_EQUAL(x, negative_binomial_quantile_data[i][2] < 0.5f ? floor(negative_binomial_quantile_data[i][4]) : ceil(negative_binomial_quantile_data[i][4]));
     //
     // Now with round to nearest integer:
     //
     negative_binomial_distribution<RealType, P6> p6(negative_binomial_quantile_data[i][0], negative_binomial_quantile_data[i][1]);
     x = quantile(p6, negative_binomial_quantile_data[i][2]);
     BOOST_CHECK_EQUAL(x, floor(negative_binomial_quantile_data[i][3] + 0.5f));
     x = quantile(complement(p6, negative_binomial_quantile_data[i][2]));
     BOOST_CHECK_EQUAL(x, floor(negative_binomial_quantile_data[i][4] + 0.5f));
  }

  return;
} // template <class RealType> void test_spots(RealType) // Any floating-point type RealType.

BOOST_AUTO_TEST_CASE( test_main )
{
  // Check that can generate negative_binomial distribution using the two convenience methods:
  using namespace boost::math;
   negative_binomial mynb1(2., 0.5); // Using typedef - default type is double.
   negative_binomial_distribution<> myf2(2., 0.5); // Using default RealType double.

  // Basic sanity-check spot values.

  // Test some simple double only examples.
  negative_binomial_distribution<double> my8dist(8., 0.25);
  // 8 successes (r), 0.25 success fraction = 35% or 1 in 4 successes.
  // Note: double values (matching the distribution definition) avoid the need for any casting.

  // Check accessor functions return exact values for double at least.
  BOOST_CHECK_EQUAL(my8dist.successes(), static_cast<double>(8));
  BOOST_CHECK_EQUAL(my8dist.success_fraction(), static_cast<double>(1./4.));

  // (Parameter value, arbitrarily zero, only communicates the floating point type).
#ifdef TEST_FLOAT
  test_spots(0.0F); // Test float.
#endif
#ifdef TEST_DOUBLE
  test_spots(0.0); // Test double.
#endif
#ifndef BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS
#ifdef TEST_LDOUBLE
  test_spots(0.0L); // Test long double.
#endif
#ifndef BOOST_MATH_NO_REAL_CONCEPT_TESTS
#ifdef TEST_REAL_CONCEPT
    test_spots(boost::math::concepts::real_concept(0.)); // Test real concept.
#endif
  #endif
#else
   std::cout << "<note>The long double tests have been disabled on this platform "
      "either because the long double overloads of the usual math functions are "
      "not available at all, or because they are too inaccurate for these tests "
      "to pass.</note>" << std::endl;
#endif

} // BOOST_AUTO_TEST_CASE( test_main )

/*

Autorun "i:\boost-06-05-03-1300\libs\math\test\Math_test\debug\test_negative_binomial.exe"
Running 1 test case...
Tolerance = 0.0119209%.
Tolerance 5 eps = 5.96046e-007%.
Tolerance = 2.22045e-011%.
Tolerance 5 eps = 1.11022e-015%.
Tolerance = 2.22045e-011%.
Tolerance 5 eps = 1.11022e-015%.
Tolerance = 2.22045e-011%.
Tolerance 5 eps = 1.11022e-015%.
*** No errors detected

*/


/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\
|  Phycas: Python software for phylogenetic analysis                          |
|  Copyright (C) 2006 Mark T. Holder, Paul O. Lewis and David L. Swofford     |
|                                                                             |
|  This program is free software; you can redistribute it and/or modify       |
|  it under the terms of the GNU General Public License as published by       |
|  the Free Software Foundation; either version 2 of the License, or          |
|  (at your option) any later version.                                        |
|                                                                             |
|  This program is distributed in the hope that it will be useful,            |
|  but WITHOUT ANY WARRANTY; without even the implied warranty of             |
|  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              |
|  GNU General Public License for more details.                               |
|                                                                             |
|  You should have received a copy of the GNU General Public License along    |
|  with this program; if not, write to the Free Software Foundation, Inc.,    |
|  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.                |
\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

#if ! defined(LARGET_SIMON_MOVE_HPP)
#define LARGET_SIMON_MOVE_HPP

#include <vector>							// for std::vector
#include <boost/shared_ptr.hpp>				// for boost::shared_ptr
#include <boost/weak_ptr.hpp>				// for boost::weak_ptr
#include "mcmc_updater.hpp"		// for base class MCMCUpdater

namespace phycas
{

class MCMCChainManager;
typedef boost::weak_ptr<MCMCChainManager>	ChainManagerWkPtr;

/*----------------------------------------------------------------------------------------------------------------------
|	Encapsulates the Larget-Simon local move. The LS move is the default move, but an exception must be made in the case
|	of a star tree, which does not have three contiguous edges required for the LS move. For star trees, the proposal
|	changes the length of just one randomly-chosen edge in the tree. An edge chosen at random is set to the value
|	Y = m*exp(`lambda'*(u - 0.5)), where m is the original length and u is a Uniform(0,1) random deviate. Under this
|	proposal scheme, the random variable Y has the following properties:
|>
|	density         f(Y) = 1/(lambda*Y)
|	cdf             F(Y) = 0.5 + (1/lambda) log(Y/m)
|	minimum         m*exp(-lambda/2)
|	maximum         m*exp(lambda/2)
|	mean            (m/lambda)[exp(lambda/2) - exp(-lambda/2)]
|	variance        (m/lambda)^2 [(lambda/2)(exp(lambda) - exp(-lambda)) - (exp(lambda/2) - exp(-lambda/2))^2]
|>
|	With a starting edge length of 1.0, the proposed edge lengths have increasing mean and variance with increasing
|	lambda, but values of lambda in the range 0.1 to 2.0 appear to be reasonable.
|>
|	lambda       mean        s.d.
|	-----------------------------
|	 0.1      1.00042     0.02888
|	 0.5      1.01045     0.14554
|	 1.0      1.04219     0.29840
|	 2.0      1.17520     0.65752
|	10.0     14.84064    29.68297
|>
*/
class LargetSimonMove : public MCMCUpdater
	{
	public:
						LargetSimonMove();
						virtual ~LargetSimonMove();

		unsigned		getWhichCase() const;
		void			setTuningParameter(double x);
		double			getTuningParameter() const;
		bool			topologyChanged() const;
		void			defaultProposeNewState();
		void			starTreeProposeNewState();
        TreeNode *      randomInternalAboveSubroot();
        TreeNode *      randomChild(TreeNode * nd);
        TreeNode *      chooseZ(TreeNode * middle);

		virtual double  sampleWorkingPrior() const;

		virtual bool    computesTopologyPrior() const {return true;}
		// These are virtual functions in the MCMCUpdater base class
		//
		double			recalcWorkingPrior() const;
		bool 			isPriorSteward() const;
		virtual bool	update();
		virtual double	getLnHastingsRatio() const;
		virtual double	getLnJacobian() const;
		virtual void	proposeNewState();
		virtual void	revert();
		virtual void	accept();

	private:

		double			    lambda;						/**< The tuning parameter for this move (the factor used in modifying backbone length) */

		TreeNode *		    ndX;						/**< Node at one end of segment involved in move; used by Revert to undo a move */
		TreeNode *		    ndY;						/**< One of two nodes in the middle of segment involved in move; used by Revert to undo a move */
		TreeNode *		    ndZ;						/**< Node at other end (from ndX) of segment involved in move; used by Revert to undo a move */
		double			    x;						    /**< Original length of ndX's branch; used by Revert to undo a move */
		double			    y;						    /**< Original length of ndX's branch; used by Revert to undo a move */
		double			    z;						    /**< Original length of ndX's branch; used by Revert to undo a move */
		TreeNode *		    swap1;						/**< First of the two nodes involved in an NNI swap; NULL if no swap was performed; used by Revert to undo a move */
		TreeNode *		    swap2;						/**< Second of the two nodes involved in an NNI swap; NULL if no swap was performed; used by Revert to undo a move */

		double			    m;							/**< Original 3-segment length; needed for computing Hastings ratio */
		double			    mstar;						/**< Modified 3-segment length; needed for computing Hastings ratio */

        double              expand_contract_factor;     /**< The factor by which the selected 3-edge segment is expanded or contracted */

		bool			    topol_changed;				/**< If true, last proposal changed topology */
		unsigned		    which_case;					/**< Which of the eight possible cases was tried last */

		std::vector<double> three_edgelens;			/**< workspace declared here to avoid unnecessary allocs/deallocs */

		void			    reset();					/**< Returns variables involved with reversing a proposed move to the state needed for the start of another proposal */

		bool			    star_tree_proposal;			/**< True if last proposed move was on a star tree (only one randomly-chosen edge changed); False if last proposed move was not on a star tree */

		// These are needed for the star tree exception
		double				orig_edge_len;	/**< Length of modified edge saved (in case revert is necessary) */
		TreeNode *			orig_node;		/**< Node owning the modified edge (in case revert is necessary) */
		std::vector<double>	one_edgelen;	/**< workspace declared here to avoid unnecessary allocs/deallocs */
	};

} // namespace phycas

#endif


//  Copyright (c) 2006 Xiaogang Zhang
//  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)

#ifndef BOOST_MATH_BESSEL_JY_HPP
#define BOOST_MATH_BESSEL_JY_HPP

#include <boost/math/tools/config.hpp>
#include <boost/math/special_functions/gamma.hpp>
#include <boost/math/special_functions/sign.hpp>
#include <boost/math/special_functions/hypot.hpp>
#include <boost/math/special_functions/sin_pi.hpp>
#include <boost/math/special_functions/cos_pi.hpp>
#include <boost/math/special_functions/detail/simple_complex.hpp>
#include <boost/math/special_functions/detail/bessel_jy_asym.hpp>
#include <boost/math/constants/constants.hpp>
#include <boost/math/policies/error_handling.hpp>
#include <boost/mpl/if.hpp>
#include <boost/type_traits/is_floating_point.hpp>
#include <complex>

// Bessel functions of the first and second kind of fractional order

namespace boost { namespace math {

namespace detail {

// Calculate Y(v, x) and Y(v+1, x) by Temme's method, see
// Temme, Journal of Computational Physics, vol 21, 343 (1976)
template <typename T, typename Policy>
int temme_jy(T v, T x, T* Y, T* Y1, const Policy& pol)
{
    T g, h, p, q, f, coef, sum, sum1, tolerance;
    T a, d, e, sigma;
    unsigned long k;

    BOOST_MATH_STD_USING
    using namespace boost::math::tools;
    using namespace boost::math::constants;

    BOOST_ASSERT(fabs(v) <= 0.5f);  // precondition for using this routine

    T gp = boost::math::tgamma1pm1(v, pol);
    T gm = boost::math::tgamma1pm1(-v, pol);
    T spv = boost::math::sin_pi(v, pol);
    T spv2 = boost::math::sin_pi(v/2, pol);
    T xp = pow(x/2, v);

    a = log(x / 2);
    sigma = -a * v;
    d = abs(sigma) < tools::epsilon<T>() ?
        T(1) : sinh(sigma) / sigma;
    e = abs(v) < tools::epsilon<T>() ? v*pi<T>()*pi<T>() / 2
        : 2 * spv2 * spv2 / v;

    T g1 = (v == 0) ? -euler<T>() : (gp - gm) / ((1 + gp) * (1 + gm) * 2 * v);
    T g2 = (2 + gp + gm) / ((1 + gp) * (1 + gm) * 2);
    T vspv = (fabs(v) < tools::epsilon<T>()) ? 1/constants::pi<T>() : v / spv;
    f = (g1 * cosh(sigma) - g2 * a * d) * 2 * vspv;

    p = vspv / (xp * (1 + gm));
    q = vspv * xp / (1 + gp);

    g = f + e * q;
    h = p;
    coef = 1;
    sum = coef * g;
    sum1 = coef * h;

    T v2 = v * v;
    T coef_mult = -x * x / 4;

    // series summation
    tolerance = tools::epsilon<T>();
    for (k = 1; k < policies::get_max_series_iterations<Policy>(); k++)
    {
        f = (k * f + p + q) / (k*k - v2);
        p /= k - v;
        q /= k + v;
        g = f + e * q;
        h = p - k * g;
        coef *= coef_mult / k;
        sum += coef * g;
        sum1 += coef * h;
        if (abs(coef * g) < abs(sum) * tolerance)
        {
           break;
        }
    }
    policies::check_series_iterations("boost::math::bessel_jy<%1%>(%1%,%1%) in temme_jy", k, pol);
    *Y = -sum;
    *Y1 = -2 * sum1 / x;

    return 0;
}

// Evaluate continued fraction fv = J_(v+1) / J_v, see
// Abramowitz and Stegun, Handbook of Mathematical Functions, 1972, 9.1.73
template <typename T, typename Policy>
int CF1_jy(T v, T x, T* fv, int* sign, const Policy& pol)
{
    T C, D, f, a, b, delta, tiny, tolerance;
    unsigned long k;
    int s = 1;

    BOOST_MATH_STD_USING

    // |x| <= |v|, CF1_jy converges rapidly
    // |x| > |v|, CF1_jy needs O(|x|) iterations to converge

    // modified Lentz's method, see
    // Lentz, Applied Optics, vol 15, 668 (1976)
    tolerance = 2 * tools::epsilon<T>();
    tiny = sqrt(tools::min_value<T>());
    C = f = tiny;                           // b0 = 0, replace with tiny
    D = 0.0L;
    for (k = 1; k < policies::get_max_series_iterations<Policy>() * 100; k++)
    {
        a = -1;
        b = 2 * (v + k) / x;
        C = b + a / C;
        D = b + a * D;
        if (C == 0) { C = tiny; }
        if (D == 0) { D = tiny; }
        D = 1 / D;
        delta = C * D;
        f *= delta;
        if (D < 0) { s = -s; }
        if (abs(delta - 1.0L) < tolerance)
        { break; }
    }
    policies::check_series_iterations("boost::math::bessel_jy<%1%>(%1%,%1%) in CF1_jy", k / 100, pol);
    *fv = -f;
    *sign = s;                              // sign of denominator

    return 0;
}

template <class T>
struct complex_trait
{
   typedef typename mpl::if_<is_floating_point<T>,
      std::complex<T>, sc::simple_complex<T> >::type type;
};

// Evaluate continued fraction p + iq = (J' + iY') / (J + iY), see
// Press et al, Numerical Recipes in C, 2nd edition, 1992
template <typename T, typename Policy>
int CF2_jy(T v, T x, T* p, T* q, const Policy& pol)
{
    BOOST_MATH_STD_USING

    typedef typename complex_trait<T>::type complex_type;

    complex_type C, D, f, a, b, delta, one(1);
    T tiny, zero(0.0L);
    unsigned long k;

    // |x| >= |v|, CF2_jy converges rapidly
    // |x| -> 0, CF2_jy fails to converge
    BOOST_ASSERT(fabs(x) > 1);

    // modified Lentz's method, complex numbers involved, see
    // Lentz, Applied Optics, vol 15, 668 (1976)
    T tolerance = 2 * tools::epsilon<T>();
    tiny = sqrt(tools::min_value<T>());
    C = f = complex_type(-0.5f/x, 1.0L);
    D = 0;
    for (k = 1; k < policies::get_max_series_iterations<Policy>(); k++)
    {
        a = (k - 0.5f)*(k - 0.5f) - v*v;
        if (k == 1)
        {
            a *= complex_type(T(0), 1/x);
        }
        b = complex_type(2*x, T(2*k));
        C = b + a / C;
        D = b + a * D;
        if (C == zero) { C = tiny; }
        if (D == zero) { D = tiny; }
        D = one / D;
        delta = C * D;
        f *= delta;
        if (abs(delta - one) < tolerance) { break; }
    }
    policies::check_series_iterations("boost::math::bessel_jy<%1%>(%1%,%1%) in CF2_jy", k, pol);
    *p = real(f);
    *q = imag(f);

    return 0;
}

enum
{
   need_j = 1, need_y = 2
};

// Compute J(v, x) and Y(v, x) simultaneously by Steed's method, see
// Barnett et al, Computer Physics Communications, vol 8, 377 (1974)
template <typename T, typename Policy>
int bessel_jy(T v, T x, T* J, T* Y, int kind, const Policy& pol)
{
    BOOST_ASSERT(x >= 0);

    T u, Jv, Ju, Yv, Yv1, Yu, Yu1(0), fv, fu;
    T W, p, q, gamma, current, prev, next;
    bool reflect = false;
    unsigned n, k;
    int s;

    static const char* function = "boost::math::bessel_jy<%1%>(%1%,%1%)";

    BOOST_MATH_STD_USING
    using namespace boost::math::tools;
    using namespace boost::math::constants;

    if (v < 0)
    {
        reflect = true;
        v = -v;                             // v is non-negative from here
        kind = need_j|need_y;               // need both for reflection formula
    }
    n = real_cast<unsigned>(v + 0.5L);
    u = v - n;                              // -1/2 <= u < 1/2

    if (x == 0)
    {
       *J = *Y = policies::raise_overflow_error<T>(
          function, 0, pol);
       return 1;
    }

    // x is positive until reflection
    W = T(2) / (x * pi<T>());               // Wronskian
    if (x <= 2)                           // x in (0, 2]
    {
        if(temme_jy(u, x, &Yu, &Yu1, pol))             // Temme series
        {
           // domain error:
           *J = *Y = Yu;
           return 1;
        }
        prev = Yu;
        current = Yu1;
        for (k = 1; k <= n; k++)            // forward recurrence for Y
        {
            next = 2 * (u + k) * current / x - prev;
            prev = current;
            current = next;
        }
        Yv = prev;
        Yv1 = current;
        if(kind&need_j)
        {
          CF1_jy(v, x, &fv, &s, pol);                 // continued fraction CF1_jy
          Jv = W / (Yv * fv - Yv1);           // Wronskian relation
        }
        else
           Jv = std::numeric_limits<T>::quiet_NaN(); // any value will do, we're not using it.
    }
    else                                    // x in (2, \infty)
    {
        // Get Y(u, x):
        // define tag type that will dispatch to right limits:
        typedef typename bessel_asymptotic_tag<T, Policy>::type tag_type;

        T lim;
        switch(kind)
        {
        case need_j:
           lim = asymptotic_bessel_j_limit<T>(v, tag_type());
           break;
        case need_y:
           lim = asymptotic_bessel_y_limit<T>(tag_type());
           break;
        default:
           lim = (std::max)(
              asymptotic_bessel_j_limit<T>(v, tag_type()),
              asymptotic_bessel_y_limit<T>(tag_type()));
           break;
        }
        if(x > lim)
        {
           if(kind&need_y)
           {
              Yu = asymptotic_bessel_y_large_x_2(u, x);
              Yu1 = asymptotic_bessel_y_large_x_2(u + 1, x);
           }
           else
              Yu = std::numeric_limits<T>::quiet_NaN(); // any value will do, we're not using it.
           if(kind&need_j)
           {
              Jv = asymptotic_bessel_j_large_x_2(v, x);
           }
           else
              Jv = std::numeric_limits<T>::quiet_NaN(); // any value will do, we're not using it.
        }
        else
        {
           CF1_jy(v, x, &fv, &s, pol);
           // tiny initial value to prevent overflow
           T init = sqrt(tools::min_value<T>());
           prev = fv * s * init;
           current = s * init;
           for (k = n; k > 0; k--)             // backward recurrence for J
           {
               next = 2 * (u + k) * current / x - prev;
               prev = current;
               current = next;
           }
           T ratio = (s * init) / current;     // scaling ratio
           // can also call CF1_jy() to get fu, not much difference in precision
           fu = prev / current;
           CF2_jy(u, x, &p, &q, pol);                  // continued fraction CF2_jy
           T t = u / x - fu;                   // t = J'/J
           gamma = (p - t) / q;
           Ju = sign(current) * sqrt(W / (q + gamma * (p - t)));

           Jv = Ju * ratio;                    // normalization

           Yu = gamma * Ju;
           Yu1 = Yu * (u/x - p - q/gamma);
        }
        if(kind&need_y)
        {
           // compute Y:
           prev = Yu;
           current = Yu1;
           for (k = 1; k <= n; k++)            // forward recurrence for Y
           {
               next = 2 * (u + k) * current / x - prev;
               prev = current;
               current = next;
           }
           Yv = prev;
        }
        else
           Yv = std::numeric_limits<T>::quiet_NaN(); // any value will do, we're not using it.
    }

    if (reflect)
    {
        T z = (u + n % 2);
        *J = boost::math::cos_pi(z, pol) * Jv - boost::math::sin_pi(z, pol) * Yv;     // reflection formula
        *Y = boost::math::sin_pi(z, pol) * Jv + boost::math::cos_pi(z, pol) * Yv;
    }
    else
    {
        *J = Jv;
        *Y = Yv;
    }

    return 0;
}

} // namespace detail

}} // namespaces

#endif // BOOST_MATH_BESSEL_JY_HPP