#include "generalized_eigensolver.h"

#include <immintrin.h>
#include <Eigen/Eigen>

#include "gefast/math/cayley.h"
#include "gefast/util/macros.h"

namespace gefast {

namespace {

Eigen::Matrix4d ComposeG(const Matrix38dRowMajor &ray_directions_matrix1,
                         const Matrix38dRowMajor &ray_directions_matrix2,
                         const Matrix38dRowMajor &ray_centers_matrix1,
                         const Matrix38dRowMajor &rcm2_cross_rdm2,
                         const cayley_t &cayley) {
  rotation_t rotation = CayleyToRotationMatrixUnscaled(cayley);

  Matrix38dRowMajor rot_ray_directions_matrix2 =
      rotation * ray_directions_matrix2;
  Matrix38dRowMajor rot_rcm2_cross_rdm2 = rotation * rcm2_cross_rdm2;

  Eigen::Matrix<double, 4, 8, Eigen::RowMajor> g;
#ifdef GEFAST_INTRINSICS_AVAILABLE
  for (auto i = 0; i < 2; ++i) {
    __m256d _a_1 = _mm256_load_pd(ray_centers_matrix1.data() + i * 4);
    __m256d _a_2 = _mm256_load_pd(ray_centers_matrix1.data() + 8 + i * 4);
    __m256d _a_3 = _mm256_load_pd(ray_centers_matrix1.data() + 16 + i * 4);

    __m256d _b_1 = _mm256_load_pd(rot_ray_directions_matrix2.data() + i * 4);
    __m256d _b_2 =
        _mm256_load_pd(rot_ray_directions_matrix2.data() + 8 + i * 4);
    __m256d _b_3 =
        _mm256_load_pd(rot_ray_directions_matrix2.data() + 16 + i * 4);

    __m256d _rdm1_1 = _mm256_load_pd(ray_directions_matrix1.data() + i * 4);
    __m256d _rdm1_2 = _mm256_load_pd(ray_directions_matrix1.data() + 8 + i * 4);
    __m256d _rdm1_3 =
        _mm256_load_pd(ray_directions_matrix1.data() + 16 + i * 4);

    // ray_centers_matrix1 cross rot_ray_directions_matrix2
    __m256d _res1 = _mm256_mul_pd(_a_3, _b_2);
    _res1 = _mm256_fmsub_pd(_a_2, _b_3, _res1);

    __m256d _res2 = _mm256_mul_pd(_a_1, _b_3);
    _res2 = _mm256_fmsub_pd(_a_3, _b_1, _res2);

    __m256d _res3 = _mm256_mul_pd(_a_2, _b_1);
    _res3 = _mm256_fmsub_pd(_a_1, _b_2, _res3);

    // rdm1 dot rcm1_cross_rot_rdm2

    __m256d _rdm1_dot_rcm1_cross_rot_rdm2 = _mm256_mul_pd(_rdm1_1, _res1);
    _rdm1_dot_rcm1_cross_rot_rdm2 =
        _mm256_fmadd_pd(_rdm1_2, _res2, _rdm1_dot_rcm1_cross_rot_rdm2);
    _rdm1_dot_rcm1_cross_rot_rdm2 =
        _mm256_fmadd_pd(_rdm1_3, _res3, _rdm1_dot_rcm1_cross_rot_rdm2);

    // rdm1 cross rot_rdm2

    _res1 = _mm256_mul_pd(_rdm1_3, _b_2);
    _res1 = _mm256_fmsub_pd(_rdm1_2, _b_3, _res1);

    _res2 = _mm256_mul_pd(_rdm1_1, _b_3);
    _res2 = _mm256_fmsub_pd(_rdm1_3, _b_1, _res2);

    _res3 = _mm256_mul_pd(_rdm1_2, _b_1);
    _res3 = _mm256_fmsub_pd(_rdm1_1, _b_2, _res3);

    _mm256_store_pd(g.data() + i * 4, _res1);
    _mm256_store_pd(g.data() + 8 + i * 4, _res2);
    _mm256_store_pd(g.data() + 16 + i * 4, _res3);

    // rdm1 dot rot_rcm2_cross_rdm2

    _a_1 = _mm256_load_pd(rot_rcm2_cross_rdm2.data() + i * 4);
    _a_2 = _mm256_load_pd(rot_rcm2_cross_rdm2.data() + 8 + i * 4);
    _a_3 = _mm256_load_pd(rot_rcm2_cross_rdm2.data() + 16 + i * 4);

    __m256d _rdm1_dot_rot_rcm2_cross_rdm2 = _mm256_mul_pd(_rdm1_1, _a_1);
    _rdm1_dot_rot_rcm2_cross_rdm2 =
        _mm256_fmadd_pd(_rdm1_2, _a_2, _rdm1_dot_rot_rcm2_cross_rdm2);
    _rdm1_dot_rot_rcm2_cross_rdm2 =
        _mm256_fmadd_pd(_rdm1_3, _a_3, _rdm1_dot_rot_rcm2_cross_rdm2);

    _mm256_store_pd(g.data() + 24 + i * 4,
                    _mm256_sub_pd(_rdm1_dot_rcm1_cross_rot_rdm2,
                                  _rdm1_dot_rot_rcm2_cross_rdm2));
  }
#else
  for (auto i = 0; i != ray_directions_matrix1.cols(); ++i) {
    g.block<3, 1>(0, i) =
        ray_directions_matrix1.col(i).cross(rot_ray_directions_matrix2.col(i));
    g(3, i) =
        ray_directions_matrix1.col(i).transpose() *
        (ray_centers_matrix1.col(i).cross(rot_ray_directions_matrix2.col(i)) -
         rot_rcm2_cross_rdm2.col(i));
  }
#endif
  return g * g.transpose();
}

eigenvalues_t GetEigenvalues(const Matrix38dRowMajor &ray_directions_matrix1,
                             const Matrix38dRowMajor &ray_directions_matrix2,
                             const Matrix38dRowMajor &ray_centers_matrix1,
                             const Matrix38dRowMajor &rcm2_cross_rdm2,
                             const cayley_t &cayley) {
  Eigen::Matrix4d G = ComposeG(ray_directions_matrix1, ray_directions_matrix2,
                               ray_centers_matrix1, rcm2_cross_rdm2, cayley);

  // now compute the roots in closed-form
  const auto G33 = G(3, 3);
  const auto G22 = G(2, 2);
  const auto G11 = G(1, 1);
  const auto G00 = G(0, 0);
  const auto G12 = G(1, 2);
  const auto G13 = G(1, 3);
  const auto G23 = G(2, 3);
  const auto G01 = G(0, 1);
  const auto G02 = G(0, 2);
  const auto G03 = G(0, 3);
  const auto G11_plus_G22 = G11 + G22;
  const auto G22_plus_G33 = G22 + G33;
  const auto G00_plus_G11 = G00 + G11;
  const auto G01_2 = G01 * G01;
  const auto G02_2 = G02 * G02;
  const auto G03_2 = G03 * G03;
  const auto G12_2 = G12 * G12;
  const auto G13_2 = G13 * G13;
  const auto G23_2 = G23 * G23;
  const auto G12_2_plus_G13_2_plus_G23_3 = G12_2 + G13_2 + G23_2;

  const double B = -(G00_plus_G11 + G22_plus_G33);
  const double C = -G12_2_plus_G13_2_plus_G23_3 + G33 * (G00_plus_G11 + G22) +
                   G11 * G22 - (G03_2 + G02_2 + G01_2) + G00 * (G11_plus_G22);
  const double D =
      G13_2 * G22 -
      2.0 * (G12 * (G13 * G23 + G01 * G02) + G03 * (G02 * G23 + G01 * G13)) +
      G11 * G23_2 - (G22 * G33 * (G00_plus_G11) + G00 * G11 * (G22_plus_G33)) +
      G03_2 * G11_plus_G22 + G02_2 * (G33 + G11) + G01_2 * G22_plus_G33 +
      G00 * (G12_2_plus_G13_2_plus_G23_3) + G12_2 * G33;

  const double E = G.determinant();

  double B_pw2 = B * B;
  double B_pw3 = B_pw2 * B;
  double B_pw4 = B_pw3 * B;
  double alpha = -0.375 * B_pw2 + C;
  double beta = B_pw3 / 8.0 - B * C / 2.0 + D;
  double gamma = -0.01171875 * B_pw4 + B_pw2 * C / 16.0 - B * D / 4.0 + E;
  double alpha_pw2 = alpha * alpha;
  double alpha_pw3 = alpha_pw2 * alpha;
  double p = -alpha_pw2 / 12.0 - gamma;
  double q = -alpha_pw3 / 108.0 + alpha * gamma / 3.0 - beta * beta / 8.0;

  double theta2 = -p / 3.0;
  double theta1 = sqrt(theta2) *
                  cos((1.0 / 3.0) * acos((-q / 2.0) / sqrt(-p * p * p / 27.0)));
  double y = -(5.0 / 6.0) * alpha -
             ((1.0 / 3.0) * p * theta1 - theta1 * theta2) / theta2;
  double w = sqrt(alpha + 2.0 * y);

  // we currently disable the computation of all other roots, they are not used
  double temp1 = -B / 4.0 - 0.5 * w;
  double temp2 = 0.5 * sqrt(-3.0 * alpha - 2.0 * y + 2.0 * beta / w);

  eigenvalues_t roots;
  roots[0] = temp1 + temp2;
  roots[1] = temp1 - temp2;
  return roots;
}

double GetCost(const Matrix38dRowMajor &ray_directions_matrix1,
               const Matrix38dRowMajor &ray_directions_matrix2,
               const Matrix38dRowMajor &ray_centers_matrix1,
               const Matrix38dRowMajor &rcm2_cross_rdm2, const cayley_t &cayley,
               bool use_smallest_ev) {
  Eigen::Vector2d roots =
      GetEigenvalues(ray_directions_matrix1, ray_directions_matrix2,
                     ray_centers_matrix1, rcm2_cross_rdm2, cayley);

  if (use_smallest_ev) return roots[1];
  return roots[0];
}

jacobian_t GetJacobian(const Matrix38dRowMajor &ray_directions_matrix1,
                       const Matrix38dRowMajor &ray_directions_matrix2,
                       const Matrix38dRowMajor &ray_centers_matrix1,
                       const Matrix38dRowMajor &rcm2_cross_rdm2,
                       const cayley_t &cayley, double current_eigenvalue,
                       bool use_smallest_ev) {
  jacobian_t jacobian;
  const double kEpsilon = 0.00000001;

  for (auto j = 0; j != 3; ++j) {
    cayley_t cayley_j = cayley;
    cayley_j(j) += kEpsilon;
    auto cost_j = GetCost(ray_directions_matrix1, ray_directions_matrix2,
                          ray_centers_matrix1, rcm2_cross_rdm2, cayley_j,
                          use_smallest_ev);
    jacobian(j) =
        cost_j - current_eigenvalue;  // division by eps can be ommited
  }
  return jacobian;
}

void FindModel(const Matrix38dRowMajor &ray_directions_matrix1,
               const Matrix38dRowMajor &ray_directions_matrix2,
               const Matrix38dRowMajor &ray_centers_matrix1,
               const Matrix38dRowMajor &rcm2_cross_rdm2,
               const cayley_t &starting_point, RelativePose &model) {
  double lambda = 0.017;
  double lambda_modifier = 2.0;
  const double kMaxLambda = 0.07;
  const double kMinLambda = 0.00001;
  const int kMaxIterations = 11;
  const bool kDisableIncrements = false;

  double disturbance_amplitude = 0.3;
  bool found = false;
  int random_trials = 0;
  const int kMaxRandomTrials = 5;
  cayley_t cayley;

  while (!found && random_trials < kMaxRandomTrials) {
    int iterations = 0;
    if (random_trials > 2) disturbance_amplitude = 0.6;
    cayley.noalias() = starting_point;
    if (random_trials != 0)
      cayley.noalias() += disturbance_amplitude * Eigen::Vector3d::Random();

    double smallestEV =
        GetCost(ray_directions_matrix1, ray_directions_matrix2,
                ray_centers_matrix1, rcm2_cross_rdm2, cayley, true);

    jacobian_t jacobian = GetJacobian(
        ray_directions_matrix1, ray_directions_matrix2, ray_centers_matrix1,
        rcm2_cross_rdm2, cayley, smallestEV, true);
    jacobian.normalize();
    Eigen::Matrix3d inverse_hessian = Eigen::Matrix3d::Identity();

    while (iterations < kMaxIterations) {
      Eigen::Vector3d searchDirection = -inverse_hessian * jacobian;
      searchDirection.normalize();

      if (jacobian.dot(searchDirection) > 0) {
        inverse_hessian = Eigen::Matrix3d::Identity();
        searchDirection = -jacobian;
      }

      lambda = 0.017;
      cayley_t next_cayley = cayley + lambda * searchDirection;

      double nextEV =
          GetCost(ray_directions_matrix1, ray_directions_matrix2,
                  ray_centers_matrix1, rcm2_cross_rdm2, next_cayley, true);

      if (iterations == 0 || !kDisableIncrements) {
        while (nextEV < smallestEV) {
          smallestEV = nextEV;
          if (lambda * lambda_modifier > kMaxLambda) break;
          lambda *= lambda_modifier;
          next_cayley.noalias() = cayley + lambda * searchDirection;
          nextEV =
              GetCost(ray_directions_matrix1, ray_directions_matrix2,
                      ray_centers_matrix1, rcm2_cross_rdm2, next_cayley, true);
        }
      }

      while (nextEV > smallestEV) {
        lambda /= lambda_modifier;
        if (lambda < kMinLambda) break;
        next_cayley = cayley + lambda * searchDirection;
        nextEV =
            GetCost(ray_directions_matrix1, ray_directions_matrix2,
                    ray_centers_matrix1, rcm2_cross_rdm2, next_cayley, true);
      }

      jacobian_t next_jacobian = GetJacobian(
          ray_directions_matrix1, ray_directions_matrix2, ray_centers_matrix1,
          rcm2_cross_rdm2, next_cayley, nextEV, true);
      next_jacobian.normalize();

      Eigen::Vector3d s = lambda * searchDirection;
      Eigen::Vector3d y = next_jacobian - jacobian;
      double rho = 1.0 / (y.dot(s));

      inverse_hessian =
          inverse_hessian -
          rho * (s * (y.transpose() * inverse_hessian) +
                 (inverse_hessian * y) * s.transpose()) +
          rho * (rho * (y).dot(inverse_hessian * y) + 1) * (s * s.transpose());

      cayley = next_cayley;
      smallestEV = nextEV;
      jacobian = next_jacobian;

      if (lambda < kMinLambda) break;
      ++iterations;
    }

    if (cayley.norm() < 0.01) {
      // we are close to the origin, test the EV 2
      double ev2 = GetCost(ray_directions_matrix1, ray_directions_matrix2,
                           ray_centers_matrix1, rcm2_cross_rdm2, cayley, false);
      if (ev2 > 0.001)
        ++random_trials;
      else
        found = true;
    } else
      found = true;
  }

  Eigen::Matrix4d G = ComposeG(ray_directions_matrix1, ray_directions_matrix2,
                               ray_centers_matrix1, rcm2_cross_rdm2, cayley);

  Eigen::EigenSolver<Eigen::Matrix4d> eigensolver_G(G, true);
  Eigen::Vector4d D = eigensolver_G.eigenvalues().real();
  Eigen::Matrix4d V = eigensolver_G.eigenvectors().real();

  auto min_eigenvalue_idx = 0;
  D.minCoeff(&min_eigenvalue_idx);

  model.rotation = CayleyToRotationMatrix(cayley);
  model.translation = V.col(min_eigenvalue_idx).hnormalized();
}

}  // namespace

void SolveGE(std::vector<Eigen::Vector3d> &ray_centers1,
             std::vector<Eigen::Vector3d> &ray_directions1,
             std::vector<Eigen::Vector3d> &ray_centers2,
             std::vector<Eigen::Vector3d> &ray_directions2,
             RelativePose &output) {
  const int kCorrespondencesNumber = static_cast<int>(ray_centers1.size());
  GEFAST_ASSERT(kCorrespondencesNumber == 8,
                "the solver only works with 8 correspondences");
  GEFAST_ASSERT(ray_centers2.size() == kCorrespondencesNumber,
                "the number of second camera ray centers must be 8");
  GEFAST_ASSERT(ray_directions1.size() == kCorrespondencesNumber,
                "the number of first camera ray directions must be 8");
  GEFAST_ASSERT(ray_directions2.size() == kCorrespondencesNumber,
                "the number of second camera ray directions must be 8");

  Matrix38dRowMajor ray_directions_matrix1 = Eigen::Map<Matrix38dColMajor>(
      reinterpret_cast<double *>(ray_directions1.data()));
  Matrix38dRowMajor ray_directions_matrix2 = Eigen::Map<Matrix38dColMajor>(
      reinterpret_cast<double *>(ray_directions2.data()));
  Matrix38dRowMajor ray_centers_matrix1 = Eigen::Map<Matrix38dColMajor>(
      reinterpret_cast<double *>(ray_centers1.data()));
  Matrix38dRowMajor ray_centers_matrix2 = Eigen::Map<Matrix38dColMajor>(
      reinterpret_cast<double *>(ray_centers2.data()));
  Matrix38dRowMajor rcm2_cross_rdm2;

  Eigen::Vector3d points_center1 = ray_directions_matrix1.rowwise().sum();
  Eigen::Vector3d points_center2 = ray_directions_matrix2.rowwise().sum();

#ifdef GEFAST_INTRINSICS_AVAILABLE
  for (auto i = 0; i < kCorrespondencesNumber / 4; ++i) {
    __m256d _rcm2_1 = _mm256_load_pd(ray_centers_matrix2.data() + i * 4);
    __m256d _rcm2_2 = _mm256_load_pd(ray_centers_matrix2.data() + 8 + i * 4);
    __m256d _rcm2_3 = _mm256_load_pd(ray_centers_matrix2.data() + 16 + i * 4);

    __m256d _rdm2_1 = _mm256_load_pd(ray_directions_matrix2.data() + i * 4);
    __m256d _rdm2_2 = _mm256_load_pd(ray_directions_matrix2.data() + 8 + i * 4);
    __m256d _rdm2_3 =
        _mm256_load_pd(ray_directions_matrix2.data() + 16 + i * 4);

    __m256d _res1 = _mm256_mul_pd(_rcm2_3, _rdm2_2);
    _res1 = _mm256_fmsub_pd(_rcm2_2, _rdm2_3, _res1);

    __m256d _res2 = _mm256_mul_pd(_rcm2_1, _rdm2_3);
    _res2 = _mm256_fmsub_pd(_rcm2_3, _rdm2_1, _res2);

    __m256d _res3 = _mm256_mul_pd(_rcm2_2, _rdm2_1);
    _res3 = _mm256_fmsub_pd(_rcm2_1, _rdm2_2, _res3);

    _mm256_store_pd(rcm2_cross_rdm2.data() + i * 4, _res1);
    _mm256_store_pd(rcm2_cross_rdm2.data() + 8 + i * 4, _res2);
    _mm256_store_pd(rcm2_cross_rdm2.data() + 16 + i * 4, _res3);
  }
#else
  for (auto i = 0; i < kCorrespondencesNumber; ++i) {
    rcm2_cross_rdm2.col(i).noalias() =
        ray_centers_matrix2.col(i).cross(ray_directions_matrix2.col(i));
  }
#endif

  points_center1 /= kCorrespondencesNumber;
  points_center2 /= kCorrespondencesNumber;

  Eigen::Matrix3d Hcross =
      (ray_directions_matrix2.colwise() - points_center2) *
      (ray_directions_matrix1.colwise() - points_center1).transpose();

  // SVD decomposition of matrix Hcross to obtain initial rotation
  Eigen::JacobiSVD<Eigen::Matrix3d> svd(
      Hcross, Eigen::ComputeFullU | Eigen::ComputeFullV);

  Eigen::Matrix3d V = svd.matrixV();
  Eigen::Matrix3d U = svd.matrixU();
  rotation_t starting_rotation = V * U.transpose();

  if (starting_rotation.determinant() < 0) {
    Eigen::Matrix3d V_prime;
    V_prime.col(0) = V.col(0);
    V_prime.col(1) = V.col(1);
    V_prime.col(2) = -V.col(2);
    starting_rotation.noalias() = V_prime * U.transpose();
  }

  FindModel(ray_directions_matrix1, ray_directions_matrix2, ray_centers_matrix1,
            rcm2_cross_rdm2, RotationMatrixToCayley(starting_rotation), output);
}

}  // namespace gefast


#pragma once

#include <polyfem/Problem.hpp>
#include <polyfem/InterpolatedFunction.hpp>
#include <polyfem/RBFInterpolation.hpp>

#include <vector>
#include <Eigen/Dense>

namespace polyfem
{
	class NodeValues
	{
	public:
		NodeValues();

		void load(const std::string &path);
		void init(const Mesh &mesh);

		double dirichlet_interpolate(const int p_id, const Eigen::MatrixXd &uv) const
		{
			return interpolate(p_id, uv, true);
		}
		double neumann_interpolate(const int p_id, const Eigen::MatrixXd &uv) const
		{
			return interpolate(p_id, uv, false);
		}

	private:
		double interpolate(const int p_id, const Eigen::MatrixXd &uv, bool is_dirichlet) const;

		std::vector<int> raw_ids_;
		std::vector<std::vector<double>> raw_data_;
		std::vector<bool> raw_dirichlet_;

		std::vector<Eigen::VectorXd> data_;
		std::vector<bool> dirichlet_;
	};

	class NodeProblem : public Problem
	{
	public:
		NodeProblem(const std::string &name);
		void init(const Mesh &mesh) override;

		void rhs(const AssemblerUtils &assembler, const std::string &formulation, const Eigen::MatrixXd &pts, const double t, Eigen::MatrixXd &val) const override;
		bool is_rhs_zero() const override { return abs(rhs_) < 1e-10; }

		void bc(const Mesh &mesh, const Eigen::MatrixXi &global_ids, const Eigen::MatrixXd &uv, const Eigen::MatrixXd &pts, const double t, Eigen::MatrixXd &val) const override;
		void neumann_bc(const Mesh &mesh, const Eigen::MatrixXi &global_ids, const Eigen::MatrixXd &uv, const Eigen::MatrixXd &pts, const Eigen::MatrixXd &normals, const double t, Eigen::MatrixXd &val) const override;

		bool has_exact_sol() const override { return false; }
		bool is_scalar() const override { return true; }

		void set_parameters(const json &params) override;

		bool is_dimension_dirichet(const int tag, const int dim) const override;
		bool all_dimensions_dirichlet() const override { return all_dimensions_dirichlet_; }

	private:
		bool all_dimensions_dirichlet_ = true;
		std::vector<Eigen::Matrix<bool, 1, 3, Eigen::RowMajor>> dirichlet_dimensions_;
		double rhs_;
		NodeValues values_;
		bool is_all_;
	};
} // namespace polyfem


//
// Copyright © 2017 Arm Ltd. All rights reserved.
// SPDX-License-Identifier: MIT
//

#include "../DriverTestHelpers.hpp"
#include "../Convolution2D.hpp"
#include "../../1.0/HalPolicy.hpp"

#include <boost/test/unit_test.hpp>
#include <log/log.h>

#include <OperationsUtils.h>

BOOST_AUTO_TEST_SUITE(Convolution2DTests)

using namespace android::hardware;
using namespace driverTestHelpers;
using namespace armnn_driver;

namespace driverTestHelpers
{

void SetModelFp16Flag(V1_0::Model&, bool)
{
    // Nothing to do, the V1_0::Model does not support fp16 precision relaxation.
    // This function is used for compatibility only.
}

} // namespace driverTestHelpers

BOOST_AUTO_TEST_CASE(ConvValidPadding_Hal_1_0)
{
    PaddingTestImpl<hal_1_0::HalPolicy>(android::nn::kPaddingValid);
}

BOOST_AUTO_TEST_CASE(ConvSamePadding_Hal_1_0)
{
    PaddingTestImpl<hal_1_0::HalPolicy>(android::nn::kPaddingSame);
}

BOOST_AUTO_TEST_SUITE_END()


#include "common/common.hpp"

#include "geodb/vector.hpp"
#include "geodb/irwi/base.hpp"

#include <boost/program_options.hpp>
#include <fmt/format.h>
#include <fmt/ostream.h>
#include <tpie/file_stream.h>

#include <iostream>
#include <random>

namespace po = boost::program_options;

using namespace geodb;

using std::cout;
using std::cerr;

static std::string output;
static u64 trajectory_units = 0;
static u32 trajectory_size = 0;
static u32 labels = 0;
static u32 seed = 0;
static double highx = 1000;
static double highy = 1000;

static void parse_options(int argc, char** argv) {
    po::options_description options("Options");
    options.add_options()
            ("help,h", "Show this message.")
            ("output", po::value(&output)->required()->value_name("PATH"),
             "Path to the output file.")
            ("seed", po::value(&seed)->value_name("S"),
             "The seed for the random number generator. Defaults to a truly random value.")
            (",n", po::value(&trajectory_units)->required()->value_name("N"),
             "The number of trajectory units.")
            (",m", po::value(&trajectory_size)->value_name("M")->default_value(1000),
             "The (average) number of trajectory units per trajectory")
            (",l", po::value(&labels)->value_name("L")->default_value(1000),
             "The number of different labels.")
            (",x", po::value(&highx)->value_name("MAX"),
             "Maximum x value for start points.")
            (",y", po::value(&highy)->value_name("MAX"),
             "Maximum y value for start points.");

    po::variables_map vm;
    try {
        po::command_line_parser p(argc, argv);
        p.options(options);
        po::store(p.run(), vm);

        if (vm.count("help")) {
            fmt::print(cerr, "Usage: {0}\n"
                             "\n"
                             "Generates a file that contains N trajectory units\n"
                             "grouped into trajectories of (about) M units each.\n"
                             "The entries in the file can be inserted into a tree\n"
                             "using the \"loader\" command.\n"
                             "\n"
                             "{1}",
                       argv[0], options);
            throw exit_main(0);
        }

        if (!vm.count("seed")) {
            seed = std::random_device{}();
        }

        po::notify(vm);
    } catch (const po::error& e) {
        fmt::print(cerr, "Failed to parse arguments: {}.\n", e.what());
        throw exit_main(1);
    }
}

using dist_t = std::uniform_real_distribution<double>;

static std::mt19937_64 rng{};

/// Returns a value in [0, 1).
static double get_random() {
    return dist_t{0, 1}(rng);
}

/// Returns a value in [low, high).
static double get_random(double low, double high) {
    geodb_assert(high >= low, "invalid bounds");
    return dist_t{low, high}(rng);
}

/// Returns the start point for a new random walk.
static vector3 point() {
    return vector3(get_random() * highx, get_random() * highy, get_random() * 100000);
}

/// Returns the next point in the random walk.
static vector3 point(const vector3& last) {
    return vector3(last.x() + get_random(-5, 5),
                   last.y() + get_random(-5, 5),
                   last.t() + get_random(5, 25)); // Always advance in time.
}

/// Returns a random label.
static label_type label() {
    return get_random() * labels;
}

/// Returns the next label in the random walk.
/// Changes to another random label with a probability of 20%.
static label_type label(label_type last) {
    if (get_random() < 0.2) {
        return label();
    }
    return last;
}

/// Generates a single trajectory as a random walk with `size` trajectory units.
static void generate_walk(u64 id, u32 size, tpie::file_stream<tree_entry>& out) {
    if (size == 0)
        return;

    vector3 p = point();
    label_type l = label();
    for (u32 index = 0; index < size; ++index) {
        vector3 q = point(p);
        tree_entry entry(id, index, trajectory_unit(p, q, l));
        out.write(entry);

        p = q;
        l = label(l);
    }
}

int main(int argc, char** argv) {
    return tpie_main([&]{
        parse_options(argc, argv);
        rng.seed(seed);

        tpie::file_stream<tree_entry> out;
        out.open(output);
        out.truncate(0);

        u64 remaining = trajectory_units;
        trajectory_id_type id = 1;
        while (remaining > 0) {
            u32 size = get_random(trajectory_size * 0.5, trajectory_size * 1.5);
            if (size > remaining) {
                size = remaining;
            }

            generate_walk(id++, size, out);

            remaining -= size;
        }

        return 0;
    });
}


//---------------------------------------------------------------------------//
//!
//! \file   tstUnitBaseTwoDGridPolicy.cpp
//! \author Luke Kersting
//! \brief  The UnitBase two-dimensional sampling policy unit tests
//!
//---------------------------------------------------------------------------//

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

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

// FRENSIE Includes
#include "Utility_TwoDGridPolicy.hpp"
#include "Utility_UnitTestHarnessWithMain.hpp"
#include "Utility_DynamicOutputFormatter.hpp"
#include "Utility_InterpolatedFullyTabularBasicBivariateDistribution.hpp"
#include "Utility_DeltaDistribution.hpp"
#include "Utility_UniformDistribution.hpp"
#include "Utility_ExponentialDistribution.hpp"
#include "Utility_ElectronVoltUnit.hpp"
#include "Utility_BarnUnit.hpp"

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;

//---------------------------------------------------------------------------//
// Testing Typenames
//---------------------------------------------------------------------------//

using XIndepType = Utility::UnitTraits<MegaElectronVolt>::template GetQuantityType<double>::type;
using YIndepType = Utility::UnitTraits<cgs::length>::template GetQuantityType<double>::type;
using ZDepType = Utility::UnitTraits<Barn>::template GetQuantityType<double>::type;

using DistributionType = std::vector<std::pair<double,std::shared_ptr<const Utility::TabularUnivariateDistribution > > >;
using UnitAwareDistributionType = std::vector<std::pair<Utility::UnitTraits<MegaElectronVolt>::template GetQuantityType<double>::type,std::shared_ptr<const Utility::UnitAwareTabularUnivariateDistribution<cgs::length,Barn> > > >;

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

std::shared_ptr<DistributionType> distribution;
std::shared_ptr<UnitAwareDistributionType> unit_aware_distribution;

std::function<double(const Utility::TabularUnivariateDistribution&)> functor;
std::function<double(const Utility::TabularUnivariateDistribution&, const double)> threshold_functor;
std::function<YIndepType(const Utility::UnitAwareTabularUnivariateDistribution<cgs::length,Barn>&)> ua_functor;
std::function<YIndepType(const Utility::UnitAwareTabularUnivariateDistribution<cgs::length,Barn>&, const double)> ua_threshold_functor;

std::function<double (double)> min_func, max_func;
std::function<YIndepType(const XIndepType)> ua_min_func, ua_max_func;

DistributionType::const_iterator bin, lower_bin, upper_bin, sampled_bin, start_bin;

UnitAwareDistributionType::const_iterator
  ua_bin, ua_lower_bin, ua_upper_bin, ua_sampled_bin, ua_start_bin;

//---------------------------------------------------------------------------//
// Tests.
//---------------------------------------------------------------------------//
// Check that the distribution is tabular in the primary dimension
FRENSIE_UNIT_TEST( UnitBase, name )
{
  std::string name = Utility::UnitBase<Utility::LinLinLin>::name();
  FRENSIE_CHECK( name == "Unit-base" );

  name = Utility::UnitBase<Utility::LinLinLin>::TwoDInterpPolicy::name();
  FRENSIE_CHECK( name == "LinLinLin" );
}

//---------------------------------------------------------------------------//
// Check that the Y lower bound can be calculated
FRENSIE_UNIT_TEST( UnitBase, calculateLowerBound )
{
  lower_bin = distribution->begin();
  upper_bin = lower_bin;
  ++upper_bin;

  // On the first bin boundary
  double x_value = 0.0;
  double bound = Utility::UnitBase<Utility::LinLinLin>::calculateLowerBound<double>(
                    x_value, lower_bin, upper_bin );
  FRENSIE_CHECK_EQUAL( bound, 0.0 );

  // In the first bin
  x_value = 0.5;
  bound = Utility::UnitBase<Utility::LinLinLin>::calculateLowerBound<double>(
                    x_value, lower_bin, upper_bin );
  FRENSIE_CHECK_EQUAL( bound, 1.25 );

  // On the second bin boundary
  ++lower_bin; ++upper_bin;
  x_value = 1.0;
  bound = Utility::UnitBase<Utility::LinLinLin>::calculateLowerBound<double>(
                    x_value, lower_bin, upper_bin );
  FRENSIE_CHECK_EQUAL( bound, 2.5 );

  // In the second bin
  x_value = 1.5;
  bound = Utility::UnitBase<Utility::LinLinLin>::calculateLowerBound<double>(
                    x_value, lower_bin, upper_bin );
  FRENSIE_CHECK_EQUAL( bound, 1.25 );

  // On the upper bin boundary
  x_value = 2.0;
  bound = Utility::UnitBase<Utility::LinLinLin>::calculateLowerBound<double>(
                    x_value, lower_bin, upper_bin );
  FRENSIE_CHECK_EQUAL( bound, 0.0 );
}

//---------------------------------------------------------------------------//
// Check that the Y lower bound can be calculated
FRENSIE_UNIT_TEST( UnitBase, calculateUpperBound )
{
  lower_bin = distribution->begin();
  upper_bin = lower_bin;
  ++upper_bin;

  // On the first bin boundary
  double x_value = 0.0;
  double bound = Utility::UnitBase<Utility::LinLinLin>::calculateUpperBound<double>(
                    x_value, lower_bin, upper_bin );
  FRENSIE_CHECK_EQUAL( bound, 10.0 );

  // In the first bin
  x_value = 0.5;
  bound = Utility::UnitBase<Utility::LinLinLin>::calculateUpperBound<double>(
                    x_value, lower_bin, upper_bin );
  FRENSIE_CHECK_EQUAL( bound, 8.75 );

  // On the second bin boundary
  ++lower_bin; ++upper_bin;
  x_value = 1.0;
  bound = Utility::UnitBase<Utility::LinLinLin>::calculateUpperBound<double>(
                    x_value, lower_bin, upper_bin );
  FRENSIE_CHECK_EQUAL( bound, 7.5 );

  // In the second bin
  x_value = 1.5;
  bound = Utility::UnitBase<Utility::LinLinLin>::calculateUpperBound<double>(
                    x_value, lower_bin, upper_bin );
  FRENSIE_CHECK_EQUAL( bound, 8.75 );

  // On the upper bin boundary
  x_value = 2.0;
  bound = Utility::UnitBase<Utility::LinLinLin>::calculateUpperBound<double>(
                    x_value, lower_bin, upper_bin );
  FRENSIE_CHECK_EQUAL( bound, 10.0 );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be evaluated
FRENSIE_UNIT_TEST( UnitBase, evaluatePDF )
{
  std::function<double(double,double)> evaluate =
  [&min_func, &max_func, &lower_bin, &upper_bin](double x_value, double y_value)
  {
    return Utility::UnitBase<Utility::LinLinLin>::evaluatePDF<Utility::TabularUnivariateDistribution,double,double,double>(
      x_value, y_value, min_func, max_func, &Utility::TabularUnivariateDistribution::evaluate, lower_bin, upper_bin );
  };

  lower_bin = distribution->begin();
  upper_bin = lower_bin;
  ++upper_bin;

  double x_value = 0.0;
  min_func = [](double x){return 0.0;}; max_func = [](double x){return 10.0;};;

  // On the first bin boundary
  FRENSIE_CHECK_EQUAL( evaluate( 0.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 0.0, 0.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 0.0, 5.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 0.0, 10.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 0.0, 11.0 ), 0.0 );

  // In the first bin
  min_func = [](double x){return 1.25;}; max_func = [](double x){return 8.75;};

  FRENSIE_CHECK_EQUAL( evaluate( 0.5, 1.0 ), 0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 0.5, 1.25 ), 0.7, 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 0.5, 5.0 ), 1.0, 1e-6 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 0.5, 8.75 ), 5.0/6.0, 1e-15 );
  FRENSIE_CHECK_EQUAL( evaluate( 0.5, 9.0 ), 0.0 );

  // On the second bin boundary
  ++lower_bin; ++upper_bin;
  min_func = [](double x){return 2.5;}; max_func = [](double x){return 7.5;};

  FRENSIE_CHECK_EQUAL( evaluate( 1.0, 2.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 1.0, 2.5 ), 0.1 );
  FRENSIE_CHECK_EQUAL( evaluate( 1.0, 5.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 1.0, 7.5 ), 0.5 );
  FRENSIE_CHECK_EQUAL( evaluate( 1.0, 8.0 ), 0.0 );

  // In the second bin
  min_func = [](double x){return 1.25;}; max_func = [](double x){return 8.75;};

  FRENSIE_CHECK_EQUAL( evaluate( 1.5, 1.0 ), 0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 1.5, 1.25 ), 0.1, 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 1.5, 5.0 ), 0.4, 1e-6 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 1.5, 8.75 ), 7.0/30.0, 1e-15 );
  FRENSIE_CHECK_EQUAL( evaluate( 1.5, 9.0 ), 0.0 );

  // On the upper bin boundary
  min_func = [](double x){return 0.0;}; max_func = [](double x){return 10.0;};

  FRENSIE_CHECK_EQUAL( evaluate( 2.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 2.0, 0.0 ), 0.1 );
  FRENSIE_CHECK_EQUAL( evaluate( 2.0, 5.0 ), 0.1 );
  FRENSIE_CHECK_EQUAL( evaluate( 2.0, 10.0 ), 0.1 );
  FRENSIE_CHECK_EQUAL( evaluate( 2.0, 11.0 ), 0.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be evaluated
FRENSIE_UNIT_TEST( UnitAwareUnitBase, evaluatePDF )
{
  std::function<ZDepType(XIndepType,YIndepType)> evaluate =
  [&ua_min_func, &ua_max_func, &ua_lower_bin, &ua_upper_bin](XIndepType x_value, YIndepType y_value)
  {
    return Utility::UnitBase<Utility::LinLinLin>::evaluatePDF<Utility::UnitAwareTabularUnivariateDistribution<cgs::length,Barn>,XIndepType,YIndepType,ZDepType>(
      x_value, y_value, ua_min_func, ua_max_func, &Utility::UnitAwareTabularUnivariateDistribution<cgs::length,Barn>::evaluate, ua_lower_bin, ua_upper_bin );
  };

  ua_lower_bin = unit_aware_distribution->begin();
  ua_upper_bin = ua_lower_bin;
  ++ua_upper_bin;

  ua_min_func = [](XIndepType x){return 0.0*cgs::centimeter;};
  ua_max_func = [](XIndepType x){return 10.0*cgs::centimeter;};

  // On the first bin boundary
  FRENSIE_CHECK_EQUAL( evaluate( 0.0*MeV, -1.0*cgs::centimeter ), 0.0*barn );
  FRENSIE_CHECK_EQUAL( evaluate( 0.0*MeV, 0.0*cgs::centimeter ), 1.0*barn );
  FRENSIE_CHECK_EQUAL( evaluate( 0.0*MeV, 5.0*cgs::centimeter ), 1.0*barn );
  FRENSIE_CHECK_EQUAL( evaluate( 0.0*MeV, 10.0*cgs::centimeter ), 1.0*barn );
  FRENSIE_CHECK_EQUAL( evaluate( 0.0*MeV, 11.0*cgs::centimeter ), 0.0*barn );

  // In the first bin
  ua_min_func = [](XIndepType x){return 1.25*cgs::centimeter;};
  ua_max_func = [](XIndepType x){return 8.75*cgs::centimeter;};

  FRENSIE_CHECK_EQUAL( evaluate( 0.5*MeV, 1.0*cgs::centimeter ), 0.0*barn );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 0.5*MeV, 1.25*cgs::centimeter ),
                                  0.7*barn,
                                  1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 0.5*MeV, 5.0*cgs::centimeter ),
                                  1.0*barn,
                                  1e-6 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 0.5*MeV, 8.75*cgs::centimeter ),
                                  5.0/6.0*barn,
                                  1e-15 );
  FRENSIE_CHECK_EQUAL( evaluate( 0.5*MeV, 9.0*cgs::centimeter ), 0.0*barn );

  // On the second bin boundary
  ++ua_lower_bin; ++ua_upper_bin;
  ua_min_func = [](XIndepType x){return 2.5*cgs::centimeter;};
  ua_max_func = [](XIndepType x){return 7.5*cgs::centimeter;};

  FRENSIE_CHECK_EQUAL( evaluate( 1.0*MeV, 2.0*cgs::centimeter ), 0.0*barn );
  FRENSIE_CHECK_EQUAL( evaluate( 1.0*MeV, 2.5*cgs::centimeter ), 0.1*barn );
  FRENSIE_CHECK_EQUAL( evaluate( 1.0*MeV, 5.0*cgs::centimeter ), 1.0*barn );
  FRENSIE_CHECK_EQUAL( evaluate( 1.0*MeV, 7.5*cgs::centimeter ), 0.5*barn );
  FRENSIE_CHECK_EQUAL( evaluate( 1.0*MeV, 8.0*cgs::centimeter ), 0.0*barn );

  // In the second bin
  ua_min_func = [](XIndepType x){return 1.25*cgs::centimeter;};
  ua_max_func = [](XIndepType x){return 8.75*cgs::centimeter;};

  FRENSIE_CHECK_EQUAL( evaluate( 1.5*MeV, 1.0*cgs::centimeter ), 0.0*barn );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 1.5*MeV, 1.25*cgs::centimeter ),
                                  0.1*barn,
                                  1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 1.5*MeV, 5.0*cgs::centimeter ),
                                  0.4*barn,
                                  1e-6 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 1.5*MeV, 8.75*cgs::centimeter ),
                                  7.0/30.0*barn,
                                  1e-15 );
  FRENSIE_CHECK_EQUAL( evaluate( 1.5*MeV, 9.0*cgs::centimeter ), 0.0*barn );

  // On the upper bin boundary
  ua_min_func = [](XIndepType x){return 0.0*cgs::centimeter;};
  ua_max_func = [](XIndepType x){return 10.0*cgs::centimeter;};

  FRENSIE_CHECK_EQUAL( evaluate( 2.0*MeV, -1.0*cgs::centimeter ), 0.0*barn );
  FRENSIE_CHECK_EQUAL( evaluate( 2.0*MeV, 0.0*cgs::centimeter ), 0.1*barn );
  FRENSIE_CHECK_EQUAL( evaluate( 2.0*MeV, 5.0*cgs::centimeter ), 0.1*barn );
  FRENSIE_CHECK_EQUAL( evaluate( 2.0*MeV, 10.0*cgs::centimeter ), 0.1*barn );
  FRENSIE_CHECK_EQUAL( evaluate( 2.0*MeV, 11.0*cgs::centimeter ), 0.0*barn );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be evaluated
FRENSIE_UNIT_TEST( UnitBase, evaluateCDF )
{
  std::function<double(double,double)> evaluate =
  [&min_func, &max_func, &lower_bin, &upper_bin](double x_value, double y_value)
  {
    return Utility::UnitBase<Utility::LinLinLin>::evaluateCDF<Utility::TabularUnivariateDistribution,double,double>(
      x_value, y_value, min_func, max_func, &Utility::TabularUnivariateDistribution::evaluateCDF, lower_bin, upper_bin );
  };

  lower_bin = distribution->begin();
  upper_bin = lower_bin;
  ++upper_bin;

  double x_value = 0.0;
  min_func = [](double x){return 0.0;}; max_func = [](double x){return 10.0;};;

  // On the first bin boundary
  FRENSIE_CHECK_EQUAL( evaluate( 0.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 0.0, 0.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 0.0, 5.0 ), 0.5 );
  FRENSIE_CHECK_EQUAL( evaluate( 0.0, 10.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 0.0, 11.0 ), 1.0 );

  // In the first bin
  min_func = [](double x){return 1.25;}; max_func = [](double x){return 8.75;};

  FRENSIE_CHECK_EQUAL( evaluate( 0.5, 1.0 ), 0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 0.5, 1.25 ), 0.0, 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 0.5, 5.0 ), 4.6153846153846156e-01, 1e-6 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 0.5, 8.75 ), 1.0, 1e-15 );
  FRENSIE_CHECK_EQUAL( evaluate( 0.5, 9.0 ), 1.0 );

  // On the second bin boundary
  ++lower_bin; ++upper_bin;
  min_func = [](double x){return 2.5;}; max_func = [](double x){return 7.5;};

  FRENSIE_CHECK_EQUAL( evaluate( 1.0, 2.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 1.0, 2.5 ), 0.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 1.0, 5.0 ), 4.23076923076923128e-01 );
  FRENSIE_CHECK_EQUAL( evaluate( 1.0, 7.5 ), 1.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 1.0, 8.0 ), 1.0 );

  // In the second bin
  min_func = [](double x){return 1.25;}; max_func = [](double x){return 8.75;};

  FRENSIE_CHECK_EQUAL( evaluate( 1.5, 1.0 ), 0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 1.5, 1.25 ), 0.0, 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 1.5, 5.0 ), 4.6153846153846156e-01, 1e-6 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 1.5, 8.75 ), 1.0, 1e-15 );
  FRENSIE_CHECK_EQUAL( evaluate( 1.5, 9.0 ), 1.0 );

  // On the upper bin boundary
  min_func = [](double x){return 0.0;}; max_func = [](double x){return 10.0;};

  FRENSIE_CHECK_EQUAL( evaluate( 2.0, -1.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 2.0, 0.0 ), 0.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 2.0, 5.0 ), 0.5 );
  FRENSIE_CHECK_EQUAL( evaluate( 2.0, 10.0 ), 1.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 2.0, 11.0 ), 1.0 );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be evaluated
FRENSIE_UNIT_TEST( UnitAwareUnitBase, evaluateCDF )
{
  std::function<double(XIndepType,YIndepType)> evaluate =
  [&ua_min_func, &ua_max_func, &ua_lower_bin, &ua_upper_bin](XIndepType x_value, YIndepType y_value)
  {
    return Utility::UnitBase<Utility::LinLinLin>::evaluateCDF<Utility::UnitAwareTabularUnivariateDistribution<cgs::length,Barn>,XIndepType,YIndepType>(
      x_value, y_value, ua_min_func, ua_max_func, &Utility::UnitAwareTabularUnivariateDistribution<cgs::length,Barn>::evaluateCDF, ua_lower_bin, ua_upper_bin );
  };

  ua_lower_bin = unit_aware_distribution->begin();
  ua_upper_bin = ua_lower_bin;
  ++ua_upper_bin;

  ua_min_func = [](XIndepType x){return 0.0*cgs::centimeter;};
  ua_max_func = [](XIndepType x){return 10.0*cgs::centimeter;};

  // On the first bin boundary
  FRENSIE_CHECK_EQUAL( evaluate( 0.0*MeV, -1.0*cgs::centimeter ), 0.0);
  FRENSIE_CHECK_EQUAL( evaluate( 0.0*MeV, 0.0*cgs::centimeter ), 0.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 0.0*MeV, 5.0*cgs::centimeter ), 0.5 );
  FRENSIE_CHECK_EQUAL( evaluate( 0.0*MeV, 10.0*cgs::centimeter ), 1.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 0.0*MeV, 11.0*cgs::centimeter ), 1.0 );

  // In the first bin
  ua_min_func = [](XIndepType x){return 1.25*cgs::centimeter;};
  ua_max_func = [](XIndepType x){return 8.75*cgs::centimeter;};

  FRENSIE_CHECK_EQUAL( evaluate( 0.5*MeV, 1.0*cgs::centimeter ), 0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 0.5*MeV, 1.25*cgs::centimeter ),
                                  0.0,
                                  1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 0.5*MeV, 5.0*cgs::centimeter ),
                                  4.6153846153846156e-01,
                                  1e-6 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 0.5*MeV, 8.75*cgs::centimeter ),
                                  1.0,
                                  1e-15 );
  FRENSIE_CHECK_EQUAL( evaluate( 0.5*MeV, 9.0*cgs::centimeter ), 1.0 );

  // On the second bin boundary
  ++ua_lower_bin; ++ua_upper_bin;
  ua_min_func = [](XIndepType x){return 2.5*cgs::centimeter;};
  ua_max_func = [](XIndepType x){return 7.5*cgs::centimeter;};

  FRENSIE_CHECK_EQUAL( evaluate( 1.0*MeV, 2.0*cgs::centimeter ), 0.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 1.0*MeV, 2.5*cgs::centimeter ), 0.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 1.0*MeV, 5.0*cgs::centimeter ), 4.23076923076923128e-01 );
  FRENSIE_CHECK_EQUAL( evaluate( 1.0*MeV, 7.5*cgs::centimeter ), 1.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 1.0*MeV, 8.0*cgs::centimeter ), 1.0 );

  // In the second bin
  ua_min_func = [](XIndepType x){return 1.25*cgs::centimeter;};
  ua_max_func = [](XIndepType x){return 8.75*cgs::centimeter;};

  FRENSIE_CHECK_EQUAL( evaluate( 1.5*MeV, 1.0*cgs::centimeter ), 0.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 1.5*MeV, 1.25*cgs::centimeter ),
                                  0.0,
                                  1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 1.5*MeV, 5.0*cgs::centimeter ),
                                  4.6153846153846156e-01,
                                  1e-6 );
  FRENSIE_CHECK_FLOATING_EQUALITY( evaluate( 1.5*MeV, 8.75*cgs::centimeter ),
                                  1.0,
                                  1e-15 );
  FRENSIE_CHECK_EQUAL( evaluate( 1.5*MeV, 9.0*cgs::centimeter ), 1.0 );

  // On the upper bin boundary
  ua_min_func = [](XIndepType x){return 0.0*cgs::centimeter;};
  ua_max_func = [](XIndepType x){return 10.0*cgs::centimeter;};

  FRENSIE_CHECK_EQUAL( evaluate( 2.0*MeV, -1.0*cgs::centimeter ), 0.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 2.0*MeV, 0.0*cgs::centimeter ), 0.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 2.0*MeV, 5.0*cgs::centimeter ), 0.5 );
  FRENSIE_CHECK_EQUAL( evaluate( 2.0*MeV, 10.0*cgs::centimeter ), 1.0 );
  FRENSIE_CHECK_EQUAL( evaluate( 2.0*MeV, 11.0*cgs::centimeter ), 1.0 );
}

//---------------------------------------------------------------------------//
// Check that a secondary conditional PDF can be sampled
FRENSIE_UNIT_TEST( UnitBase, sample )
{
  // On the first bin
  std::vector<double> fake_stream( 6 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.0;
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.0;
  fake_stream[5] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  lower_bin = distribution->begin();
  upper_bin = lower_bin;
  ++upper_bin;

  double x_value = 0.0;
  min_func = [](double x){return 0.0;}; max_func = [](double x){return 10.0;};

  double sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_EQUAL( sample, 0.0 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_EQUAL( sample, 5.0 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

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

  // In the first bin
  fake_stream.resize( 12 );
  fake_stream[0] = 0.5; // use lower bin boundary
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.5; // use lower bin boundary
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.5; // use lower bin boundary
  fake_stream[5] = 1.0-1e-15;
  fake_stream[6] = 0.49; // use upper bin boundary
  fake_stream[7] = 0.0;
  fake_stream[8] = 0.49; // use upper bin boundary
  fake_stream[9] = 4.23076923076923128e-01;
  fake_stream[10] = 0.49; // use upper bin boundary
  fake_stream[11] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );
  x_value = 0.5;
  min_func = [](double x){return 1.25;}; max_func = [](double x){return 8.75;};

  // Samples from lower boundary of first bin
  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_EQUAL( sample, 1.25 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_EQUAL( sample, 5.0 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 8.75, 1e-14 );

  // Samples from the upper boundary of the first bin
  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_EQUAL( sample, 1.25 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 5.0, 1e-15 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 8.75, 1e-14 );

  // On the second bin
  fake_stream.resize( 6 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.0;
  fake_stream[3] = 4.23076923076923128e-01;
  fake_stream[4] = 0.0;
  fake_stream[5] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );
  x_value = 1.0;
  min_func = [](double x){return 2.5;}; max_func = [](double x){return 7.5;};

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_EQUAL( sample, 2.5 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 5.0, 1e-15 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 7.5, 1e-15 );

  // In the second bin
  ++lower_bin; ++upper_bin;
  fake_stream.resize( 12 );
  fake_stream[0] = 0.5; // use lower bin boundary
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.5; // use lower bin boundary
  fake_stream[3] = 4.23076923076923128e-01;
  fake_stream[4] = 0.5; // use lower bin boundary
  fake_stream[5] = 1.0-1e-15;
  fake_stream[6] = 0.49; // use upper bin boundary
  fake_stream[7] = 0.0;
  fake_stream[8] = 0.49; // use upper bin boundary
  fake_stream[9] = 0.5;
  fake_stream[10] = 0.49; // use upper bin boundary
  fake_stream[11] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );
  x_value = 1.5;
  min_func = [](double x){return 1.25;}; max_func = [](double x){return 8.75;};

  // Samples from lower boundary of second bin
  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_EQUAL( sample, 1.25 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 5.0, 1e-15 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 8.75, 1e-14 );

  // Samples from upper boundary of second bin
  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_EQUAL( sample, 1.25 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_EQUAL( sample, 5.0 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 8.75, 1e-14 );

  // On the upper bin boundary
  fake_stream.resize( 6 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.0;
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.0;
  fake_stream[5] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );
  x_value = 2.0;
  min_func = [](double x){return 0.0;}; max_func = [](double x){return 10.0;};

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_EQUAL( sample, 0.0 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

  FRENSIE_CHECK_EQUAL( sample, 5.0 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<double,double>(
                  functor, min_func, max_func, x_value, lower_bin, upper_bin );

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

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that a unit-aware secondary conditional PDF can be sampled
FRENSIE_UNIT_TEST( UnitAwareUnitBase, sample )
{
  // On the first bin
  std::vector<double> fake_stream( 6 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.0;
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.0;
  fake_stream[5] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  ua_lower_bin = unit_aware_distribution->begin();
  ua_upper_bin = ua_lower_bin;
  ++ua_upper_bin;
  quantity<MegaElectronVolt> x_value = 0.0*MeV;
  ua_min_func = [](quantity<MegaElectronVolt> x){return 0.0*cgs::centimeter;};
  ua_max_func = [](quantity<MegaElectronVolt> x){return 10.0*cgs::centimeter;};

  quantity<cgs::length> sample =
    Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
      ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

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

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

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

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

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

  // In the first bin
  fake_stream.resize( 12 );
  fake_stream[0] = 0.5; // use lower bin boundary
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.5; // use lower bin boundary
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.5; // use lower bin boundary
  fake_stream[5] = 1.0-1e-15;
  fake_stream[6] = 0.49; // use upper bin boundary
  fake_stream[7] = 0.0;
  fake_stream[8] = 0.49; // use upper bin boundary
  fake_stream[9] = 4.23076923076923128e-01;
  fake_stream[10] = 0.49; // use upper bin boundary
  fake_stream[11] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );
  x_value = 0.5*MeV;
  ua_min_func = [](quantity<MegaElectronVolt> x){return 1.25*cgs::centimeter;};
  ua_max_func = [](quantity<MegaElectronVolt> x){return 8.75*cgs::centimeter;};

  // Samples from lower boundary of first bin
  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

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

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

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

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 8.75*cgs::centimeter, 1e-14 );

  // Samples from the upper boundary of the first bin
  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

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

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 5.0*cgs::centimeter, 1e-15 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 8.75*cgs::centimeter, 1e-14 );

  // On the second bin
  fake_stream.resize( 6 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.0;
  fake_stream[3] = 4.23076923076923128e-01;
  fake_stream[4] = 0.0;
  fake_stream[5] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );
  x_value = 1.0*MeV;
  ua_min_func = [](quantity<MegaElectronVolt> x){return 2.5*cgs::centimeter;};
  ua_max_func = [](quantity<MegaElectronVolt> x){return 7.5*cgs::centimeter;};

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

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

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 5.0*cgs::centimeter, 1e-15 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 7.5*cgs::centimeter, 1e-15 );

  // In the second bin
  ++ua_lower_bin; ++ua_upper_bin;
  fake_stream.resize( 12 );
  fake_stream[0] = 0.5; // use lower bin boundary
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.5; // use lower bin boundary
  fake_stream[3] = 4.23076923076923128e-01;
  fake_stream[4] = 0.5; // use lower bin boundary
  fake_stream[5] = 1.0-1e-15;
  fake_stream[6] = 0.49; // use upper bin boundary
  fake_stream[7] = 0.0;
  fake_stream[8] = 0.49; // use upper bin boundary
  fake_stream[9] = 0.5;
  fake_stream[10] = 0.49; // use upper bin boundary
  fake_stream[11] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );
  x_value = 1.5*MeV;
  ua_min_func = [](quantity<MegaElectronVolt> x){return 1.25*cgs::centimeter;};
  ua_max_func = [](quantity<MegaElectronVolt> x){return 8.75*cgs::centimeter;};

  // Samples from lower boundary of second bin
  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

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

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 5.0*cgs::centimeter, 1e-15 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 8.75*cgs::centimeter, 1e-14 );

  // Samples from upper boundary of second bin
  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

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

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

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

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 8.75*cgs::centimeter, 1e-14 );

  // On the upper bin boundary
  fake_stream.resize( 6 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.0;
  fake_stream[3] = 0.5;\
  fake_stream[4] = 0.0;
  fake_stream[5] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );
  x_value = 2.0*MeV;
  ua_min_func = [](quantity<MegaElectronVolt> x){return 0.0*cgs::centimeter;};
  ua_max_func = [](quantity<MegaElectronVolt> x){return 10.0*cgs::centimeter;};

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

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

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

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

  sample = Utility::UnitBase<Utility::LinLinLin>::sample<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, x_value, ua_lower_bin, ua_upper_bin );

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

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that a secondary conditional PDF can be sampled
FRENSIE_UNIT_TEST( UnitBase, sampleDetailed )
{
  // On the first bin
  std::vector<double> fake_stream( 6 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.0;
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.0;
  fake_stream[5] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double raw_sample;
  unsigned bin_index;

  start_bin = distribution->begin();
  lower_bin = start_bin;
  upper_bin = lower_bin;
  ++upper_bin;
  min_func = [](double x){return 0.0;}; max_func = [](double x){return 10.0;};

  double sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
      functor, min_func, max_func, 0.0, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );
  FRENSIE_CHECK_EQUAL( sample, 0.0 );
  FRENSIE_CHECK_EQUAL( raw_sample, 0.0 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 0.0, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );
  FRENSIE_CHECK_EQUAL( sample, 5.0 );
  FRENSIE_CHECK_EQUAL( raw_sample, 5.0 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 0.0, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 10.0, 1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 10.0, 1e-14 );

  // In the first bin
  fake_stream.resize( 12 );
  fake_stream[0] = 0.5; // use lower bin boundary
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.5; // use lower bin boundary
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.5; // use lower bin boundary
  fake_stream[5] = 1.0-1e-15;
  fake_stream[6] = 0.49; // use upper bin boundary
  fake_stream[7] = 0.0;
  fake_stream[8] = 0.49; // use upper bin boundary
  fake_stream[9] = 4.23076923076923128e-01;
  fake_stream[10] = 0.49; // use upper bin boundary
  fake_stream[11] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );
  min_func = [](double x){return 1.25;}; max_func = [](double x){return 8.75;};

  // Samples from lower boundary of first bin
  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 0.5, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );
  FRENSIE_CHECK_EQUAL( sample, 1.25 );
  FRENSIE_CHECK_EQUAL( raw_sample, 0.0 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 0.5, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );
  FRENSIE_CHECK_EQUAL( sample, 5.0 );
  FRENSIE_CHECK_EQUAL( raw_sample, 5.0 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 0.5, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 8.75, 1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 10.0, 1e-14 );

  // Samples from the upper boundary of the first bin
  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 0.5, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_EQUAL( sample, 1.25 );
  FRENSIE_CHECK_EQUAL( raw_sample, 2.5 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 0.5, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 5.0, 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 5.0, 1e-15 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 0.5, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 8.75, 1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 7.5, 1e-14 );

  // On the second bin
  fake_stream.resize( 6 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.0;
  fake_stream[3] = 4.23076923076923128e-01;
  fake_stream[4] = 0.0;
  fake_stream[5] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );
  min_func = [](double x){return 2.5;}; max_func = [](double x){return 7.5;};

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 1.0, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_EQUAL( sample, 2.5 );
  FRENSIE_CHECK_EQUAL( raw_sample, 2.5 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 1.0, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 5.0, 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 5.0, 1e-15 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 1.0, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 7.5, 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 7.5, 1e-15 );

  // In the second bin
  ++lower_bin; ++upper_bin;
  fake_stream.resize( 12 );
  fake_stream[0] = 0.5; // use lower bin boundary
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.5; // use lower bin boundary
  fake_stream[3] = 4.23076923076923128e-01;
  fake_stream[4] = 0.5; // use lower bin boundary
  fake_stream[5] = 1.0-1e-15;
  fake_stream[6] = 0.49; // use upper bin boundary
  fake_stream[7] = 0.0;
  fake_stream[8] = 0.49; // use upper bin boundary
  fake_stream[9] = 0.5;
  fake_stream[10] = 0.49; // use upper bin boundary
  fake_stream[11] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );
  min_func = [](double x){return 1.25;}; max_func = [](double x){return 8.75;};

  // Samples from lower boundary of second bin
  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 1.5, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_EQUAL( sample, 1.25 );
  FRENSIE_CHECK_EQUAL( raw_sample, 2.5 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 1.5, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 5.0, 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 5.0, 1e-15 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 1.5, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 8.75, 1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 7.5, 1e-14 );

  // Samples from upper boundary of second bin
  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 1.5, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );
  FRENSIE_CHECK_EQUAL( sample, 1.25 );
  FRENSIE_CHECK_EQUAL( raw_sample, 0.0 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 1.5, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );
  FRENSIE_CHECK_EQUAL( sample, 5.0 );
  FRENSIE_CHECK_EQUAL( raw_sample, 5.0 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 1.5, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 8.75, 1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 10.0, 1e-14 );

  // On the upper bin boundary
  fake_stream.resize( 6 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.0;
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.0;
  fake_stream[5] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );
  min_func = [](double x){return 0.0;}; max_func = [](double x){return 10.0;};

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 2.0, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );
  FRENSIE_CHECK_EQUAL( sample, 0.0 );
  FRENSIE_CHECK_EQUAL( raw_sample, 0.0 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 2.0, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );
  FRENSIE_CHECK_EQUAL( sample, 5.0 );
  FRENSIE_CHECK_EQUAL( raw_sample, 5.0 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<double,double>(
    functor, min_func, max_func, 2.0, lower_bin, upper_bin, sampled_bin, raw_sample );

  bin_index = std::distance( start_bin, sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 10.0, 1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 10.0, 1e-14 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that a unit-aware secondary conditional PDF can be sampled
FRENSIE_UNIT_TEST( UnitAwareUnitBase, sampleDetailed )
{
  // On the first bin
  std::vector<double> fake_stream( 6 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.0;
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.0;
  fake_stream[5] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  unsigned bin_index;
  quantity<cgs::length> raw_sample;
  ua_start_bin = unit_aware_distribution->begin();
  ua_lower_bin = ua_start_bin;
  ua_upper_bin = ua_lower_bin;
  ++ua_upper_bin;
  ua_min_func = [](quantity<MegaElectronVolt> x){return 0.0*cgs::centimeter;};
  ua_max_func = [](quantity<MegaElectronVolt> x){return 10.0*cgs::centimeter;};

  quantity<cgs::length> sample =
    Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
      ua_functor, ua_min_func, ua_max_func, 0.0*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );
  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( raw_sample, 0.0*cgs::centimeter );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 0.0*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );
  FRENSIE_CHECK_EQUAL( sample, 5.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( raw_sample, 5.0*cgs::centimeter );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 0.0*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 10.0*cgs::centimeter, 1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 10.0*cgs::centimeter, 1e-14 );

  // In the first bin
  fake_stream.resize( 12 );
  fake_stream[0] = 0.5; // use lower bin boundary
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.5; // use lower bin boundary
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.5; // use lower bin boundary
  fake_stream[5] = 1.0-1e-15;
  fake_stream[6] = 0.49; // use upper bin boundary
  fake_stream[7] = 0.0;
  fake_stream[8] = 0.49; // use upper bin boundary
  fake_stream[9] = 4.23076923076923128e-01;
  fake_stream[10] = 0.49; // use upper bin boundary
  fake_stream[11] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );
  ua_min_func = [](quantity<MegaElectronVolt> x){return 1.25*cgs::centimeter;};
  ua_max_func = [](quantity<MegaElectronVolt> x){return 8.75*cgs::centimeter;};

  // Samples from lower boundary of first bin
  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 0.5*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );
  FRENSIE_CHECK_EQUAL( sample, 1.25*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( raw_sample, 0.0*cgs::centimeter );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 0.5*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );
  FRENSIE_CHECK_EQUAL( sample, 5.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( raw_sample, 5.0*cgs::centimeter );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 0.5*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 0u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 8.75*cgs::centimeter, 1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 10.0*cgs::centimeter, 1e-14 );

  // Samples from the upper boundary of the first bin
  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 0.5*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_EQUAL( sample, 1.25*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( raw_sample, 2.5*cgs::centimeter );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 0.5*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 5.0*cgs::centimeter, 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 5.0*cgs::centimeter, 1e-15 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 0.5*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 8.75*cgs::centimeter, 1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 7.5*cgs::centimeter, 1e-14 );

  // On the second bin
  fake_stream.resize( 6 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.0;
  fake_stream[3] = 4.23076923076923128e-01;
  fake_stream[4] = 0.0;
  fake_stream[5] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );
  ua_min_func = [](quantity<MegaElectronVolt> x){return 2.5*cgs::centimeter;};
  ua_max_func = [](quantity<MegaElectronVolt> x){return 7.5*cgs::centimeter;};

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 1.0*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_EQUAL( sample, 2.5*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( raw_sample, 2.5*cgs::centimeter );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 1.0*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 5.0*cgs::centimeter, 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 5.0*cgs::centimeter, 1e-15 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 1.0*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 7.5*cgs::centimeter, 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 7.5*cgs::centimeter, 1e-15 );

  // In the second bin
  ++ua_lower_bin; ++ua_upper_bin;
  fake_stream.resize( 12 );
  fake_stream[0] = 0.5; // use lower bin boundary
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.5; // use lower bin boundary
  fake_stream[3] = 4.23076923076923128e-01;
  fake_stream[4] = 0.5; // use lower bin boundary
  fake_stream[5] = 1.0-1e-15;
  fake_stream[6] = 0.49; // use upper bin boundary
  fake_stream[7] = 0.0;
  fake_stream[8] = 0.49; // use upper bin boundary
  fake_stream[9] = 0.5;
  fake_stream[10] = 0.49; // use upper bin boundary
  fake_stream[11] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );
  ua_min_func = [](quantity<MegaElectronVolt> x){return 1.25*cgs::centimeter;};
  ua_max_func = [](quantity<MegaElectronVolt> x){return 8.75*cgs::centimeter;};

  // Samples from lower boundary of second bin
  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 1.5*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_EQUAL( sample, 1.25*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( raw_sample, 2.5*cgs::centimeter );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 1.5*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 5.0*cgs::centimeter, 1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 5.0*cgs::centimeter, 1e-15 );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 1.5*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 1u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 8.75*cgs::centimeter, 1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 7.5*cgs::centimeter, 1e-14 );

  // Samples from upper boundary of second bin
  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 1.5*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );
  FRENSIE_CHECK_EQUAL( sample, 1.25*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( raw_sample, 0.0*cgs::centimeter );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 1.5*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );
  FRENSIE_CHECK_EQUAL( sample, 5.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( raw_sample, 5.0*cgs::centimeter );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 1.5*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 8.75*cgs::centimeter, 1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 10.0*cgs::centimeter, 1e-14 );

  // On the upper bin boundary
  fake_stream.resize( 6 );
  fake_stream[0] = 0.0;
  fake_stream[1] = 0.0;
  fake_stream[2] = 0.0;
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.0;
  fake_stream[5] = 1.0-1e-15;
  Utility::RandomNumberGenerator::setFakeStream( fake_stream );
  ua_min_func = [](quantity<MegaElectronVolt> x){return 0.0*cgs::centimeter;};
  ua_max_func = [](quantity<MegaElectronVolt> x){return 10.0*cgs::centimeter;};

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 2.0*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );
  FRENSIE_CHECK_EQUAL( sample, 0.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( raw_sample, 0.0*cgs::centimeter );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 2.0*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );
  FRENSIE_CHECK_EQUAL( sample, 5.0*cgs::centimeter );
  FRENSIE_CHECK_EQUAL( raw_sample, 5.0*cgs::centimeter );

  sample = Utility::UnitBase<Utility::LinLinLin>::sampleDetailed<XIndepType,YIndepType>(
    ua_functor, ua_min_func, ua_max_func, 2.0*MeV, ua_lower_bin, ua_upper_bin, ua_sampled_bin, raw_sample );

  bin_index = std::distance( ua_start_bin, ua_sampled_bin );
  FRENSIE_CHECK_EQUAL( bin_index, 2u );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 10.0*cgs::centimeter, 1e-14 );
  FRENSIE_CHECK_FLOATING_EQUALITY( raw_sample, 10.0*cgs::centimeter, 1e-14 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

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

FRENSIE_CUSTOM_UNIT_TEST_INIT()
{
  // Create the two-dimensional distribution
  {
    DistributionType distribution_data( 3 );

    // Create the secondary distribution in the first bin
    Utility::get<0>( distribution_data[0] ) = 0.0;
    Utility::get<1>( distribution_data[0] ) = std::make_shared<Utility::UniformDistribution>( 0.0, 10.0, 1.0 );

    // Create the secondary distribution in the second bin
    std::vector<double> bin_boundaries( 3 ), values( 3 );
    bin_boundaries[0] = 2.5; values[0] = 0.1;
    bin_boundaries[1] = 5.0; values[1] = 1.0;
    bin_boundaries[2] = 7.5; values[2] = 0.5;

    Utility::get<0>( distribution_data[1] ) = 1.0;
    Utility::get<1>( distribution_data[1] ) = std::make_shared<Utility::TabularDistribution<Utility::LinLin> >( bin_boundaries, values );

    // Create the secondary distribution beyond the second bin
    Utility::get<0>( distribution_data[2] ) = 2.0;
    Utility::get<1>( distribution_data[2] ) = std::make_shared<Utility::UniformDistribution>( 0.0, 10.0, 0.1 );

    distribution.reset( new DistributionType( distribution_data ) );

    // Create the sampling functor
    functor = std::bind<double>( &Utility::TabularUnivariateDistribution::sample,
                                 std::placeholders::_1 );
    threshold_functor =std::bind<double>(
        &Utility::TabularUnivariateDistribution::sampleWithRandomNumber,
          std::placeholders::_1,
          std::placeholders::_2 );
  }

  // Create the unit-aware two-dimensional distribution
  {
    UnitAwareDistributionType distribution_data( 3 );

    // Create the secondary distribution in the first bin
    Utility::get<0>( distribution_data[0] ) = 0.0*MeV;
    Utility::get<1>( distribution_data[0] ) = std::make_shared<Utility::UnitAwareUniformDistribution<cgs::length,Barn> >( 0.0*cgs::centimeter, 10.0*cgs::centimeter, 1.0*barn );

    // Create the secondary distribution in the second bin
    std::vector<quantity<cgs::length> > bin_boundaries( 3 );
    std::vector<quantity<Barn> > values( 3 );
    bin_boundaries[0] = 2.5*cgs::centimeter; values[0] = 0.1*barn;
    bin_boundaries[1] = 5.0*cgs::centimeter; values[1] = 1.0*barn;
    bin_boundaries[2] = 7.5*cgs::centimeter; values[2] = 0.5*barn;

    Utility::get<0>( distribution_data[1] ) = 1.0*MeV;
    Utility::get<1>( distribution_data[1] ) = std::make_shared<Utility::UnitAwareTabularDistribution<Utility::LinLin,cgs::length,Barn> >( bin_boundaries, values );

    // Create the secondary distribution beyond the second bin
    Utility::get<0>( distribution_data[2] ) = 2.0*MeV;
    Utility::get<1>( distribution_data[2] ) = std::make_shared<Utility::UnitAwareUniformDistribution<cgs::length,Barn> >( 0.0*cgs::centimeter, 10.0*cgs::centimeter, 0.1*barn );

    unit_aware_distribution.reset(
        new UnitAwareDistributionType( distribution_data ) );

    // Create the sampling functor
    ua_functor = std::bind<YIndepType>(
      &Utility::UnitAwareTabularUnivariateDistribution<cgs::length,Barn>::sample,
      std::placeholders::_1 );

    ua_threshold_functor = std::bind<YIndepType>(
      &Utility::UnitAwareTabularUnivariateDistribution<cgs::length,Barn>::sampleWithRandomNumber,
        std::placeholders::_1,
        std::placeholders::_2 );
  }

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

FRENSIE_CUSTOM_UNIT_TEST_SETUP_END();

//---------------------------------------------------------------------------//
// end tstUnitBaseTwoDGridPolicy.cpp
//---------------------------------------------------------------------------//


#include "heightmap/GridElevationProvider.hpp"

#include "config.hpp"
#include <boost/test/unit_test.hpp>

using namespace utymap;
using namespace utymap::heightmap;

namespace {

// Elevation data for given quadkey is:
//  3  4  5
//  0  1  2
// -3 -2 -1
struct Heightmap_GridElevationProviderFixture {
  Heightmap_GridElevationProviderFixture() :
      quadKey(16, 35205, 21489),
      bbox(utymap::utils::GeoUtils::quadKeyToBoundingBox(quadKey)),
      eleProvider(TEST_ASSETS_PATH "index/") {
  }

  QuadKey quadKey;
  BoundingBox bbox;
  GridElevationProvider eleProvider;
};

const double Precision = 0.1;
}

BOOST_FIXTURE_TEST_SUITE(Heightmap_GridElevationProvider, Heightmap_GridElevationProviderFixture)

BOOST_AUTO_TEST_CASE(GivenLocationAtBottomLeft_WhenGetElevation_ThenReturnExpectedNegativeHeight) {
  double ele = eleProvider.getElevation(quadKey, bbox.minPoint);

  BOOST_CHECK_CLOSE(ele, -3, Precision);
}

BOOST_AUTO_TEST_CASE(GivenLocationAtTopRight_WhenGetElevation_ThenReturnExpectedPositiveHeight) {
  double ele = eleProvider.getElevation(quadKey, bbox.maxPoint);

  BOOST_CHECK_CLOSE(ele, 5, Precision);
}

BOOST_AUTO_TEST_CASE(GivenLocationInCenter_WhenGetElevation_ThenReturnExpectedHeight) {
  double ele = eleProvider.getElevation(quadKey, bbox.center());

  BOOST_CHECK_CLOSE(ele, 1, Precision);
}

BOOST_AUTO_TEST_CASE(GivenLocationInTheMiddleFromCenterToTopRight_WhenGetElevation_ThenReturnExpectedInterpolatedHeight) {
  GeoCoordinate coordinate(bbox.center().latitude + bbox.height()/4,
                           bbox.center().longitude + bbox.width()/4);

  double ele = eleProvider.getElevation(quadKey, coordinate);

  BOOST_CHECK_CLOSE(ele, 3, Precision);
}

BOOST_AUTO_TEST_CASE(GivenLocationNearTopRight_WhenGetElevation_ThenReturnExpectedInterpolatedHeight) {
  GeoCoordinate coordinate(bbox.maxPoint.latitude,
                           bbox.center().longitude + bbox.width()/4);

  double ele = eleProvider.getElevation(quadKey, coordinate);

  BOOST_CHECK_CLOSE(ele, 4.5, Precision);
}

BOOST_AUTO_TEST_SUITE_END()


/**
 * @file ncl.cpp
 *
 */

#include "ncl.h"
#include "forecast_time.h"
#include "level.h"
#include "logger.h"
#include "plugin_factory.h"
#include <boost/lexical_cast.hpp>

#include "neons.h"
#include "radon.h"

using namespace std;
using namespace himan::plugin;

const string itsName("ncl");

ncl::ncl() : itsBottomLevel(kHPMissingInt), itsTopLevel(kHPMissingInt), itsTargetTemperature(kHPMissingInt)
{
	itsLogger = logger(itsName);
}

void ncl::Process(std::shared_ptr<const plugin_configuration> conf)
{
	Init(conf);

	HPDatabaseType dbtype = conf->DatabaseType();

	if (dbtype == kNeons || dbtype == kNeonsAndRadon)
	{
		auto n = GET_PLUGIN(neons);

		itsBottomLevel = boost::lexical_cast<int>(
		    n->ProducerMetaData(itsConfiguration->SourceProducer().Id(), "last hybrid level number"));
		itsTopLevel = boost::lexical_cast<int>(
		    n->ProducerMetaData(itsConfiguration->SourceProducer().Id(), "first hybrid level number"));
	}

	if ((dbtype == kRadon || dbtype == kNeonsAndRadon) &&
	    (itsBottomLevel == kHPMissingInt || itsTopLevel == kHPMissingInt))
	{
		auto r = GET_PLUGIN(radon);

		itsBottomLevel = boost::lexical_cast<int>(
		    r->RadonDB().GetProducerMetaData(itsConfiguration->SourceProducer().Id(), "last hybrid level number"));
		itsTopLevel = boost::lexical_cast<int>(
		    r->RadonDB().GetProducerMetaData(itsConfiguration->SourceProducer().Id(), "first hybrid level number"));
	}

	param theRequestedParam;

	if (itsConfiguration->Exists("temp") && itsConfiguration->GetValue("temp") == "-20")
	{
		theRequestedParam.Name("HM20C-M");
		theRequestedParam.UnivId(28);
		itsTargetTemperature = -20;
	}

	if (itsConfiguration->Exists("temp") && itsConfiguration->GetValue("temp") == "0")
	{
		theRequestedParam.Name("H0C-M");
		theRequestedParam.UnivId(270);
		itsTargetTemperature = 0;
	}

	SetParams({theRequestedParam});

	Start();
}

/*
 * Calculate()
 *
 * This function does the actual calculation.
 */

void ncl::Calculate(shared_ptr<info> myTargetInfo, unsigned short threadIndex)
{
	const param HParam("HL-M");
	const param TParam("T-K");

	int levelNumber = itsBottomLevel;

	level HLevel(himan::kHybrid, static_cast<float>(levelNumber), "HYBRID");

	auto myThreadedLogger = logger(itsName + "Thread #" + to_string(threadIndex));

	forecast_time forecastTime = myTargetInfo->Time();
	level forecastLevel = myTargetInfo->Level();
	forecast_type forecastType = myTargetInfo->ForecastType();

	myThreadedLogger.Info("Calculating time " + static_cast<string>(forecastTime.ValidDateTime()) + " level " +
						  static_cast<string>(forecastLevel));

	info_t HInfo = Fetch(forecastTime, HLevel, HParam, forecastType, false);
	info_t TInfo = Fetch(forecastTime, HLevel, TParam, forecastType, false);

	if (!HInfo || !TInfo)
	{
		myThreadedLogger.Error("Skipping step " + to_string(forecastTime.Step()) + ", level " +
							   static_cast<string>(forecastLevel));
		return;
	}

	bool firstLevel = true;

	myTargetInfo->Data().Fill(-1);

	HInfo->ResetLocation();
	TInfo->ResetLocation();

	level curLevel = HLevel;
	level prevLevel;

	string deviceType = "CPU";

	info_t prevHInfo, prevTInfo;

	while (--levelNumber >= itsTopLevel)
	{
		myThreadedLogger.Trace("Level: " + to_string(levelNumber));

		if (prevHInfo)
		{
			prevHInfo->ResetLocation();
		}

		if (prevTInfo)
		{
			prevTInfo->ResetLocation();
		}

		assert(HInfo && TInfo);

		LOCKSTEP(myTargetInfo, TInfo, HInfo)
		{
			double height = HInfo->Value();
			double temp = TInfo->Value();

			double prevHeight(kFloatMissing);
			double prevTemp(kFloatMissing);

			double targetHeight = myTargetInfo->Value();

			if (!firstLevel)
			{
				prevHInfo->NextLocation();
				prevHeight = prevHInfo->Value();

				prevTInfo->NextLocation();
				prevTemp = prevTInfo->Value();
			}

			if (height == kFloatMissing || temp == kFloatMissing ||
			    (!firstLevel && (prevHeight == kFloatMissing || prevTemp == kFloatMissing)))
			{
				continue;
			}

			temp -= himan::constants::kKelvin;
			prevTemp -= himan::constants::kKelvin;

			if (targetHeight != -1)
			{
				if (temp >= itsTargetTemperature)  // && levelNumber >= (itsBottomLevel - 5))
				{
					/*
					 * Lowest 5 model levels and "inversion fix".
					 *
					 * Especially in winter time surface inversion is common: temperature
					 * close to ground surfaceis colder than the temperature above it.
					 * This inversion is usually very limited in height, maybe 10 .. 200
					 * meters. When calculating height of zero level, we can take into
					 * account this inversion so we test if the temperature at lowest level
					 * is already below target temperature (0 or -20), and if that is the
					 * situation we do not directly set height to kFloatMissing but
					 * move onwards and only if the temperature stays below target for
					 * the first 5 levels (in Hirlam lowest hybrid levels are spaced ~30m
					 * apart) we can say that surface inversion does not exist and the
					 * air temperature is simply too cold for this target temperature.
					 *
					 * In order to imitate hilake however the inversion fix limitation for
					 * bottom 5 has been disabled, since what hilake does is it applies
					 * the fix as long as the process is running, at least up to hybrid
					 * level 29 which on Hirlam is on average 3700m above ground.
					 *
					 * This seems like a non-optimal approach but this is how hilake
					 * does it and until we have confirmation that the inversion
					 * height should be limited, use the hilake way also in himan.
					 *
					 * Inversion: http://blogi.foreca.fi/2012/01/pakkanen-ja-inversio/
					 */

					myTargetInfo->Value(-1);
				}

				// No inversion fix, value already found for this gridpoint
				// (real value or missing value)

				continue;
			}

			if ((firstLevel && temp < itsTargetTemperature) ||
			    (prevTemp < itsTargetTemperature && temp < itsTargetTemperature))
			{
				/*
				 * Height is below ground (first model level) or its so cold
				 * in the lower atmosphere that we cannot find the requested
				 * temperature, set height to to missing value.
				 */

				targetHeight = kFloatMissing;
			}
			else if (prevTemp > itsTargetTemperature && temp < itsTargetTemperature)
			{
				// Found value, interpolate

				double p_rel = (itsTargetTemperature - temp) / (prevTemp - temp);
				targetHeight = height + (prevHeight - height) * p_rel;
			}
			else
			{
				// Too warm
				continue;
			}

			myTargetInfo->Value(targetHeight);
		}

		if (CountValues(myTargetInfo))
		{
			break;
		}

		prevLevel = curLevel;
		curLevel = level(himan::kHybrid, static_cast<float>(levelNumber), "HYBRID");

		HInfo = Fetch(forecastTime, curLevel, HParam, forecastType, false);
		TInfo = Fetch(forecastTime, curLevel, TParam, forecastType, false);

		prevHInfo = Fetch(forecastTime, prevLevel, HParam, forecastType, false);
		prevTInfo = Fetch(forecastTime, prevLevel, TParam, forecastType, false);

		if (!HInfo || !TInfo || !prevHInfo || !prevTInfo)
		{
			myThreadedLogger.Error("Not enough data for step " + to_string(forecastTime.Step()) +
								   ", level " + static_cast<string>(forecastLevel));
			break;
		}

		firstLevel = false;
	}

	/*
	 * Replaces all unset values
	 */

	myTargetInfo->ResetLocation();

	while (myTargetInfo->NextLocation())
	{
		if (myTargetInfo->Value() == -1.)
		{
			myTargetInfo->Value(kFloatMissing);
		}
	}

	myThreadedLogger.Info("[" + deviceType + "] Missing values: " + to_string(myTargetInfo->Data().MissingCount()) +
	                      "/" + to_string(myTargetInfo->Data().Size()));
}

bool ncl::CountValues(const shared_ptr<himan::info> values)
{
	size_t s = values->Data().Size();

#ifdef DEBUG
	size_t foundVals = s;
#endif

	for (size_t j = 0; j < s; j++)
	{
		if (values->Data().At(j) == -1)
		{
#ifdef DEBUG
			foundVals--;
#else
			return false;
#endif
		}
	}

#ifdef DEBUG
	itsLogger.Debug("Found value for " + to_string(foundVals) + "/" + to_string(s) + " gridpoints");

	if (foundVals != s) return false;
#endif

	return true;
}