#include <igl/per_vertex_normals.h>
#include <igl/principal_curvature.h>
#include <igl/avg_edge_length.h>
#include <igl/massmatrix.h>
#include <igl/adjacency_list.h>
#include <igl/per_face_normals.h>
#include <igl/barycenter.h>
#include <igl/pinv.h>
#include <igl/edges.h>
#include <Eigen/SparseCore>
#include <igl/adjacency_list.h>
#include <igl/adjacency_matrix.h>
#include <igl/per_face_normals.h>
#include <igl/per_vertex_normals.h>
#include <igl/avg_edge_length.h>
#include <igl/edge_flaps.h>
#include <igl/unique_edge_map.h>
#include <igl/vertex_triangle_adjacency.h>
#include <igl/principal_curvature.h>
#include <igl/collapse_edge.h>
#include <igl/C_STR.h>
#include <igl/circulation.h>
#include <igl/decimate.h>
#include <igl/shortest_edge_and_midpoint.h>
#include <igl/infinite_cost_stopping_condition.h>
using namespace std;

void split_edges(Eigen::MatrixXd & V, Eigen::MatrixXi & F, Eigen::MatrixXi & E0, Eigen::MatrixXi & uE, Eigen::VectorXi & EMAP0, std::vector<std::vector<int>> & uE2E,Eigen::VectorXd & high, Eigen::VectorXd & low,const std::vector<int> & edges_to_split){
    using namespace Eigen;

    Eigen::VectorXi EMAP;
    Eigen::MatrixXi E;

    // These are the sizes *before* the splits.
    const int n = V.rows();
    const int m = F.rows();
    const int r = uE2E.size();
    const int k = uE.rows();
    const int num_edges_to_split = edges_to_split.size();
    // Used for assignments later

    // I don't really know how to get rid of this :'(
    for (int j = 0; j<r; j++) {
        int add0 = 0;
        if(uE2E[j][0]>=m){
            add0 = 2*num_edges_to_split;
        }
        if(uE2E[j][0]>=(2*m)){
            add0 = 4*num_edges_to_split;
        }
        uE2E[j][0] = uE2E[j][0]+add0;
        int add1 = 0;
        if(uE2E[j][1]>=m){
            add1 = 2*num_edges_to_split;
        }
        if(uE2E[j][1]>=(2*m)){
            add1 = 4*num_edges_to_split;
        }
        uE2E[j][1] = uE2E[j][1]+add1;
    }
    // I guess it only adds more linear time...

    // These are the sizes *after* the splits.
    int num_faces = m+2*num_edges_to_split;
    int num_vertices = n+num_edges_to_split;
    int num_uE = k+3*num_edges_to_split;
    int num_uE2E = r+3*num_edges_to_split;
    int num_E = E0.rows()+6*num_edges_to_split;
    int num_EMAP = EMAP0.size()+6*num_edges_to_split;
    // Used for indexing and labeling.

    F.conservativeResize(num_faces,3);
    V.conservativeResize(num_vertices,3);
    high.conservativeResize(num_vertices);
    low.conservativeResize(num_vertices);
    uE.conservativeResize(num_uE,2);
    std::vector<int> val;
    val.push_back(0);
    val.push_back(0);
    uE2E.resize(num_uE2E,val);

    // INITIALIZE E and EMAP!!!!!

    E.resize(num_E,2);
    E.setZero();
    EMAP.resize(num_EMAP);
    EMAP.setZero();
    //std::cout << "debug" << std::endl;
    E.block(0,0,m,2) = E0.block(0,0,m,2);
    // std::cout << "debug" << std::endl;
    E.block(num_faces,0,m,2) = E0.block(m,0,m,2);
    // std::cout << "debug" << std::endl;
    E.block(2*num_faces,0,m,2) = E0.block(2*m,0,m,2);
    // std::cout << "debug" << std::endl;
    EMAP.segment(0,m) = EMAP0.segment(0,m);
    // std::cout << "debug" << std::endl;
    EMAP.segment(num_faces,m) = EMAP0.segment(m,m);
    // std::cout << "debug" << std::endl;
    EMAP.segment(2*num_faces,m) = EMAP0.segment(2*m,m);

    for (int i = 0; i<num_edges_to_split; i++) {

        int uei = edges_to_split[i];
        assert(uE2E[uei].size()==2);
        //          v1
        //          /|\
        //         / | \
        //     v3 /f1|f0\ v4
        //        \  |  /
        //         \ | /
        //          \|/
        //          v2

        int e0 = uE2E[uei][0];
        int e1 = uE2E[uei][1];

        int f0 = e0 % num_faces;
        int f1 = e1 % num_faces;
        int c0 = e0 / num_faces;
        int c1 = e1 / num_faces;

        // Order of edges: anti-clockwise starting at v1
        int e2 = f1+((c1+1)%3)*num_faces;
        int e3 = f1+((c1+2)%3)*num_faces;
        int e4 = f0+((c0+1)%3)*num_faces;
        int e5 = f0+((c0+2)%3)*num_faces;

        int ue2 = EMAP(e2);
        int ue3 = EMAP(e3);
        int ue4 = EMAP(e4);
        int ue5 = EMAP(e5);
//        std::cout << EMAP(e0) << std::endl;
//        std::cout << EMAP(e1) << std::endl;
//        std::cout << uei << std::endl;
        assert(EMAP(e0)==uei);
        assert(EMAP(e1)==uei);

        int v1 = F(f0, (c0+1)%3);
        int v2 = F(f0, (c0+2)%3);
        int v4 = F(f0, c0);
        int v3 = F(f1, c1);

        // Assertions for debugging
        // I think I can actually just "imagine" E existing? Actually no.
//        std::cout << "break" << std::endl;
//        std::cout << uei << std::endl;
//        std::cout << e2 << std::endl;
//        std::cout << v1 << std::endl;
//        std::cout << E(e2,0) << std::endl;
//        std::cout << E(e2,1) << std::endl;
        assert(E(e2,0)==v1);
        assert(E(e2,1)==v3);
        assert(E(e3,0)==v3);
        assert(E(e3,1)==v2);
        assert(E(e4,0)==v2);
        assert(E(e4,1)==v4);
        assert(E(e5,0)==v4);
        assert(E(e5,1)==v1);
        assert(E(e0,0)==v1);
        assert(E(e0,1)==v2);
        assert(E(e1,0)==v2);
        assert(E(e1,1)==v1);

        assert(c0 < 3);
        assert(c1 < 3);

        assert(f0 != f1);

        // *** UPDATE V ***

        // naïve: use mid-point
        V.row(n+i) = (V.row(v1)+V.row(v2))/2;
        high(n+i) = (high(v1)+high(v2))/2;
        low(n+i) = (low(v1)+low(v2))/2;
        // *** UPDATE F ***

        // f0

        F(f0,c0) = v4; // redundant
        F(f0,(c0+1)%3) = v1;
        F(f0,(c0+2)%3) = n+i; // new vertex
        // f1
        F(f1,c1) = v3; // redundant
        F(f1,(c1+1)%3) = n+i;
        F(f1,(c1+2)%3) = v1;
        // fm
        F(m+(2*i),0) = v2;
        F(m+(2*i),1) = n+i;
        F(m+(2*i),2) = v3;
        // fm+1
        F(m+(2*i)+1,0) = v2;
        F(m+(2*i)+1,1) = v4;
        F(m+(2*i)+1,2) = n+i;

        // *** UPDATE UE ***

        // uei
        uE(uei,0) = n+i;
        uE(uei,1) = v1;
        // k
        uE(k+(3*i),0) = n+i;
        uE(k+(3*i),1) = v3;
        // k+1
        uE(k+(3*i)+1,0) = n+i;
        uE(k+(3*i)+1,1) = v4;
        // k+2
        uE(k+(3*i)+2,0) = n+i;
        uE(k+(3*i)+2,1) = v2;

        // *** UPDATE uE2E ***
//        for (int j = 0; j<uE2E.size(); j++) {
//        std::cout << uE2E[j][0] << std::endl;
//        std::cout << uE2E[j][1] << std::endl;
//        }
        // uei
        uE2E[uei][0] = e0;
        uE2E[uei][1] = e1;
        // k

        uE2E[k+(3*i)][0] = f1 + ((c1+2)%3)*num_faces;
        uE2E[k+(3*i)][1] = m+(2*i) + 0*num_faces;

        // k+1
        uE2E[k+(3*i)+1][0] = f0 + ((c0+1)%3)*num_faces;
        uE2E[k+(3*i)+1][1] = m+(2*i)+1 + 0*num_faces;
        // k+2
        uE2E[k+(3*i)+2][0] = m+(2*i) + 2*num_faces;
        uE2E[k+(3*i)+2][1] = m+(2*i)+1 + 1*num_faces;
        // ue2

        if(uE2E[ue2][0]==e2){
            uE2E[ue2][0] = f1 + ((c1+1)%3)*num_faces;
        }else{
            assert(uE2E[ue2][1]==e2);
            uE2E[ue2][1] = f1 + ((c1+1)%3)*num_faces;
        }
        // ue3
        if(uE2E[ue3][0]==e3){
            uE2E[ue3][0] = m+(2*i) + 1*num_faces;
        }else{
            assert(uE2E[ue3][1]==e3);
            uE2E[ue3][1] = m+(2*i) + 1*num_faces;
        }
        // ue4
        if(uE2E[ue4][0]==e4){
            uE2E[ue4][0] = m+(2*i)+1 + 2*num_faces;
        }else{
            assert(uE2E[ue4][1]==e4);
            uE2E[ue4][1] = m+(2*i)+1 + 2*num_faces;
        }
        // ue5
        if(uE2E[ue5][0]==e5){
            uE2E[ue5][0] = f0 + ((c0+2)%3)*num_faces;
        }else{
            assert(uE2E[ue5][1]==e5);
            uE2E[ue5][1] = f0 + ((c0+2)%3)*num_faces;
        }
        // *** UPDATE E ***
        // edges in f0

        E(f0 + ((c0)%3)*num_faces,0) = v1;
        E(f0 + ((c0)%3)*num_faces,1) = n+i;
        E(f0 + ((c0+1)%3)*num_faces,0) = n+i;
        E(f0 + ((c0+1)%3)*num_faces,1) = v4;
        E(f0 + ((c0+2)%3)*num_faces,0) = v4;
        E(f0 + ((c0+2)%3)*num_faces,1) = v1;
        // edges in f1
        E(f1 + ((c1)%3)*num_faces,0) = n+i;
        E(f1 + ((c1)%3)*num_faces,1) = v1;
        E(f1 + ((c1+1)%3)*num_faces,0) = v1;
        E(f1 + ((c1+1)%3)*num_faces,1) = v3;
        E(f1 + ((c1+2)%3)*num_faces,0) = v3;
        E(f1 + ((c1+2)%3)*num_faces,1) = n+i;
        // edges in fm
        E(m+(2*i) + 0*num_faces,0) = n+i;
        E(m+(2*i) + 0*num_faces,1) = v3;
        E(m+(2*i) + 1*num_faces,0) = v3;
        E(m+(2*i) + 1*num_faces,1) = v2;
        E(m+(2*i) + 2*num_faces,0) = v2;
        E(m+(2*i) + 2*num_faces,1) = n+i;
        // edges in fm+1
        E(m+(2*i)+1 + 0*num_faces,0) = v4;
        E(m+(2*i)+1 + 0*num_faces,1) = n+i;
        E(m+(2*i)+1 + 1*num_faces,0) = n+i;
        E(m+(2*i)+1 + 1*num_faces,1) = v2;
        E(m+(2*i)+1 + 2*num_faces,0) = v2;
        E(m+(2*i)+1 + 2*num_faces,1) = v4;

        // *** UPDATE EMAP ***
        // edges in f0
        EMAP(f0 + ((c0)%3)*num_faces) = uei;
        EMAP(f0 + ((c0+1)%3)*num_faces) = k+(3*i)+1;
        EMAP(f0 + ((c0+2)%3)*num_faces) = ue5;
        // edges in f1
        EMAP(f1 + ((c1)%3)*num_faces) = uei;
        EMAP(f1 + ((c1+1)%3)*num_faces) = ue2;
        EMAP(f1 + ((c1+2)%3)*num_faces) = k+(3*i);
        // edges in fm
        EMAP(m+(2*i) + 0*num_faces) = k+(3*i);
        EMAP(m+(2*i) + 1*num_faces) = ue3;
        EMAP(m+(2*i) + 2*num_faces) = k+(3*i)+2;
        // edges in fm+1
        EMAP(m+(2*i)+1 + 0*num_faces) = k+(3*i)+1;
        EMAP(m+(2*i)+1 + 1*num_faces) = k+(3*i)+2;
        EMAP(m+(2*i)+1 + 2*num_faces) = ue4;

    }

    E0 = E;
    EMAP0 = EMAP;
}

// g++ -I/usr/local/libigl/external/eigen -I/usr/local/libigl/include -std=c++11 -framework Accelerate main.cpp remesh_botsch.cpp -o main


// Copyright (c) 2021 CNES
//
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#pragma once
#include <gsl/gsl_interp.h>

#include <Eigen/Core>

#include "pyinterp/detail/gsl/interpolate1d.hpp"
#include "pyinterp/detail/math/frame.hpp"

namespace pyinterp::detail::math {

/// Spline gridded 2D interpolation
class Spline2D {
 public:
  /// Default constructor
  ///
  /// @param xr Calculation window.
  /// @param type method of calculation
  explicit Spline2D(const Frame2D &xr, const std::string &kind)
      : column_(xr.y()->size()),
        x_interpolator_(xr.x()->size(), Spline2D::parse_interp_type(kind),
                        gsl::Accelerator()),
        y_interpolator_(xr.y()->size(), Spline2D::parse_interp_type(kind),
                        gsl::Accelerator()) {}

  /// Return the interpolated value of y for a given point x
  auto interpolate(const double x, const double y, const Frame2D &xr)
      -> double {
    return evaluate(&gsl::Interpolate1D::interpolate, x, y, xr);
  }

  /// Return the derivative for a given point x
  auto derivative(const double x, const double y, const Frame2D &xr) -> double {
    return evaluate(&gsl::Interpolate1D::derivative, x, y, xr);
  }

  /// Return the second derivative for a given point x
  auto second_derivative(const double x, const double y, const Frame2D &xr)
      -> double {
    return evaluate(&gsl::Interpolate1D::second_derivative, x, y, xr);
  }

 private:
  using InterpolateFunction = double (gsl::Interpolate1D::*)(
      const Eigen::VectorXd &, const Eigen::VectorXd &, const double);
  /// Column of the interpolation window (interpolation according to Y
  /// coordinates)
  Eigen::VectorXd column_;

  /// GSL interpolators
  gsl::Interpolate1D x_interpolator_;
  gsl::Interpolate1D y_interpolator_;

  /// Evaluation of the GSL function performing the calculation.
  auto evaluate(
      const std::function<double(gsl::Interpolate1D &, const Eigen::VectorXd &,
                                 const Eigen::VectorXd &, const double)>
          &function,
      const double x, const double y, const Frame2D &xr) -> double {
    // Spline interpolation as function of X-coordinate
    for (Eigen::Index ix = 0; ix < xr.y()->size(); ++ix) {
      column_(ix) = function(x_interpolator_, *(xr.x()), xr.q()->col(ix), x);
    }
    return function(y_interpolator_, *(xr.y()), column_, y);
  }

  static inline auto parse_interp_type(const std::string &kind)
      -> const gsl_interp_type * {
    if (kind == "linear") {
      return gsl_interp_linear;
    }
    if (kind == "polynomial") {
      return gsl_interp_polynomial;
    }
    if (kind == "c_spline") {
      return gsl_interp_cspline;
    }
    if (kind == "c_spline_periodic") {
      return gsl_interp_cspline_periodic;
    }
    if (kind == "akima") {
      return gsl_interp_akima;
    }
    if (kind == "akima_periodic") {
      return gsl_interp_akima_periodic;
    }
    if (kind == "steffen") {
      return gsl_interp_steffen;
    }
    throw std::invalid_argument("Invalid spline type: " + kind);
  }
};

}  // namespace pyinterp::detail::math


#define BOOST_TEST_MODULE "test_contact_interaction"

#ifdef BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
#else
#include <boost/test/included/unit_test.hpp>
#endif

#include <mjolnir/core/BoundaryCondition.hpp>
#include <mjolnir/core/SimulatorTraits.hpp>
#include <mjolnir/forcefield/local/ContactInteraction.hpp>
#include <mjolnir/forcefield/local/GoContactPotential.hpp>
#include <mjolnir/util/make_unique.hpp>

BOOST_AUTO_TEST_CASE(Contact_calc_force)
{
    mjolnir::LoggerManager::set_default_logger("test_contact_calc_force");

    using traits_type      = mjolnir::SimulatorTraits<double, mjolnir::UnlimitedBoundary>;
    using real_type        = traits_type::real_type;
    using coord_type       = traits_type::coordinate_type;
    using boundary_type    = traits_type::boundary_type;
    using system_type      = mjolnir::System<traits_type>;
    using potential_type   = mjolnir::GoContactPotential<real_type>;
    using interaction_type = mjolnir::ContactInteraction<traits_type, potential_type>;

    constexpr real_type tol = 1e-8;

    auto normalize = [](const coord_type& v){return v / mjolnir::math::length(v);};

    const real_type k(100.);
    const real_type native(2.0);

    potential_type   potential(k, native);
    interaction_type interaction("none", {{ {{0,1}}, potential}});

    system_type sys(2, boundary_type{});

    sys.at(0).mass = 1.0;
    sys.at(1).mass = 1.0;
    sys.at(0).rmass = 1.0;
    sys.at(1).rmass = 1.0;

    sys.at(0).position = coord_type(0,0,0);
    sys.at(1).position = coord_type(0,0,0);
    sys.at(0).velocity = coord_type(0,0,0);
    sys.at(1).velocity = coord_type(0,0,0);
    sys.at(0).force    = coord_type(0,0,0);
    sys.at(1).force    = coord_type(0,0,0);

    sys.at(0).name  = "X";
    sys.at(1).name  = "X";
    sys.at(0).group = "NONE";
    sys.at(1).group = "NONE";

    const real_type dr = 1e-3;
    real_type dist = 1e0;
    sys[1].position[0] = dist;

    interaction.initialize(sys);

    for(int i = 0; i < 2000; ++i)
    {
        sys[0].position = coord_type(0,0,0);
        sys[1].position = coord_type(0,0,0);
        sys[0].force    = coord_type(0,0,0);
        sys[1].force    = coord_type(0,0,0);
        sys[1].position[0] = dist;

        interaction.reduce_margin(dr, sys);

        const real_type deriv = potential.derivative(dist);
        const real_type coef  = std::abs(deriv);

        interaction.calc_force(sys);

        const real_type force_strength1 = mjolnir::math::length(sys.force(0));
        const real_type force_strength2 = mjolnir::math::length(sys.force(1));
        BOOST_TEST(coef == force_strength1, boost::test_tools::tolerance(tol));
        BOOST_TEST(coef == force_strength2, boost::test_tools::tolerance(tol));

        // direction
        if(dist < native) // repulsive
        {
            const real_type dir1 = mjolnir::math::dot_product(
                normalize(sys[0].force), normalize(sys[0].position - sys[1].position));
            const real_type dir2 = mjolnir::math::dot_product(
                normalize(sys[1].force), normalize(sys[1].position - sys[0].position));

            BOOST_TEST(dir1 == 1.0, boost::test_tools::tolerance(tol));
            BOOST_TEST(dir2 == 1.0, boost::test_tools::tolerance(tol));
        }
        else
        {
            const real_type dir1 = mjolnir::math::dot_product(
                normalize(sys[0].force), normalize(sys[1].position - sys[0].position));
            const real_type dir2 = mjolnir::math::dot_product(
                normalize(sys[1].force), normalize(sys[0].position - sys[1].position));

            BOOST_TEST(dir1 == 1e0, boost::test_tools::tolerance(tol));
            BOOST_TEST(dir2 == 1e0, boost::test_tools::tolerance(tol));
        }
        BOOST_TEST(mjolnir::math::length(sys[0].force + sys[1].force) == 0.0,
                   boost::test_tools::tolerance(tol));

        dist += dr;
    }
}


#include <iostream>
#include <mutex>
#include <boost/thread/thread.hpp>
#include <boost/thread/tss.hpp>
#include <chrono>
#include <atomic>
#include <iomanip>

#define NTHREADS 16

using namespace std;

template <typename V=int> class counter {
private:
  atomic<V> gcount;
  mutex m;
  boost::thread_specific_ptr<V> lcount;
  boost::thread_specific_ptr<V> lgcount;

public:

 void init(){ //instatiate thread local object parts
    lcount.reset(new V(0));
    lgcount.reset(new V(0));
  }

  void weakInc(V tosum={1}){
    *lcount += tosum;
  }

  void weakDec(V tosum={1}){
    *lcount -= tosum;
  }

  V weakValue(){
    V readed = *lgcount + *lcount;
    return readed;
  }

  void strongInc(V tosum={1}){
     gcount.fetch_add(tosum);
  }

  void strongDec(V tosub={1}){
    gcount.fetch_sub(tosub);
  }

  void merge(){ //mergeToGlobal
    gcount.fetch_add(*lcount); //merging thread local state with global state
    *lgcount = gcount;  // updating the snapshot
    *lcount = 0; //resetting thread local state
  }

  V globalValue(){
    V globalValue = gcount;
    return globalValue;
  }

};

counter<int> mdt;

void work(){

  mdt.init();

  mdt.weakInc();
  mdt.weakDec();
  mdt.merge();
  mdt.strongInc();
  mdt.strongDec();

}

int main(){

  boost::thread_group threads;
  for (int a=0; a < NTHREADS; a++){
    threads.create_thread(&work);
  }

  // Work baby threads, work ...
  threads.join_all();

  cout << mdt.globalValue() << endl;

  return 0;
}


//  (C) Copyright Matt Borland 2021.
//  (C) Copyright John Maddock 2021.
//  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_CCMATH_LDEXP_HPP
#define BOOST_MATH_CCMATH_LDEXP_HPP

#include <cmath>
#include <limits>
#include <type_traits>
#include <stdexcept>
#include <boost/math/tools/is_constant_evaluated.hpp>
#include <boost/math/ccmath/abs.hpp>
#include <boost/math/ccmath/isinf.hpp>
#include <boost/math/ccmath/isnan.hpp>

namespace boost::math::ccmath {

namespace detail {

template <typename Real>
inline constexpr Real ldexp_impl(Real arg, int exp) noexcept
{
    while(exp > 0)
    {
        arg *= 2;
        --exp;
    }
    while(exp < 0)
    {
        arg /= 2;
        ++exp;
    }

    return arg;
}

} // Namespace detail

template <typename Real, std::enable_if_t<!std::is_integral_v<Real>, bool> = true>
inline constexpr Real ldexp(Real arg, int exp) noexcept
{
    if(BOOST_MATH_IS_CONSTANT_EVALUATED(arg))
    {
        return boost::math::ccmath::abs(arg) == Real(0) ? arg :
               boost::math::ccmath::isinf(arg) ? arg :
               boost::math::ccmath::isnan(arg) ? arg :
               boost::math::ccmath::detail::ldexp_impl(arg, exp);
    }
    else
    {
        using std::ldexp;
        return ldexp(arg, exp);
    }
}

template <typename Z, std::enable_if_t<std::is_integral_v<Z>, bool> = true>
inline constexpr double ldexp(Z arg, int exp) noexcept
{
    return boost::math::ccmath::ldexp(static_cast<double>(arg), exp);
}

inline constexpr float ldexpf(float arg, int exp) noexcept
{
    return boost::math::ccmath::ldexp(arg, exp);
}

#ifndef BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS
inline constexpr long double ldexpl(long double arg, int exp) noexcept
{
    return boost::math::ccmath::ldexp(arg, exp);
}
#endif

} // Namespaces

#endif // BOOST_MATH_CCMATH_LDEXP_HPP


/* statistic_tests.cpp file
 *
 * Copyright Jens Maurer 2000, 2002
 * Distributed under 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)
 *
 * $Id: statistic_tests.cpp 24096 2004-07-27 03:43:34Z dgregor $
 *
 * Revision history
 */

/*
 * NOTE: This is not part of the official boost submission.  It exists
 * only as a collection of ideas.
 */

#include <iostream>
#include <iomanip>
#include <string>
#include <functional>
#include <math.h>  // lgamma is not in namespace std
#include <vector>
#include <algorithm>

#include <boost/cstdint.hpp>
#include <boost/random.hpp>

#include "statistic_tests.hpp"
#include "integrate.hpp"

namespace boost {
namespace random {

// Wikramaratna 1989  ACORN
template<class IntType, int k, IntType m, IntType val>
class additive_congruential
{
public:
  typedef IntType result_type;
#ifndef BOOST_NO_INCLASS_MEMBER_INITIALIZATION
  static const bool has_fixed_range = true;
  static const result_type min_value = 0;
  static const result_type max_value = m-1;
#else
  enum {
    has_fixed_range = true,
    min_value = 0,
    max_value = m-1
  };
#endif
  template<class InputIterator>
  explicit additive_congruential(InputIterator start) { seed(start); }
  template<class InputIterator>
  void seed(InputIterator start)
  {
    for(int i = 0; i <= k; ++i, ++start)
      values[i] = *start;
  }

  result_type operator()()
  {
    for(int i = 1; i <= k; ++i) {
      IntType tmp = values[i-1] + values[i];
      if(tmp >= m)
        tmp -= m;
      values[i] = tmp;
    }
    return values[k];
  }
  result_type validation() const { return val; }
private:
  IntType values[k+1];
};

template<class IntType, int r, int s, IntType m, IntType val>
class lagged_fibonacci_int
{
public:
  typedef IntType result_type;
#ifndef BOOST_NO_INCLASS_MEMBER_INITIALIZATION
  static const bool has_fixed_range = true;
  static const result_type min_value = 0;
  static const result_type max_value = m-1;
#else
  enum {
    has_fixed_range = true,
    min_value = 0,
    max_value = m-1
  };
#endif
  explicit lagged_fibonacci_int(IntType start) { seed(start); }
  template<class Generator>
  explicit lagged_fibonacci_int(Generator & gen) { seed(gen); }
  void seed(IntType start)
  {
    linear_congruential<uint32_t, 299375077, 0, 0, 0> init;
    seed(init);
  }
  template<class Generator>
  void seed(Generator & gen)
  {
    assert(r > s);
    for(int i = 0; i < 607; ++i)
      values[i] = gen();
    current = 0;
    lag = r-s;
  }

  result_type operator()()
  {
    result_type tmp = values[current] + values[lag];
    if(tmp >= m)
      tmp -= m;
    values[current] = tmp;
    ++current;
    if(current >= r)
      current = 0;
    ++lag;
    if(lag >= r)
      lag = 0;
    return tmp;
  }
  result_type validation() const { return val; }
private:
  result_type values[r];
  int current, lag;
};

} // namespace random
} // namespace boost

// distributions from Haertel's dissertation
// (additional parameterizations of the basic templates)
namespace Haertel {
  typedef boost::random::linear_congruential<boost::uint64_t, 45965, 453816691,
    (boost::uint64_t(1)<<31), 0> LCG_Af2;
  typedef boost::random::linear_congruential<boost::uint64_t, 211936855, 0,
    (boost::uint64_t(1)<<29)-3, 0> LCG_Die1;
  typedef boost::random::linear_congruential<boost::uint32_t, 2824527309u, 0,
    0, 0> LCG_Fis;
  typedef boost::random::linear_congruential<boost::uint64_t, 950706376u, 0,
    (boost::uint64_t(1)<<31)-1, 0> LCG_FM;
  typedef boost::random::linear_congruential<boost::int32_t, 51081, 0,
    2147483647, 0> LCG_Hae;
  typedef boost::random::linear_congruential<boost::uint32_t, 69069, 1,
    0, 0> LCG_VAX;
  typedef boost::random::inversive_congruential<boost::int64_t, 240318, 197,
    1000081, 0> NLG_Inv1;
  typedef boost::random::inversive_congruential<boost::int64_t, 15707262,
    13262967, (1<<24)-17, 0> NLG_Inv2;
  typedef boost::random::inversive_congruential<boost::int32_t, 1, 1,
    2147483647, 0> NLG_Inv4;
  typedef boost::random::inversive_congruential<boost::int32_t, 1, 2,
    1<<30, 0> NLG_Inv5;
  typedef boost::random::additive_congruential<boost::int32_t, 6,
    (1<<30)-35, 0> MRG_Acorn7;
  typedef boost::random::lagged_fibonacci_int<boost::uint32_t, 607, 273,
    0, 0> MRG_Fib2;

  template<class Gen, class T>
  inline void check_validation(Gen & gen, T value, const std::string & name)
  {
    for(int i = 0; i < 100000-1; ++i)
      gen();
    if(value != gen())
      std::cout << name << ": validation failed" << std::endl;
  }

  // we have validation after 100000 steps with Haertel's generators
  template<class Gen, class T>
  void validate(T value, const std::string & name)
  {
    Gen gen(1234567);
    check_validation(gen, value, name);
  }

  void validate_all()
  {
    validate<LCG_Af2>(183269031u, "LCG_Af2");
    validate<LCG_Die1>(522319944u, "LCG_Die1");
    validate<LCG_Fis>(-2065162233u, "LCG_Fis");
    validate<LCG_FM>(581815473u, "LCG_FM");
    validate<LCG_Hae>(28931709, "LCG_Hae");
    validate<LCG_VAX>(1508154087u, "LCG_VAX");
    validate<NLG_Inv2>(6666884, "NLG_Inv2");
    validate<NLG_Inv4>(1521640076, "NLG_Inv4");
    validate<NLG_Inv5>(641840839, "NLG_Inv5");
    static const int acorn7_init[]
      = { 1234567, 7654321, 246810, 108642, 13579, 97531, 555555 };
    MRG_Acorn7 acorn7(acorn7_init);
    check_validation(acorn7, 874294697, "MRG_Acorn7");
    validate<MRG_Fib2>(1234567u, "MRG_Fib2");
  }
} // namespace Haertel

double normal_density(double x)
{
  const double pi = 3.14159265358979323846;
  return 1/std::sqrt(2*pi) * std::exp(-x*x/2);
}

namespace std {
#ifdef _CXXRTCF_H__
  using _CS_swamp::lgamma;
#elif defined __SGI_STL_PORT
  using ::lgamma;
#endif
}

class chi_square_density : public std::unary_function<double, double>
{
public:
  chi_square_density(int freedom)
    : _exponent( static_cast<double>(freedom)/2-1 ),
      _factor(1/(std::pow(2, _exponent+1) * std::exp(lgamma(_exponent+1))))
  { }

  double operator()(double x)
  {
    return _factor*std::pow(x, _exponent)*std::exp(-x/2);
  }
private:
  double _exponent, _factor;
};

// computes F(x) or F(y) - F(x)
class chi_square_probability : public distribution_function<double>
{
public:
  chi_square_probability(int freedom) : dens(freedom) {}
  double operator()(double x) { return operator()(0, x); }
  double operator()(double x, double y)
  { return trapezoid(dens, x, y, 1000); }
private:
  chi_square_density dens;
};

class uniform_distribution : public distribution_function<double>
{
public:
  uniform_distribution(double from, double to) : from(from), to(to)
  { assert(from < to); }
  double operator()(double x)
  {
    if(x < from)
      return 0;
    else if(x > to)
      return 1;
    else
      return (x-from)/(to-from);
  }
  double operator()(double x, double delta)
  { return operator()(x+delta) - operator()(x); }
private:
  double from, to;
};

class test_environment;

class test_base
{
protected:
  explicit test_base(test_environment & env) : environment(env) { }
  void check(double val) const;
private:
  test_environment & environment;
};

class equidistribution_test : test_base
{
public:
  equidistribution_test(test_environment & env, unsigned int classes,
                        unsigned int high_classes)
    : test_base(env), classes(classes),
      test_distrib_chi_square(chi_square_probability(classes-1), high_classes)
  { }

  template<class RNG>
  void run(RNG & rng, int n1, int n2)
  {
    using namespace boost;
    std::cout << "equidistribution: " << std::flush;
    equidistribution_experiment equi(classes);
    uniform_smallint<RNG> uint_linear(rng, 0, classes-1);
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(equi, uint_linear, n1), n2));
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(equi, uint_linear, n1), 2*n2));

    std::cout << "  2D: " << std::flush;
    equidistribution_2d_experiment equi_2d(classes);
    unsigned int root = static_cast<unsigned int>(std::sqrt(double(classes)));
    assert(root * root == classes);
    uniform_smallint<RNG> uint_square(rng, 0, root-1);
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(equi_2d, uint_square, n1), n2));
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(equi_2d, uint_square, n1), 2*n2));
    std::cout << std::endl;
  }
private:
  unsigned int classes;
  distribution_experiment test_distrib_chi_square;
};

class ks_equidistribution_test : test_base
{
public:
  ks_equidistribution_test(test_environment & env, unsigned int classes)
    : test_base(env),
      test_distrib_chi_square(kolmogorov_smirnov_probability(5000),
                              classes)
  { }

  template<class RNG>
  void run(RNG & rng, int n1, int n2)
  {
    using namespace boost;
    std::cout << "KS: " << std::flush;
    // generator_reference_t<RNG> gen_ref(rng);
    RNG& gen_ref(rng);
    kolmogorov_experiment ks(n1);
    uniform_distribution ud((rng.min)(), (rng.max)());
    check(run_experiment(test_distrib_chi_square,
                         ks_experiment_generator(ks, gen_ref, ud), n2));
    check(run_experiment(test_distrib_chi_square,
                         ks_experiment_generator(ks, gen_ref, ud), 2*n2));
  }
private:
  distribution_experiment test_distrib_chi_square;
};

class runs_test : test_base
{
public:
  runs_test(test_environment & env, unsigned int classes,
            unsigned int high_classes)
    : test_base(env), classes(classes),
      test_distrib_chi_square(chi_square_probability(classes-1), high_classes)
  { }

  template<class RNG>
  void run(RNG & rng, int n1, int n2)
  {
    using namespace boost;
    std::cout << "runs: up: " << std::flush;
    runs_experiment<true> r_up(classes);
    // generator_reference_t<RNG> gen_ref(rng);
    RNG& gen_ref(rng);
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(r_up, gen_ref, n1), n2));
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(r_up, gen_ref, n1), 2*n2));

    std::cout << "  down: " << std::flush;
    runs_experiment<false> r_down(classes);
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(r_down, gen_ref, n1), n2));
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(r_down, gen_ref, n1), 2*n2));

    std::cout << std::endl;
  }
private:
  unsigned int classes;
  distribution_experiment test_distrib_chi_square;
};

class gap_test : test_base
{
public:
  gap_test(test_environment & env, unsigned int classes,
            unsigned int high_classes)
    : test_base(env), classes(classes),
      test_distrib_chi_square(chi_square_probability(classes-1), high_classes)
  { }

  template<class RNG>
  void run(RNG & rng, int n1, int n2)
  {
    using namespace boost;
    std::cout << "gaps: " << std::flush;
    gap_experiment gap(classes, 0.2, 0.8);
    // generator_reference_t<RNG> gen_ref(rng);
    RNG& gen_ref(rng);
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(gap, gen_ref, n1), n2));
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(gap, gen_ref, n1), 2*n2));

    std::cout << std::endl;
  }
private:
  unsigned int classes;
  distribution_experiment test_distrib_chi_square;
};

class poker_test : test_base
{
public:
  poker_test(test_environment & env, unsigned int classes,
             unsigned int high_classes)
    : test_base(env), classes(classes),
      test_distrib_chi_square(chi_square_probability(classes-1), high_classes)
  { }

  template<class RNG>
  void run(RNG & rng, int n1, int n2)
  {
    using namespace boost;
    std::cout << "poker: " << std::flush;
    poker_experiment poker(8, classes);
    uniform_smallint<RNG> usmall(rng, 0, 7);
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(poker, usmall, n1), n2));
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(poker, usmall, n1), 2*n2));
    std::cout << std::endl;
  }
private:
  unsigned int classes;
  distribution_experiment test_distrib_chi_square;
};

class coupon_collector_test : test_base
{
public:
  coupon_collector_test(test_environment & env, unsigned int classes,
                        unsigned int high_classes)
    : test_base(env), classes(classes),
      test_distrib_chi_square(chi_square_probability(classes-1), high_classes)
  { }

  template<class RNG>
  void run(RNG & rng, int n1, int n2)
  {
    using namespace boost;
    std::cout << "coupon collector: " << std::flush;
    coupon_collector_experiment coupon(5, classes);

    uniform_smallint<RNG> usmall(rng, 0, 4);
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(coupon, usmall, n1), n2));
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(coupon, usmall, n1), 2*n2));
    std::cout << std::endl;
  }
private:
  unsigned int classes;
  distribution_experiment test_distrib_chi_square;
};

class permutation_test : test_base
{
public:
  permutation_test(test_environment & env, unsigned int classes,
                   unsigned int high_classes)
    : test_base(env), classes(classes),
      test_distrib_chi_square(chi_square_probability(fac<int>(classes)-1),
                              high_classes)
  { }

  template<class RNG>
  void run(RNG & rng, int n1, int n2)
  {
    using namespace boost;
    std::cout << "permutation: " << std::flush;
    permutation_experiment perm(classes);

    // generator_reference_t<RNG> gen_ref(rng);
    RNG& gen_ref(rng);
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(perm, gen_ref, n1), n2));
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(perm, gen_ref, n1), 2*n2));
    std::cout << std::endl;
  }
private:
  unsigned int classes;
  distribution_experiment test_distrib_chi_square;
};

class maximum_test : test_base
{
public:
  maximum_test(test_environment & env, unsigned int high_classes)
    : test_base(env),
      test_distrib_chi_square(kolmogorov_smirnov_probability(1000),
                              high_classes)
  { }

  template<class RNG>
  void run(RNG & rng, int n1, int n2)
  {
    using namespace boost;
    std::cout << "maximum-of-t: " << std::flush;
    maximum_experiment<RNG> mx(rng, n1, 5);
    check(run_experiment(test_distrib_chi_square, mx, n2));
    check(run_experiment(test_distrib_chi_square, mx, 2*n2));
    std::cout << std::endl;
  }
private:
  distribution_experiment test_distrib_chi_square;
};

class birthday_test : test_base
{
public:
  birthday_test(test_environment & env)
    : test_base(env)
  { }

  template<class RNG>
  void run(RNG & rng, int n1, int n2)
  {
    using namespace boost;
    std::cout << "birthday spacing: " << std::flush;
    uniform_int<RNG> uni(rng, 0, (1<<25)-1);
    birthday_spacing_experiment bsp(4, 512, (1<<25));
    std::cout << run_experiment(bsp, uni, n1);
#if 0
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(perm, gen_ref, n1), n2));
    check(run_experiment(test_distrib_chi_square,
                         experiment_generator(perm, gen_ref, n1), 2*n2));
#endif
    std::cout << std::endl;
  }

};

class test_environment
{
public:
  static const int classes = 20;
  explicit test_environment(double confid)
    : confidence(confid),
      confidence_chi_square_quantil(quantil(chi_square_density(classes-1), 0, confidence, 1e-4)),
      test_distrib_chi_square6(chi_square_probability(7-1), classes),
      ksequi_test(*this, classes),
      equi_test(*this, 100, classes),
      rns_test(*this, 7, classes),
      gp_test(*this, 7, classes),
      pk_test(*this, 5, classes),
      cpn_test(*this, 15, classes),
      perm_test(*this, 5, classes),
      max_test(*this, classes),
      bday_test(*this)
  {
    std::cout << "Confidence level: " << confid
              << "; 1-alpha = " << (1-confid)
              << "; chi_square(" << (classes-1)
              << ", " << confidence_chi_square_quantil
              << ") = "
              << chi_square_probability(classes-1)(0, confidence_chi_square_quantil)
              << std::endl;
  }

  bool check_confidence(double val, double chi_square_conf) const
  {
    std::cout << val;
    bool result = (val <= chi_square_conf);
    if(!result) {
      std::cout << "* [";
      double prob = (val > 10*chi_square_conf ? 1 :
                     chi_square_probability(classes-1)(0, val));
      std::cout << (1-prob) << "]";
    }
    std::cout << " " << std::flush;
    return result;
  }

  bool check(double chi_square_value) const
  {
    return check_confidence(chi_square_value, confidence_chi_square_quantil);
  }

  template<class RNG>
  void run_test(const std::string & name)
  {
    using namespace boost;

    std::cout << "Running tests on " << name << std::endl;

    RNG rng(1234567);
    typedef boost::uniform_01<RNG> UGen;

#if 1
    ksequi_test.run(rng, 5000, 250);
    equi_test.run(rng, 5000, 250);
    rns_test.run(rng, 100000, 250);
    gp_test.run(rng, 10000, 250);
    pk_test.run(rng, 5000, 250);
    cpn_test.run(rng, 500, 250);
    perm_test.run(rng, 1200, 250);
    max_test.run(rng, 1000, 250);
#endif
    bday_test.run(rng, 1000, 150);

    std::cout << std::endl;
  }

private:
  double confidence;
  double confidence_chi_square_quantil;
  distribution_experiment test_distrib_chi_square6;
  ks_equidistribution_test ksequi_test;
  equidistribution_test equi_test;
  runs_test rns_test;
  gap_test gp_test;
  poker_test pk_test;
  coupon_collector_test cpn_test;
  permutation_test perm_test;
  maximum_test max_test;
  birthday_test bday_test;
};

void test_base::check(double val) const
{
  environment.check(val);
}

void print_ks_table()
{
  std::cout.setf(std::ios::fixed);
  std::cout.precision(5);
  static const double all_p[] = { 0.01, 0.05, 0.25, 0.5, 0.75, 0.95, 0.99 };
  for(int n = 0; n <= 10000;  (n < 55 ? ++n : n *= 10)) {
    std::cout << std::setw(4) << n << " ";
    for(unsigned int i = 0; i < sizeof(all_p)/sizeof(all_p[0]); ++i) {
      std::cout << std::setw(8)
                << (n == 0 ? all_p[i] :
                    invert_monotone_inc(kolmogorov_smirnov_probability(n), all_p[i], 0, 10))
                << " ";
    }
    std::cout << std::endl;
  }
}

int main()
{
  //  Haertel::validate_all();
  test_environment env(0.99);
  env.run_test<boost::minstd_rand>("minstd_rand");
  env.run_test<boost::mt19937>("mt19937");
  env.run_test<Haertel::LCG_Af2>("LCG_Af2");
  env.run_test<Haertel::LCG_Die1>("LCG_Die1");
  env.run_test<Haertel::LCG_Fis>("LCG_Fis");
  env.run_test<Haertel::LCG_FM>("LCG_FM");
  env.run_test<Haertel::LCG_Hae>("LCG_Hae");
  env.run_test<Haertel::LCG_VAX>("LCG_VAX");
  env.run_test<Haertel::NLG_Inv1>("NLG_Inv1");
  env.run_test<Haertel::NLG_Inv2>("NLG_Inv2");
  env.run_test<Haertel::NLG_Inv4>("NLG_Inv4");
  env.run_test<Haertel::NLG_Inv5>("NLG_Inv5");
}


//---------------------------------------------------------------------------//
//!
//! \file   tstUniformDistribution.cpp
//! \author Alex Robinson
//! \brief  Uniform 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_UniformDistribution.hpp"
#include "Utility_RandomNumberGenerator.hpp"
#include "Utility_PhysicalConstants.hpp"
#include "Utility_UnitTraits.hpp"
#include "Utility_QuantityTraits.hpp"
#include "Utility_ElectronVoltUnit.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 TestArchiveHelper::TestArchives TestArchives;

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

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

std::shared_ptr<Utility::TabularUnivariateDistribution> tab_distribution(
			  new Utility::UniformDistribution( -1.0, 1.0, 2.0 ) );

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

std::shared_ptr<Utility::UnitAwareTabularUnivariateDistribution<si::energy,si::amount> >
  unit_aware_tab_distribution;

std::shared_ptr<Utility::UnitAwareUnivariateDistribution<si::energy,si::amount> >
  unit_aware_distribution;

//---------------------------------------------------------------------------//
// Tests.
//---------------------------------------------------------------------------//
// Check that the distribution can be evaluated
FRENSIE_UNIT_TEST( UniformDistribution, evaluate )
{
  FRENSIE_CHECK_EQUAL( distribution->evaluate( -2.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( -1.0 ), 2.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 0.0 ), 2.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1.0 ), 2.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( -2.0 ), 0.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit aware distribution can be evaluated
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution, evaluate )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( -1.0*si::joule ),
		       0.0*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.0*si::joule ),
  		       1.0*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.5*si::joule ),
  		       1.0*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 1.0*si::joule ),
  		       1.0*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 2.0*si::joule ),
  		       0.0*si::mole );
}

//---------------------------------------------------------------------------//
// Check that the PDF can be evaluated
FRENSIE_UNIT_TEST( UniformDistribution, evaluatePDF )
{
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( -2.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( -1.0 ), 0.5 );
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 0.0 ), 0.5 );
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 1.0 ), 0.5 );
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 2.0 ), 0.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit aware PDF can be evaluated
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution, evaluatePDF )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( -1.0*si::joule ),
  		       0.0/si::joule );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( 0.0*si::joule ),
  		       1.0/si::joule );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( 0.5*si::joule ),
  		       1.0/si::joule );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( 1.0*si::joule ),
  		       1.0/si::joule );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( 2.0*si::joule ),
  		       0.0/si::joule );
}

//---------------------------------------------------------------------------//
// Check that the CDF can be evaluated
FRENSIE_UNIT_TEST( UniformDistribution, evaluateCDF )
{
  FRENSIE_CHECK_EQUAL( tab_distribution->evaluateCDF( -2.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( tab_distribution->evaluateCDF( -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( tab_distribution->evaluateCDF( 0.0 ), 0.5 );
  FRENSIE_CHECK_EQUAL( tab_distribution->evaluateCDF( 1.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( tab_distribution->evaluateCDF( 2.0 ), 1.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit aware CDF can be evaluated
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution, evaluateCDF )
{
  FRENSIE_CHECK_EQUAL(unit_aware_tab_distribution->evaluateCDF(-1.0*si::joule),
		      0.0 );
  FRENSIE_CHECK_EQUAL(unit_aware_tab_distribution->evaluateCDF(0.0*si::joule),
		      0.0 );
  FRENSIE_CHECK_EQUAL(unit_aware_tab_distribution->evaluateCDF(0.5*si::joule),
		      0.5 );
  FRENSIE_CHECK_EQUAL(unit_aware_tab_distribution->evaluateCDF(1.0*si::joule),
		      1.0 );
  FRENSIE_CHECK_EQUAL(unit_aware_tab_distribution->evaluateCDF(2.0*si::joule),
		      1.0 );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST( UniformDistribution, sample )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.5;
  fake_stream[2] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

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

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

  sample = distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0, 1e-14 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit aware distribution can be sampled
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution, sample )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.5;
  fake_stream[2] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<si::energy> sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_EQUAL( sample, 0.0*si::joule );

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_EQUAL( sample, 0.5*si::joule );

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0*si::joule, 1e-14 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled w/o an instance
FRENSIE_UNIT_TEST( UniformDistribution, sample_static )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.5;
  fake_stream[2] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double sample = Utility::UniformDistribution::sample( -1.0, 1.0 );
  FRENSIE_CHECK_EQUAL( sample, -1.0 );

  sample = Utility::UniformDistribution::sample( -1.0, 1.0 );
  FRENSIE_CHECK_EQUAL( sample, 0.0 );

  sample = Utility::UniformDistribution::sample( -1.0, 1.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0, 1e-14 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled w/o an instance
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution, sample_static )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.5;
  fake_stream[2] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<si::energy> sample =
    Utility::UnitAwareUniformDistribution<si::energy>::sample(
					        0.0*si::joule, 1.0*si::joule );
  FRENSIE_CHECK_EQUAL( sample, 0.0*si::joule );

  sample =
    Utility::UnitAwareUniformDistribution<si::energy>::sample(
					        0.0*si::joule, 1.0*si::joule );
  FRENSIE_CHECK_EQUAL( sample, 0.5*si::joule );

  sample =
    Utility::UnitAwareUniformDistribution<si::energy>::sample(
					        0.0*si::joule, 1.0*si::joule );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0*si::joule, 1e-14 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST( UniformDistribution, sampleAndRecordTrials )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.5;
  fake_stream[2] = 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_EQUAL( sample, 0.0 );
  FRENSIE_CHECK_EQUAL( 2.0/trials, 1.0 );

  sample = distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0, 1e-14 );
  FRENSIE_CHECK_EQUAL( 3.0/trials, 1.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit aware distribution can be sampled
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution, sampleAndRecordTrials )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.5;
  fake_stream[2] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  Utility::DistributionTraits::Counter trials = 0;

  quantity<si::energy> sample =
    unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_EQUAL( sample, 0.0*si::joule );
  FRENSIE_CHECK_EQUAL( 1.0/trials, 1.0 );

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_EQUAL( sample, 0.5*si::joule );
  FRENSIE_CHECK_EQUAL( 2.0/trials, 1.0 );

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0*si::joule, 1e-14 );
  FRENSIE_CHECK_EQUAL( 3.0/trials, 1.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled w/o an instance
FRENSIE_UNIT_TEST( UniformDistribution, sampleAndRecordTrials_static )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.5;
  fake_stream[2] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  Utility::DistributionTraits::Counter trials = 0;

  double sample = Utility::UniformDistribution::sampleAndRecordTrials(
							   -1.0, 1.0, trials );
  FRENSIE_CHECK_EQUAL( sample, -1.0 );
  FRENSIE_CHECK_EQUAL( 1.0/trials, 1.0 );

  sample = Utility::UniformDistribution::sampleAndRecordTrials(
							   -1.0, 1.0, trials );
  FRENSIE_CHECK_EQUAL( sample, 0.0 );
  FRENSIE_CHECK_EQUAL( 2.0/trials, 1.0 );

  sample = Utility::UniformDistribution::sampleAndRecordTrials(
							   -1.0, 1.0, trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0, 1e-14 );
  FRENSIE_CHECK_EQUAL( 3.0/trials, 1.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled w/o an instance
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution, sampleAndRecordTrials_static )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.5;
  fake_stream[2] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  Utility::DistributionTraits::Counter trials = 0;

  quantity<si::energy> sample = Utility::UnitAwareUniformDistribution<si::energy,si::amount>::sampleAndRecordTrials(
				        0.0*si::joule, 1.0*si::joule, trials );
  FRENSIE_CHECK_EQUAL( sample, 0.0*si::joule );
  FRENSIE_CHECK_EQUAL( 1.0/trials, 1.0 );

  sample =
    Utility::UnitAwareUniformDistribution<si::energy,si::amount>::sampleAndRecordTrials(
				        0.0*si::joule, 1.0*si::joule, trials );
  FRENSIE_CHECK_EQUAL( sample, 0.5*si::joule );
  FRENSIE_CHECK_EQUAL( 2.0/trials, 1.0 );

  sample =
    Utility::UnitAwareUniformDistribution<si::energy,si::amount>::sampleAndRecordTrials(
				        0.0*si::joule, 1.0*si::joule, trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0*si::joule, 1e-14 );
  FRENSIE_CHECK_EQUAL( 3.0/trials, 1.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST( UniformDistribution, sampleAndRecordBinIndex )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.5;
  fake_stream[2] = 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, 0.0 );

  sample = tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_EQUAL( sample, 0.0 );
  FRENSIE_CHECK_EQUAL( bin_index, 0.0 );

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

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution, sampleAndRecordBinIndex )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.5;
  fake_stream[2] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  size_t bin_index;

  quantity<si::energy> sample =
    unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_EQUAL( sample, 0.0*si::joule );
  FRENSIE_CHECK_EQUAL( bin_index, 0.0 );

  sample = unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_EQUAL( sample, 0.5*si::joule );
  FRENSIE_CHECK_EQUAL( bin_index, 0.0 );

  sample = unit_aware_tab_distribution->sampleAndRecordBinIndex( bin_index );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0*si::joule, 1e-14 );
  FRENSIE_CHECK_EQUAL( bin_index, 0.0 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST( UniformDistribution, sampleWithRandomNumber )
{
  double sample = tab_distribution->sampleWithRandomNumber( 0.0 );
  FRENSIE_CHECK_EQUAL( sample, -1.0 );

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

  sample = tab_distribution->sampleWithRandomNumber( 1.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0, 1e-14 );
}

//---------------------------------------------------------------------------//
// Check that the unit aware distribution can be sampled
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution, sampleWithRandomNumber )
{
  quantity<si::energy> sample =
    unit_aware_tab_distribution->sampleWithRandomNumber( 0.0 );
  FRENSIE_CHECK_EQUAL( sample, 0.0*si::joule );

  sample = unit_aware_tab_distribution->sampleWithRandomNumber( 0.5 );
  FRENSIE_CHECK_EQUAL( sample, 0.5*si::joule );

  sample = unit_aware_tab_distribution->sampleWithRandomNumber( 1.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0*si::joule, 1e-14 );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled w/o an instance
FRENSIE_UNIT_TEST( UniformDistribution, sampleWithRandomNumber_static )
{
  double sample = Utility::UniformDistribution::sampleWithRandomNumber(
							      -1.0, 1.0, 0.0 );
  FRENSIE_CHECK_EQUAL( sample, -1.0 );

  sample = Utility::UniformDistribution::sampleWithRandomNumber(
							      -1.0, 1.0, 0.5 );
  FRENSIE_CHECK_EQUAL( sample, 0.0 );

  sample = Utility::UniformDistribution::sampleWithRandomNumber(
							      -1.0, 1.0, 1.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0, 1e-14 );
}

//---------------------------------------------------------------------------//
// Check that the unit aware distribution can be sampled w/o an instance
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution,
		   sampleWithRandomNumber_static )
{
  quantity<si::energy> sample = Utility::UnitAwareUniformDistribution<si::energy,si::amount>::sampleWithRandomNumber(
					   0.0*si::joule, 1.0*si::joule, 0.0 );
  FRENSIE_CHECK_EQUAL( sample, 0.0*si::joule );

  sample = Utility::UnitAwareUniformDistribution<si::energy,si::amount>::sampleWithRandomNumber(
					   0.0*si::joule, 1.0*si::joule, 0.5 );
  FRENSIE_CHECK_EQUAL( sample, 0.5*si::joule );

  sample = Utility::UnitAwareUniformDistribution<si::energy,si::amount>::sampleWithRandomNumber(
					   0.0*si::joule, 1.0*si::joule, 1.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 1.0*si::joule, 1e-14 );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST( UniformDistribution, sampleInSubrange )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.5;
  fake_stream[2] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

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

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

  sample = tab_distribution->sampleInSubrange( 0.0 );
  FRENSIE_CHECK_SMALL( sample, 1e-14 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit aware distribution can be sampled
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution, sampleInSubrange )
{
  std::vector<double> fake_stream( 3 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.5;
  fake_stream[2] = 1.0 - 1e-15;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<si::energy> sample =
    unit_aware_tab_distribution->sampleInSubrange( 0.5*si::joule );
  FRENSIE_CHECK_EQUAL( sample, 0.0*si::joule );

  sample = unit_aware_tab_distribution->sampleInSubrange( 0.5*si::joule );
  FRENSIE_CHECK_EQUAL( sample, 0.25*si::joule );

  sample = unit_aware_tab_distribution->sampleInSubrange( 0.5*si::joule );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5*si::joule, 1e-14 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST( UniformDistribution, sampleWithRandomNumberInSubrange )
{
  double sample = tab_distribution->sampleWithRandomNumberInSubrange( 0.0, 0.0 );
  FRENSIE_CHECK_EQUAL( sample, -1.0 );

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

  sample = tab_distribution->sampleWithRandomNumberInSubrange( 1.0, 0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.0, 1e-14 );
}

//---------------------------------------------------------------------------//
// Check that the unit aware distribution can be sampled
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution,
		   sampleWithRandomNumberInSubrange )
{
  quantity<si::energy> sample =
    unit_aware_tab_distribution->sampleWithRandomNumberInSubrange(
							  0.0, 0.5*si::joule );
  FRENSIE_CHECK_EQUAL( sample, 0.0*si::joule );

  sample = unit_aware_tab_distribution->sampleWithRandomNumberInSubrange(
							  0.5, 0.5*si::joule );
  FRENSIE_CHECK_EQUAL( sample, 0.25*si::joule );

  sample = unit_aware_tab_distribution->sampleWithRandomNumberInSubrange(
							  1.0, 0.5*si::joule );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.5*si::joule, 1e-14 );
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the distribution independent variable can be
// returned
FRENSIE_UNIT_TEST( UniformDistribution, getUpperBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( distribution->getUpperBoundOfIndepVar(), 1.0 );
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the unit-aware distribution independent
// variable can be returned
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution, getUpperBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getUpperBoundOfIndepVar(),
		       1.0*si::joule );
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the distribution independent variable can be
// returned
FRENSIE_UNIT_TEST( UniformDistribution, getLowerBoundOfIndepVar )
{
  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( UnitAwareUniformDistribution, getLowerBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getLowerBoundOfIndepVar(),
		       0.0*si::joule );
}

//---------------------------------------------------------------------------//
// Check that the distribution type can be returned
FRENSIE_UNIT_TEST( UniformDistribution, getDistributionType )
{
  FRENSIE_CHECK_EQUAL( distribution->getDistributionType(),
		       Utility::UNIFORM_DISTRIBUTION );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution type can be returned
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution, getDistributionType )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getDistributionType(),
		       Utility::UNIFORM_DISTRIBUTION );
}

//---------------------------------------------------------------------------//
// Check if the distribution is tabular
FRENSIE_UNIT_TEST( UniformDistribution, isTabular )
{
  FRENSIE_CHECK( distribution->isTabular() );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is tabular
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution, isTabular )
{
  FRENSIE_CHECK( unit_aware_distribution->isTabular() );
}

//---------------------------------------------------------------------------//
// Check if the distribution is continuous
FRENSIE_UNIT_TEST( UniformDistribution, isContinuous )
{
  FRENSIE_CHECK( distribution->isContinuous() );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is continuous
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution, isContinuous )
{
  FRENSIE_CHECK( unit_aware_distribution->isContinuous() );
}

//---------------------------------------------------------------------------//
// Check if the distribution is compatible with the interpolation type
FRENSIE_UNIT_TEST( UniformDistribution, isCompatibleWithInterpType )
{
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LinLin>() );
  FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( !distribution->isCompatibleWithInterpType<Utility::LogLog>() );

  // Create another distribution that is compatible with all interpolation
  // types
  Utility::UniformDistribution test_dist( 1.0, 2.0, 1.0 );

  FRENSIE_CHECK( test_dist.isCompatibleWithInterpType<Utility::LinLin>() );
  FRENSIE_CHECK( test_dist.isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( test_dist.isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( test_dist.isCompatibleWithInterpType<Utility::LogLog>() );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is compatible with the interp type
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution, isCompatibleWithInterpType )
{
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLin>() );
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( unit_aware_distribution->isCompatibleWithInterpType<Utility::LogLog>() );

  // Create another distribution that is compatible with all interpolation
  // types
  Utility::UnitAwareUniformDistribution<si::energy,si::amount>
    test_dist( 1.0*si::joule, 2.0*si::joule, 1.0*si::mole );

  FRENSIE_CHECK( test_dist.isCompatibleWithInterpType<Utility::LinLin>() );
  FRENSIE_CHECK( test_dist.isCompatibleWithInterpType<Utility::LinLog>() );
  FRENSIE_CHECK( test_dist.isCompatibleWithInterpType<Utility::LogLin>() );
  FRENSIE_CHECK( test_dist.isCompatibleWithInterpType<Utility::LogLog>() );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be placed in a stream
FRENSIE_UNIT_TEST( UniformDistribution, ostream_operator )
{
  std::ostringstream oss;

  oss << Utility::UniformDistribution();

  Utility::VariantMap dist_data =
    Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Uniform Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent value"].toDouble(), 1.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toDouble(), 0.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toDouble(), 1.0, SHOW_LHS );

  oss.str( "" );
  oss.clear();

  oss << Utility::UniformDistribution( -2.0, 2.0 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Uniform Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent value"].toDouble(), 1.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toDouble(), -2.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toDouble(), 2.0, SHOW_LHS );

  oss.str( "" );
  oss.clear();

  oss << Utility::UniformDistribution( -2.0, 2.0, 3.0 );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Uniform Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent value"].toDouble(), 3.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toDouble(), -2.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toDouble(), 2.0, SHOW_LHS );

  oss.str( "" );
  oss.clear();

  oss << *distribution;

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Uniform Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent value"].toDouble(), 2.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toDouble(), -1.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toDouble(), 1.0, SHOW_LHS );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be placed in a stream
FRENSIE_UNIT_TEST( UnitAwareUniformDistribution, ostream_operator )
{
  std::ostringstream oss;

  oss << Utility::UnitAwareUniformDistribution<si::energy,si::amount>();

  Utility::VariantMap dist_data =
    Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Uniform Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<si::energy>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent value"].toType<quantity<si::amount> >(),
                       1.0*si::mole,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toType<quantity<si::energy> >(),
                       0.0*si::joule,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toType<quantity<si::energy> >(),
                       1.0*si::joule,
                       SHOW_LHS );

  oss.str( "" );
  oss.clear();

  oss << Utility::UnitAwareUniformDistribution<si::energy,si::amount>( -2.0*si::joule, 2.0*si::joule );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Uniform Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<si::energy>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent value"].toType<quantity<si::amount> >(),
                       1.0*si::mole,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toType<quantity<si::energy> >(),
                       -2.0*si::joule,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toType<quantity<si::energy> >(),
                       2.0*si::joule,
                       SHOW_LHS );

  oss.str( "" );
  oss.clear();

  oss << Utility::UnitAwareUniformDistribution<si::energy,si::amount>( -2.0*si::joule, 2.0*si::joule, 3.0*si::mole );

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Uniform Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<si::energy>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent value"].toType<quantity<si::amount> >(),
                       3.0*si::mole,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toType<quantity<si::energy> >(),
                       -2.0*si::joule,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toType<quantity<si::energy> >(),
                       2.0*si::joule,
                       SHOW_LHS );

  oss.str( "" );
  oss.clear();

  oss << *unit_aware_distribution;

  dist_data = Utility::fromString<Utility::VariantMap>( oss.str() );

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Uniform Distribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<si::energy>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent value"].toType<quantity<si::amount> >(),
                       1.0*si::mole,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toType<quantity<si::energy> >(),
                       0.0*si::joule,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toType<quantity<si::energy> >(),
                       1.0*si::joule,
                       SHOW_LHS );
}

//---------------------------------------------------------------------------//
// Check that a distribution can be archived
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( UniformDistribution, 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_uniform_dist" );
  std::ostringstream archive_ostream;

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

    createOArchive( archive_base_name, archive_ostream, oarchive );

    Utility::UniformDistribution dist_a( -1.0, 1.0, 0.5 );
    Utility::UniformDistribution dist_b( 0.0, 2.0, 1.0 );

    FRENSIE_REQUIRE_NO_THROW(
                            (*oarchive) << BOOST_SERIALIZATION_NVP( dist_a ) );
    FRENSIE_REQUIRE_NO_THROW(
                            (*oarchive) << BOOST_SERIALIZATION_NVP( dist_b ) );
    FRENSIE_REQUIRE_NO_THROW(
                  (*oarchive) << BOOST_SERIALIZATION_NVP( tab_distribution ) );
    FRENSIE_REQUIRE_NO_THROW(
                      (*oarchive) << BOOST_SERIALIZATION_NVP( distribution ) );
  }

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

  Utility::UniformDistribution dist_a;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_a ) );
  FRENSIE_CHECK_EQUAL( dist_a, Utility::UniformDistribution( -1.0, 1.0, 0.5 ) );

  Utility::UniformDistribution dist_b;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_b ) );
  FRENSIE_CHECK_EQUAL( dist_b, Utility::UniformDistribution( 0.0, 2.0, 1.0 ) );

  std::shared_ptr<Utility::TabularUnivariateDistribution> shared_tab_dist;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> boost::serialization::make_nvp( "tab_distribution", shared_tab_dist ) );
  FRENSIE_CHECK_EQUAL( *dynamic_cast<Utility::UniformDistribution*>( shared_tab_dist.get() ),
                       *dynamic_cast<Utility::UniformDistribution*>( tab_distribution.get() ) );

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

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> boost::serialization::make_nvp( "distribution", shared_dist ) );
  FRENSIE_CHECK_EQUAL( *dynamic_cast<Utility::UniformDistribution*>( shared_dist.get() ),
                       *dynamic_cast<Utility::UniformDistribution*>( distribution.get() ) );
}

//---------------------------------------------------------------------------//
// Check that a unit-aware distribution can be archived
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( UnitAwareUniformDistribution,
                                   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_uniform_dist" );
  std::ostringstream archive_ostream;

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

    createOArchive( archive_base_name, archive_ostream, oarchive );

    Utility::UnitAwareUniformDistribution<si::energy,si::amount> dist_a( -1.0*si::joule, 1.0*si::joule, 0.5*si::mole );
    Utility::UnitAwareUniformDistribution<si::energy,si::amount> dist_b( 0.0*si::joule, 2.0*si::joule, 1.0*si::mole );

    FRENSIE_REQUIRE_NO_THROW(
                            (*oarchive) << BOOST_SERIALIZATION_NVP( dist_a ) );
    FRENSIE_REQUIRE_NO_THROW(
                            (*oarchive) << BOOST_SERIALIZATION_NVP( dist_b ) );
    FRENSIE_REQUIRE_NO_THROW(
       (*oarchive) << BOOST_SERIALIZATION_NVP( unit_aware_tab_distribution ) );
    FRENSIE_REQUIRE_NO_THROW(
           (*oarchive) << BOOST_SERIALIZATION_NVP( unit_aware_distribution ) );
  }

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

  Utility::UnitAwareUniformDistribution<si::energy,si::amount> dist_a;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_a ) );
  FRENSIE_CHECK_EQUAL( dist_a, (Utility::UnitAwareUniformDistribution<si::energy,si::amount>( -1.0*si::joule, 1.0*si::joule, 0.5*si::mole )) );

  Utility::UnitAwareUniformDistribution<si::energy,si::amount> dist_b;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_b ) );
  FRENSIE_CHECK_EQUAL( dist_b, (Utility::UnitAwareUniformDistribution<si::energy,si::amount>( 0.0*si::joule, 2.0*si::joule, 1.0*si::mole )) );

  std::shared_ptr<Utility::UnitAwareTabularUnivariateDistribution<si::energy,si::amount> > shared_tab_dist;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> boost::serialization::make_nvp( "unit_aware_tab_distribution", shared_tab_dist ) );
  FRENSIE_CHECK_EQUAL( (*dynamic_cast<Utility::UnitAwareUniformDistribution<si::energy,si::amount>*>( shared_tab_dist.get() )),
                       (*dynamic_cast<Utility::UnitAwareUniformDistribution<si::energy,si::amount>*>( unit_aware_tab_distribution.get() )) );

  std::shared_ptr<Utility::UnitAwareUnivariateDistribution<si::energy,si::amount> > shared_dist;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> boost::serialization::make_nvp( "unit_aware_distribution", shared_dist ) );
  FRENSIE_CHECK_EQUAL( (*dynamic_cast<Utility::UnitAwareUniformDistribution<si::energy,si::amount>*>( shared_dist.get() )),
                       (*dynamic_cast<Utility::UnitAwareUniformDistribution<si::energy,si::amount>*>( unit_aware_distribution.get() )) );
}

//---------------------------------------------------------------------------//
// Check that a unit-aware distribution can be scaled
FRENSIE_UNIT_TEST_TEMPLATE_EXPAND( UnitAwareUniformDistribution,
				   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<typename Utility::UnitTraits<IndepUnitA>::InverseUnit>::template GetQuantityType<double>::type InverseIndepQuantityA;

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

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

  // Copy from a unitless distribution to distribution type A (static method)
  Utility::UnitAwareUniformDistribution<IndepUnitA,DepUnitA>
    unit_aware_dist_a_copy = Utility::UnitAwareUniformDistribution<IndepUnitA,DepUnitA>::fromUnitlessDistribution( *dynamic_cast<Utility::UniformDistribution*>( distribution.get() ) );

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

  IndepQuantityA indep_quantity_a =
    Utility::QuantityTraits<IndepQuantityA>::initializeQuantity( -1.0 );
  InverseIndepQuantityA inv_indep_quantity_a =
    Utility::QuantityTraits<InverseIndepQuantityA>::initializeQuantity( 0.5 );
  DepQuantityA dep_quantity_a =
    Utility::QuantityTraits<DepQuantityA>::initializeQuantity( 2.0 );

  IndepQuantityB indep_quantity_b( indep_quantity_a );
  InverseIndepQuantityB inv_indep_quantity_b( inv_indep_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.evaluatePDF( indep_quantity_a ),
			inv_indep_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.evaluatePDF( indep_quantity_b ),
			inv_indep_quantity_b,
			1e-15 );

  Utility::setQuantity( indep_quantity_a, 1.0 );
  Utility::setQuantity( inv_indep_quantity_a, 0.5 );
  Utility::setQuantity( dep_quantity_a, 2.0 );

  indep_quantity_b = IndepQuantityB( indep_quantity_a );
  inv_indep_quantity_b = InverseIndepQuantityB( inv_indep_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.evaluatePDF( indep_quantity_a ),
			inv_indep_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.evaluatePDF( indep_quantity_b ),
			inv_indep_quantity_b,
			1e-15 );
}

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

FRENSIE_CUSTOM_UNIT_TEST_INIT()
{
  // Initialize the unit-aware distributions
  unit_aware_tab_distribution.reset(
	     new Utility::UnitAwareUniformDistribution<si::energy,si::amount>(
				      quantity<si::energy>( 0.0*si::joule ),
				      quantity<si::energy>( 1.0*si::joule ),
				      quantity<si::amount>( 1.0*si::mole ) ) );

  unit_aware_distribution = unit_aware_tab_distribution;

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

FRENSIE_CUSTOM_UNIT_TEST_SETUP_END();

//---------------------------------------------------------------------------//
// end tstUniformDistribution.cpp
//---------------------------------------------------------------------------//


/*
 [auto_generated]
 integrate.cpp

 [begin_description]
 tba.
 [end_description]

 Copyright 2009-2012 Karsten Ahnert
 Copyright 2009-2012 Mario Mulansky

 Distributed under the Boost Software License, Version 1.0.
 (See accompanying file LICENSE_1_0.txt or
 copy at http://www.boost.org/LICENSE_1_0.txt)
 */

#include <boost/config.hpp>
#ifdef BOOST_MSVC
    #pragma warning(disable:4996)
#endif

#define BOOST_TEST_MODULE odeint_eigen_integrate

#include <boost/numeric/odeint/integrate/integrate.hpp>
#include <boost/numeric/odeint/external/eigen/eigen.hpp>

#include <boost/test/unit_test.hpp>

#include "dummy_odes.hpp"

using namespace boost::unit_test;
using namespace boost::numeric::odeint;

BOOST_AUTO_TEST_SUITE( eigen_integrate )

BOOST_AUTO_TEST_CASE( test_const_sys )
{
    typedef Eigen::Matrix< double , 1 , 1 > state_type;
    state_type x;
    x[0] = 10.0;
    double t_start = 0.0 , t_end = 1.0 , dt = 0.1;
    integrate< double >( constant_system_functor_standard() , x , t_start , t_end , dt );
    BOOST_CHECK_CLOSE( x[0] , 11.0 , 1.0e-13 );
}

BOOST_AUTO_TEST_CASE( test_lorenz )
{
    typedef Eigen::Matrix< double , 3 , 1 > state_type;
    state_type x;
    x[0] = 10.0;
    x[1] = 10.0;
    x[2] = 10.0;
    double t_start = 0.0 , t_end = 1000.0 , dt = 0.1;
    integrate< double >( lorenz() , x , t_start , t_end , dt );

    std::vector< double > x2( 3 );
    x2[0] = 10.0;
    x2[1] = 10.0;
    x2[2] = 10.0;
    integrate( lorenz() , x2 , t_start , t_end , dt );

    BOOST_CHECK_CLOSE( x[0] , x2[0] , 1.0e-13 );
    BOOST_CHECK_CLOSE( x[1] , x2[1] , 1.0e-13 );
    BOOST_CHECK_CLOSE( x[2] , x2[2] , 1.0e-13 );
}

BOOST_AUTO_TEST_SUITE_END()


/**
 * Copyright (C) Omar Thor <omarthoro@gmail.com> - All Rights Reserved
 * Unauthorized copying of this file, via any medium is strictly prohibited
 * Proprietary and confidential
 *
 * Written by Omar Thor <omarthoro@gmail.com>, 2017
 */

#ifndef SP_ALGO_TESTING_GRADIENT_CHECK_HPP
#define	SP_ALGO_TESTING_GRADIENT_CHECK_HPP

#include <cmath>
#include <limits>
#include <random>
#include <vector>
#include <iterator>
#include <utility>
#include <memory>

#include <boost/assert.hpp>

#include "sp/config.hpp"
#include "random.hpp"
#include "sp/algo/nn/matrix.hpp"
#include "sp/algo/nn/types.hpp"
#include "sp/algo/nn/layer/detail/layers.hpp"
#include "layer/layer.hpp"

/**
 * @file Gradient check and validations
 *
 *
 * References:
 * - http://ufldl.stanford.edu/wiki/index.php/Gradient_checking_and_advanced_optimization
 * - http://cs231n.github.io/neural-networks-3/#gradcheck
 */

SP_ALGO_NN_NAMESPACE_BEGIN

namespace detail {

    /**
     * Helper
     * @param res
     */
    template<typename Tensor>
    void generate_uniform_into(Tensor& res) {
        auto& gen = random_generator::get();
        std:: uniform_real_distribution<float_t> dist(-1.0f, 1.0);
        std::for_each(res.data(), res.data() + res.size(), [&](auto& v) {
            v = dist(gen);
        });
    }

    template<typename T>
    void zero_range(T* start, T* end) {
        std::for_each(start, end, [](auto& v) { v = 0.0f; });
    }
}

/**
 * \brief Generate a random input_type
 */
template<typename Layer>
tensor_4 generate_inputs_for(const Layer& layer, const size_t& batch_size = 1) {
    using dims = typename Layer::input_dims;
    tensor_4 res(batch_size, dims::d, dims::h, dims::w);
    detail::generate_uniform_into(res);
    return res;
}

namespace detail {

    template<typename Container, typename TensorOrLayer>
    void layer_raw_data_vector_helper(Container& cont, TensorOrLayer& tol) {
        if constexpr (detail::is_layer_v<TensorOrLayer>) {
            if constexpr(detail::has_weight_and_delta_v<TensorOrLayer>) {
                BOOST_ASSERT(tol.w.size() > 0);
                cont.emplace_back(tol.w.data(), tol.w.data() + tol.w.size());
            }
            if constexpr(detail::has_bias_and_delta_v<TensorOrLayer>) {
                BOOST_ASSERT(tol.b.size() > 0);
                cont.emplace_back(tol.b.data(), tol.b.data() + tol.b.size());
            }
        } else {
            BOOST_ASSERT(tol.size() > 0);
            cont.emplace_back(tol.data(), tol.data() + tol.size());
        }
    }
}

/**
 * \brief A vector of pairs containing pointers to the range which the element
 *        of a layer, such as input, weight, bias are defined for.
 */
using layer_raw_data_vector_type = std::vector<std::pair<float_t*, float_t*>>;

/**
 * \brief Helper function that converts a layer and its data to a vector of
 *        pairwise range of pointers representing each tensor of different ranks
 *        such that they can be accessed index-wise regardless of dimensionality.
 */
template<typename ... TensorOrLayers>
layer_raw_data_vector_type
layer_raw_data_vector(TensorOrLayers& ... tols) {
   layer_raw_data_vector_type data;
   data.reserve(1 + sizeof...(TensorOrLayers));
   (detail::layer_raw_data_vector_helper(data, tols),  ...);
   return data;
}

/**
 * \brief Calculate the numerical gradient
 *
 *
 * Procedure is outlined in https://github.com/amaas/stanford_dl_ex/blob/master/ex1/grad_check.m
 * \param get_perturb_ref Returns a reference to the location to perturb.
 *        Function is invoked as get_perturb_ref(layer, in, out).
 * \param get_result Retrieves the output of the f(x). Same arguments as before.
 */
template<typename Layer>
float_t numerical_gradient(         Layer                               layer,
                                    tensor_4                          in,
                                    const std::pair<size_t, size_t>&    in_indexes,
                                    const size_t&                       out_index) {
    /**
     * sqrt(epsilon)
     * https://scicomp.stackexchange.com/questions/14355/choosing-epsilons
     */
    constexpr float_t h = std::sqrt(std::numeric_limits<float_t>::epsilon());
    const size_t batch_size = in.dimension(0);

    tensor_4 out;
    detail::prepare_and_zero_tensor<typename Layer::output_dims>(batch_size, out);

    /* retrieve array of pairs of pointers to floats, */
    auto in_data = layer_raw_data_vector(in);
    auto out_data = layer_raw_data_vector(out);

    auto& preturb = in_data[in_indexes.first].first[in_indexes.second];
    auto prev = preturb;

    preturb = prev - h;
    layer.forward_prop(in, out);
    float_t fx1 = out_data[0].first[out_index];

    //reset
    out.setZero();

    preturb = prev + h;
    layer.forward_prop(in, out);
    float_t fx2 = out_data[0].first[out_index];

    return (fx2 - fx1) / ( float_t(2.0f) * h);
}

/**
 * \brief Calculate the analytical gradient
 *
 * \param get_result Retrieves the output of the f'(x). Invoked as
 *        '''get_result(layer, in_delta,
 */
template<typename Layer>
float_t analytical_gradient(    Layer                               layer,
                                tensor_4                          prev_out,
                                const std::pair<size_t, size_t>&    in_indexes,
                                const size_t&                       out_index) {
    const size_t batch_size = prev_out.dimension(0);

    tensor_4 prev_delta;
    tensor_4 curr_out;
    tensor_4 curr_delta;

    /* initialize parameters */
    detail::prepare_and_zero_tensor<typename Layer::input_dims>(batch_size, prev_delta);
    detail::prepare_and_zero_tensor<typename Layer::output_dims>(batch_size, curr_out);
    detail::prepare_and_zero_tensor<typename Layer::output_dims>(batch_size, curr_delta);

    /* retrieve array of pairs of pointers to floats */
    auto in_grad= layer_raw_data_vector(prev_delta);
    auto out_grad = layer_raw_data_vector(curr_delta);

    out_grad[0].first[out_index] = 1.0f;

    layer.forward_prop(prev_out, curr_out);

    layer.backward_prop(prev_out, prev_delta, curr_out, curr_delta);

    return in_grad[in_indexes.first].first[in_indexes.second];
}

/**
 * \brief Calculates the relative error given the numerical gradient and the
 *        analytical gradient.
 */
float_t relative_error(float_t numerical_gradient, float_t analytical_gradient) {
    return (
            std::abs(numerical_gradient - analytical_gradient)
            /
            (std::max(std::abs(numerical_gradient), std::abs(analytical_gradient)))
    );
}

/**
 * \brief Selects a random element n out of Layer::output_dims::size
 */
template<typename Layer>
size_t gradient_random_output(const Layer& layer) {
    auto& rand = random_generator::get();
    return rand() % Layer::output_dims::size;
}

/**
 * \brief Selects a random input tensor and a random element n
 *        out of that
 */
template<typename Layer>
std::pair<size_t, size_t> gradient_random_input(const Layer& layer) {
    auto& rand = random_generator::get();
    size_t in_data_size = 1;
//    if constexpr(detail::has_weight_and_delta_v<Layer>) { ++in_data_size; }
    //    if constexpr(detail::has_bias_and_delta_v<Layer>)   { ++in_data_size; }
    auto data_idx = rand() % in_data_size;
    auto element_idx = rand() % Layer::input_dims::size;
    return {data_idx, element_idx};
}

SP_ALGO_NN_NAMESPACE_END

#endif	/* SP_ALGO_TESTING_GRADIENT_CHECK_HPP */


#include <boost/numeric/odeint/stepper/generation/generation_rosenbrock4.hpp>


/*
 * @file
 * state_plugin_estimator.hpp
 *
 *  Created on: 26.07.2018
 *      Author: tomlucas
 */

#ifndef PLUGINS_STATE_PLUGIN_ROTATION_VIZ_ESTIMATOR_HPP_
#define PLUGINS_STATE_PLUGIN_ROTATION_VIZ_ESTIMATOR_HPP_

#include "state_plugin_base.hpp"
#include "../Estimators/Estimator.hpp"
#include <zavi/utils/OSG_Utils.hpp>

#include <Eigen/Core>
#include <osg/Vec3d>
#include <osg/Quat>
namespace zavi::plugin{

	class StatePluginRotationViz : public StatePlugin{
public:

		StatePluginRotationViz(){

				}
				inline ::osg::Vec3d getPositionOSG(){
						return osg::Vec3d(0,0,0);}
				inline Eigen::Matrix3d getPositionError(){
						throw "getPositionError: Not implemented in StatePluginRotationViz";
				}

				inline ::osg::Quat getRotationOSG(){
					static double r =0,p=0,y=1;
					double rd=0.0001;
					double pd=-rd*cos(p)*(sin(y)-cos(y))/(sin(y)+cos(y));
					double yd=-pd*sin(y)-rd*(cos(p)*cos(y)+sin(p));
					r+=rd;
					p+=pd;
					y+=yd;
					Eigen::Matrix3d ry, rp,rr, rotation;
					ry << cos(y), -sin(y), 0,sin(y), cos(y), 0 ,0,0,1;
					rp << cos(p), 0,sin(p),0,1,0,-sin(p),0,cos(p);
					rr <<1,0,0,0,cos(r),-sin(r),0,sin(r),cos(r);
					rotation=ry*rp*rr;

					return zavi::osg_viz::eigenToOSGQuat(Eigen::Quaterniond(rotation));
				}

private:

	};

}//zavi::plugin

#endif /* PLUGINS_STATE_PLUGIN_ROTATION_VIZ_ESTIMATOR_HPP_ */


#include "testsuite.h"
#include <blitz/array.h>

//#undef BZ_FAST_COMPILE

BZ_USING_NAMESPACE(blitz)

// test that member function functors work. (There was a bug where
// they would use an out-of-scope temporary copy of the class itself
// if BZ_ETPARM was pass-by-value.)

class testclass {
private:
  testclass(const testclass&) {
    BZTEST(0);
  };

public:
  int d_;

  testclass(int d) : d_(d) {};

  int func(int d) const {cout << "In func, this is " << this << endl; return d_*d; };

  BZ_DECLARE_MEMBER_FUNCTION(testclass, func);
};

int main()
{
  Array<int,1> a(3), b(3);
  a=1,2,3;

  testclass t(2);
  cout << "&t is " << &t << endl;
  // this expression should not require copying t.
  b=t.func(2*a);

  return 0;
}