// ====================================================================
// This file is part of FlexibleSUSY.
//
// FlexibleSUSY 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 3 of the License,
// or (at your option) any later version.
//
// FlexibleSUSY 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 FlexibleSUSY.  If not, see
// <http://www.gnu.org/licenses/>.
// ====================================================================

#ifndef WRAPPERS_H
#define WRAPPERS_H

#include <algorithm>
#include <cmath>
#include <complex>
#include <functional>
#include <limits>
#include <numeric>
#include <sstream>
#include <string>
#include <type_traits>
#include <vector>
#include <Eigen/Core>
#include <boost/lexical_cast.hpp>

#include "dilog.hpp"
#include "eigen_tensor.hpp"

namespace flexiblesusy {

static const double Pi = M_PI;
static const double oneOver16PiSqr = 1./(16. * M_PI * M_PI);
static const double twoLoop = oneOver16PiSqr * oneOver16PiSqr;
static const double threeLoop = oneOver16PiSqr * oneOver16PiSqr * oneOver16PiSqr;
static const bool True = true;

template <typename T>
T Abs(T a)
{
   return std::abs(a);
}

template <typename T>
T Abs(const std::complex<T>& z)
{
   return std::abs(z);
}

template <typename Scalar, int M, int N>
Eigen::Array<Scalar, M, N> Abs(const Eigen::Array<Scalar, M, N>& a)
{
   return a.cwiseAbs();
}

template <class T>
std::vector<T> Abs(std::vector<T> v)
{
   for (typename std::vector<T>::iterator it = v.begin(),
           end = v.end(); it != end; ++it)
      *it = Abs(*it);
   return v;
}

inline double AbsSqr(double z)
{
   return z * z;
}

inline double AbsSqr(const std::complex<double>& z)
{
   return std::norm(z);
}

inline double AbsSqrt(double x)
{
   return std::sqrt(std::fabs(x));
}

template <typename Derived>
Derived AbsSqrt(const Eigen::MatrixBase<Derived>& m)
{
   return m.cwiseAbs().cwiseSqrt();
}

template <typename Derived>
Derived AbsSqrt(const Eigen::ArrayBase<Derived>& m)
{
   return m.cwiseAbs().cwiseSqrt();
}

/**
 * Calculates the mass of a singlet from a (possibly complex)
 * numerical value by taking the magnitude of the value.
 *
 * @param value numerical value
 * @return mass
 */
template <typename T>
double calculate_singlet_mass(T value)
{
   return std::abs(value);
}

/**
 * Calculates the mass of a Majoran fermion singlet from a (possibly
 * complex) numerical value by taking the magnitude of the value.
 *
 * The phase is set to exp(i theta/2), where theta is the phase angle
 * of the complex value.  If the value is pure real, then the phase
 * will be set to 1.  If the value is purely imaginary, then the phase
 * will be set to \f$e^{i \pi/2}\f$.
 *
 * @param value numerical value
 * @param[out] phase phase
 * @return mass
 */
template <typename T>
double calculate_majorana_singlet_mass(T value, std::complex<double>& phase)
{
   phase = std::polar(1., 0.5 * std::arg(std::complex<double>(value)));
   return std::abs(value);
}

/**
 * Calculates the mass of a Dirac fermion singlet from a (possibly
 * complex) numerical value by taking the magnitude of the value.
 *
 * The phase is set to exp(i theta), where theta is the phase angle of
 * the complex value.  If the value is pure real, then the phase will
 * be set to 1.  If the value is purely imaginary, then the phase will
 * be set to \f$e^{i \pi}\f$.
 *
 * @param value numerical value
 * @param[out] phase phase
 * @return mass
 */
template <typename T>
double calculate_dirac_singlet_mass(T value, std::complex<double>& phase)
{
   phase = std::polar(1., std::arg(std::complex<double>(value)));
   return std::abs(value);
}

inline double ArcTan(double a)
{
   return std::atan(a);
}

inline double ArcSin(double a)
{
   return std::asin(a);
}

inline double ArcCos(double a)
{
   return std::acos(a);
}

inline double Arg(const std::complex<double>& z)
{
   return std::arg(z);
}

inline double Conj(double a)
{
   return a;
}

inline std::complex<double> Conj(const std::complex<double>& a)
{
   return std::conj(a);
}

template <class T>
T Conjugate(T a)
{
   return Conj(a);
}

template <typename T>
T Exp(T z)
{
   return std::exp(z);
}

inline double Tan(double a)
{
   return std::tan(a);
}

inline double Cos(double x)
{
   return std::cos(x);
}

inline double Sin(double x)
{
   return std::sin(x);
}

inline double Sec(double x)
{
   return 1./Cos(x);
}

inline double Csc(double x)
{
   return 1./Sin(x);
}

inline int Delta(int i, int j)
{
   return i == j;
}

template <typename T>
T If(bool c, T a, T b) { return c ? a : b; }

template <typename T>
T If(bool c, int a, T b) { return c ? T(a) : b; }

template <typename T>
T If(bool c, T a, int b) { return c ? a : T(b); }

inline bool IsClose(double a, double b,
                    double eps = std::numeric_limits<double>::epsilon())
{
   return std::abs(a - b) < eps;
}

inline bool IsCloseRel(double a, double b,
                       double eps = std::numeric_limits<double>::epsilon())
{
   if (IsClose(a, b, std::numeric_limits<double>::epsilon()))
      return true;

   if (std::abs(a) < std::numeric_limits<double>::epsilon())
      return IsClose(a, b, eps);

   return std::abs((a - b)/a) < eps;
}

inline bool IsFinite(double x)
{
   return std::isfinite(x);
}

inline bool IsFinite(const std::complex<double>& x)
{
   return std::isfinite(x.real()) && std::isfinite(x.imag());
}

template <class Derived>
bool IsFinite(const Eigen::DenseBase<Derived>& m)
{
   return m.allFinite();
}

inline int KroneckerDelta(int i, int j)
{
   return i == j;
}

template <class Derived>
typename Eigen::MatrixBase<Derived>::PlainObject Diag(const Eigen::MatrixBase<Derived>& m)
{
   static_assert(Eigen::MatrixBase<Derived>::RowsAtCompileTime ==
                 Eigen::MatrixBase<Derived>::ColsAtCompileTime,
                 "Diag is only defined for squared matrices");

   typename Eigen::MatrixBase<Derived>::PlainObject diag(m);

   for (int i = 0; i < Eigen::MatrixBase<Derived>::RowsAtCompileTime; ++i)
      for (int k = i + 1; k < Eigen::MatrixBase<Derived>::ColsAtCompileTime; ++k)
         diag(i,k) = 0.0;

   for (int i = 0; i < Eigen::MatrixBase<Derived>::RowsAtCompileTime; ++i)
      for (int k = 0; k < i; ++k)
         diag(i,k) = 0.0;

   return diag;
}

inline double FiniteLog(double a)
{
   return (std::isfinite(a) && a > std::numeric_limits<double>::epsilon())
      ? std::log(a) : 0;
}

/**
 * Fills lower triangle of hermitian matrix from values
 * in upper triangle.
 *
 * @param m matrix
 */
template <typename Derived>
void Hermitianize(Eigen::MatrixBase<Derived>& m)
{
   static_assert(Eigen::MatrixBase<Derived>::RowsAtCompileTime ==
                 Eigen::MatrixBase<Derived>::ColsAtCompileTime,
                 "Hermitianize is only defined for squared matrices");

   for (int i = 0; i < Eigen::MatrixBase<Derived>::RowsAtCompileTime; i++)
      for (int k = 0; k < i; k++)
         m(i,k) = Conj(m(k,i));
}

inline double Log(double a)
{
   return std::log(a);
}

double MaxRelDiff(double, double);

template <class Derived>
double MaxRelDiff(const Eigen::MatrixBase<Derived>& a,
                  const Eigen::MatrixBase<Derived>& b)
{
   typename Eigen::MatrixBase<Derived>::PlainObject sumTol(a.rows());

   assert(a.rows() == b.rows());

   for (int i = 0; i < a.rows(); i++)
      sumTol(i) = MaxRelDiff(a(i), b(i));

   return sumTol.maxCoeff();
}

template <class Derived>
double MaxRelDiff(const Eigen::ArrayBase<Derived>& a,
                  const Eigen::ArrayBase<Derived>& b)
{
   return MaxRelDiff(a.matrix(), b.matrix());
}

inline double MaxAbsValue(double x)
{
   return Abs(x);
}

inline double MaxAbsValue(const std::complex<double>& x)
{
   return Abs(x);
}

template <class Derived>
double MaxAbsValue(const Eigen::MatrixBase<Derived>& x)
{
   return x.cwiseAbs().maxCoeff();
}

inline int Sign(double x)
{
   return (x >= 0.0 ? 1 : -1);
}

inline int Sign(int x)
{
   return (x >= 0 ? 1 : -1);
}

template <typename T>
T PolyLog(int n, T z) {
   if (n == 2)
      return gm2calc::dilog(z);
   assert(false && "PolyLog(n!=2) not implemented");
}

template <typename Base, typename Exponent>
Base Power(Base base, Exponent exp)
{
   return std::pow(base, exp);
}

inline double Re(double x)
{
   return x;
}

inline double Re(const std::complex<double>& x)
{
   return std::real(x);
}

template<int M, int N>
Eigen::Matrix<double,M,N> Re(const Eigen::Matrix<double,M,N>& x)
{
   return x;
}

template<class Derived>
typename Eigen::Matrix<
   double,
   Eigen::MatrixBase<Derived>::RowsAtCompileTime,
   Eigen::MatrixBase<Derived>::ColsAtCompileTime>
Re(const Eigen::MatrixBase<Derived>& x)
{
   return x.real();
}

inline double Im(double)
{
   return 0.;
}

inline double Im(const std::complex<double>& x)
{
   return std::imag(x);
}

template<int M, int N>
Eigen::Matrix<double,M,N> Im(const Eigen::Matrix<double,M,N>& x)
{
   return Eigen::Matrix<double,M,N>::Zero();
}

template<class Derived>
typename Eigen::Matrix<
   double,
   Eigen::MatrixBase<Derived>::RowsAtCompileTime,
   Eigen::MatrixBase<Derived>::ColsAtCompileTime>
Im(const Eigen::MatrixBase<Derived>& x)
{
   return x.imag();
}

namespace {
   struct CompareAbs_d {
      bool operator() (double a, double b) { return std::abs(a) < std::abs(b); }
   };
}

template <typename T>
T RelDiff(T a, T b, T eps = std::numeric_limits<T>::epsilon())
{
   const T max = std::max(a, b);

   if (std::abs(max) < eps)
      return T();

   return (a - b) / max;
}

inline int Round(double a)
{
   return static_cast<int>(a >= 0. ? a + 0.5 : a - 0.5);
}

template<int N>
void Sort(Eigen::Array<double, N, 1>& v)
{
   std::sort(v.data(), v.data() + v.size(), CompareAbs_d());
}

inline double SignedAbsSqrt(double a)
{
   return Sign(a) * AbsSqrt(a);
}

namespace {
   inline double SignedAbsSqrt_d(double a)
   {
      return SignedAbsSqrt(a);
   }
}

template <typename Derived>
Derived SignedAbsSqrt(const Eigen::ArrayBase<Derived>& m)
{
   return m.unaryExpr(std::ptr_fun(SignedAbsSqrt_d));
}

template <class T, typename = typename std::enable_if<std::is_floating_point<T>::value,T>::type>
T Sqrt(T a)
{
   return std::sqrt(a);
}

template <class T, typename = typename std::enable_if<std::is_integral<T>::value,T>::type>
double Sqrt(T a)
{
   return std::sqrt(static_cast<double>(a));
}

template <typename Scalar, int M, int N>
Eigen::Array<Scalar, M, N> Sqrt(const Eigen::Array<Scalar, M, N>& m)
{
   return m.unaryExpr(std::ptr_fun(Sqrt<Scalar>));
}

template <class T>
std::vector<T> Sqrt(std::vector<T> v)
{
   for (typename std::vector<T>::iterator it = v.begin(),
           end = v.end(); it != end; ++it)
      *it = Sqrt(*it);
   return v;
}

template <typename T>
T Sqr(T a)
{
   return a * a;
}

template <typename Scalar, int M, int N>
Eigen::Array<Scalar, M, N> Sqr(const Eigen::Array<Scalar, M, N>& a)
{
   return a.unaryExpr(std::ptr_fun(Sqr<Scalar>));
}

template <class T>
std::vector<T> Sqr(std::vector<T> v)
{
   for (typename std::vector<T>::iterator it = v.begin(),
           end = v.end(); it != end; ++it)
      *it = Sqr(*it);
   return v;
}

#define DEFINE_COMMUTATIVE_OPERATOR_COMPLEX_INT(op)                     \
   template <typename T>                                                \
   std::complex<T> operator op(const std::complex<T>& lhs, int rhs)     \
   {                                                                    \
      return lhs op static_cast<T>(rhs);                                \
   }                                                                    \
                                                                        \
   template <typename T>                                                \
   std::complex<T> operator op(int lhs, const std::complex<T>& rhs)     \
   {                                                                    \
      return static_cast<T>(lhs) op rhs;                                \
   }

DEFINE_COMMUTATIVE_OPERATOR_COMPLEX_INT(*)
DEFINE_COMMUTATIVE_OPERATOR_COMPLEX_INT(/)
DEFINE_COMMUTATIVE_OPERATOR_COMPLEX_INT(+)
DEFINE_COMMUTATIVE_OPERATOR_COMPLEX_INT(-)

/**
 * Fills lower triangle of symmetric matrix from values in upper
 * triangle.
 *
 * @param m matrix
 */
template <typename Derived>
void Symmetrize(Eigen::MatrixBase<Derived>& m)
{
   static_assert(Eigen::MatrixBase<Derived>::RowsAtCompileTime ==
                 Eigen::MatrixBase<Derived>::ColsAtCompileTime,
                 "Symmetrize is only defined for squared matrices");

   for (int i = 0; i < Eigen::MatrixBase<Derived>::RowsAtCompileTime; i++)
      for (int k = 0; k < i; k++)
         m(i,k) = m(k,i);
}

#define UNITMATRIX(rows)             Eigen::Matrix<double,rows,rows>::Identity()
#define ZEROMATRIX(rows,cols)        Eigen::Matrix<double,rows,cols>::Zero()
#define ZEROTENSOR3(d1,d2,d3)        ZeroTensor3<double,d1,d2,d3>()
#define ZEROTENSOR4(d1,d2,d3,d4)     ZeroTensor4<double,d1,d2,d3,d4>()
#define ZEROVECTOR(rows)             Eigen::Matrix<double,rows,1>::Zero()
#define ZEROARRAY(rows)              Eigen::Array<double,rows,1>::Zero()
#define UNITMATRIXCOMPLEX(rows)      Eigen::Matrix<std::complex<double>,rows,rows>::Identity()
#define ZEROMATRIXCOMPLEX(rows,cols) Eigen::Matrix<std::complex<double>,rows,cols>::Zero()
#define ZEROVECTORCOMPLEX(rows)      Eigen::Matrix<std::complex<double>,rows,1>::Zero()
#define ZEROTENSOR3COMPLEX(d1,d2,d3) ZeroTensor3<std::complex<double>,d1,d2,d3>()
#define ZEROTENSOR4COMPLEX(d1,d2,d3,d4) ZeroTensor4<std::complex<double>,d1,d2,d3,d4>()
#define ZEROARRAYCOMPLEX(rows)       Eigen::Array<std::complex<double>,rows,1>::Zero()

// MxN matrix projection operator, which projects on the (X,Y)
// component
#define PROJECTOR Proj
#define DEFINE_PROJECTOR(M,N,X,Y)                                       \
   Eigen::Matrix<double,M,N> Proj(Eigen::Matrix<double,M,N>::Zero());   \
   Proj(X-1,Y-1) = 1;

template<class Scalar, int M>
Eigen::Matrix<Scalar,M,M> ToMatrix(const Eigen::Array<Scalar,M,1>& a)
{
   return Eigen::Matrix<Scalar,M,M>(a.matrix().asDiagonal());
}

template<class Scalar, int M, int N>
Eigen::Matrix<Scalar,M,N> ToMatrix(const Eigen::Matrix<Scalar,M,N>& a)
{
   return a;
}

template <typename T>
std::string ToString(T a)
{
   return boost::lexical_cast<std::string>(a);
}

template <class T>
T Total(const std::vector<T>& v)
{
   return std::accumulate(v.begin(), v.end(), T(0));
}

template <typename Scalar, int M, int N>
Scalar Total(const Eigen::Array<Scalar, M, N>& a)
{
   return a.sum();
}

template <class Scalar, int M, int N>
Eigen::Array<Scalar,M,N> Total(const std::vector<Eigen::Array<Scalar,M,N> >& v)
{
   if (v.empty()) {
      Eigen::Array<Scalar,M,N> result(0,0);
      result.setZero();
      return result;
   }

   Eigen::Array<Scalar,M,N> result(v[0].rows(), v[0].cols());
   result.setZero();

   for (std::size_t i = 0; i < v.size(); i++)
      result += v[i];

   return result;
}

/// step function (0 for x < 0, 1 otherwise)
template <typename T>
unsigned UnitStep(T x)
{
   return x < T() ? 0 : 1;
}

template <typename T>
T Which(bool cond, T value)
{
   return cond ? value : T(0);
}

template<typename T, typename ... Trest>
T Which(bool cond, T value, Trest... rest)
{
   return cond ? value : Which(rest...);
}

inline double ZeroSqrt(double x)
{
   return (x > 0.0 ? std::sqrt(x) : 0.0);
}

namespace {
  inline double ZeroSqrt_d(double x)
  {
    return ZeroSqrt(x);
  }
}

template <typename Derived>
Derived ZeroSqrt(const Eigen::ArrayBase<Derived>& m)
{
   return m.unaryExpr(std::ptr_fun(ZeroSqrt_d));
}

}

#endif


//---------------------------------------------------------------------------//
//!
//! \file   tstTabularCDFDistribution.cpp
//! \author Luke Kersting
//! \brief  TabularCDF distribution unit tests.
//!
//---------------------------------------------------------------------------//

// Std Lib Includes
#include <iostream>

// Boost Includes
#include <boost/units/systems/si.hpp>
#include <boost/units/systems/cgs.hpp>
#include <boost/units/io.hpp>

// FRENSIE Includes
#include "Utility_TabularUnivariateDistribution.hpp"
#include "Utility_TabularCDFDistribution.hpp"
#include "Utility_RandomNumberGenerator.hpp"
#include "Utility_PhysicalConstants.hpp"
#include "Utility_UnitTraits.hpp"
#include "Utility_QuantityTraits.hpp"
#include "Utility_UnitTestHarnessWithMain.hpp"
#include "ArchiveTestHelpers.hpp"

//---------------------------------------------------------------------------//
// Testing Types
//---------------------------------------------------------------------------//
using boost::units::quantity;
using namespace Utility::Units;
namespace si = boost::units::si;
namespace cgs = boost::units::cgs;

typedef std::tuple<Utility::LogLogCos,Utility::LinLogCos,Utility::LogNudgedLogCos,Utility::LinNudgedLogCos > InterpTypes;

typedef TestArchiveHelper::TestArchives TestArchives;

typedef std::tuple<
  std::tuple<si::dimensionless,si::amount,cgs::dimensionless,si::amount>,
  std::tuple<cgs::dimensionless,si::amount,si::dimensionless,si::amount>,
  std::tuple<void*,cgs::dimensionless,void*,si::dimensionless>
  > TestUnitTypeQuads;

//---------------------------------------------------------------------------//
// Testing Variables
//---------------------------------------------------------------------------//

std::shared_ptr<Utility::UnivariateDistribution> distribution;
std::shared_ptr<Utility::TabularUnivariateDistribution> tab_distribution;

std::shared_ptr<Utility::UnitAwareUnivariateDistribution<cgs::dimensionless,si::amount> >
  unit_aware_distribution;
std::shared_ptr<Utility::UnitAwareTabularUnivariateDistribution<cgs::dimensionless,si::amount> >
  unit_aware_tab_distribution;

double norm_const = 1.0/1.999999;

//---------------------------------------------------------------------------//
// Testing Functions.
//---------------------------------------------------------------------------//
// Initialize the pdf distribution
template<typename InterpolationPolicy, typename BaseDistribution>
void initialize( std::shared_ptr<BaseDistribution>& dist )
{
  // Use the basic constructor
  std::vector<typename BaseDistribution::IndepQuantity>
    independent_values( 4 );
  Utility::setQuantity( independent_values[0], -1.0 );
  Utility::setQuantity( independent_values[1], 0.0 );
  Utility::setQuantity( independent_values[2], 0.5 );
  Utility::setQuantity( independent_values[3], 0.999999 );

  std::vector<typename BaseDistribution::DepQuantity> dependent_values( 4 );
  Utility::setQuantity( dependent_values[0], 1.0 );
  Utility::setQuantity( dependent_values[1], 1.0 );
  Utility::setQuantity( dependent_values[2], 1.0 );
  Utility::setQuantity( dependent_values[3], 1.0 );

  dist.reset(new Utility::UnitAwareTabularCDFDistribution<InterpolationPolicy,typename BaseDistribution::IndepUnit, typename BaseDistribution::DepUnit>(
                                                          independent_values,
                                                          dependent_values ) );
}

//---------------------------------------------------------------------------//
// Initialize the cdf distribution
template<typename InterpolationPolicy, typename BaseDistribution>
void initializeCDF( std::shared_ptr<BaseDistribution>& dist )
{
  // Use the basic constructor
  std::vector<typename BaseDistribution::IndepQuantity>
    independent_values( 4 );
  Utility::setQuantity( independent_values[0], -1.0 );
  Utility::setQuantity( independent_values[1], 0.0 );
  Utility::setQuantity( independent_values[2], 0.5 );
  Utility::setQuantity( independent_values[3], 0.999999 );

  std::vector<double> dependent_values( 4 );
  dependent_values[0] = 0.0;
  dependent_values[1] = 1e-1;
  dependent_values[2] = 1.0;
  dependent_values[3] = 1e2;

  dist.reset(new Utility::UnitAwareTabularCDFDistribution<InterpolationPolicy,typename BaseDistribution::IndepUnit, typename BaseDistribution::DepUnit>(
                                                          independent_values,
                                                          dependent_values,
                                                          true ) );
}

//---------------------------------------------------------------------------//
// Initialize the cdf distribution
template<typename InterpolationPolicy, typename BaseDistribution>
void initializeUnitAwareCDF( std::shared_ptr<BaseDistribution>& dist )
{
  // Use the basic constructor
  std::vector<typename BaseDistribution::IndepQuantity>
    independent_values( 4 );
  Utility::setQuantity( independent_values[0], -1.0 );
  Utility::setQuantity( independent_values[1], 0.0 );
  Utility::setQuantity( independent_values[2], 0.5 );
  Utility::setQuantity( independent_values[3], 0.999999 );

  std::vector<double> dependent_values( 4 );
  dependent_values[0] = 0.0;
  dependent_values[1] = 1e-1;
  dependent_values[2] = 1.0;
  dependent_values[3] = 1e2;

  dist.reset(new Utility::UnitAwareTabularCDFDistribution<InterpolationPolicy,typename BaseDistribution::IndepUnit, typename BaseDistribution::DepUnit>(
                                                          independent_values,
                                                          dependent_values ) );
}

//---------------------------------------------------------------------------//
// Tests.
//---------------------------------------------------------------------------//
// Check that the distribution can be evaluated
FRENSIE_UNIT_TEST_TEMPLATE( TabularCDFDistribution,
                            evaluate,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  FRENSIE_CHECK_EQUAL( distribution->evaluate( 0.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1e-3 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 2.0 ), 0.0 );

  initializeCDF<InterpolationPolicy>( distribution );

  FRENSIE_CHECK_EQUAL( distribution->evaluate( 0.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1e-3 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 2.0 ), 0.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be evaluated
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularCDFDistribution,
                            evaluate,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( -1.001*cgs::dimensionless() ),
    0.0*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( -1.0*cgs::dimensionless() ),
    0.0*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.999999*cgs::dimensionless() ),
    0.0*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 1.001*cgs::dimensionless() ),
    0.0*si::mole );

  initializeUnitAwareCDF<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( -1.001*cgs::dimensionless() ),
    0.0*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( -1.0*cgs::dimensionless() ),
    0.0*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.999999*cgs::dimensionless() ),
    0.0*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 1.001*cgs::dimensionless() ),
    0.0*si::mole );
}

//---------------------------------------------------------------------------//
// Check that the PDF can be evaluated
FRENSIE_UNIT_TEST_TEMPLATE( TabularCDFDistribution,
                            evaluatePDF,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 0.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 1e-3 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 2.0 ), 0.0 );

  initializeCDF<InterpolationPolicy>( distribution );

  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 0.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 1e-3 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 2.0 ), 0.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware PDF can be evaluated
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularCDFDistribution,
                            evaluatePDF,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( -1.001*cgs::dimensionless() ), 0.0/cgs::dimensionless() );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( -1.0*cgs::dimensionless() ), 0.0/cgs::dimensionless() );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF(1.0*cgs::dimensionless() ), 0.0/cgs::dimensionless() );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( 1.001*cgs::dimensionless() ), 0.0/cgs::dimensionless() );

  initializeUnitAwareCDF<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( -1.001*cgs::dimensionless() ), 0.0/cgs::dimensionless() );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( -1.0*cgs::dimensionless() ), 0.0/cgs::dimensionless() );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF(1.0*cgs::dimensionless() ), 0.0/cgs::dimensionless() );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( 1.001*cgs::dimensionless() ), 0.0/cgs::dimensionless() );
}

//---------------------------------------------------------------------------//
// Check that the CDF can be evaluated
FRENSIE_UNIT_TEST_TEMPLATE( TabularCDFDistribution,
                            evaluateCDF,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( tab_distribution );

  FRENSIE_CHECK_EQUAL( tab_distribution->evaluateCDF( -1.001 ), 0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( tab_distribution->evaluateCDF( -1.0 ), 0.0, 1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( tab_distribution->evaluateCDF( 0.0 ), norm_const, 1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( tab_distribution->evaluateCDF( 0.5 ), 1.5*norm_const, 1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( tab_distribution->evaluateCDF( 1.0 ), 1.0, 1e-10 );
  FRENSIE_CHECK_EQUAL( tab_distribution->evaluateCDF( 1.001 ), 1.0 );

  initializeCDF<InterpolationPolicy>( tab_distribution );

  FRENSIE_CHECK_EQUAL( tab_distribution->evaluateCDF( -1.001 ), 0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( tab_distribution->evaluateCDF( -1.0 ), 0.0, 1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( tab_distribution->evaluateCDF( 0.0 ), 1e-3, 1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( tab_distribution->evaluateCDF( 0.5 ), 1e-2, 1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( tab_distribution->evaluateCDF( 1.0 ), 1.0, 1e-10 );
  FRENSIE_CHECK_EQUAL( tab_distribution->evaluateCDF( 1.001 ), 1.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware CDF can be evaluated
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularCDFDistribution,
                            evaluateCDF,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_tab_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_tab_distribution->evaluateCDF( -1.001*cgs::dimensionless() ),
                       0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( unit_aware_tab_distribution->evaluateCDF( -1.0*cgs::dimensionless() ),
                          0.0,
                          1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( unit_aware_tab_distribution->evaluateCDF( 0.0*cgs::dimensionless() ),
                          norm_const,
                          1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( unit_aware_tab_distribution->evaluateCDF( 0.5*cgs::dimensionless() ),
                          1.5*norm_const,
                          1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( unit_aware_tab_distribution->evaluateCDF( 0.999999*cgs::dimensionless() ),
                          1.0,
                          1e-10 );
  FRENSIE_CHECK_EQUAL( unit_aware_tab_distribution->evaluateCDF( 1.001*cgs::dimensionless() ),
                       1.0 );

  initializeUnitAwareCDF<InterpolationPolicy>( unit_aware_tab_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_tab_distribution->evaluateCDF( -1.001*cgs::dimensionless() ),
                       0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( unit_aware_tab_distribution->evaluateCDF( -1.0*cgs::dimensionless() ),
                          0.0,
                          1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( unit_aware_tab_distribution->evaluateCDF( 0.0*cgs::dimensionless() ),
                          1e-3,
                          1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( unit_aware_tab_distribution->evaluateCDF( 0.5*cgs::dimensionless() ),
                          1e-2,
                          1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( unit_aware_tab_distribution->evaluateCDF( 0.999999*cgs::dimensionless() ),
                          1.0,
                          1e-10 );
  FRENSIE_CHECK_EQUAL( unit_aware_tab_distribution->evaluateCDF( 1.001*cgs::dimensionless() ),
                       1.0 );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST_TEMPLATE( TabularCDFDistribution,
                            sample,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double sample = distribution->sample();
  FRENSIE_CHECK_EQUAL( sample, -1.0 );

  sample = distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.999999, 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = distribution->sample();
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, -1.0 );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 0.999999 );

  initializeCDF<InterpolationPolicy>( distribution );

  fake_stream.resize( 4 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1e-3;
  fake_stream[2] = 1e-2;
  fake_stream[3] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = distribution->sample();
  FRENSIE_CHECK_EQUAL( sample, -1.0 );

  sample = distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.0, 1e-12 );

  sample = distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5, 1e-12 );

  sample = distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.999999, 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = distribution->sample();
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, -1.0 );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 0.999999 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularCDFDistribution,
                            sample,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<cgs::dimensionless> sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_EQUAL( sample, -1.0*cgs::dimensionless() );

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.999999*cgs::dimensionless(), 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, -1.0*cgs::dimensionless() );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 0.999999*cgs::dimensionless() );

  initializeUnitAwareCDF<InterpolationPolicy>( unit_aware_distribution );

  fake_stream.resize( 4 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1e-3;
  fake_stream[2] = 1e-2;
  fake_stream[3] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_EQUAL( sample, -1.0*cgs::dimensionless() );

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.0*cgs::dimensionless(), 1e-12 );

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5*cgs::dimensionless(), 1e-12 );

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.999999*cgs::dimensionless(), 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, -1.0*cgs::dimensionless() );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 0.999999*cgs::dimensionless() );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST_TEMPLATE( TabularCDFDistribution,
                            sampleAndRecordTrials,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  Utility::DistributionTraits::Counter trials = 0;

  double sample = distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_EQUAL( sample, -1.0 );
  FRENSIE_CHECK_EQUAL( 1.0/trials, 1.0 );

  sample = distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.999999, 1e-12 );
  FRENSIE_CHECK_EQUAL( 2.0/trials, 1.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, -1.0 );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 0.999999 );
  FRENSIE_CHECK_EQUAL( 3.0/trials, 1.0 );

  initializeCDF<InterpolationPolicy>( distribution );

  fake_stream.resize( 4 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1e-3;
  fake_stream[2] = 1e-2;
  fake_stream[3] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_EQUAL( sample, -1.0 );
  FRENSIE_CHECK_EQUAL( 4.0/trials, 1.0 );

  sample = distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.0, 1e-12 );
  FRENSIE_CHECK_EQUAL( 5.0/trials, 1.0 );

  sample = distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5, 1e-12 );
  FRENSIE_CHECK_EQUAL( 6.0/trials, 1.0 );

  sample = distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.999999, 1e-12 );
  FRENSIE_CHECK_EQUAL( 7.0/trials, 1.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, -1.0 );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 0.999999 );
  FRENSIE_CHECK_EQUAL( 8.0/trials, 1.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularCDFDistribution,
                            sampleAndRecordTrials,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  Utility::DistributionTraits::Counter trials = 0;

  quantity<cgs::dimensionless> sample =
  unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_EQUAL( sample, -1.0*cgs::dimensionless() );
  FRENSIE_CHECK_EQUAL( 1.0/trials, 1.0 );

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.999999*cgs::dimensionless(), 1e-12 );
  FRENSIE_CHECK_EQUAL( 2.0/trials, 1.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, -1.0*cgs::dimensionless() );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 0.999999*cgs::dimensionless() );
  FRENSIE_CHECK_EQUAL( 3.0/trials, 1.0 );

  initializeUnitAwareCDF<InterpolationPolicy>( unit_aware_distribution );

  fake_stream.resize( 4 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1e-3;
  fake_stream[2] = 1e-2;
  fake_stream[3] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  trials = 0;

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_EQUAL( sample, -1.0*cgs::dimensionless() );
  FRENSIE_CHECK_EQUAL( 1.0/trials, 1.0 );

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.0*cgs::dimensionless(), 1e-12 );
  FRENSIE_CHECK_EQUAL( 2.0/trials, 1.0 );

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5*cgs::dimensionless(), 1e-12 );
  FRENSIE_CHECK_EQUAL( 3.0/trials, 1.0 );

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.999999*cgs::dimensionless(), 1e-12 );
  FRENSIE_CHECK_EQUAL( 4.0/trials, 1.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, -1.0*cgs::dimensionless() );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 0.999999*cgs::dimensionless() );
  FRENSIE_CHECK_EQUAL( 5.0/trials, 1.0 );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST_TEMPLATE( TabularCDFDistribution,
                            sampleAndRecordBinIndex,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( tab_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  size_t bin_index;

  double sample = tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_EQUAL( sample, -1.0 );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );

  sample = tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.999999, 1e-12 );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, -1.0 );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 0.999999 );

  initializeCDF<InterpolationPolicy>( tab_distribution );

  fake_stream.resize( 4 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1e-3;
  fake_stream[2] = 1e-2;
  fake_stream[3] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_EQUAL( sample, -1.0 );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );

  sample = tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.0, 1e-12 );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );

  sample = tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5, 1e-12 );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );

  sample = tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.999999, 1e-12 );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, -1.0 );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 0.999999 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularCDFDistribution,
                            sampleAndRecordBinIndex,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_tab_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  size_t bin_index;

  quantity<cgs::dimensionless> sample =
  unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_EQUAL( sample, -1.0*cgs::dimensionless() );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );

  sample = unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.999999*cgs::dimensionless(), 1e-12 );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, -1.0*cgs::dimensionless() );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 0.999999*cgs::dimensionless() );

  initializeUnitAwareCDF<InterpolationPolicy>( unit_aware_tab_distribution );

  fake_stream.resize( 4 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1e-3;
  fake_stream[2] = 1e-2;
  fake_stream[3] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_EQUAL( sample, -1.0*cgs::dimensionless() );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );

  sample = unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.0*cgs::dimensionless(), 1e-12 );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );

  sample = unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5*cgs::dimensionless(), 1e-12 );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );

  sample = unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.999999*cgs::dimensionless(), 1e-12 );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, -1.0*cgs::dimensionless() );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 0.999999*cgs::dimensionless() );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST_TEMPLATE( TabularCDFDistribution,
                            sampleWithRandomNumber,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( tab_distribution );

  double sample = tab_distribution->sampleWithRandomNumber( 0.0 );
  FRENSIE_CHECK_EQUAL( sample, -1.0 );

  sample = tab_distribution->sampleWithRandomNumber( 1.0 - 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.999999, 1e-12 );

  initializeCDF<InterpolationPolicy>( tab_distribution );

  sample = tab_distribution->sampleWithRandomNumber( 0.0 );
  FRENSIE_CHECK_EQUAL( sample, -1.0 );

  sample = tab_distribution->sampleWithRandomNumber( 1e-3 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.0, 1e-12 );

  sample = tab_distribution->sampleWithRandomNumber( 1e-2 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5, 1e-12 );

  sample = tab_distribution->sampleWithRandomNumber( 1.0 - 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.999999, 1e-12 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularCDFDistribution,
                            sampleWithRandomNumber,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_tab_distribution );

  quantity<cgs::dimensionless> sample =
  unit_aware_tab_distribution->sampleWithRandomNumber( 0.0 );
  FRENSIE_CHECK_EQUAL( sample, -1.0*cgs::dimensionless() );

  sample = unit_aware_tab_distribution->sampleWithRandomNumber( 1.0 - 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.999999*cgs::dimensionless(), 1e-12 );

  initializeUnitAwareCDF<InterpolationPolicy>( unit_aware_tab_distribution );

  sample = unit_aware_tab_distribution->sampleWithRandomNumber( 0.0 );
  FRENSIE_CHECK_EQUAL( sample, -1.0*cgs::dimensionless() );

  sample = unit_aware_tab_distribution->sampleWithRandomNumber( 1e-3 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.0*cgs::dimensionless(), 1e-12 );

  sample = unit_aware_tab_distribution->sampleWithRandomNumber( 1e-2 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5*cgs::dimensionless(), 1e-12 );

  sample = unit_aware_tab_distribution->sampleWithRandomNumber( 1.0 - 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.999999*cgs::dimensionless(), 1e-12 );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled from a subrange
FRENSIE_UNIT_TEST_TEMPLATE( TabularCDFDistribution,
                            sampleInSubrange,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( tab_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double sample = tab_distribution->sampleInSubrange( 0.5 );
  FRENSIE_CHECK_EQUAL( sample, -1.0 );

  sample = tab_distribution->sampleInSubrange( 0.5 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5, 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = tab_distribution->sampleInSubrange( 0.5 );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, -1.0 );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 0.5 );

  initializeCDF<InterpolationPolicy>( tab_distribution );

  fake_stream.resize( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1e-1;
  fake_stream[2] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = tab_distribution->sampleInSubrange( 0.5 );
  FRENSIE_CHECK_EQUAL( sample, -1.0 );

  sample = tab_distribution->sampleInSubrange( 0.5 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.0, 1e-12 );

  sample = tab_distribution->sampleInSubrange( 0.5 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5, 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = tab_distribution->sampleInSubrange( 0.5 );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, -1.0 );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 0.5 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled from a subrange
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularCDFDistribution,
                            sampleInSubrange,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_tab_distribution );

  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<cgs::dimensionless> sample =
  unit_aware_tab_distribution->sampleInSubrange( 0.5*cgs::dimensionless()  );
  FRENSIE_CHECK_EQUAL( sample, -1.0*cgs::dimensionless() );

  sample = unit_aware_tab_distribution->sampleInSubrange( 0.5*cgs::dimensionless() );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5*cgs::dimensionless(), 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = unit_aware_tab_distribution->sampleInSubrange( 0.5*cgs::dimensionless() );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, -1.0*cgs::dimensionless() );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 0.5*cgs::dimensionless() );

  initializeUnitAwareCDF<InterpolationPolicy>( unit_aware_tab_distribution );

  fake_stream.resize( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 1e-1;
  fake_stream[2] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = unit_aware_tab_distribution->sampleInSubrange( 0.5*cgs::dimensionless()  );
  FRENSIE_CHECK_EQUAL( sample, -1.0*cgs::dimensionless() );

  sample = unit_aware_tab_distribution->sampleInSubrange( 0.5*cgs::dimensionless() );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.0*cgs::dimensionless(), 1e-12 );

  sample = unit_aware_tab_distribution->sampleInSubrange( 0.5*cgs::dimensionless() );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5*cgs::dimensionless(), 1e-12 );

  Utility::RandomNumberGenerator::unsetFakeStream();
  Utility::RandomNumberGenerator::initialize();

  sample = unit_aware_tab_distribution->sampleInSubrange( 0.5*cgs::dimensionless() );
  FRENSIE_CHECK_GREATER_OR_EQUAL( sample, -1.0*cgs::dimensionless() );
  FRENSIE_CHECK_LESS_OR_EQUAL( sample, 0.5*cgs::dimensionless() );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled from a subrange
FRENSIE_UNIT_TEST_TEMPLATE( TabularCDFDistribution,
                            sampleWithRandomNumberInSubrange,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( tab_distribution );

  double sample =
  tab_distribution->sampleWithRandomNumberInSubrange( 0.0, 0.5 );
  FRENSIE_CHECK_EQUAL( sample, -1.0 );

  sample = tab_distribution->sampleWithRandomNumberInSubrange( 1.0, 0.5 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5, 1e-12 );

  initializeCDF<InterpolationPolicy>( tab_distribution );

  sample = tab_distribution->sampleWithRandomNumberInSubrange( 0.0, 0.5 );
  FRENSIE_CHECK_EQUAL( sample, -1.0 );

  sample = tab_distribution->sampleWithRandomNumberInSubrange( 1e-1, 0.5 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.0, 1e-12 );

  sample = tab_distribution->sampleWithRandomNumberInSubrange( 1.0, 0.5 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5, 1e-12 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled from a subrange
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularCDFDistribution,
                            sampleWithRandomNumberInSubrange,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_tab_distribution );

  quantity<cgs::dimensionless> sample =
  unit_aware_tab_distribution->sampleWithRandomNumberInSubrange(
              0.0, 0.5*cgs::dimensionless() );
  FRENSIE_CHECK_EQUAL( sample, -1.0*cgs::dimensionless() );

  sample = unit_aware_tab_distribution->sampleWithRandomNumberInSubrange(
              1.0, 0.5*cgs::dimensionless() );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5*cgs::dimensionless(), 1e-12 );

  initializeUnitAwareCDF<InterpolationPolicy>( unit_aware_tab_distribution );

  sample = unit_aware_tab_distribution->sampleWithRandomNumberInSubrange(
              0.0, 0.5*cgs::dimensionless() );
  FRENSIE_CHECK_EQUAL( sample, -1.0*cgs::dimensionless() );

  sample = unit_aware_tab_distribution->sampleWithRandomNumberInSubrange(
              1e-1, 0.5*cgs::dimensionless() );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.0*cgs::dimensionless(), 1e-12 );

  sample = unit_aware_tab_distribution->sampleWithRandomNumberInSubrange(
              1.0, 0.5*cgs::dimensionless() );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5*cgs::dimensionless(), 1e-12 );
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the distribution independent variable can be
// returned
FRENSIE_UNIT_TEST_TEMPLATE( TabularCDFDistribution,
                            getUpperBoundOfIndepVar,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  FRENSIE_CHECK_EQUAL( distribution->getUpperBoundOfIndepVar(),0.999999 );

  initializeCDF<InterpolationPolicy>( distribution );

  FRENSIE_CHECK_EQUAL( distribution->getUpperBoundOfIndepVar(),0.999999 );
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the unit-aware distribution independent
// variable can be returned
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularCDFDistribution,
                            getUpperBoundOfIndepVar,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getUpperBoundOfIndepVar(),
    0.999999*cgs::dimensionless() );

  initializeUnitAwareCDF<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getUpperBoundOfIndepVar(),
    0.999999*cgs::dimensionless() );
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the distribution independent variable can be
// returned
FRENSIE_UNIT_TEST_TEMPLATE( TabularCDFDistribution,
                            getLowerBoundOfIndepVar,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  FRENSIE_CHECK_EQUAL( distribution->getLowerBoundOfIndepVar(), -1.0 );

  initializeCDF<InterpolationPolicy>( distribution );

  FRENSIE_CHECK_EQUAL( distribution->getLowerBoundOfIndepVar(), -1.0 );
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the unit-aware distribution independent
// variable can be returned
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularCDFDistribution,
                            getLowerBoundOfIndepVar,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getLowerBoundOfIndepVar(),
    -1.0*cgs::dimensionless() );

  initializeUnitAwareCDF<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getLowerBoundOfIndepVar(),
    -1.0*cgs::dimensionless() );
}

//---------------------------------------------------------------------------//
// Check that the distribution type can be returned
FRENSIE_UNIT_TEST_TEMPLATE( TabularCDFDistribution,
                            getDistributionType,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  FRENSIE_CHECK_EQUAL( distribution->getDistributionType(),
    Utility::TABULAR_CDF_DISTRIBUTION );

  initializeCDF<InterpolationPolicy>( distribution );

  FRENSIE_CHECK_EQUAL( distribution->getDistributionType(),
    Utility::TABULAR_CDF_DISTRIBUTION );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution type can be returned
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularCDFDistribution,
                            getDistributionType,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getDistributionType(),
    Utility::TABULAR_CDF_DISTRIBUTION );

  initializeUnitAwareCDF<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getDistributionType(),
    Utility::TABULAR_CDF_DISTRIBUTION );
}

//---------------------------------------------------------------------------//
// Check if the distribution is tabular
FRENSIE_UNIT_TEST_TEMPLATE( TabularCDFDistribution,
                            isTabular,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  FRENSIE_CHECK( distribution->isTabular() );

  initializeCDF<InterpolationPolicy>( distribution );

  FRENSIE_CHECK( distribution->isTabular() );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is tabular
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularCDFDistribution,
                            isTabular,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK( unit_aware_distribution->isTabular() );

  initializeUnitAwareCDF<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK( unit_aware_distribution->isTabular() );
}

//---------------------------------------------------------------------------//
// Check that the distribution is continuous
FRENSIE_UNIT_TEST_TEMPLATE( TabularCDFDistribution,
                            isContinuous,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  FRENSIE_CHECK( distribution->isContinuous() );

  initializeCDF<InterpolationPolicy>( distribution );

  FRENSIE_CHECK( distribution->isContinuous() );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution is continuous
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularCDFDistribution,
                            isContinuous,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK( unit_aware_distribution->isContinuous() );

  initializeUnitAwareCDF<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK( unit_aware_distribution->isContinuous() );
}

//---------------------------------------------------------------------------//
// Check if the distribution is compatible with the interpolation type
FRENSIE_UNIT_TEST_TEMPLATE( TabularCDFDistribution,
                            isCompatibleWithInterpType,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( distribution );

  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LogLogCos >() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLogCos >() );
  FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogCosLog >() );
  FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogCosLin >() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LogNudgedLogCos >() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinNudgedLogCos >() );
  FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::NudgedLogCosLog >() );
  FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::NudgedLogCosLin >() );
  FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogLog>() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLin>() );

  // if( boost::is_same<InterpolationPolicy,Utility::LogLogCos>::value )
  // {
  //   FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LogLogCos>() );
  // }
  // else
  // {
  //   FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogLogCos>() );
  // }

  // if( boost::is_same<InterpolationPolicy,Utility::LinLogCos>::value )
  // {
  //   FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLogCos>() );
  // }
  // else
  // {
  //   FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LinLogCos>() );
  // }

  // FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogCosLog>() );
  // FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogCosLin>() );
  // FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogLog>() );
  // FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  // FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  // FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LinLin>() );

  initializeCDF<InterpolationPolicy>( distribution );

  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LogLogCos >() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLogCos >() );
  FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogCosLog >() );
  FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogCosLin >() );
  FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogLog>() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLin>() );

  // if( boost::is_same<InterpolationPolicy,Utility::LogLogCos>::value )
  // {
  //   FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LogLogCos>() );
  // }
  // else
  // {
  //   FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogLogCos>() );
  // }

  // if( boost::is_same<InterpolationPolicy,Utility::LinLogCos>::value )
  // {
  //   FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLogCos>() );
  // }
  // else
  // {
  //   FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LinLogCos>() );
  // }

  // FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogCosLog>() );
  // FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogCosLin>() );
  // FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogLog>() );
  // FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  // FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  // FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LinLin>() );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is compatible with the interp type
FRENSIE_UNIT_TEST_TEMPLATE( UnitAwareTabularCDFDistribution,
                            isCompatibleWithInterpType,
                            InterpTypes )
{
  FETCH_TEMPLATE_PARAM( 0, InterpolationPolicy );

  initialize<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLogCos >() );
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLogCos >() );
  FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogCosLog >() );
  FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogCosLin >() );
  FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLog>() );
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLin>() );

  // if( boost::is_same<InterpolationPolicy,Utility::LogLogCos>::value )
  // {
  //   FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLogCos>() );
  // }
  // else
  // {
  //   FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLogCos>() );
  // }

  // if( boost::is_same<InterpolationPolicy,Utility::LinLogCos>::value )
  // {
  //   FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLogCos>() );
  // }
  // else
  // {
  //   FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLogCos>() );
  // }

  // FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogCosLog>() );
  // FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogCosLin>() );
  // FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLog>() );
  // FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  // FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  // FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLin>() );

  initializeUnitAwareCDF<InterpolationPolicy>( unit_aware_distribution );

  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLogCos >() );
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLogCos >() );
  FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogCosLog >() );
  FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogCosLin >() );
  FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLog>() );
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLin>() );

  // if( boost::is_same<InterpolationPolicy,Utility::LogLogCos>::value )
  // {
  //   FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLogCos>() );
  // }
  // else
  // {
  //   FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLogCos>() );
  // }

  // if( boost::is_same<InterpolationPolicy,Utility::LinLogCos>::value )
  // {
  //   FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLogCos>() );
  // }
  // else
  // {
  //   FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLogCos>() );
  // }

  // FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogCosLog>() );
  // FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogCosLin>() );
  // FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLog>() );
  // FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  // FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  // FRENSIE_CHECK( !unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLin>() );
}

//---------------------------------------------------------------------------//
// Check that distributions can be scaled
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( UnitAwareTabularCDFDistribution,
                                   explicit_conversion,
                                   TestUnitTypeQuads )
{
  FETCH_TEMPLATE_PARAM( 0, RawIndepUnitA );
  FETCH_TEMPLATE_PARAM( 1, RawDepUnitA );
  FETCH_TEMPLATE_PARAM( 2, RawIndepUnitB );
  FETCH_TEMPLATE_PARAM( 3, RawDepUnitB );

  typedef typename std::remove_pointer<RawIndepUnitA>::type IndepUnitA;
  typedef typename std::remove_pointer<RawDepUnitA>::type DepUnitA;
  typedef typename std::remove_pointer<RawIndepUnitB>::type IndepUnitB;
  typedef typename std::remove_pointer<RawDepUnitB>::type DepUnitB;

  typedef typename Utility::UnitTraits<IndepUnitA>::template GetQuantityType<double>::type IndepQuantityA;

  typedef typename Utility::UnitTraits<IndepUnitB>::template GetQuantityType<double>::type IndepQuantityB;

  typedef typename Utility::UnitTraits<DepUnitA>::template GetQuantityType<double>::type DepQuantityA;
  typedef typename Utility::UnitTraits<DepUnitB>::template GetQuantityType<double>::type DepQuantityB;

  initializeCDF<Utility::LogLogCos >( distribution );

  // Copy from unitless distribution to distribution type A
  Utility::UnitAwareTabularCDFDistribution<Utility::LogLogCos,IndepUnitA,DepUnitA>
    unit_aware_dist_a_copy = Utility::UnitAwareTabularCDFDistribution<Utility::LogLogCos,IndepUnitA,DepUnitA>::fromUnitlessDistribution( *dynamic_cast<Utility::TabularCDFDistribution<Utility::LogLogCos >*>( distribution.get() ) );

  // Copy from distribution type A to distribution type B
  Utility::UnitAwareTabularCDFDistribution<Utility::LogLogCos,IndepUnitB,DepUnitB>
  unit_aware_dist_b_copy( unit_aware_dist_a_copy );

  IndepQuantityA indep_quantity_a =
  Utility::QuantityTraits<IndepQuantityA>::initializeQuantity( 0.0 );
  double cdf_quantity_a =
  Utility::QuantityTraits<double>::initializeQuantity( 1e-3 );
  DepQuantityA dep_quantity_a =
  Utility::QuantityTraits<DepQuantityA>::initializeQuantity( 0.0 );

  IndepQuantityB indep_quantity_b( indep_quantity_a );
  double cdf_quantity_b( cdf_quantity_a );
  DepQuantityB dep_quantity_b( dep_quantity_a );

  FRENSIE_CHECK_FLOATING_EQUALITY(
      unit_aware_dist_a_copy.evaluate( indep_quantity_a ),
      dep_quantity_a,
      1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
      unit_aware_dist_a_copy.evaluateCDF( indep_quantity_a ),
      cdf_quantity_a,
      1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
      unit_aware_dist_b_copy.evaluate( indep_quantity_b ),
      dep_quantity_b,
      1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
      unit_aware_dist_b_copy.evaluateCDF( indep_quantity_b ),
      cdf_quantity_b,
      1e-15 );

  Utility::setQuantity( indep_quantity_a, 0.5 );
  Utility::setQuantity( cdf_quantity_a, 1e-2 );
  Utility::setQuantity( dep_quantity_a, 0.0 );

  indep_quantity_b = IndepQuantityB( indep_quantity_a );
  cdf_quantity_b = cdf_quantity_a;
  dep_quantity_b = DepQuantityB( dep_quantity_a );

  FRENSIE_CHECK_FLOATING_EQUALITY(
      unit_aware_dist_a_copy.evaluate( indep_quantity_a ),
      dep_quantity_a,
      1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
      unit_aware_dist_a_copy.evaluateCDF( indep_quantity_a ),
      cdf_quantity_a,
      1e-6 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
      unit_aware_dist_b_copy.evaluate( indep_quantity_b ),
      dep_quantity_b,
      1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
      unit_aware_dist_b_copy.evaluateCDF( indep_quantity_b ),
      cdf_quantity_b,
      1e-6 );
}

//---------------------------------------------------------------------------//
// Check that a distribution can be archived
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( TabularCDFDistribution,
                                   archive,
                                   TestArchives )
{
  FETCH_TEMPLATE_PARAM( 0, RawOArchive );
  FETCH_TEMPLATE_PARAM( 1, RawIArchive );

  typedef typename std::remove_pointer<RawOArchive>::type OArchive;
  typedef typename std::remove_pointer<RawIArchive>::type IArchive;

  std::string archive_base_name( "test_tabular_cdf_dist_cosine" );
  std::ostringstream archive_ostream;

  // Create and archive some uniform distributions
  {
    std::unique_ptr<OArchive> oarchive;

    createOArchive( archive_base_name, archive_ostream, oarchive );

    std::shared_ptr<Utility::UnivariateDistribution> base_dist_a;

    initialize<Utility::LogLogCos >( base_dist_a );

    std::shared_ptr<Utility::UnivariateDistribution> base_dist_b;

    initialize<Utility::LinLogCos >( base_dist_b );

    FRENSIE_REQUIRE_NO_THROW( (*oarchive) << BOOST_SERIALIZATION_NVP( base_dist_a ) );
    FRENSIE_REQUIRE_NO_THROW( (*oarchive) << BOOST_SERIALIZATION_NVP( base_dist_b ) );
  }

  // Copy the archive ostream to an istream
  std::istringstream archive_istream( archive_ostream.str() );

  // Load the archived distributions
  std::unique_ptr<IArchive> iarchive;

  createIArchive( archive_istream, iarchive );

  std::shared_ptr<Utility::UnivariateDistribution> base_dist_a;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> BOOST_SERIALIZATION_NVP( base_dist_a ) );

  Utility::TabularUnivariateDistribution* downcast_base_dist =
    dynamic_cast<Utility::TabularUnivariateDistribution*>( base_dist_a.get() );

  FRENSIE_REQUIRE( downcast_base_dist != NULL );

  FRENSIE_CHECK_EQUAL( downcast_base_dist->evaluateCDF( -1.001 ), 0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( downcast_base_dist->evaluateCDF( -1.0 ), 0.0, 1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( downcast_base_dist->evaluateCDF( 0.0 ), norm_const, 1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( downcast_base_dist->evaluateCDF( 0.5 ), 1.5*norm_const, 1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( downcast_base_dist->evaluateCDF( 1.0 ), 1.0, 1e-10 );
  FRENSIE_CHECK_EQUAL( downcast_base_dist->evaluateCDF( 1.001 ), 1.0 );

  std::shared_ptr<Utility::UnivariateDistribution> base_dist_b;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> BOOST_SERIALIZATION_NVP( base_dist_b ) );

  downcast_base_dist =
    dynamic_cast<Utility::TabularUnivariateDistribution*>( base_dist_b.get() );

  FRENSIE_REQUIRE( downcast_base_dist != NULL );

  FRENSIE_CHECK_EQUAL( downcast_base_dist->evaluateCDF( -1.001 ), 0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( downcast_base_dist->evaluateCDF( -1.0 ), 0.0, 1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( downcast_base_dist->evaluateCDF( 0.0 ), norm_const, 1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( downcast_base_dist->evaluateCDF( 0.5 ), 1.5*norm_const, 1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( downcast_base_dist->evaluateCDF( 1.0 ), 1.0, 1e-10 );
  FRENSIE_CHECK_EQUAL( downcast_base_dist->evaluateCDF( 1.001 ), 1.0 );
}

//---------------------------------------------------------------------------//
// Check that a distribution can be archived
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( UnitAwareTabularCDFDistribution,
                                   archive,
                                   TestArchives )
{
  FETCH_TEMPLATE_PARAM( 0, RawOArchive );
  FETCH_TEMPLATE_PARAM( 1, RawIArchive );

  typedef typename std::remove_pointer<RawOArchive>::type OArchive;
  typedef typename std::remove_pointer<RawIArchive>::type IArchive;

  std::string archive_base_name( "unit_aware_test_tabular_cdf_dist_cosine" );
  std::ostringstream archive_ostream;

  // Create and archive some uniform distributions
  {
    std::unique_ptr<OArchive> oarchive;

    createOArchive( archive_base_name, archive_ostream, oarchive );

    std::shared_ptr<Utility::UnitAwareUnivariateDistribution<void,si::amount> > base_dist_a;

    initialize<Utility::LogLogCos >( base_dist_a );

    std::shared_ptr<Utility::UnitAwareUnivariateDistribution<void,si::amount> > base_dist_b;

    initialize<Utility::LinLogCos >( base_dist_b );

    FRENSIE_REQUIRE_NO_THROW( (*oarchive) << BOOST_SERIALIZATION_NVP( base_dist_a ) );
    FRENSIE_REQUIRE_NO_THROW( (*oarchive) << BOOST_SERIALIZATION_NVP( base_dist_b ) );
  }

  // Copy the archive ostream to an istream
  std::istringstream archive_istream( archive_ostream.str() );

  std::cout << archive_ostream.str() << std::endl;

  // Load the archived distributions
  std::unique_ptr<IArchive> iarchive;

  createIArchive( archive_istream, iarchive );

  std::shared_ptr<Utility::UnitAwareUnivariateDistribution<void,si::amount> > base_dist_a;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> BOOST_SERIALIZATION_NVP( base_dist_a ) );

  Utility::UnitAwareTabularUnivariateDistribution<void,si::amount>* downcast_base_dist =
    dynamic_cast<Utility::UnitAwareTabularUnivariateDistribution<void,si::amount>*>( base_dist_a.get() );

  FRENSIE_REQUIRE( downcast_base_dist != NULL );

  FRENSIE_CHECK_EQUAL( downcast_base_dist->evaluateCDF( -1.001*cgs::dimensionless() ),
                       0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( downcast_base_dist->evaluateCDF( -1.0*cgs::dimensionless() ),
                          0.0,
                          1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( downcast_base_dist->evaluateCDF( 0.0*cgs::dimensionless() ),
                          norm_const,
                          1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( downcast_base_dist->evaluateCDF( 0.5*cgs::dimensionless() ),
                          1.5*norm_const,
                          1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( downcast_base_dist->evaluateCDF( 0.999999*cgs::dimensionless() ),
                          1.0,
                          1e-10 );
  FRENSIE_CHECK_EQUAL( downcast_base_dist->evaluateCDF( 1.001*cgs::dimensionless() ),
                       1.0 );

  std::shared_ptr<Utility::UnitAwareUnivariateDistribution<void,si::amount> > base_dist_b;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> BOOST_SERIALIZATION_NVP( base_dist_b ) );

  downcast_base_dist =
    dynamic_cast<Utility::UnitAwareTabularUnivariateDistribution<void,si::amount>*>( base_dist_b.get() );

  FRENSIE_REQUIRE( downcast_base_dist != NULL );

  FRENSIE_CHECK_EQUAL( downcast_base_dist->evaluateCDF( -1.001*cgs::dimensionless() ),
                       0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( downcast_base_dist->evaluateCDF( -1.0*cgs::dimensionless() ),
                          0.0,
                          1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( downcast_base_dist->evaluateCDF( 0.0*cgs::dimensionless() ),
                          norm_const,
                          1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( downcast_base_dist->evaluateCDF( 0.5*cgs::dimensionless() ),
                          1.5*norm_const,
                          1e-10 );
  FRENSIE_CHECK_FLOATING_EQUALITY( downcast_base_dist->evaluateCDF( 0.999999*cgs::dimensionless() ),
                          1.0,
                          1e-10 );
  FRENSIE_CHECK_EQUAL( downcast_base_dist->evaluateCDF( 1.001*cgs::dimensionless() ),
                       1.0 );
}

//---------------------------------------------------------------------------//
// Custom setup
//---------------------------------------------------------------------------//
FRENSIE_CUSTOM_UNIT_TEST_SETUP_BEGIN();

FRENSIE_CUSTOM_UNIT_TEST_INIT()
{
  // Initialize the random number generator
  Utility::RandomNumberGenerator::createStreams();
}

FRENSIE_CUSTOM_UNIT_TEST_SETUP_END();

//---------------------------------------------------------------------------//
// end tstTabularCDFDistribution.cpp
//---------------------------------------------------------------------------//


// Copyright (c) 2019 Christopher A. Taylor.  All rights reserved.

/*
    From a set of point clouds, find a transform that best fits them together,
    producing the extrinsics for the depth cameras that generated the clouds.
*/

#pragma once

#include <DepthMesh.hpp> // depth_mesh
#include <Eigen/Eigen>

#include <cstdint>
#include <vector>

namespace core {

//------------------------------------------------------------------------------
// Registration

struct VerticesInfo
{
    // Vertices for mesh represented as repeated: x,y,z,u,v
    uint32_t FloatsCount = 0;
    float* XyzuvVertices = nullptr;

    // Accelerometer reading for extrinsics calibration
    float Accelerometer[3];

    CameraCalibration* Calibration = nullptr;

    // Image format is NV12, which is two channels.
    // Size of image and Y channel
    int32_t Width = 0, Height = 0;
    uint8_t* Y = nullptr;

    // Size of U, V channels
    int32_t ChromaWidth = 0, ChromaHeight = 0;
    uint8_t* UV = nullptr;
};

// Transform from this camera to the scene
struct AlignmentTransform
{
    float Transform[16];
    bool Identity = true;

    inline void operator=(const Eigen::Matrix4f& src)
    {
        Identity = src.isIdentity();
        for (int row = 0; row < 4; ++row) {
            for (int col = 0; col < 4; ++col) {
                Transform[row * 4 + col] = src(row, col);
            }
        }
    }
    inline void Set(Eigen::Matrix4f& dest) const
    {
        if (Identity) {
            dest = Eigen::Matrix4f::Identity();
        } else {
            for (int row = 0; row < 4; ++row) {
                for (int col = 0; col < 4; ++col) {
                    dest(row, col) = Transform[row * 4 + col];
                }
            }
        }
    }
};

// Returns result in `extrinsics`.
// Returns false if registration was not possible.  Try adding more features to the scene.
bool CalculateExtrinsics(
    const std::vector<VerticesInfo>& vertices,
    std::vector<AlignmentTransform>& extrinsics);

// Requires previous extrinsics otherwise it will fail.
// If successful it will update the extrinsics with the new results.
// Returns false if registration was not possible.  Try adding more features to the scene.
bool RefineExtrinsics(
    const std::vector<VerticesInfo>& vertices,
    std::vector<AlignmentTransform>& extrinsics);

} // namespace core