// TODO: write conversion functions Julia ↔ C++
#include <Eigen/Dense>
#include <stdlib.h>
#define BOOST_BIND_GLOBAL_PLACEHOLDERS
#include <igl/copyleft/cgal/mesh_boolean.h>
#include <igl/copyleft/cgal/minkowski_sum.h>
#include <igl/copyleft/cgal/piecewise_constant_winding_number.h>
#include <igl/offset_surface.h>
#include <igl/decimate.h>
#include <igl/loop.h>
#include <igl/copyleft/cgal/intersect_with_half_space.h>
#include "iglwrap.h"
#include <boost/bind/bind.hpp>

using namespace boost::placeholders;
using namespace Eigen;

typedef Matrix<double,Dynamic,3> VertexMatrix;
typedef Matrix<int,Dynamic,3> FaceMatrix;

// typedef Matrix<double, Dynamic, 3, RowMajor> vertices_t;
// typedef Matrix<int, Dynamic, 3, RowMajor> faces_t;

// Eigen::IOFormat CommaInitFmt(32, DontAlignCols, ", ", ", ", "", "", " << ", ";");

struct Mesh {/*««*/
	MatrixXd v;
	MatrixXi f;
//   Matrix<double,Dynamic,3> v;
//   Matrix<int,Dynamic,3> f;
  Mesh(): v(), f() { }
  Mesh(int nv, int nf, const double *mv, const int *mf, int d = 3):
			v(nv, d), f(nf, d) {
    for(int i = 0; i < nv; i++) {
      for(int j = 0; j < d; j++) {
				v(i, j) = mv[d*i+j];
      }
    }
    for(int i = 0; i < nf; i++) {
      for(int j = 0; j < d; j++) {
	f(i, j) = mf[d*i+j]-1;
      }
    };
  };
};/*»»*/

void to_jl(Mesh m, int *nv, int *nf, double **mv, int **mf) {/*««*/
  // sets mv to zero in case of failure
  *nv = m.v.rows();
  *nf = m.f.rows();
  *mv = (double *) malloc(3*(*nv)*sizeof(double));
  if(*mv == 0) {
    return;
  }
  *mf = (int *) malloc(3* (*nf)*sizeof(int));
  if(*mf == 0) {
    free(mv);
    *mv = 0;
    return;
  }
  for(int i = 0; i < *nv; i++) {
    for(int j = 0; j < 3; j++) {
      (*mv)[3*i+j] = m.v(i, j);
    }
  }
  for(int i = 0; i < *nf; i++) {
    for(int j = 0; j < 3; j++) {
      (*mf)[3*i+j] = 1 + m.f(i,j);
    }
  }
}/*»»*/

int mesh_boolean(/*««*/
  int op,
  int nv1, int nf1, const double *mv1, const int *mf1,
  int nv2, int nf2, const double *mv2, const int *mf2,
  int *nv3, int *nf3, double **mv3, int **mf3, int **index) {

  Mesh m1(nv1, nf1, mv1, mf1),
       m2(nv2, nf2, mv2, mf2),
       m3;
  VectorXi j;
  igl::copyleft::cgal::mesh_boolean(m1.v,m1.f,m2.v,m2.f,
  igl::MeshBooleanType(op), m3.v,m3.f, j);

  *index = (int *) malloc(m3.f.rows()*sizeof(int));
  if (*index == 0) {
    return -1;
  }
  for(int i = 0; i < m3.f.rows(); i++) {
    (*index)[i] = j(i)+1;
  }
  to_jl(m3, nv3, nf3, mv3, mf3);
  if(*mv3 == 0) {
    free(*index);
    return -1;
  }
  return 0;
}/*»»*/
int minkowski_sum(/*««*/
  int nv1, int nf1, const double *mv1, const int *mf1,
  int nv2, int nf2, const double *mv2, const int *mf2, int dim2,
  int *nv3, int *nf3, double **mv3, int **mf3, int **index) {

  Mesh m1(nv1, nf1, mv1, mf1),
       m2(nv2, nf2, mv2, mf2, dim2),
       m3;
  MatrixXi j;
// 	std::cout << "mesh m1: "
// 		<< m1.v.rows() << " vertices:\n" << m1.v << "\n"
// 		<< m1.f.rows() << " faces:\n" << m1.f << "\n\n";
// 	std::cout << "mesh m2: "
// 		<< m2.v.rows() << " vertices:\n" << m2.v << "\n"
// 		<< m2.f.rows() << " faces:\n" << m2.f << "\n\n";
  igl::copyleft::cgal::minkowski_sum(m1.v,m1.f,m2.v,m2.f, true,
  	m3.v,m3.f, j);
// 	std::cout << "mesh m3: "
// 		<< m3.v.rows() << " vertices:\n" << m3.v << "\n"
// 		<< m3.f.rows() << " faces:\n" << m3.f << "\n\n";
// 	std::cout << "j has " << j.rows() << " rows:\n"  << j << "\n";

	int n3 = m3.f.rows();
  *index = (int *) malloc(2*n3*sizeof(int));
  if (*index == 0) {
    return -1;
  }
  for(int i = 0; i < n3; i++) {
    (*index)[i] = j(i,0)+1;
		(*index)[i+n3] = j(i,1)+1;
  }
  to_jl(m3, nv3, nf3, mv3, mf3);
  if(*mv3 == 0) {
    free(*index);
    return -1;
  }
  return 0;
}/*»»*/

int mesh_is_pwn(int nv, int nf, const double *mv, const int *mf) {
  Mesh m(nv, nf, mv, mf);
  return (int)igl::copyleft::cgal::piecewise_constant_winding_number(m.v, m.f);
}

int offset_surface(/*««*/
	int nv, int nf, const double *mv, const int *mf,
	double level, int grid,
	int *nvout, int *nfout, double **mvout, int **mfout) {

	Mesh m(nv, nf, mv, mf), mout;
	MatrixXd GV;
	MatrixXi side;
	MatrixXd S;

	igl::offset_surface(m.v, m.f, level, grid,
		igl::SignedDistanceType::SIGNED_DISTANCE_TYPE_DEFAULT,
		mout.v, mout.f, GV, side, S);

	to_jl(mout, nvout, nfout, mvout, mfout);
	if(*mvout == 0) {
		return -1;
	}
	return 0;
}/*»»*/
int decimate(int nv, int nf, const double *mv, const int *mf, int faces,/*««*/
	int *nvout, int *nfout, double **mvout, int **mfout, int **index) {
	Mesh m(nv, nf, mv, mf), mout;
	VectorXi fidx, vidx;
	igl::decimate(m.v, m.f, faces,
		mout.v, mout.f, fidx, vidx);

  *index = (int *) malloc(fidx.rows()*sizeof(int));
  if (*index == 0) {
    return -1;
  }
  for(int i = 0; i < fidx.rows(); i++) {
    (*index)[i] = fidx(i)+1;
  }
	to_jl(mout, nvout, nfout, mvout, mfout);
	if(*mvout == 0) {
		free(*index);
		return -1;
	}
	return 0;
}/*»»*/
int loop(int nv, int nf, const double *mv, const int *mf, int n,/*««*/
	int *nvout, int *nfout, double **mvout, int **mfout) {
	Mesh m(nv, nf, mv, mf), mout;
	VectorXi fidx, vidx;

	igl::loop(m.v, m.f, mout.v, mout.f, n);

	to_jl(mout, nvout, nfout, mvout, mfout);
	return 0;
}/*»»*/

int intersect_with_half_space(int nv, int nf, /*««*/
	const double *mv, const int *mf,
	const struct vec3d *p, const struct vec3d *n,
	int *nvout, int *nfout, double **mvout, int **mfout, int **index) {
	Mesh m(nv, nf, mv, mf), mout;
	Vector3d vp; vp << p->x, p->y, p->z;
	Vector3d vn; vn << n->x, n->y, n->z;
	VectorXi fidx;

	igl::copyleft::cgal::intersect_with_half_space(m.v, m.f, vp, vn,
		mout.v, mout.f, fidx);

  *index = (int *) malloc(fidx.rows()*sizeof(int));
  if (*index == 0) {
    return -1;
  }
  for(int i = 0; i < fidx.rows(); i++) {
    (*index)[i] = fidx(i)+1;
  }
	to_jl(mout, nvout, nfout, mvout, mfout);
	if(*mvout == 0) {
		free(*index);
		return -1;
	}
	return 0;
}/*»»*/

// vim: noet ts=2 sw=2 fmr=««,»»:


/* Author: Wolfgang Bangerth, University of Heidelberg, 1999 */

/*    $Id: step-4.cc 27657 2012-11-21 13:19:08Z bangerth $       */
/*                                                                */
/*    Copyright (C) 1999-2012 by the deal.II authors */
/*                                                                */
/*    This file is subject to QPL and may not be  distributed     */
/*    without copyright and license information. Please refer     */
/*    to the file deal.II/doc/license.html for the  text  and     */
/*    further information on this license.                        */

// @sect3{Include files}

// The first few (many?) include files have already been used in the previous
// example, so we will not explain their meaning here again.
#include <deal.II/grid/tria.h>
#include <deal.II/dofs/dof_handler.h>
#include <deal.II/grid/grid_generator.h>
#include <deal.II/grid/tria_accessor.h>
#include <deal.II/grid/tria_iterator.h>
#include <deal.II/dofs/dof_accessor.h>
#include <deal.II/fe/fe_q.h>
#include <deal.II/dofs/dof_tools.h>
#include <deal.II/fe/fe_values.h>
#include <deal.II/base/quadrature_lib.h>
#include <deal.II/base/function.h>
#include <deal.II/numerics/vector_tools.h>
#include <deal.II/numerics/matrix_tools.h>
#include <deal.II/lac/vector.h>
#include <deal.II/lac/full_matrix.h>
#include <deal.II/lac/sparse_matrix.h>
#include <deal.II/lac/compressed_sparsity_pattern.h>
#include <deal.II/lac/solver_cg.h>
#include <deal.II/lac/precondition.h>

#include <deal.II/numerics/data_out.h>
#include <fstream>
#include <iostream>

// This is new, however: in the previous example we got some unwanted output
// from the linear solvers. If we want to suppress it, we have to include this
// file and add a single line somewhere to the program (see the main()
// function below for that):
#include <deal.II/base/logstream.h>

// The final step, as in previous programs, is to import all the deal.II class
// and function names into the global namespace:
using namespace dealii;

// @sect3{The Step4 class template}

// This is again the same Step4 class as in the previous
// example. The only difference is that we have now declared it as a class
// with a template parameter, and the template parameter is of course the
// spatial dimension in which we would like to solve the Laplace equation. Of
// course, several of the member variables depend on this dimension as well,
// in particular the Triangulation class, which has to represent
// quadrilaterals or hexahedra, respectively. Apart from this, everything is
// as before.
template <int dim>
class Step4
{
public:
  Step4 ();
  void run ();

private:
  void make_grid ();
  void setup_system();
  void assemble_system ();
  void solve ();
  void output_results () const;

  Triangulation<dim>   triangulation;
  FE_Q<dim>            fe;
  DoFHandler<dim>      dof_handler;

  SparsityPattern      sparsity_pattern;
  SparseMatrix<double> system_matrix;

  Vector<double>       solution;
  Vector<double>       system_rhs;
};

// @sect3{Right hand side and boundary values}

// In the following, we declare two more classes denoting the right hand side
// and the non-homogeneous Dirichlet boundary values. Both are functions of a
// dim-dimensional space variable, so we declare them as templates as well.
//
// Each of these classes is derived from a common, abstract base class
// Function, which declares the common interface which all functions have to
// follow. In particular, concrete classes have to overload the
// value function, which takes a point in dim-dimensional space
// as parameters and shall return the value at that point as a
// double variable.
//
// The value function takes a second argument, which we have here
// named component: This is only meant for vector valued
// functions, where you may want to access a certain component of the vector
// at the point p. However, our functions are scalar, so we need
// not worry about this parameter and we will not use it in the implementation
// of the functions. Inside the library's header files, the Function base
// class's declaration of the value function has a default value
// of zero for the component, so we will access the value
// function of the right hand side with only one parameter, namely the point
// where we want to evaluate the function. A value for the component can then
// simply be omitted for scalar functions.
//
// Note that the C++ language forces us to declare and define a constructor to
// the following classes even though they are empty. This is due to the fact
// that the base class has no default constructor (i.e. one without
// arguments), even though it has a constructor which has default values for
// all arguments.
template <int dim>
class RightHandSide : public Function<dim>
{
public:
  RightHandSide () : Function<dim>() {}

  virtual double value (const Point<dim>   &p,
                        const unsigned int  component = 0) const;
};

template <int dim>
class BoundaryValues : public Function<dim>
{
public:
  BoundaryValues () : Function<dim>() {}

  virtual double value (const Point<dim>   &p,
                        const unsigned int  component = 0) const;
};

// For this example, we choose as right hand side function to function
// $4(x^4+y^4)$ in 2D, or $4(x^4+y^4+z^4)$ in 3D. We could write this
// distinction using an if-statement on the space dimension, but here is a
// simple way that also allows us to use the same function in 1D (or in 4D, if
// you should desire to do so), by using a short loop.  Fortunately, the
// compiler knows the size of the loop at compile time (remember that at the
// time when you define the template, the compiler doesn't know the value of
// dim, but when it later encounters a statement or declaration
// RightHandSide@<2@>, it will take the template, replace all
// occurrences of dim by 2 and compile the resulting function); in other
// words, at the time of compiling this function, the number of times the body
// will be executed is known, and the compiler can optimize away the overhead
// needed for the loop and the result will be as fast as if we had used the
// formulas above right away.
//
// The last thing to note is that a Point@<dim@> denotes a point
// in dim-dimensionsal space, and its individual components (i.e. $x$, $y$,
// ... coordinates) can be accessed using the () operator (in fact, the []
// operator will work just as well) with indices starting at zero as usual in
// C and C++.
template <int dim>
double RightHandSide<dim>::value (const Point<dim> &p,
                                  const unsigned int /*component*/) const
{
  double return_value = 0;
  for (unsigned int i=0; i<dim; ++i)
    return_value += 4*std::pow(p(i), 4);

  return return_value;
}

// As boundary values, we choose x*x+y*y in 2D, and x*x+y*y+z*z in 3D. This
// happens to be equal to the square of the vector from the origin to the
// point at which we would like to evaluate the function, irrespective of the
// dimension. So that is what we return:
template <int dim>
double BoundaryValues<dim>::value (const Point<dim> &p,
                                   const unsigned int /*component*/) const
{
  return p.square();
}

// @sect3{Implementation of the Step4 class}

// Next for the implementation of the class template that makes use of the
// functions above. As before, we will write everything as templates that have
// a formal parameter dim that we assume unknown at the time we
// define the template functions. Only later, the compiler will find a
// declaration of Step4@<2@> (in the main function,
// actually) and compile the entire class with dim replaced by 2,
// a process referred to as `instantiation of a template'. When doing so, it
// will also replace instances of RightHandSide@<dim@> by
// RightHandSide@<2@> and instantiate the latter class from the
// class template.
//
// In fact, the compiler will also find a declaration Step4@<3@>
// in main(). This will cause it to again go back to the general
// Step4@<dim@> template, replace all occurrences of
// dim, this time by 3, and compile the class a second time. Note
// that the two instantiations Step4@<2@> and
// Step4@<3@> are completely independent classes; their only
// common feature is that they are both instantiated from the same general
// template, but they are not convertible into each other, for example, and
// share no code (both instantiations are compiled completely independently).

// @sect4{Step4::Step4}

// After this introduction, here is the constructor of the Step4
// class. It specifies the desired polynomial degree of the finite elements
// and associates the DoFHandler to the triangulation just as in the previous
// example program, step-3:
template <int dim>
Step4<dim>::Step4 ()
  :
  fe (1),
  dof_handler (triangulation)
{}

// @sect4{Step4::make_grid}

// Grid creation is something inherently dimension dependent. However, as long
// as the domains are sufficiently similar in 2D or 3D, the library can
// abstract for you. In our case, we would like to again solve on the square
// $[-1,1]\times [-1,1]$ in 2D, or on the cube $[-1,1] \times [-1,1] \times
// [-1,1]$ in 3D; both can be termed GridGenerator::hyper_cube(), so we may
// use the same function in whatever dimension we are. Of course, the
// functions that create a hypercube in two and three dimensions are very much
// different, but that is something you need not care about. Let the library
// handle the difficult things.
template <int dim>
void Step4<dim>::make_grid ()
{
  GridGenerator::hyper_cube (triangulation, -1, 1);
  triangulation.refine_global (4);

  std::cout << "   Number of active cells: "
            << triangulation.n_active_cells()
            << std::endl
            << "   Total number of cells: "
            << triangulation.n_cells()
            << std::endl;
}

// @sect4{Step4::setup_system}

// This function looks exactly like in the previous example, although it
// performs actions that in their details are quite different if
// dim happens to be 3. The only significant difference from a
// user's perspective is the number of cells resulting, which is much higher
// in three than in two space dimensions!
template <int dim>
void Step4<dim>::setup_system ()
{
  dof_handler.distribute_dofs (fe);

  std::cout << "   Number of degrees of freedom: "
            << dof_handler.n_dofs()
            << std::endl;

  CompressedSparsityPattern c_sparsity(dof_handler.n_dofs());
  DoFTools::make_sparsity_pattern (dof_handler, c_sparsity);
  sparsity_pattern.copy_from(c_sparsity);

  system_matrix.reinit (sparsity_pattern);

  solution.reinit (dof_handler.n_dofs());
  system_rhs.reinit (dof_handler.n_dofs());
}

// @sect4{Step4::assemble_system}

// Unlike in the previous example, we would now like to use a non-constant
// right hand side function and non-zero boundary values. Both are tasks that
// are readily achieved with only a few new lines of code in the assemblage of
// the matrix and right hand side.
//
// More interesting, though, is the way we assemble matrix and right hand side
// vector dimension independently: there is simply no difference to the
// two-dimensional case. Since the important objects used in this function
// (quadrature formula, FEValues) depend on the dimension by way of a template
// parameter as well, they can take care of setting up properly everything for
// the dimension for which this function is compiled. By declaring all classes
// which might depend on the dimension using a template parameter, the library
// can make nearly all work for you and you don't have to care about most
// things.
template <int dim>
void Step4<dim>::assemble_system ()
{
  QGauss<dim>  quadrature_formula(2);

  // We wanted to have a non-constant right hand side, so we use an object of
  // the class declared above to generate the necessary data. Since this right
  // hand side object is only used locally in the present function, we declare
  // it here as a local variable:
  const RightHandSide<dim> right_hand_side;

  // Compared to the previous example, in order to evaluate the non-constant
  // right hand side function we now also need the quadrature points on the
  // cell we are presently on (previously, we only required values and
  // gradients of the shape function from the FEValues object, as well as the
  // quadrature weights, FEValues::JxW() ). We can tell the FEValues object to
  // do for us by also giving it the #update_quadrature_points flag:
  FEValues<dim> fe_values (fe, quadrature_formula,
                           update_values   | update_gradients |
                           update_quadrature_points | update_JxW_values);

  // We then again define a few abbreviations. The values of these variables
  // of course depend on the dimension which we are presently using. However,
  // the FE and Quadrature classes do all the necessary work for you and you
  // don't have to care about the dimension dependent parts:
  const unsigned int   dofs_per_cell = fe.dofs_per_cell;
  const unsigned int   n_q_points    = quadrature_formula.size();

  FullMatrix<double>   cell_matrix (dofs_per_cell, dofs_per_cell);
  Vector<double>       cell_rhs (dofs_per_cell);

  std::vector<unsigned int> local_dof_indices (dofs_per_cell);

  // Next, we again have to loop over all cells and assemble local
  // contributions.  Note, that a cell is a quadrilateral in two space
  // dimensions, but a hexahedron in 3D. In fact, the
  // active_cell_iterator data type is something different,
  // depending on the dimension we are in, but to the outside world they look
  // alike and you will probably never see a difference although the classes
  // that this typedef stands for are in fact completely unrelated:
  typename DoFHandler<dim>::active_cell_iterator
  cell = dof_handler.begin_active(),
  endc = dof_handler.end();

  for (; cell!=endc; ++cell)
    {
      fe_values.reinit (cell);
      cell_matrix = 0;
      cell_rhs = 0;

      // Now we have to assemble the local matrix and right hand side. This is
      // done exactly like in the previous example, but now we revert the
      // order of the loops (which we can safely do since they are independent
      // of each other) and merge the loops for the local matrix and the local
      // vector as far as possible to make things a bit faster.
      //
      // Assembling the right hand side presents the only significant
      // difference to how we did things in step-3: Instead of using a
      // constant right hand side with value 1, we use the object representing
      // the right hand side and evaluate it at the quadrature points:
      for (unsigned int q_point=0; q_point<n_q_points; ++q_point)
        for (unsigned int i=0; i<dofs_per_cell; ++i)
          {
            for (unsigned int j=0; j<dofs_per_cell; ++j)
              cell_matrix(i,j) += (fe_values.shape_grad (i, q_point) *
                                   fe_values.shape_grad (j, q_point) *
                                   fe_values.JxW (q_point));

            cell_rhs(i) += (fe_values.shape_value (i, q_point) *
                            right_hand_side.value (fe_values.quadrature_point (q_point)) *
                            fe_values.JxW (q_point));
          }
      // As a final remark to these loops: when we assemble the local
      // contributions into cell_matrix(i,j), we have to multiply
      // the gradients of shape functions $i$ and $j$ at point q_point and
      // multiply it with the scalar weights JxW. This is what actually
      // happens: fe_values.shape_grad(i,q_point) returns a
      // dim dimensional vector, represented by a
      // Tensor@<1,dim@> object, and the operator* that
      // multiplies it with the result of
      // fe_values.shape_grad(j,q_point) makes sure that the
      // dim components of the two vectors are properly
      // contracted, and the result is a scalar floating point number that
      // then is multiplied with the weights. Internally, this operator* makes
      // sure that this happens correctly for all dim components
      // of the vectors, whether dim be 2, 3, or any other space
      // dimension; from a user's perspective, this is not something worth
      // bothering with, however, making things a lot simpler if one wants to
      // write code dimension independently.

      // With the local systems assembled, the transfer into the global matrix
      // and right hand side is done exactly as before, but here we have again
      // merged some loops for efficiency:
      cell->get_dof_indices (local_dof_indices);
      for (unsigned int i=0; i<dofs_per_cell; ++i)
        {
          for (unsigned int j=0; j<dofs_per_cell; ++j)
            system_matrix.add (local_dof_indices[i],
                               local_dof_indices[j],
                               cell_matrix(i,j));

          system_rhs(local_dof_indices[i]) += cell_rhs(i);
        }
    }

  // As the final step in this function, we wanted to have non-homogeneous
  // boundary values in this example, unlike the one before. This is a simple
  // task, we only have to replace the ZeroFunction used there by an object of
  // the class which describes the boundary values we would like to use
  // (i.e. the BoundaryValues class declared above):
  std::map<unsigned int,double> boundary_values;
  VectorTools::interpolate_boundary_values (dof_handler,
                                            0,
                                            BoundaryValues<dim>(),
                                            boundary_values);
  MatrixTools::apply_boundary_values (boundary_values,
                                      system_matrix,
                                      solution,
                                      system_rhs);
}

// @sect4{Step4::solve}

// Solving the linear system of equations is something that looks almost
// identical in most programs. In particular, it is dimension independent, so
// this function is copied verbatim from the previous example.
template <int dim>
void Step4<dim>::solve ()
{
  SolverControl           solver_control (1000, 1e-12);
  SolverCG<>              solver (solver_control);
  solver.solve (system_matrix, solution, system_rhs,
                PreconditionIdentity());

  // We have made one addition, though: since we suppress output from the
  // linear solvers, we have to print the number of iterations by hand.
  std::cout << "   " << solver_control.last_step()
            << " CG iterations needed to obtain convergence."
            << std::endl;
}

// @sect4{Step4::output_results}

// This function also does what the respective one did in step-3. No changes
// here for dimension independence either.
//
// The only difference to the previous example is that we want to write output
// in VTK format, rather than for gnuplot. VTK format is currently the most
// widely used one and is supported by a number of visualization programs such
// as Visit and Paraview (for ways to obtain these programs see the ReadMe
// file of deal.II). To write data in this format, we simply replace the
// data_out.write_gnuplot call by
// data_out.write_vtk.
//
// Since the program will run both 2d and 3d versions of the laplace solver,
// we use the dimension in the filename to generate distinct filenames for
// each run (in a better program, one would check whether dim can
// have other values than 2 or 3, but we neglect this here for the sake of
// brevity).
template <int dim>
void Step4<dim>::output_results () const
{
  DataOut<dim> data_out;

  data_out.attach_dof_handler (dof_handler);
  data_out.add_data_vector (solution, "solution");

  data_out.build_patches ();

  std::ofstream output (dim == 2 ?
                        "solution-2d.vtk" :
                        "solution-3d.vtk");
  data_out.write_vtk (output);
}

// @sect4{Step4::run}

// This is the function which has the top-level control over everything. Apart
// from one line of additional output, it is the same as for the previous
// example.
template <int dim>
void Step4<dim>::run ()
{
  std::cout << "Solving problem in " << dim << " space dimensions." << std::endl;

  make_grid();
  setup_system ();
  assemble_system ();
  solve ();
  output_results ();
}

// @sect3{The main function}

// And this is the main function. It also looks mostly like in step-3, but if
// you look at the code below, note how we first create a variable of type
// Step4@<2@> (forcing the compiler to compile the class template
// with dim replaced by 2) and run a 2d simulation,
// and then we do the whole thing over in 3d.
//
// In practice, this is probably not what you would do very frequently (you
// probably either want to solve a 2d problem, or one in 3d, but not both at
// the same time). However, it demonstrates the mechanism by which we can
// simply change which dimension we want in a single place, and thereby force
// the compiler to recompile the dimension independent class templates for the
// dimension we request. The emphasis here lies on the fact that we only need
// to change a single place. This makes it rather trivial to debug the program
// in 2d where computations are fast, and then switch a single place to a 3 to
// run the much more computing intensive program in 3d for `real'
// computations.
//
// Each of the two blocks is enclosed in braces to make sure that the
// laplace_problem_2d variable goes out of scope (and releases
// the memory it holds) before we move on to allocate memory for the 3d
// case. Without the additional braces, the laplace_problem_2d
// variable would only be destroyed at the end of the function, i.e. after
// running the 3d problem, and would needlessly hog memory while the 3d run
// could actually use it.
//
// Finally, the first line of the function is used to suppress some output.
// Remember that in the previous example, we had the output from the linear
// solvers about the starting residual and the number of the iteration where
// convergence was detected. This can be suppressed through the
// deallog.depth_console(0) call.
//
// The rationale here is the following: the deallog (i.e. deal-log, not
// de-allog) variable represents a stream to which some parts of the library
// write output. It redirects this output to the console and if required to a
// file. The output is nested in a way so that each function can use a prefix
// string (separated by colons) for each line of output; if it calls another
// function, that may also use its prefix which is then printed after the one
// of the calling function. Since output from functions which are nested deep
// below is usually not as important as top-level output, you can give the
// deallog variable a maximal depth of nested output for output to console and
// file. The depth zero which we gave here means that no output is written. By
// changing it you can get more information about the innards of the library.
int main ()
{
  deallog.depth_console (0);
  {
    Step4<2> laplace_problem_2d;
    laplace_problem_2d.run ();
  }

  {
    Step4<3> laplace_problem_3d;
    laplace_problem_3d.run ();
  }

  return 0;
}


#define BOOST_MPL11_NO_ASSERTIONS
#include <boost/mpl11/list.hpp>

<%= render('_main.erb') do |f, state, xs|
    list = "boost::mpl11::list<#{xs.join(', ')}>"
    "boost::mpl11::foldl<#{f}, #{state}, #{list}>::type"
end %>


//---------------------------------------------------------------------------//
//!
//! \file   tstHistogramPartiallyTabularBivariateDistribution.cpp
//! \author Alex Robinson
//! \brief  The histogram partially tabular two-dimensional dist. unit tests
//!
//---------------------------------------------------------------------------//

// Std Lib Includes
#include <iostream>
#include <memory>

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

// FRENSIE Includes
#include "Utility_HistogramPartiallyTabularBasicBivariateDistribution.hpp"
#include "Utility_DeltaDistribution.hpp"
#include "Utility_UniformDistribution.hpp"
#include "Utility_ExponentialDistribution.hpp"
#include "Utility_ElectronVoltUnit.hpp"
#include "Utility_BarnUnit.hpp"
#include "Utility_UnitTestHarnessWithMain.hpp"
#include "ArchiveTestHelpers.hpp"

//---------------------------------------------------------------------------//
// Testing Types
//---------------------------------------------------------------------------//

using boost::units::quantity;
using Utility::Units::MegaElectronVolt;
using Utility::Units::MeV;
using Utility::Units::Barn;
using Utility::Units::barn;
using Utility::Units::barns;
namespace cgs = boost::units::cgs;

typedef TestArchiveHelper::TestArchives TestArchives;

//---------------------------------------------------------------------------//
// Testing Variables
//---------------------------------------------------------------------------//
std::shared_ptr<Utility::UnitAwarePartiallyTabularBasicBivariateDistribution<MegaElectronVolt,cgs::length,Barn> >
  unit_aware_distribution;

std::shared_ptr<Utility::PartiallyTabularBasicBivariateDistribution> distribution;

//---------------------------------------------------------------------------//
// Tests.
//---------------------------------------------------------------------------//
// Check that the upper bound of the distribution primary independent variable
// can be returned
FRENSIE_UNIT_TEST( HistogramPartiallyTabularBasicBivariateDistribution,
                   getUpperBoundOfPrimaryIndepVar )
{
  FRENSIE_CHECK_EQUAL( distribution->getUpperBoundOfPrimaryIndepVar(), 2.0 );
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the unit-aware distribution primary
// independent variable can be returned
FRENSIE_UNIT_TEST( UnitAwareHistogramPartiallyTabularBasicBivariateDistribution,
                   getUpperBoundOfPrimaryIndepVar )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getUpperBoundOfPrimaryIndepVar(), 2.0*MeV );
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the distribution primary independent variable
// can be returned
FRENSIE_UNIT_TEST( HistogramPartiallyTabularBasicBivariateDistribution,
                   getLowerBoundOfPrimaryIndepVar )
{
  FRENSIE_CHECK_EQUAL( distribution->getLowerBoundOfPrimaryIndepVar(), 0.0 );
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the unit-aware distribution primary
// independent variable can be returned
FRENSIE_UNIT_TEST( UnitAwareHistogramPartiallyTabularBasicBivariateDistribution,
                   getLowerBoundOfPrimaryIndepVar )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getLowerBoundOfPrimaryIndepVar(), 0.0*MeV );
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the conditional distribution independent
// variable can be returned
FRENSIE_UNIT_TEST( HistogramPartiallyTabularBasicBivariateDistribution,
                   getUpperBoundOfSecondaryConditionalIndepVar )
{
  // Before the first bin - no extension
  FRENSIE_CHECK_EQUAL(
                 distribution->getUpperBoundOfSecondaryConditionalIndepVar(-1.0), 0.0 );

  // Before the first bin - with extension
  distribution->extendBeyondPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL( distribution->getLowerBoundOfSecondaryConditionalIndepVar(-1.0),
                       0.0 );

  distribution->limitToPrimaryIndepLimits();

  // On the second bin (first bin boundary = second bin boundary)
  FRENSIE_CHECK_EQUAL( distribution->getUpperBoundOfSecondaryConditionalIndepVar(0.0),
                       std::numeric_limits<double>::infinity() );

  // In the second bin
  FRENSIE_CHECK_EQUAL( distribution->getUpperBoundOfSecondaryConditionalIndepVar(0.5),
                       std::numeric_limits<double>::infinity() );

  // On the third bin
  FRENSIE_CHECK_EQUAL(
                 distribution->getUpperBoundOfSecondaryConditionalIndepVar(1.0), 10.0 );

  // In the third bin
  FRENSIE_CHECK_EQUAL(
                 distribution->getUpperBoundOfSecondaryConditionalIndepVar(1.5), 10.0 );

  // On the upper bin boundary - this should be treated as part of the third
  // bin (special case)
  FRENSIE_CHECK_EQUAL(
                 distribution->getUpperBoundOfSecondaryConditionalIndepVar(2.0), 10.0 );

  // After the third bin - no extension
  FRENSIE_CHECK_EQUAL(
                  distribution->getUpperBoundOfSecondaryConditionalIndepVar(3.0), 0.0 );

  // After the third bin - with extension
  distribution->extendBeyondPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL(
                  distribution->getUpperBoundOfSecondaryConditionalIndepVar(3.0), 0.0 );

  distribution->limitToPrimaryIndepLimits();
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the unit-aware conditional distribution
// independent variable can be returned
FRENSIE_UNIT_TEST( UnitAwareHistogramPartiallyTabularBasicBivariateDistribution,
                   getUpperBoundOfSecondaryConditionalIndepVar )
{
  // Before the first bin - no extension
  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getUpperBoundOfSecondaryConditionalIndepVar(-1.0*MeV),
         0.0*cgs::centimeter );

  // Before the first bin - with extension
  unit_aware_distribution->extendBeyondPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getUpperBoundOfSecondaryConditionalIndepVar(-1.0*MeV),
         0.0*cgs::centimeter );

  unit_aware_distribution->limitToPrimaryIndepLimits();

  // On the second bin (first bin boundary = second bin boundary)
  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getUpperBoundOfSecondaryConditionalIndepVar(0.0*MeV),
         std::numeric_limits<double>::infinity()*cgs::centimeter );

  // In the second bin
  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getUpperBoundOfSecondaryConditionalIndepVar(0.5*MeV),
         std::numeric_limits<double>::infinity()*cgs::centimeter );

  // On the third bin
  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getUpperBoundOfSecondaryConditionalIndepVar(1.0*MeV),
         10.0*cgs::centimeter );

  // In the third bin
  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getUpperBoundOfSecondaryConditionalIndepVar(1.5*MeV),
         10.0*cgs::centimeter );

  // On the upper bin boundary - this should be treated as part of the third
  // bin (special case)
  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getUpperBoundOfSecondaryConditionalIndepVar(2.0*MeV),
         10.0*cgs::centimeter );

  // After the third bin - no extension
  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getUpperBoundOfSecondaryConditionalIndepVar(3.0*MeV),
         0.0*cgs::centimeter );

  // After the third bin - with extension
  unit_aware_distribution->extendBeyondPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getUpperBoundOfSecondaryConditionalIndepVar(3.0*MeV),
         0.0*cgs::centimeter );

  unit_aware_distribution->limitToPrimaryIndepLimits();
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the conditional distribution independent
// variable can be returned
FRENSIE_UNIT_TEST( HistogramPartiallyTabularBasicBivariateDistribution,
                   getLowerBoundOfSecondaryConditionalIndepVar )
{
  // Before the first bin - no extension
  FRENSIE_CHECK_EQUAL(
                 distribution->getLowerBoundOfSecondaryConditionalIndepVar(-1.0), 0.0 );

  // Before the first bin - with extension
  distribution->extendBeyondPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL(
                 distribution->getLowerBoundOfSecondaryConditionalIndepVar(-1.0), 0.0 );

  distribution->limitToPrimaryIndepLimits();

  // On the second bin (first bin boundary = second bin boundary)
  FRENSIE_CHECK_EQUAL(
                  distribution->getLowerBoundOfSecondaryConditionalIndepVar(0.0), 0.0 );

  // In the second bin
  FRENSIE_CHECK_EQUAL(
                  distribution->getLowerBoundOfSecondaryConditionalIndepVar(0.5), 0.0 );

  // On the third bin
  FRENSIE_CHECK_EQUAL(
                  distribution->getLowerBoundOfSecondaryConditionalIndepVar(1.0), 0.0 );

  // In the third bin
  FRENSIE_CHECK_EQUAL(
                  distribution->getLowerBoundOfSecondaryConditionalIndepVar(1.5), 0.0 );

  // On the upper bin boundary - this should be treated as part of the third
  // bin (special case)
  FRENSIE_CHECK_EQUAL(
                  distribution->getLowerBoundOfSecondaryConditionalIndepVar(2.0), 0.0 );

  // After the third bin - no extension
  FRENSIE_CHECK_EQUAL(
                  distribution->getLowerBoundOfSecondaryConditionalIndepVar(3.0), 0.0 );

  // After the third bin - with extension
  distribution->extendBeyondPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL(
                  distribution->getLowerBoundOfSecondaryConditionalIndepVar(3.0), 0.0 );

  distribution->limitToPrimaryIndepLimits();
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the unit-aware conditional distribution
// independent variable can be returned
FRENSIE_UNIT_TEST( UnitAwareHistogramPartiallyTabularBasicBivariateDistribution,
                   getLowerBoundOfSecondaryConditionalIndepVar )
{
  // Before the first bin - no extension
  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getLowerBoundOfSecondaryConditionalIndepVar(-1.0*MeV),
         0.0*cgs::centimeter );

  // Before the first bin - with extensino
  unit_aware_distribution->extendBeyondPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getLowerBoundOfSecondaryConditionalIndepVar(-1.0*MeV),
         0.0*cgs::centimeter );

  unit_aware_distribution->limitToPrimaryIndepLimits();

  // On the second bin (first bin boundary = second bin boundary)
  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getLowerBoundOfSecondaryConditionalIndepVar(0.0*MeV),
         0.0*cgs::centimeter );

  // In the second bin
  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getLowerBoundOfSecondaryConditionalIndepVar(0.5*MeV),
         0.0*cgs::centimeter );

  // On the third bin
  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getLowerBoundOfSecondaryConditionalIndepVar(1.0*MeV),
         0.0*cgs::centimeter );

  // In the third bin
  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getLowerBoundOfSecondaryConditionalIndepVar(1.5*MeV),
         0.0*cgs::centimeter );

  // On the upper bin boundary - this should be treated as part of the third
  // bin (special case)
  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getLowerBoundOfSecondaryConditionalIndepVar(2.0*MeV),
         0.0*cgs::centimeter );

  // After the third bin - no extension
  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getLowerBoundOfSecondaryConditionalIndepVar(3.0*MeV),
         0.0*cgs::centimeter );

  // After the third bin - with extension
  unit_aware_distribution->extendBeyondPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL(
         unit_aware_distribution->getLowerBoundOfSecondaryConditionalIndepVar(2.0*MeV),
         0.0*cgs::centimeter );

  unit_aware_distribution->limitToPrimaryIndepLimits();
}

//---------------------------------------------------------------------------//
// Check if the distribution is tabular in the primary dimension
FRENSIE_UNIT_TEST( HistogramPartiallyTabularBasicBivariateDistribution,
                   isPrimaryDimensionTabular )
{
  FRENSIE_CHECK( distribution->isPrimaryDimensionTabular() );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is tabular in the primary dimension
FRENSIE_UNIT_TEST( UnitAwareHistogramPartiallyTabularBasicBivariateDistribution,
                   isPrimaryDimensionTabular )
{
  FRENSIE_CHECK( unit_aware_distribution->isPrimaryDimensionTabular() );
}

//---------------------------------------------------------------------------//
// Check if the distribution is continuous in the primary dimension
FRENSIE_UNIT_TEST( HistogramPartiallyTabularBasicBivariateDistribution,
                   isPrimaryDimensionContinuous )
{
  FRENSIE_CHECK( distribution->isPrimaryDimensionContinuous() );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution is continuous in the primary dimension
FRENSIE_UNIT_TEST( UnitAwareHistogramPartiallyTabularBasicBivariateDistribution,
                   isPrimaryDimensionContinuous )
{
  FRENSIE_CHECK( unit_aware_distribution->isPrimaryDimensionContinuous() );
}

//---------------------------------------------------------------------------//
// Check if the distribution has the same bounds as another distribution
FRENSIE_UNIT_TEST( HistogramPartiallyTabularBasicBivariateDistribution,
                   hasSamePrimaryBounds )
{
  std::unique_ptr<Utility::BasicBivariateDistribution> other_distribution;

  {
    std::vector<double> primary_grid( {0.0, 2.0} );

    std::vector<std::shared_ptr<const Utility::UnivariateDistribution> >
      secondary_dists( 2 );

    secondary_dists[0].reset( new Utility::DeltaDistribution( 0.0 ) );
    secondary_dists[1].reset( new Utility::DeltaDistribution( 1.0 ) );

    other_distribution.reset(
             new Utility::HistogramPartiallyTabularBasicBivariateDistribution(
                                             primary_grid, secondary_dists ) );
  }

  FRENSIE_CHECK( distribution->hasSamePrimaryBounds( *other_distribution ) );

  {
    std::vector<double> primary_grid( {-1.0, 2.0} );

    std::vector<std::shared_ptr<const Utility::UnivariateDistribution> >
      secondary_dists( 2 );

    secondary_dists[0].reset( new Utility::DeltaDistribution( 0.0 ) );
    secondary_dists[1].reset( new Utility::DeltaDistribution( 1.0 ) );

    other_distribution.reset(
              new Utility::HistogramPartiallyTabularBasicBivariateDistribution(
                                             primary_grid, secondary_dists ) );
  }

  FRENSIE_CHECK( !distribution->hasSamePrimaryBounds( *other_distribution ) );

  {
    std::vector<double> primary_grid( {0.0, 3.0} );

    std::vector<std::shared_ptr<const Utility::UnivariateDistribution> >
      secondary_dists( 2 );

    secondary_dists[0].reset( new Utility::DeltaDistribution( 0.0 ) );
    secondary_dists[1].reset( new Utility::DeltaDistribution( 1.0 ) );

    other_distribution.reset(
             new Utility::HistogramPartiallyTabularBasicBivariateDistribution(
                                             primary_grid, secondary_dists ) );
  }

  FRENSIE_CHECK( !distribution->hasSamePrimaryBounds( *other_distribution ) );

  {
    std::vector<double> primary_grid( {-1.0, 3.0} );

    std::vector<std::shared_ptr<const Utility::UnivariateDistribution> >
      secondary_dists( 2 );

    secondary_dists[0].reset( new Utility::DeltaDistribution( 0.0 ) );
    secondary_dists[1].reset( new Utility::DeltaDistribution( 1.0 ) );

    other_distribution.reset(
              new Utility::HistogramPartiallyTabularBasicBivariateDistribution(
                                             primary_grid, secondary_dists ) );
  }

  FRENSIE_CHECK( !distribution->hasSamePrimaryBounds( *other_distribution ) );
}

//---------------------------------------------------------------------------//
// Check if the unit-aware distribution has the same bounds as another
// distribution
FRENSIE_UNIT_TEST( UnitAwareHistogramPartiallyTabularBasicBivariateDistribution,
                   hasSamePrimaryBounds )
{
  std::unique_ptr<Utility::UnitAwareBasicBivariateDistribution<MegaElectronVolt,cgs::length,Barn> > other_distribution;

  {
    std::vector<quantity<MegaElectronVolt> >
      primary_grid( {0.0*MeV, 2.0*MeV} );

    std::vector<std::shared_ptr<const Utility::UnitAwareUnivariateDistribution<cgs::length,Barn> > > secondary_dists( 2 );

    secondary_dists[0].reset( new Utility::UnitAwareDeltaDistribution<cgs::length,Barn>( 0.0*cgs::centimeter ) );
    secondary_dists[1].reset( new Utility::UnitAwareDeltaDistribution<cgs::length,Barn>( 1.0*cgs::centimeter ) );

    other_distribution.reset(
          new Utility::UnitAwareHistogramPartiallyTabularBasicBivariateDistribution<MegaElectronVolt,cgs::length,Barn>(
                                             primary_grid, secondary_dists ) );
  }

  FRENSIE_CHECK( unit_aware_distribution->hasSamePrimaryBounds( *other_distribution ) );

  {
    std::vector<quantity<MegaElectronVolt> >
      primary_grid( {-1.0*MeV, 2.0*MeV} );

    std::vector<std::shared_ptr<const Utility::UnitAwareUnivariateDistribution<cgs::length,Barn> > > secondary_dists( 2 );

    secondary_dists[0].reset( new Utility::UnitAwareDeltaDistribution<cgs::length,Barn>( 0.0*cgs::centimeter ) );
    secondary_dists[1].reset( new Utility::UnitAwareDeltaDistribution<cgs::length,Barn>( 1.0*cgs::centimeter ) );

    other_distribution.reset(
          new Utility::UnitAwareHistogramPartiallyTabularBasicBivariateDistribution<MegaElectronVolt,cgs::length,Barn>(
                                             primary_grid, secondary_dists ) );
  }

  FRENSIE_CHECK( !unit_aware_distribution->hasSamePrimaryBounds( *other_distribution ) );

  {
    std::vector<quantity<MegaElectronVolt> >
      primary_grid( {0.0*MeV, 3.0*MeV} );

    std::vector<std::shared_ptr<const Utility::UnitAwareUnivariateDistribution<cgs::length,Barn> > > secondary_dists( 2 );

    secondary_dists[0].reset( new Utility::UnitAwareDeltaDistribution<cgs::length,Barn>( 0.0*cgs::centimeter ) );
    secondary_dists[1].reset( new Utility::UnitAwareDeltaDistribution<cgs::length,Barn>( 1.0*cgs::centimeter ) );

    other_distribution.reset(
          new Utility::UnitAwareHistogramPartiallyTabularBasicBivariateDistribution<MegaElectronVolt,cgs::length,Barn>(
                                             primary_grid, secondary_dists ) );
  }

  FRENSIE_CHECK( !unit_aware_distribution->hasSamePrimaryBounds( *other_distribution ) );

  {
    std::vector<quantity<MegaElectronVolt> >
      primary_grid( {-1.0*MeV, 3.0*MeV} );

    std::vector<std::shared_ptr<const Utility::UnitAwareUnivariateDistribution<cgs::length,Barn> > > secondary_dists( 2 );

    secondary_dists[0].reset( new Utility::UnitAwareDeltaDistribution<cgs::length,Barn>( 0.0*cgs::centimeter ) );
    secondary_dists[1].reset( new Utility::UnitAwareDeltaDistribution<cgs::length,Barn>( 1.0*cgs::centimeter ) );

    other_distribution.reset(
          new Utility::UnitAwareHistogramPartiallyTabularBasicBivariateDistribution<MegaElectronVolt,cgs::length,Barn>(
                                             primary_grid, secondary_dists ) );
  }

  FRENSIE_CHECK( !unit_aware_distribution->hasSamePrimaryBounds( *other_distribution ) );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be evaluated
FRENSIE_UNIT_TEST( HistogramPartiallyTabularBasicBivariateDistribution, evaluate )
{
  // Before the first bin - no extension
  FRENSIE_CHECK_EQUAL( distribution->evaluate( -1.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( -1.0, 0.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( -1.0, 1.0 ), 0.0 );

  // Before the first bin - with extension
  distribution->extendBeyondPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL( distribution->evaluate( -1.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( -1.0, 0.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( -1.0, 1.0 ), 0.0 );

  distribution->limitToPrimaryIndepLimits();

  // On the second bin (first bin boundary = second bin boundary)
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 0.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 0.0, 0.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 0.0, 1.0 ), exp( -1.0 ) );

  // In the second bin
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 0.5, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 0.5, 0.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 0.5, 1.0 ), exp( -1.0 ) );

  // On the third bin
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1.0, 0.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1.0, 1.0 ), 1.0 );

  // In the third bin
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1.5, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1.5, 0.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 1.5, 1.0 ), 1.0 );

  // On the upper bin boundary - this should be treated as part of the third
  // bin (special case)
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 2.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 2.0, 0.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 2.0, 1.0 ), 1.0 );

  // After the third bin - no extension
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 3.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 3.0, 0.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 3.0, 1.0 ), 0.0 );

  // After the third bin - with extension
  distribution->extendBeyondPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL( distribution->evaluate( 3.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 3.0, 0.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluate( 3.0, 1.0 ), 0.0 );

  distribution->limitToPrimaryIndepLimits();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be evaluated
FRENSIE_UNIT_TEST( UnitAwareHistogramPartiallyTabularBasicBivariateDistribution,
                   evaluate )
{
  // Before the first bin - no extension
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( -1.0*MeV, -1.0*cgs::centimeter ), 0.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( -1.0*MeV, 0.0*cgs::centimeter ), 0.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( -1.0*MeV, 1.0*cgs::centimeter ), 0.0*barn );

  // Before the first bin - with extension
  unit_aware_distribution->extendBeyondPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( -1.0*MeV, -1.0*cgs::centimeter ), 0.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( -1.0*MeV, 0.0*cgs::centimeter ), 1.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( -1.0*MeV, 1.0*cgs::centimeter ), 0.0*barn );

  unit_aware_distribution->limitToPrimaryIndepLimits();

  // On the second bin (first bin boundary = second bin boundary)
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.0*MeV, -1.0*cgs::centimeter ), 0.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.0*MeV, 0.0*cgs::centimeter ), 1.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.0*MeV, 1.0*cgs::centimeter ), exp( -1.0 )*barns );

  // In the second bin
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.5*MeV, -1.0*cgs::centimeter ), 0.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.5*MeV, 0.0*cgs::centimeter ), 1.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.5*MeV, 1.0*cgs::centimeter ), exp( -1.0 )*barns );

  // On the third bin
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 1.0*MeV, -1.0*cgs::centimeter ), 0.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 1.0*MeV, 0.0*cgs::centimeter ), 1.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 1.0*MeV, 1.0*cgs::centimeter ), 1.0*barn );

  // In the third bin
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 1.5*MeV, -1.0*cgs::centimeter ), 0.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 1.5*MeV, 0.0*cgs::centimeter ), 1.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 1.5*MeV, 1.0*cgs::centimeter ), 1.0*barn );

  // On the upper bin boundary - this should be treated as part of the third
  // bin (special case)
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 2.0*MeV, -1.0*cgs::centimeter ), 0.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 2.0*MeV, 0.0*cgs::centimeter ), 1.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 2.0*MeV, 1.0*cgs::centimeter ), 1.0*barn );

  // After the third bin - no extension
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 3.0*MeV, -1.0*cgs::centimeter ), 0.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 3.0*MeV, 0.0*cgs::centimeter ), 0.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 3.0*MeV, 1.0*cgs::centimeter ), 0.0*barn );

  // After the third bin - with extension
  unit_aware_distribution->extendBeyondPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 3.0*MeV, -1.0*cgs::centimeter ), 0.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 3.0*MeV, 0.0*cgs::centimeter ), 1.0*barn );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 3.0*MeV, 1.0*cgs::centimeter ), 0.0*barn );

  unit_aware_distribution->limitToPrimaryIndepLimits();
}

//---------------------------------------------------------------------------//
// Check that the secondary conditional PDF can be evaluated
FRENSIE_UNIT_TEST( HistogramPartiallyTabularBasicBivariateDistribution,
                   evaluateSecondaryConditionalPDF )
{
  // Before the first bin - no extension
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( -1.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( -1.0, 0.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( -1.0, 1.0 ), 0.0 );

  // Before the first bin - with extension
  distribution->extendBeyondPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( -1.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( -1.0, 0.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( -1.0, 1.0 ), 0.0 );

  distribution->limitToPrimaryIndepLimits();

  // On the second bin (first bin boundary = second bin boundary)
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 0.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 0.0, 0.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 0.0, 1.0 ), exp( -1.0 ) );

  // In the second bin
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 0.5, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 0.5, 0.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 0.5, 1.0 ), exp( -1.0 ) );

  // On the third bin
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 1.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 1.0, 0.0 ), 0.1 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 1.0, 1.0 ), 0.1 );

  // In the third bin
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 1.5, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 1.5, 0.0 ), 0.1 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 1.5, 1.0 ), 0.1 );

  // On the upper bin boundary - this should be treated as part of the third
  // bin (special case)
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 2.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 2.0, 0.0 ), 0.1 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 2.0, 1.0 ), 0.1 );

  // After the third bin - no extension
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 3.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 3.0, 0.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 3.0, 1.0 ), 0.0 );

  // After the third bin - with extension
  distribution->extendBeyondPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 3.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 3.0, 0.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( distribution->evaluateSecondaryConditionalPDF( 3.0, 1.0 ), 0.0 );

  distribution->limitToPrimaryIndepLimits();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware secondary conditional PDF can be evaluated
FRENSIE_UNIT_TEST( UnitAwareHistogramPartiallyTabularBasicBivariateDistribution,
                   evaluateSecondaryConditionalPDF )
{
  // Before the first bin - no extension
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( -1.0*MeV, -1.0*cgs::centimeter ), 0.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( -1.0*MeV, 0.0*cgs::centimeter ), 0.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( -1.0*MeV, 1.0*cgs::centimeter ), 0.0/cgs::centimeter );

  // Before the first bin - with extension
  unit_aware_distribution->extendBeyondPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( -1.0*MeV, -1.0*cgs::centimeter ), 0.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( -1.0*MeV, 0.0*cgs::centimeter ), 1.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( -1.0*MeV, 1.0*cgs::centimeter ), 0.0/cgs::centimeter );

  unit_aware_distribution->limitToPrimaryIndepLimits();

  // On the second bin (first bin boundary = second bin boundary)
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 0.0*MeV, -1.0*cgs::centimeter ), 0.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 0.0*MeV, 0.0*cgs::centimeter ), 1.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 0.0*MeV, 1.0*cgs::centimeter ), exp( -1.0 )/cgs::centimeter );

  // In the second bin
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 0.5*MeV, -1.0*cgs::centimeter ), 0.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 0.5*MeV, 0.0*cgs::centimeter ), 1.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 0.5*MeV, 1.0*cgs::centimeter ), exp( -1.0 )/cgs::centimeter );

  // On the third bin
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 1.0*MeV, -1.0*cgs::centimeter ), 0.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 1.0*MeV, 0.0*cgs::centimeter ), 0.1/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 1.0*MeV, 1.0*cgs::centimeter ), 0.1/cgs::centimeter );

  // In the third bin
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 1.5*MeV, -1.0*cgs::centimeter ), 0.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 1.5*MeV, 0.0*cgs::centimeter ), 0.1/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 1.5*MeV, 1.0*cgs::centimeter ), 0.1/cgs::centimeter );

  // On the upper bin boundary - this should be treated as part of the third
  // bin (special case)
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 2.0*MeV, -1.0*cgs::centimeter ), 0.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 2.0*MeV, 0.0*cgs::centimeter ), 0.1/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 2.0*MeV, 1.0*cgs::centimeter ), 0.1/cgs::centimeter );

  // After the third bin - no extension
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 3.0*MeV, -1.0*cgs::centimeter ), 0.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 3.0*MeV, 0.0*cgs::centimeter ), 0.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 3.0*MeV, 1.0*cgs::centimeter ), 0.0/cgs::centimeter );

  // After the third bin - with extension
  unit_aware_distribution->extendBeyondPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 3.0*MeV, -1.0*cgs::centimeter ), 0.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 3.0*MeV, 0.0*cgs::centimeter ), 1.0/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluateSecondaryConditionalPDF( 3.0*MeV, 1.0*cgs::centimeter ), 0.0/cgs::centimeter );

  unit_aware_distribution->limitToPrimaryIndepLimits();
}

//---------------------------------------------------------------------------//
// Check that a secondary conditional PDF can be sampled
FRENSIE_UNIT_TEST( HistogramPartiallyTabularBasicBivariateDistribution,
                   sampleSecondaryConditional )
{
  // Before the first bin - no extension
  FRENSIE_CHECK_THROW( distribution->sampleSecondaryConditional( -1.0 ),
              std::logic_error );

  // Before the first bin - with extension
  distribution->extendBeyondPrimaryIndepLimits();

  double sample = distribution->sampleSecondaryConditional( -1.0 );

  FRENSIE_CHECK_EQUAL( sample, 0.0 );

  distribution->limitToPrimaryIndepLimits();

  // On the second bin (first bin boundary = second bin boundary)
  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.5;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = distribution->sampleSecondaryConditional( 0.0 );

  FRENSIE_CHECK_EQUAL( sample, 0.0 );

  sample = distribution->sampleSecondaryConditional( 0.0 );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.6931471805599453, 1e-9 );

  // In the second bin
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = distribution->sampleSecondaryConditional( 0.5 );

  FRENSIE_CHECK_EQUAL( sample, 0.0 );

  sample = distribution->sampleSecondaryConditional( 0.5 );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.6931471805599453, 1e-9 );

  // On the third bin
  fake_stream.push_back( 1.0 - 1e-15 );
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = distribution->sampleSecondaryConditional( 1.0 );

  FRENSIE_CHECK_EQUAL( sample, 0.0 );

  sample = distribution->sampleSecondaryConditional( 1.0 );

  FRENSIE_CHECK_EQUAL( sample, 5.0 );

  sample = distribution->sampleSecondaryConditional( 1.0 );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 10.0, 1e-9 );

  // In the third bin
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = distribution->sampleSecondaryConditional( 1.5 );

  FRENSIE_CHECK_EQUAL( sample, 0.0 );

  sample = distribution->sampleSecondaryConditional( 1.5 );

  FRENSIE_CHECK_EQUAL( sample, 5.0 );

  sample = distribution->sampleSecondaryConditional( 1.5 );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 10.0, 1e-9 );

  // On the upper bin boundary - this should be treated as part of the third
  // bin (special case)
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = distribution->sampleSecondaryConditional( 2.0 );

  FRENSIE_CHECK_EQUAL( sample, 0.0 );

  sample = distribution->sampleSecondaryConditional( 2.0 );

  FRENSIE_CHECK_EQUAL( sample, 5.0 );

  sample = distribution->sampleSecondaryConditional( 2.0 );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 10.0, 1e-9 );

  // After the third bin - no extension
  Utility::RandomNumberGenerator::unsetFakeStream();

  FRENSIE_CHECK_THROW( distribution->sampleSecondaryConditional( 3.0 ),
              std::logic_error );

  // After the third bin - with extension
  distribution->extendBeyondPrimaryIndepLimits();

  sample = distribution->sampleSecondaryConditional( 3.0 );

  FRENSIE_CHECK_EQUAL( sample, 0.0 );

  distribution->limitToPrimaryIndepLimits();
}

//---------------------------------------------------------------------------//
// Check that a unit-aware secondary conditional PDF can be sampled
FRENSIE_UNIT_TEST( UnitAwareHistogramPartiallyTabularBasicBivariateDistribution,
                   sampleSecondaryConditional )
{
  // Before the first bin - no extension
  FRENSIE_CHECK_THROW( unit_aware_distribution->sampleSecondaryConditional( -1.0*MeV ),
              std::logic_error );

  // Before the first bin - with extension
  unit_aware_distribution->extendBeyondPrimaryIndepLimits();

  quantity<cgs::length> sample = unit_aware_distribution->sampleSecondaryConditional( -1.0*MeV );

  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );

  unit_aware_distribution->limitToPrimaryIndepLimits();

  // On the second bin (first bin boundary = second bin boundary)
  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.5;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = unit_aware_distribution->sampleSecondaryConditional( 0.0*MeV );

  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );

  sample = unit_aware_distribution->sampleSecondaryConditional( 0.0*MeV );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.6931471805599453*cgs::centimeter, 1e-9 );

  // In the second bin
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = unit_aware_distribution->sampleSecondaryConditional( 0.5*MeV );

  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );

  sample = unit_aware_distribution->sampleSecondaryConditional( 0.5*MeV );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.6931471805599453*cgs::centimeter, 1e-9 );

  // On the third bin
  fake_stream.push_back( 1.0 - 1e-15 );
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = unit_aware_distribution->sampleSecondaryConditional( 1.0*MeV );

  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );

  sample = unit_aware_distribution->sampleSecondaryConditional( 1.0*MeV );

  FRENSIE_CHECK_EQUAL( sample, 5.0*cgs::centimeter );

  sample = unit_aware_distribution->sampleSecondaryConditional( 1.0*MeV );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 10.0*cgs::centimeter, 1e-9 );

  // In the third bin
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = unit_aware_distribution->sampleSecondaryConditional( 1.5*MeV );

  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );

  sample = unit_aware_distribution->sampleSecondaryConditional( 1.5*MeV );

  FRENSIE_CHECK_EQUAL( sample, 5.0*cgs::centimeter );

  sample = unit_aware_distribution->sampleSecondaryConditional( 1.5*MeV );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 10.0*cgs::centimeter, 1e-9 );

  // On the upper bin boundary - this should be treated as part of the third
  // bin (special case)
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = unit_aware_distribution->sampleSecondaryConditional( 2.0*MeV );

  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );

  sample = unit_aware_distribution->sampleSecondaryConditional( 2.0*MeV );

  FRENSIE_CHECK_EQUAL( sample, 5.0*cgs::centimeter );

  sample = unit_aware_distribution->sampleSecondaryConditional( 2.0*MeV );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 10.0*cgs::centimeter, 1e-9 );

  // After the third bin - no extension
  Utility::RandomNumberGenerator::unsetFakeStream();

  FRENSIE_CHECK_THROW( unit_aware_distribution->sampleSecondaryConditional( 3.0*MeV ),
              std::logic_error );

  // After the third bin - with extension
  unit_aware_distribution->extendBeyondPrimaryIndepLimits();

  sample = unit_aware_distribution->sampleSecondaryConditional( 3.0*MeV );

  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );

  unit_aware_distribution->limitToPrimaryIndepLimits();
}

//---------------------------------------------------------------------------//
// Check that a secondary conditional PDF can be sampled
FRENSIE_UNIT_TEST( HistogramPartiallyTabularBasicBivariateDistribution,
                   sampleSecondaryConditionalAndRecordTrials )
{
  Utility::DistributionTraits::Counter trials = 0u;

  // Before the first bin - no extension
  FRENSIE_CHECK_THROW( distribution->sampleSecondaryConditionalAndRecordTrials( -1.0, trials ),
              std::logic_error );
  FRENSIE_CHECK_EQUAL( trials, 0u );

  // Before the first bin - with extension
  distribution->extendBeyondPrimaryIndepLimits();

  double sample = distribution->sampleSecondaryConditionalAndRecordTrials( -1.0 , trials );

  distribution->limitToPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL( sample, 0.0 );
  FRENSIE_CHECK_EQUAL( trials, 1u );

  // On the second bin (first bin boundary = second bin boundary)
  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.5;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = distribution->sampleSecondaryConditionalAndRecordTrials( 0.0, trials );

  FRENSIE_CHECK_EQUAL( sample, 0.0 );
  FRENSIE_CHECK_EQUAL( trials, 2u );

  sample = distribution->sampleSecondaryConditionalAndRecordTrials( 0.0, trials );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.6931471805599453, 1e-9 );
  FRENSIE_CHECK_EQUAL( trials, 3u );

  // In the second bin
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = distribution->sampleSecondaryConditionalAndRecordTrials( 0.5, trials );

  FRENSIE_CHECK_EQUAL( sample, 0.0 );
  FRENSIE_CHECK_EQUAL( trials, 4u );

  sample = distribution->sampleSecondaryConditionalAndRecordTrials( 0.5, trials );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.6931471805599453, 1e-9 );
  FRENSIE_CHECK_EQUAL( trials, 5u );

  // On the third bin
  fake_stream.push_back( 1.0 - 1e-15 );
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = distribution->sampleSecondaryConditionalAndRecordTrials( 1.0, trials );

  FRENSIE_CHECK_EQUAL( sample, 0.0 );
  FRENSIE_CHECK_EQUAL( trials, 6u );

  sample = distribution->sampleSecondaryConditionalAndRecordTrials( 1.0, trials );

  FRENSIE_CHECK_EQUAL( sample, 5.0 );
  FRENSIE_CHECK_EQUAL( trials, 7u );

  sample = distribution->sampleSecondaryConditionalAndRecordTrials( 1.0, trials );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 10.0, 1e-9 );
  FRENSIE_CHECK_EQUAL( trials, 8u );

  // In the third bin
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = distribution->sampleSecondaryConditionalAndRecordTrials( 1.5, trials );

  FRENSIE_CHECK_EQUAL( sample, 0.0 );
  FRENSIE_CHECK_EQUAL( trials, 9u );

  sample = distribution->sampleSecondaryConditionalAndRecordTrials( 1.5, trials );

  FRENSIE_CHECK_EQUAL( sample, 5.0 );
  FRENSIE_CHECK_EQUAL( trials, 10u );

  sample = distribution->sampleSecondaryConditionalAndRecordTrials( 1.5, trials );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 10.0, 1e-9 );
  FRENSIE_CHECK_EQUAL( trials, 11u );

  // On the upper bin boundary - this should be treated as part of the third
  // bin (special case)
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = distribution->sampleSecondaryConditionalAndRecordTrials( 2.0, trials );

  FRENSIE_CHECK_EQUAL( sample, 0.0 );
  FRENSIE_CHECK_EQUAL( trials, 12u );

  sample = distribution->sampleSecondaryConditionalAndRecordTrials( 2.0, trials );

  FRENSIE_CHECK_EQUAL( sample, 5.0 );
  FRENSIE_CHECK_EQUAL( trials, 13u );

  sample = distribution->sampleSecondaryConditionalAndRecordTrials( 2.0, trials );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 10.0, 1e-9 );
  FRENSIE_CHECK_EQUAL( trials, 14u );

  // After the third bin - no extension
  Utility::RandomNumberGenerator::unsetFakeStream();

  FRENSIE_CHECK_THROW( distribution->sampleSecondaryConditionalAndRecordTrials( 3.0, trials ),
              std::logic_error );

  // After the third bin - with extension
  distribution->extendBeyondPrimaryIndepLimits();

  sample = distribution->sampleSecondaryConditionalAndRecordTrials( 3.0, trials );

  FRENSIE_CHECK_EQUAL( sample, 0.0 );
  FRENSIE_CHECK_EQUAL( trials, 15u );

  distribution->limitToPrimaryIndepLimits();
}

//---------------------------------------------------------------------------//
// Check that a unit-aware secondary conditional PDF can be sampled
FRENSIE_UNIT_TEST( UnitAwareHistogramPartiallyTabularBasicBivariateDistribution,
                   sampleSecondaryConditionalAndRecordTrials )
{
  Utility::DistributionTraits::Counter trials = 0u;

  // Before the first bin - no extension
  FRENSIE_CHECK_THROW( unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( -1.0*MeV, trials ),
              std::logic_error );
  FRENSIE_CHECK_EQUAL( trials, 0u );

  // Before the first bin - with extension
  unit_aware_distribution->extendBeyondPrimaryIndepLimits();

  quantity<cgs::length> sample = unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( -1.0*MeV, trials );

  unit_aware_distribution->limitToPrimaryIndepLimits();

  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( trials, 1u );

  // On the second bin (first bin boundary = second bin boundary)
  std::vector<double> fake_stream( 2 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.5;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( 0.0*MeV, trials );

  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( trials, 2u );

  sample = unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( 0.0*MeV, trials );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.6931471805599453*cgs::centimeter, 1e-9 );
  FRENSIE_CHECK_EQUAL( trials, 3u );

  // In the second bin
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( 0.5*MeV, trials );

  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( trials, 4u );

  sample = unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( 0.5*MeV, trials );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.6931471805599453*cgs::centimeter, 1e-9 );
  FRENSIE_CHECK_EQUAL( trials, 5u );

  // On the third bin
  fake_stream.push_back( 1.0 - 1e-15 );
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( 1.0*MeV, trials );

  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( trials, 6u );

  sample = unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( 1.0*MeV, trials );

  FRENSIE_CHECK_EQUAL( sample, 5.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( trials, 7u );

  sample = unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( 1.0*MeV, trials );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 10.0*cgs::centimeter, 1e-9 );
  FRENSIE_CHECK_EQUAL( trials, 8u );

  // In the third bin
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( 1.5*MeV, trials );

  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( trials, 9u );

  sample = unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( 1.5*MeV, trials );

  FRENSIE_CHECK_EQUAL( sample, 5.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( trials, 10u );

  sample = unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( 1.5*MeV, trials );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 10.0*cgs::centimeter, 1e-9 );
  FRENSIE_CHECK_EQUAL( trials, 11u );

  // On the upper bin boundary - this should be treated as part of the third
  // bin (special case)
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  sample = unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( 2.0*MeV, trials );

  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( trials, 12u );

  sample = unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( 2.0*MeV, trials );

  FRENSIE_CHECK_EQUAL( sample, 5.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( trials, 13u );

  sample = unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( 2.0*MeV, trials );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 10.0*cgs::centimeter, 1e-9 );
  FRENSIE_CHECK_EQUAL( trials, 14u );

  // After the third bin - no extension
  Utility::RandomNumberGenerator::unsetFakeStream();

  FRENSIE_CHECK_THROW( unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( 3.0*MeV, trials ),
              std::logic_error );

  // After the third bin - with extension
  unit_aware_distribution->extendBeyondPrimaryIndepLimits();

  sample = unit_aware_distribution->sampleSecondaryConditionalAndRecordTrials( 3.0*MeV, trials );

  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( trials, 15u );

  unit_aware_distribution->limitToPrimaryIndepLimits();
}

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

  oss << *distribution;

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "HistogramPartiallyTabularBasicBivariateDistribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["primary independent unit"].toString(),
                       "void",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["secondary independent unit"].toString(),
                       "void",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       "void",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["primary grid"].toType<std::vector<double> >(),
                       std::vector<double>( {0.0, 0.0, 1.0, 2.0} ),
                       SHOW_LHS );

  Utility::VariantVector secondary_dists =
    dist_data["secondary dists"].toVector();

  FRENSIE_REQUIRE_EQUAL( secondary_dists.size(), 4, SHOW_LHS );

  Utility::VariantMap secondary_dist_data = secondary_dists[0].toMap();

  FRENSIE_CHECK_EQUAL( secondary_dist_data["type"].toString(),
                       "Delta Distribution",
                       SHOW_LHS );

  secondary_dist_data = secondary_dists[1].toMap();

  FRENSIE_CHECK_EQUAL( secondary_dist_data["type"].toString(),
                       "Exponential Distribution",
                       SHOW_LHS );

  secondary_dist_data = secondary_dists[2].toMap();

  FRENSIE_CHECK_EQUAL( secondary_dist_data["type"].toString(),
                       "Uniform Distribution",
                       SHOW_LHS );

  FRENSIE_CHECK_EQUAL( secondary_dists[0].toMap(),
                       secondary_dists[3].toMap(),
                       SHOW_BOTH );
}

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

  oss << *unit_aware_distribution;

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "HistogramPartiallyTabularBasicBivariateDistribution",
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["primary independent unit"].toString(),
                       Utility::UnitTraits<MegaElectronVolt>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["secondary independent unit"].toString(),
                       Utility::UnitTraits<cgs::length>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<Barn>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["primary grid"].toType<std::vector<quantity<MegaElectronVolt> > >(),
                       std::vector<quantity<MegaElectronVolt> >( {0.0*MeV, 0.0*MeV, 1.0*MeV, 2.0*MeV} ),
                       SHOW_LHS );

  Utility::VariantVector secondary_dists =
    dist_data["secondary dists"].toVector();

  FRENSIE_REQUIRE_EQUAL( secondary_dists.size(), 4, SHOW_LHS );

  Utility::VariantMap secondary_dist_data = secondary_dists[0].toMap();

  FRENSIE_CHECK_EQUAL( secondary_dist_data["type"].toString(),
                       "Delta Distribution",
                       SHOW_LHS );

  secondary_dist_data = secondary_dists[1].toMap();

  FRENSIE_CHECK_EQUAL( secondary_dist_data["type"].toString(),
                       "Exponential Distribution",
                       SHOW_LHS );

  secondary_dist_data = secondary_dists[2].toMap();

  FRENSIE_CHECK_EQUAL( secondary_dist_data["type"].toString(),
                       "Uniform Distribution",
                       SHOW_LHS );

  FRENSIE_CHECK_EQUAL( secondary_dists[0].toMap(),
                       secondary_dists[3].toMap(),
                       SHOW_BOTH );
}

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

  std::string test_dist_string;

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

    createOArchive( archive_base_name, archive_ostream, oarchive );

    std::vector<double> primary_grid( {-1.0, 1.0} );
    std::vector<std::shared_ptr<const Utility::UnivariateDistribution> >
      secondary_dists( 2 );

    secondary_dists[0].reset( new Utility::DeltaDistribution( -1.0 ) );
    secondary_dists[1].reset( new Utility::DeltaDistribution( 1.0 ) );

    std::shared_ptr<Utility::HistogramPartiallyTabularBasicBivariateDistribution>
      concrete_dist( new Utility::HistogramPartiallyTabularBasicBivariateDistribution( primary_grid, secondary_dists ) );

    test_dist_string = Utility::toString( *concrete_dist );

    std::shared_ptr<Utility::BasicBivariateDistribution>
      basic_dist( concrete_dist );

    std::shared_ptr<Utility::PartiallyTabularBasicBivariateDistribution>
      partially_tabular_dist( concrete_dist );

    FRENSIE_REQUIRE_NO_THROW( (*oarchive) << BOOST_SERIALIZATION_NVP( concrete_dist ) );
    FRENSIE_REQUIRE_NO_THROW( (*oarchive) << BOOST_SERIALIZATION_NVP( basic_dist ) );
    FRENSIE_REQUIRE_NO_THROW( (*oarchive) << BOOST_SERIALIZATION_NVP( partially_tabular_dist ) );
  }

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

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

  createIArchive( archive_istream, iarchive );

  std::shared_ptr<Utility::HistogramPartiallyTabularBasicBivariateDistribution> concrete_dist;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> BOOST_SERIALIZATION_NVP( concrete_dist ) );
  FRENSIE_CHECK_EQUAL( Utility::toString( *concrete_dist ),
                       test_dist_string,
                       SHOW_LHS );

  std::shared_ptr<Utility::BasicBivariateDistribution> basic_dist;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> BOOST_SERIALIZATION_NVP( basic_dist ) );
  FRENSIE_CHECK_EQUAL( Utility::toString( *basic_dist ),
                       test_dist_string,
                       SHOW_LHS );

  std::shared_ptr<Utility::PartiallyTabularBasicBivariateDistribution> partially_tabular_dist;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> BOOST_SERIALIZATION_NVP( partially_tabular_dist ) );
  FRENSIE_CHECK_EQUAL( Utility::toString( *partially_tabular_dist ),
                       test_dist_string,
                       SHOW_LHS );
}

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

  std::string test_dist_string;

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

    createOArchive( archive_base_name, archive_ostream, oarchive );

    std::vector<quantity<MegaElectronVolt> > primary_grid( {0.0*MeV, 1.0*MeV} );
    std::vector<std::shared_ptr<const Utility::UnitAwareUnivariateDistribution<cgs::length,Barn> > >
      secondary_dists( 2 );

    secondary_dists[0].reset( new Utility::UnitAwareDeltaDistribution<cgs::length,Barn>( -1.0*cgs::centimeter ) );
    secondary_dists[1].reset( new Utility::UnitAwareDeltaDistribution<cgs::length,Barn>( 1.0*cgs::centimeter ) );

    std::shared_ptr<Utility::UnitAwareHistogramPartiallyTabularBasicBivariateDistribution<MegaElectronVolt,cgs::length,Barn> >
      concrete_dist( new Utility::UnitAwareHistogramPartiallyTabularBasicBivariateDistribution<MegaElectronVolt,cgs::length,Barn>( primary_grid, secondary_dists ) );

    test_dist_string = Utility::toString( *concrete_dist );

    std::shared_ptr<Utility::UnitAwareBasicBivariateDistribution<MegaElectronVolt,cgs::length,Barn> >
      basic_dist( concrete_dist );

    std::shared_ptr<Utility::UnitAwarePartiallyTabularBasicBivariateDistribution<MegaElectronVolt,cgs::length,Barn> >
      partially_tabular_dist( concrete_dist );

    FRENSIE_REQUIRE_NO_THROW( (*oarchive) << BOOST_SERIALIZATION_NVP( concrete_dist ) );
    FRENSIE_REQUIRE_NO_THROW( (*oarchive) << BOOST_SERIALIZATION_NVP( basic_dist ) );
    FRENSIE_REQUIRE_NO_THROW( (*oarchive) << BOOST_SERIALIZATION_NVP( partially_tabular_dist ) );
  }

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

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

  createIArchive( archive_istream, iarchive );

  std::shared_ptr<Utility::UnitAwareHistogramPartiallyTabularBasicBivariateDistribution<MegaElectronVolt,cgs::length,Barn> > concrete_dist;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> BOOST_SERIALIZATION_NVP( concrete_dist ) );
  FRENSIE_CHECK_EQUAL( Utility::toString( *concrete_dist ),
                       test_dist_string,
                       SHOW_LHS );

  std::shared_ptr<Utility::UnitAwareBasicBivariateDistribution<MegaElectronVolt,cgs::length,Barn> > basic_dist;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> BOOST_SERIALIZATION_NVP( basic_dist ) );
  FRENSIE_CHECK_EQUAL( Utility::toString( *basic_dist ),
                       test_dist_string,
                       SHOW_LHS );

  std::shared_ptr<Utility::UnitAwarePartiallyTabularBasicBivariateDistribution<MegaElectronVolt,cgs::length,Barn> > partially_tabular_dist;

  FRENSIE_REQUIRE_NO_THROW( (*iarchive) >> BOOST_SERIALIZATION_NVP( partially_tabular_dist ) );
  FRENSIE_CHECK_EQUAL( Utility::toString( *partially_tabular_dist ),
                       test_dist_string,
                       SHOW_LHS );
}

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

FRENSIE_CUSTOM_UNIT_TEST_INIT()
{
  // Create the two-dimensional distribution
  {
    std::vector<double> primary_grid( 4 );
    std::vector<std::shared_ptr<const Utility::UnivariateDistribution> >
      secondary_dists( 4 );

    // Create the secondary distribution in the first bin
    primary_grid[0] = 0.0;
    secondary_dists[0].reset( new Utility::DeltaDistribution( 0.0 ) );

    // Create the secondary distribution in the second bin
    primary_grid[1] = 0.0;
    secondary_dists[1].reset( new Utility::ExponentialDistribution( 1.0, 1.0 ) );

    // Create the secondary distribution in the third bin
    primary_grid[2] = 1.0;
    secondary_dists[2].reset( new Utility::UniformDistribution( 0.0, 10.0, 1.0 ) );
    // Create the secondary distribution beyond the third bin
    primary_grid[3] = 2.0;
    secondary_dists[3] = secondary_dists[0];

    distribution.reset(
              new Utility::HistogramPartiallyTabularBasicBivariateDistribution(
                                                           primary_grid,
                                                           secondary_dists ) );
  }

  // Create the unit-aware two-dimensional distribution
  {
    std::vector<quantity<MegaElectronVolt> > primary_bins( 4 );

    std::vector<std::shared_ptr<const Utility::UnitAwareUnivariateDistribution<cgs::length,Barn> > > secondary_dists( 4 );

    // Create the secondary distribution in the first bin
    primary_bins[0] = 0.0*MeV;
    secondary_dists[0].reset( new Utility::UnitAwareDeltaDistribution<cgs::length,Barn>( 0.0*cgs::centimeter ) );

    // Create the secondary distribution in the second bin
    primary_bins[1] = 0.0*MeV;
    secondary_dists[1].reset( new Utility::UnitAwareExponentialDistribution<cgs::length,Barn>( 1.0*barn, 1.0/cgs::centimeter ) );

    // Create the secondary distribution in the third bin
    primary_bins[2] = 1.0*MeV;
    secondary_dists[2].reset( new Utility::UnitAwareUniformDistribution<cgs::length,Barn>( 0.0*cgs::centimeter, 10.0*cgs::centimeter, 1.0*barn ) );

    // Create the secondary distribution beyond the third bin
    primary_bins[3] = 2.0*MeV;
    secondary_dists[3] = secondary_dists[0];

    unit_aware_distribution.reset( new Utility::UnitAwareHistogramPartiallyTabularBasicBivariateDistribution<MegaElectronVolt,cgs::length,Barn>( primary_bins, secondary_dists ) );
  }

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

FRENSIE_CUSTOM_UNIT_TEST_SETUP_END();

//---------------------------------------------------------------------------//
// end tstHistogramPartiallyTabularBasicBivariateDistribution.cpp
//---------------------------------------------------------------------------//


#include "../numbers.h"

#include "parser.h"

#include <limits>

#include <boost/numeric/interval.hpp>

#ifdef USE_CLN_NUMBERS
namespace carl
{
    template<>
    cln::cl_RA rationalize<cln::cl_RA>(double n) {
        switch (std::fpclassify(n)) {
            case FP_NORMAL: // normalized are fully supported
                return cln::rationalize(convert<mpq_class, cln::cl_RA>(n));
            case FP_SUBNORMAL: { // subnormals result in underflows, hence the value of the double is 0.f, where f is the significand precision
				static_assert(sizeof(n) == 8, "double is assumed to be eight bytes wide.");
                auto significandBits = reinterpret_cast<sint>(&n);
                significandBits = (significandBits << 12) >> 12;
                if( n < 0 )
                    significandBits = -significandBits;
                return cln::cl_RA( significandBits ) * ONE_DIVIDED_BY_10_TO_THE_POWER_OF_52;
        }
        case FP_ZERO:
            return 0;
        case FP_NAN: // NaN and infinite are not supported
        case FP_INFINITE:
            assert(false);
            break;
        }
        return 0;
    }

    template<>
    cln::cl_RA rationalize<cln::cl_RA>(float n)
    {
        switch (std::fpclassify(n))
        {
            case FP_NORMAL: // normalized are fully supported
                return cln::rationalize(convert<mpq_class, cln::cl_RA>(n));
            case FP_SUBNORMAL: { // subnormals result in underflows, hence the value of the double is 0.f, where f is the significand precision
				static_assert(sizeof(n) == 4, "float is assumed to be four bytes wide.");
                auto significandBits = reinterpret_cast<sint>(&n);
                significandBits = (significandBits << 9) >> 9;
                if( n < 0 )
                    significandBits = -significandBits;
                return cln::cl_RA( significandBits ) * ONE_DIVIDED_BY_10_TO_THE_POWER_OF_23;
        }
        case FP_ZERO:
            return 0;
        case FP_NAN: // NaN and infinite are not supported
        case FP_INFINITE:
            assert(false);
            break;
        }
        return 0;
    }

    bool sqrt_exact(const cln::cl_RA& a, cln::cl_RA& b)
    {
        if( a < 0 ) return false;
        return cln::sqrtp( a, &b );
    }

    cln::cl_RA sqrt(const cln::cl_RA& a)
    {
        auto r = sqrt_safe(a);
        return (r.first + r.second) / 2;
    }

	cln::cl_RA scaleByPowerOfTwo(const cln::cl_RA& a, int exp) {
		if (exp > 0) {
			return cln::cl_RA(cln::numerator(a) << exp) / cln::denominator(a);
		} else if (exp < 0) {
			return cln::cl_RA(cln::numerator(a)) / (cln::denominator(a) << -exp);
		}
		return a;
	}

    std::pair<cln::cl_RA, cln::cl_RA> sqrt_safe(const cln::cl_RA& a)
    {
        assert( a >= 0 );
        cln::cl_RA exact_root;
        if (cln::sqrtp(a, &exact_root)) {
            // root can be computed exactly.
            return std::make_pair(exact_root, exact_root);
        } else {
			auto factor = int(cln::integer_length(cln::denominator(a))) - int(cln::integer_length(cln::numerator(a)));
			if (cln::oddp(factor)) factor += 1;
			cln::cl_RA n = scaleByPowerOfTwo(a, factor);
			double dn = toDouble(n);
			cln::cl_RA nra = cln::rationalize(dn);
			boost::numeric::interval<double> i;
			if (nra > n) {
				i.assign(toDouble(2*n-nra), dn);
				assert(2*n-nra <= n);
			} else {
				i.assign(dn, toDouble(2*n-nra));
				assert(n <= 2*n-nra);
			}
			i = boost::numeric::sqrt(i);
			i.assign(
				std::nexttoward(i.lower(), -std::numeric_limits<double>::infinity()),
				std::nexttoward(i.upper(), std::numeric_limits<double>::infinity())
			);
			factor = factor / 2;
			cln::cl_RA lower = scaleByPowerOfTwo(cln::rationalize(i.lower()), -factor);
			cln::cl_RA upper = scaleByPowerOfTwo(cln::rationalize(i.upper()), -factor);
			assert(lower*lower <= a);
			assert(a <= upper*upper);
			return std::make_pair(lower, upper);
        }
    }

    std::pair<cln::cl_RA, cln::cl_RA> sqrt_fast(const cln::cl_RA& a)
    {
		assert(a >= 0);
		cln::cl_RA exact_root;
		if (cln::sqrtp(a, &exact_root)) {
			// root can be computed exactly.
			return std::make_pair(exact_root, exact_root);
		} else {
			// compute an approximation with sqrt(). we can assume that the surrounding integers contain the actual root.
			//auto factor = cln::integer_length(cln::denominator(a)) - cln::integer_length(cln::numerator(a));
			//if (cln::oddp(factor)) factor += 1;
			cln::cl_I lower = cln::floor1(cln::sqrt(toLF(a)));
            cln::cl_I upper = lower + 1;
            assert(cln::expt_pos(lower,2) < a);
            assert(cln::expt_pos(upper,2) > a);
            return std::make_pair(lower, upper);
        }
    }

	std::pair<cln::cl_RA, cln::cl_RA> root_safe(const cln::cl_RA& a, uint n) {
		auto res = cln::realpart(cln::expt(a, cln::cl_RA(1)/n));
		return std::make_pair(cln::cl_RA(cln::floor1(res)), cln::cl_RA(cln::ceiling1(res)));
	}

    template<>
    cln::cl_RA rationalize<cln::cl_RA>(const std::string& n)
    {
        return parse<cln::cl_RA>(n);
    }

    template<>
    cln::cl_I parse<cln::cl_I>(const std::string& n) {
        cln::cl_I res;
        bool success = parser::parseDecimal(n, res);
        assert(success);
        return res;
    }

	template<>
	bool try_parse<cln::cl_I>(const std::string& n, cln::cl_I& res) {
		return parser::parseDecimal(n, res);
	}

    template<>
    cln::cl_RA parse<cln::cl_RA>(const std::string& n) {
        cln::cl_RA res;
        bool success = parser::parseRational(n, res);
        assert(success);
        return res;
    }

	template<>
	bool try_parse<cln::cl_RA>(const std::string& n, cln::cl_RA& res) {
		return parser::parseRational(n, res);
	}

    std::string toString(const cln::cl_RA& _number, bool _infix)
    {
        std::stringstream s;
        bool negative = (_number < cln::cl_RA(0));
        if(negative) s << "(-" << (_infix ? "" : " ");
        if(_infix) s << carl::abs(_number);
        else
        {
            cln::cl_I d = carl::getDenom(_number);
            if(d != cln::cl_I(1)) s << "(/ " << carl::abs(carl::getNum(_number)) << " " << carl::abs(d) << ")";
            else s << carl::abs(_number);
        }
        if(negative)
            s << ")";
        return s.str();
    }

    std::string toString(const cln::cl_I& _number, bool _infix)
    {
        std::stringstream s;
        bool negative = (_number < cln::cl_I(0));
        if(negative) s << "(-" << (_infix ? "" : " ");
        s << carl::abs(_number);
        if(negative)
            s << ")";
        return s.str();
    }
}
#endif