#include <opencv2/opencv.hpp>
#include <dlib/opencv.h>
#include <opencv2/highgui/highgui.hpp>
#include <dlib/image_processing/frontal_face_detector.h>
#include <dlib/image_processing/render_face_detections.h>
#include <dlib/image_processing.h>
#include <dlib/gui_widgets.h>
#include <vector>
#include "FacePose.h"

#define PI 3.141592

using namespace dlib;
using namespace std;

extern cv::VideoCapture cap;

// 特徴点集合
std::vector< cv::Point2d > feature(6);

// 3次元モデル
std::vector< cv::Point3d > base(6);

// 顔検出モデル
frontal_face_detector detector;

// 姿勢推定モデル
shape_predictor pose_model;

// 理想カメラモデル
double focal_length;
cv::Point2d cent=cv::Point2d(0,0);
cv::Mat camera_matrix;
cv::Mat dist_coeffs;

Rotate r={};

bool LoadModel() {
  try {
    // 顔検出モデルを読み込む
    detector = get_frontal_face_detector();

    // 姿勢推定モデルを読み込む
    // 実行ファイルと同じ場所にshumon_deep_learning.datを置いておく
    deserialize("shumon_deep_learning.dat") >> pose_model;

    // 3次元モデル
    base[0] = cv::Point3d(0.0f, 0.0f, 0.0f); // 鼻先
    base[1] = cv::Point3d(0.0f, -330.0f, -65.0f); // 顎
    base[2] = cv::Point3d(-225.0f, 170.0f, -135.0f); // 左目尻
    base[3] = cv::Point3d(225.0f, 170.0f, -135.0f); // 右目尻
    base[4] = cv::Point3d(-150.0f, -150.0f, -125.0f); // 口左端
    base[5] = cv::Point3d(150.0f, -150.0f, -125.0f); // 口右端

    return true;
  }
  catch (...) {
    return false;
  }
}

void GetFeature() {
  cv::Mat temp;
  if(!cap.read(temp)) {
    return;
  }
  // 理想カメラモデル
  focal_length = temp.cols;
  cent = cv::Point2d(temp.cols / 2, temp.rows / 2);
  camera_matrix = (cv::Mat_<double>(3,3) << focal_length,
                   0,
                   cent.x,
                   0,
                   focal_length, cent.y,
                   0,
                   0,
                   1);
  dist_coeffs = cv::Mat::zeros(4,
                               1,
                               cv::DataType<double>::type); // 歪なし

  cv_image<bgr_pixel> cimg(temp);

  // 顔検出
  std::vector<rectangle> faces = detector(cimg);
  // 姿勢推定
  std::vector<full_object_detection> shapes;
  for (unsigned long i = 0; i < faces.size(); ++i) {
    shapes.push_back(pose_model(cimg, faces[i]));
  }

  if(faces.size() != 0) {
    // 鼻先
    feature[0] = cv::Point2d(shapes[0].part(34).x(),
                             shapes[0].part(34).y());
    // 顎
    feature[1] = cv::Point2d(shapes[0].part(9).x(),
                             shapes[0].part(9).y());
    // 左目尻
    feature[2] = cv::Point2d(shapes[0].part(46).x(),
                             shapes[0].part(46).y());
    // 右目尻
    feature[3] = cv::Point2d(shapes[0].part(37).x(),
                             shapes[0].part(37).y());
    // 口左端
    feature[4] = cv::Point2d(shapes[0].part(55).x(),
                             shapes[0].part(55).y());
    // 口右端
    feature[5] = cv::Point2d(shapes[0].part(49).x(),
                             shapes[0].part(49).y());
 }
}

Rotate ReadRotate() {
  GetFeature();
  cv::Mat rotation_vector;
  cv::Mat translation_vector;
  cv::solvePnP(base, feature, camera_matrix, dist_coeffs, rotation_vector, translation_vector);
  cv::Mat rotation_matrix;
  cv::Rodrigues(rotation_vector, rotation_matrix);
  double threshold = 0.001;
  if(abs(rotation_matrix.at<double>(2, 1) - 1.0) < threshold) { // rotation_matrix(2,1) = sin(x) = 1の時
    r.x = PI / 2;
    r.y = 0;
    r.z = atan2(rotation_matrix.at<double>(1, 0), rotation_matrix.at<double>(0, 0));
  }
  else if(abs(rotation_matrix.at<double>(2, 1) + 1.0) < threshold) { // rotation_matrix(2,1) = sin(x) = -1の時
    r.x = - PI / 2;
    r.y = 0;
    r.z = atan2(rotation_matrix.at<double>(1, 0), rotation_matrix.at<double>(0, 0));
  }
  else {
    r.x = asin(rotation_matrix.at<double>(2, 1));
    r.y = atan2(-rotation_matrix.at<double>(2, 0), rotation_matrix.at<double>(2, 2));
    r.z = atan2(-rotation_matrix.at<double>(0, 1), rotation_matrix.at<double>(1, 1));
  }
return r;
}


#include <boost/version.hpp>
#if BOOST_VERSION < 106300 && BOOST_VERSION >= 106200
// Boost 1.62 is missing an iostream include...
#include <iostream>
#endif
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/geometries/pointing_segment.hpp>
#include <boost/geometry/index/rtree.hpp>

#include "lanelet2_core/geometry/LineString.h"
#include "lanelet2_core/geometry/Polygon.h"

namespace lanelet {
namespace geometry {
namespace {
using V3d = BasicPoint3d;

struct LineParams {
  double sN;
  double sD;
  double tN;
  double tD;
};

constexpr double SmallNum = 1.e-10;

inline LineParams calculateLineParams(double a, double b, double c, double d, double e, double den) {
  // compute the line parameters of the two closest points
  if (den < SmallNum) {  // the lines are almost parallel
    // force using point P0 on segment S1
    // to prevent possible division by 0.0 later
    return {0.0, 1.0, e, c};
  }
  LineParams lp{};
  lp.sD = den;
  lp.tD = den;
  // get the closest points on the infinite lines
  lp.sN = (b * e - c * d);
  lp.tN = (a * e - b * d);
  if (lp.sN < 0.0) {  // sc < 0 => the s=0 edge is visible
    lp.sN = 0.0;
    lp.tN = e;
    lp.tD = c;
  } else if (lp.sN > lp.sD) {  // sc > 1  => the s=1 edge is visible
    lp.sN = lp.sD;
    lp.tN = e + b;
    lp.tD = c;
  }

  return lp;
}

std::pair<BasicPoint3d, BasicPoint3d> projectedPoint3d(const BasicPoint3d& p1, const BasicPoint3d& p2,
                                                       const BasicPoint3d& q1, const BasicPoint3d& q2) {
  // see http://geomalgorithms.com/a07-_distance.html
  V3d w = p1 - q1;
  V3d u = p2 - p1;
  V3d v = q2 - q1;
  double a = u.dot(u);
  double b = u.dot(v);
  double c = v.dot(v);
  double d = u.dot(w);
  double e = v.dot(w);
  auto den = a * c - b * b;

  auto lp = calculateLineParams(a, b, c, d, e, den);

  if (lp.tN < 0.0) {  // tc < 0 => the t=0 edge is visible
    lp.tN = 0.0;
    // recompute sc for this edge
    if (-d < 0.0) {
      lp.sN = 0.0;
    } else if (-d > a) {
      lp.sN = lp.sD;
    } else {
      lp.sN = -d;
      lp.sD = a;
    }
  } else if (lp.tN > lp.tD) {  // tc > 1  => the t=1 edge is visible
    lp.tN = lp.tD;
    // recompute sc for this edge
    if ((-d + b) < 0.0) {
      lp.sN = 0;
    } else if ((-d + b) > a) {
      lp.sN = lp.sD;
    } else {
      lp.sN = (-d + b);
      lp.sD = a;
    }
  }
  // finally do the division to get sc and tc
  double sc = (std::abs(lp.sN) < SmallNum ? 0.0 : lp.sN / lp.sD);
  double tc = (std::abs(lp.tN) < SmallNum ? 0.0 : lp.tN / lp.tD);

  return {p1 + (sc * u), q1 + (tc * v)};  // return the closest distance
}

namespace bg = boost::geometry;
namespace bgi = bg::index;
namespace bgm = boost::geometry::model;
using BasicSegment = bgm::pointing_segment<const BasicPoint3d>;
using Box = bgm::box<bgm::point<double, 3, boost::geometry::cs::cartesian>>;
using Node = std::pair<Box, BasicSegment>;
using RTree = bgi::rtree<Node, bgi::linear<8>>;

template <typename LineString1T, typename LineString2T>
std::pair<BasicPoint3d, BasicPoint3d> projectedPoint3dOrdered(const LineString1T& smallerRange,
                                                              const LineString2T& greaterRange) {
  // catch some degerated cases
  ConstHybridLineString3d duplicate;
  if (smallerRange.size() == 1 && greaterRange.size() == 1) {
    std::pair<BasicPoint3d, BasicPoint3d> ret(greaterRange.front(), smallerRange.front());
    return ret;
  }
  auto values =
      utils::transform(bg::segments_begin(greaterRange), bg::segments_end(greaterRange), [](const auto& segm) {
        Box box;
        boost::geometry::envelope(segm, box);
        return Node(box, segm);
      });
  RTree tree(values.begin(), values.end());

  bool first = true;
  double dMin{};
  std::pair<BasicPoint3d, BasicPoint3d> closestPair;
  for (auto it = bg::segments_begin(smallerRange); it != bg::segments_end(smallerRange); ++it) {
    Box queryBox;
    bg::envelope(*it, queryBox);
    for (auto qIt = tree.qbegin(bgi::nearest(queryBox, unsigned(greaterRange.size()))); qIt != tree.qend();
         ++qIt, first = false) {
      const auto& nearest = *qIt;
      auto dBox = boost::geometry::distance(nearest.first, queryBox);
      if (!first && dBox > dMin) {
        break;
      }
      auto projPair = projectedPoint3d(*nearest.second.first, *nearest.second.second, *it->first, *it->second);
      auto d = (projPair.first - projPair.second).norm();
      if (first || d < dMin) {
        closestPair = projPair;
        dMin = d;
      }
    }
  }
  return closestPair;
}

template <typename LineString1T, typename LineString2T>
std::pair<BasicPoint3d, BasicPoint3d> projectedPoint3dImpl(const LineString1T& l1, const LineString2T& l2) {
  if (l1.size() < l2.size()) {
    return projectedPoint3dOrdered(l1, l2);
  }
  auto res = projectedPoint3dOrdered(l2, l1);
  return {res.second, res.first};
}
}  // namespace

namespace internal {
std::pair<BasicPoint3d, BasicPoint3d> projectedPoint3d(const CompoundHybridLineString3d& l1,
                                                       const CompoundHybridLineString3d& l2) {
  return projectedPoint3dImpl(l1, l2);
}

std::pair<BasicPoint3d, BasicPoint3d> projectedPoint3d(const ConstHybridLineString3d& l1,
                                                       const CompoundHybridLineString3d& l2) {
  return projectedPoint3dImpl(l1, l2);
}

std::pair<BasicPoint3d, BasicPoint3d> projectedPoint3d(const CompoundHybridLineString3d& l1,
                                                       const ConstHybridLineString3d& l2) {
  return projectedPoint3dImpl(l1, l2);
}

std::pair<BasicPoint3d, BasicPoint3d> projectedPoint3d(const ConstHybridLineString3d& l1,
                                                       const ConstHybridLineString3d& l2) {
  return projectedPoint3dImpl(l1, l2);
}

std::pair<BasicPoint3d, BasicPoint3d> projectedPoint3d(const ConstHybridLineString3d& l1, const BasicLineString3d& l2) {
  return projectedPoint3dImpl(l1, l2);
}

std::pair<BasicPoint3d, BasicPoint3d> projectedPoint3d(const BasicLineString3d& l1, const ConstHybridLineString3d& l2) {
  return projectedPoint3dImpl(l1, l2);
}

std::pair<BasicPoint3d, BasicPoint3d> projectedPoint3d(const BasicLineString3d& l1, const BasicLineString3d& l2) {
  return projectedPoint3dImpl(l1, l2);
}

std::pair<BasicPoint3d, BasicPoint3d> projectedBorderPoint3d(const ConstHybridPolygon3d& l1,
                                                             const ConstHybridPolygon3d& l2) {
  return projectedPoint3dImpl(l1, l2);
}

std::pair<BasicPoint3d, BasicPoint3d> projectedBorderPoint3d(const CompoundHybridPolygon3d& l1,
                                                             const CompoundHybridPolygon3d& l2) {
  return projectedPoint3dImpl(l1, l2);  // NOLINT
}

}  // namespace internal

Segment<BasicPoint2d> closestSegment(const BasicLineString2d& lineString, const BasicPoint2d& pointToProject) {
  helper::ProjectedPoint<BasicPoint2d> projectedPoint;
  distance(lineString, pointToProject, projectedPoint);
  return Segment<BasicPoint2d>(projectedPoint.result->segmentPoint1, projectedPoint.result->segmentPoint2);
}
Segment<BasicPoint3d> closestSegment(const BasicLineString3d& lineString, const BasicPoint3d& pointToProject) {
  helper::ProjectedPoint<BasicPoint3d> projectedPoint;
  distance(lineString, pointToProject, projectedPoint);
  return Segment<BasicPoint3d>(projectedPoint.result->segmentPoint1, projectedPoint.result->segmentPoint2);
}
}  // namespace geometry
}  // namespace lanelet


#include <iostream>
#include <iomanip>
#include <string>
#include <fstream>
#include <boost/lexical_cast.hpp>

using namespace std;
using boost::lexical_cast;

int main(int argc, char* argv[])
{
	if (argc == 1)
	{
		cout << "insertprop 16_lig.pdbqt 16_prop.tsv\n";
		return 0;
	}

	cout.setf(ios::fixed, ios::floatfield);
	cout << setprecision(3);
	string line;
	ifstream lig(argv[1]);
	ifstream prop(argv[2]);
	while (getline(lig, line))
	{
		if (line[0] == 'H')
		{
			cout << "REMARK     " << line.substr(14, 8);
			getline(prop, line);
			const auto t1 = line.find_first_of('\t',      3); // mwt\tlgp
			const auto t2 = line.find_first_of('\t', t1 + 5); // lgp\tads
			const auto t3 = line.find_first_of('\t', t2 + 2); // ads\tpds
			const auto t4 = line.find_first_of('\t', t3 + 2); // pds\thbd
			const auto t5 = line.find_first_of('\t', t4 + 2); // hbd\thba
			const auto t6 = line.find_first_of('\t', t5 + 2); // hba\tpsa
			const auto t7 = line.find_first_of('\t', t6 + 2); // psa\tchg
			const auto t8 = line.find_first_of('\t', t7 + 2); // chg\tnrb
			const auto mwt = lexical_cast<float>(line.substr(0, t1));
			const auto lgp = lexical_cast<float>(line.substr(t1 + 1, t2 - t1 - 1));
			const auto ads = lexical_cast<float>(line.substr(t2 + 1, t3 - t2 - 1));
			const auto pds = lexical_cast<float>(line.substr(t3 + 1, t4 - t3 - 1));
			const auto hbd = lexical_cast<uint32_t>(line.substr(t4 + 1, t5 - t4 - 1));
			const auto hba = lexical_cast<uint32_t>(line.substr(t5 + 1, t6 - t5 - 1));
			const auto psa = lexical_cast<uint32_t>(line.substr(t6 + 1, t7 - t6 - 1));
			const auto chg = lexical_cast<int32_t>(line.substr(t7 + 1, t8 - t7 - 1));
			const auto nrb = lexical_cast<uint32_t>(line.substr(t8 + 1));
			cout << ' ' << setw(8) << mwt
			     << ' ' << setw(8) << lgp
			     << ' ' << setw(8) << ads
			     << ' ' << setw(8) << pds
			     << ' ' << setw(3) << hbd
			     << ' ' << setw(3) << hba
			     << ' ' << setw(3) << psa
			     << ' ' << setw(3) << chg
			     << ' ' << setw(3) << nrb
			     << "    \n";
		}
		else
		{
			cout << line << '\n';
		}
	}
}


// Copyright Louis Dionne 2013-2016
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)

#include <boost/hana/assert.hpp>
#include <boost/hana/equal.hpp>
#include <boost/hana/ext/std/ratio.hpp>
#include <boost/hana/not.hpp>

#include <ratio>
namespace hana = boost::hana;

int main() {
    BOOST_HANA_CONSTANT_CHECK(hana::equal(
        std::ratio<0>{},
        std::ratio<0>{}
    ));

    BOOST_HANA_CONSTANT_CHECK(hana::equal(
        std::ratio<3>{},
        std::ratio<3>{}
    ));

    BOOST_HANA_CONSTANT_CHECK(hana::equal(
        std::ratio<3, 5>{},
        std::ratio<6, 10>{}
    ));

    BOOST_HANA_CONSTANT_CHECK(hana::not_(hana::equal(
        std::ratio<4, 5>{},
        std::ratio<6, 10>{}
    )));
}


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2012 Mateusz Loskot, London, UK.
// Copyright (c) 2013 Adam Wulkiewicz, Lodz, Poland.

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_EXTENSIONS_ALGEBRA_GEOMETRIES_QUATERNION_HPP
#define BOOST_GEOMETRY_EXTENSIONS_ALGEBRA_GEOMETRIES_QUATERNION_HPP

#include <cstddef>

#include <boost/geometry/extensions/algebra/core/tags.hpp>
#include <boost/geometry/extensions/algebra/geometries/concepts/quaternion_concept.hpp>

// WARNING!
// It is probable that the sequence of coordinate will change in the future
// at the beginning there would be xyz, w would become the last coordinate

namespace boost { namespace geometry
{

namespace model
{

template <typename T>
class quaternion
{
    BOOST_CONCEPT_ASSERT( (concepts::Quaternion<quaternion>) );

public:

    /// @brief Default constructor, no initialization
    inline quaternion()
    {}

    /// @brief Constructor to set components
    inline quaternion(T const& w, T const& x, T const& y, T const& z)
    {
        m_values[0] = w;
        m_values[1] = x;
        m_values[2] = y;
        m_values[3] = z;
    }

    /// @brief Get a coordinate
    /// @tparam K coordinate to get
    /// @return the coordinate
    template <std::size_t K>
    inline T const& get() const
    {
        BOOST_STATIC_ASSERT(K < 4);
        return m_values[K];
    }

    /// @brief Set a coordinate
    /// @tparam K coordinate to set
    /// @param value value to set
    template <std::size_t K>
    inline void set(T const& value)
    {
        BOOST_STATIC_ASSERT(K < 4);
        m_values[K] = value;
    }

private:

    T m_values[4];
};

} // namespace model

#ifndef DOXYGEN_NO_TRAITS_SPECIALIZATIONS
namespace traits
{

template <typename CoordinateType>
struct tag<model::quaternion<CoordinateType> >
{
    typedef quaternion_tag type;
};

template <typename CoordinateType>
struct coordinate_type<model::quaternion<CoordinateType> >
{
    typedef CoordinateType type;
};

//template <typename CoordinateType>
//struct coordinate_system<model::quaternion<CoordinateType> >
//{
//    typedef cs::cartesian type;
//};

template <typename CoordinateType>
struct dimension<model::quaternion<CoordinateType> >
    : boost::integral_constant<std::size_t, 4>
{};

template<typename CoordinateType, std::size_t Dimension>
struct access<model::quaternion<CoordinateType>, Dimension>
{
    static inline CoordinateType get(
        model::quaternion<CoordinateType> const& v)
    {
        return v.template get<Dimension>();
    }

    static inline void set(
        model::quaternion<CoordinateType> & v,
        CoordinateType const& value)
    {
        v.template set<Dimension>(value);
    }
};

} // namespace traits
#endif // DOXYGEN_NO_TRAITS_SPECIALIZATIONS

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_EXTENSIONS_ALGEBRA_GEOMETRIES_QUATERNION_HPP


#pragma once

#include <polyfem/ElementBases.hpp>
#include <polyfem/Mesh3D.hpp>
#include <polyfem/LocalBoundary.hpp>
#include <polyfem/InterfaceData.hpp>
#include <polyfem/Navigation3D.hpp>
#include <polyfem/MeshNodes.hpp>

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

namespace polyfem
{
	class FEBasis3d
	{
	public:

		///
		/// @brief      Builds FE basis functions over the entire mesh (Q1, Q2).
		///
		/// @param[in]  mesh               The input volume mesh
		/// @param[in]  quadrature_order   The quadrature order
		/// @param[in]  discr_order        The order of the elements (1 or 2)
		/// @param[out] bases              List of basis functions per element
		/// @param[out] local_boundary     List of descriptor per element, indicating which facet of
		///                                the canonical hex lie on the boundary of the mesh
		/// @param      poly_face_to_data  Data for faces at the interface with a polyhedra
		///
		/// @return     The number of basis functions created.
		///
		static int build_bases(
			const Mesh3D &mesh,
			const int quadrature_order,
			const int discr_order,
			const bool serendipity,
			const bool has_polys,
			const bool is_geom_bases,
			std::vector< ElementBases > &bases,
			std::vector< LocalBoundary > &local_boundary,
			std::map<int, InterfaceData> &poly_face_to_data);

		static int build_bases(
			const Mesh3D &mesh,
			const int quadrature_order,
			const Eigen::VectorXi &discr_order,
			const bool serendipity,
			const bool has_polys,
			const bool is_geom_bases,
			std::vector< ElementBases > &bases,
			std::vector< LocalBoundary > &local_boundary,
			std::map<int, InterfaceData> &poly_face_to_data);

		static Eigen::VectorXi tet_face_local_nodes(const int p, const Mesh3D &mesh, Navigation3D::Index index);
		static Eigen::VectorXi hex_face_local_nodes(const bool serendipity, const int q, const Mesh3D &mesh, Navigation3D::Index index);
	private:

		static Eigen::MatrixXd linear_hex_face_local_nodes_coordinates(const Mesh3D &mesh, Navigation3D::Index index);

		static Eigen::RowVector3d quadr_hex_local_node_coordinates(int local_index);
	};
}


#include <boost/polygon/polygon.hpp>

#include <cbag/common/box_t_adapt.h>
#include <cbag/common/box_t_util.h>
#include <cbag/layout/geo_object.h>
#include <cbag/util/overload.h>

namespace bp = boost::polygon;

namespace cbag {
namespace layout {

bool geo_object::operator==(const geo_object &v) const {
    return val == v.val && spx == v.spx && spy == v.spy;
}

const geo_instance *geo_object::get_instance() const { return std::get_if<geo_instance>(&val); }

bg_box get_bnd_box(const geo_object::value_type &val, offset_t spx, offset_t spy) {
    box_t box;
    std::visit(overload{[&box](const box_t &v) { box = v; },
                        [&box](const polygon90 &v) {
                            bool success = bp::extents(box, v);
                            if (!success)
                                box = box_t::get_invalid_box();
                        },
                        [&box](const polygon45 &v) {
                            bool success = bp::extents(box, v);
                            if (!success)
                                box = box_t::get_invalid_box();
                        },
                        [&box](const polygon &v) {
                            bool success = bp::extents(box, v);
                            if (!success)
                                box = box_t::get_invalid_box();
                        },
                        [&box](const geo_instance &v) { box = v.get_bbox(); }},
               val);

    if (!is_valid(box))
        throw std::invalid_argument("Cannot add geometry object with invalid bound box.");
    return bg_box(bg_point(xl(box) - spx, yl(box) - spy), bg_point(xh(box) + spx, yh(box) + spy));
}

} // namespace layout
} // namespace cbag


#include <boost/accumulators/statistics/p_square_cumulative_distribution.hpp>


#ifndef PYTHONIC_INCLUDE_NUMPY_POWER_HPP
#define PYTHONIC_INCLUDE_NUMPY_POWER_HPP

#include "pythonic/include/types/numpy_op_helper.hpp"
#include "pythonic/include/utils/meta.hpp"
#include "pythonic/include/utils/numpy_traits.hpp"
#include "pythonic/include/utils/functor.hpp"

#include <boost/simd/function/pow.hpp>
#ifdef USE_GMP
#include "pythonic/types/long.hpp"
#endif

PYTHONIC_NS_BEGIN

namespace numpy
{
  namespace wrapper
  {
    template <class T0, class T1>
    auto pow(T0 const &t0, T1 const &t1) -> decltype(
        boost::simd::pow((typename std::common_type<T0, T1>::type)t0,
                         (typename std::common_type<T0, T1>::type)t1))
    {
      return boost::simd::pow((typename std::common_type<T0, T1>::type)t0,
                              (typename std::common_type<T0, T1>::type)t1);
    }
    // See https://github.com/MetaScale/nt2/issues/794
    double pow(long const &n, double const &m)
    {
      return pow(static_cast<double>(n), m);
    }
    template <class T>
    std::complex<T> pow(T const &n, std::complex<T> const &m)
    {
      return std::pow(n, m);
    }
    template <class T>
    std::complex<T> pow(std::complex<T> const &n, std::complex<T> const &m)
    {
      return std::pow(n, m);
    }
    template <class T>
    std::complex<T> pow(std::complex<T> const &n, T const &m)
    {
      return std::pow(n, m);
    }
#ifdef USE_GMP
    template <class T0, class U0, class T>
    pythran_long_t pow(__gmp_expr<T0, U0> const &arg0, T const &arg1)
    {
      mpz_class rop;
      mpz_pow_ui(rop.get_mpz_t(), arg0.get_mpz_t(), arg1);
      return rop;
    }
#endif
  }
#define NUMPY_NARY_FUNC_NAME power
#define NUMPY_NARY_FUNC_SYM wrapper::pow
// no need to adapt_type here, as it may turn a**2 into a**2.f
#define NUMPY_NARY_RESHAPE_MODE reshape_type
#include "pythonic/include/types/numpy_nary_expr.hpp"
}
PYTHONIC_NS_END

#endif


//==================================================================================================
/*!
  @file

  @copyright 2012-2015 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_CONSTANT_LOG10_ELO_HPP_INCLUDED
#define BOOST_SIMD_CONSTANT_LOG10_ELO_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{
  /*!

    @ingroup group-constant

    Generates constant log10_elo.This constant is coupled with Log10_ehi and is
    used in the float logarithms computations
    We have abs( double(log10_elo<float>())+double(Log10_ehi<float>()) - Log_10(e)) < 3e-11

    @par Semantic:

    @code
    T r = log10_elo<T>();
    @endcode

**/
  template<typename T> T Log10_elo();

  namespace functional
  {
    /*!
      @ingroup group-callable-constant

      Generates constant log10_elo.This constant is coupled with Log10_ehi and is
      used in the float logarithms computations
      We have abs( double(log10_elo<float>())+double(Log10_ehi<float>()) - Log_10(e)) < 3e-11

      Generate the  constant log10_elo.

      @return The Log10_elo constant for the proper type
    **/
    const boost::dispatch::functor<tag::log10_elo_> log10_elo = {};
  }
} }
#endif

#include <boost/simd/constant/definition/log10_elo.hpp>
#include <boost/simd/arch/common/scalar/constant/constant_value.hpp>
#include <boost/simd/arch/common/simd/constant/constant_value.hpp>

#endif


//---------------------------------------------------------------------------//
//!
//! \file   tstNormalDistribution.cpp
//! \author Alex Robinson
//! \brief  Normal distribution unit tests.
//!
//---------------------------------------------------------------------------//

// Std Lib Includes
#include <iostream>

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

// FRENSIE Includes
#include "Utility_UnivariateDistribution.hpp"
#include "Utility_NormalDistribution.hpp"
#include "Utility_PhysicalConstants.hpp"
#include "Utility_RandomNumberGenerator.hpp"
#include "Utility_QuantityTraits.hpp"
#include "Utility_ElectronVoltUnit.hpp"
#include "Utility_UnitTestHarnessWithMain.hpp"
#include "ArchiveTestHelpers.hpp"

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

using boost::units::quantity;
using namespace Utility::Units;
namespace si = boost::units::si;
namespace cgs = boost::units::cgs;

typedef TestArchiveHelper::TestArchives TestArchives;

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

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

std::shared_ptr<Utility::UnivariateDistribution> distribution(
				 new Utility::NormalDistribution( 0.0, 1.0 ) );

std::shared_ptr<Utility::UnitAwareUnivariateDistribution<cgs::length,si::amount> > unit_aware_distribution( new Utility::UnitAwareNormalDistribution<cgs::length,si::amount>( 0.0*si::meter, 0.01*si::meter, 0.5*si::mole, -Utility::QuantityTraits<quantity<si::length> >::inf(), Utility::QuantityTraits<quantity<si::length> >::inf() ) );

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

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be evaluated
FRENSIE_UNIT_TEST( UnitAwareNormalDistribution, evaluate )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 0.0*cgs::centimeter ),
		       0.5*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( 2.0*cgs::centimeter ),
		       0.5*exp( -4.0/2.0 )*si::mole );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluate( -2.0*cgs::centimeter ),
		       0.5*exp( -4.0/2.0 )*si::mole );
}

//---------------------------------------------------------------------------//
// Check that the PDF can be evaluated
FRENSIE_UNIT_TEST( NormalDistribution, evaluatePDF )
{
  double center_value = 1.0/sqrt( 2.0*Utility::PhysicalConstants::pi );
  double off_center_value = center_value*exp( -4.0/2.0 );

  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 0.0 ), center_value);
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( 2.0 ), off_center_value );
  FRENSIE_CHECK_EQUAL( distribution->evaluatePDF( -2.0 ), off_center_value );
}

//---------------------------------------------------------------------------//
// Check that the unit-aware PDF can be evaluated
FRENSIE_UNIT_TEST( UnitAwareNormalDistribution, evaluatePDF )
{
  double center_value = 1.0/sqrt( 2.0*Utility::PhysicalConstants::pi );

  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( 0.0*cgs::centimeter ),
		       center_value/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( 2.0*cgs::centimeter ),
		       center_value*exp( -4.0/2.0 )/cgs::centimeter );
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->evaluatePDF( -2.0*cgs::centimeter ),
		       center_value*exp( -4.0/2.0 )/cgs::centimeter );
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST( NormalDistribution, sample_static )
{
  std::vector<double> fake_stream( 11 );
  fake_stream[0] = 0.5;
  fake_stream[1] = 0.5;
  fake_stream[2] = 0.9;
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.5;
  fake_stream[5] = 0.2;
  fake_stream[6] = 0.049787;
  fake_stream[7] = 0.449329;
  fake_stream[8] = 0.5;
  fake_stream[9] = 0.5;
  fake_stream[10] = 0.4;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  double sample = Utility::NormalDistribution::sample( 0.0, 1.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.69314718055995, 1e-14 );

  sample = Utility::NormalDistribution::sample( 0.0, 1.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, -0.69314718055995, 1e-14 );

  sample = Utility::NormalDistribution::sample( 0.0, 1.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, -0.69314718055995, 1e-14 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST( UnitAwareNormalDistribution, sample_static )
{
  std::vector<double> fake_stream( 11 );
  fake_stream[0] = 0.5;
  fake_stream[1] = 0.5;
  fake_stream[2] = 0.9;
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.5;
  fake_stream[5] = 0.2;
  fake_stream[6] = 0.049787;
  fake_stream[7] = 0.449329;
  fake_stream[8] = 0.5;
  fake_stream[9] = 0.5;
  fake_stream[10] = 0.4;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<cgs::length> sample =
    Utility::UnitAwareNormalDistribution<cgs::length>::sample(
				    0.0*cgs::centimeter, 1.0*cgs::centimeter );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  0.69314718055995*cgs::centimeter,
				  1e-14 );

  sample = Utility::UnitAwareNormalDistribution<cgs::length>::sample(
				    0.0*cgs::centimeter, 1.0*cgs::centimeter );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  -0.69314718055995*cgs::centimeter,
				  1e-14 );

  sample = Utility::UnitAwareNormalDistribution<cgs::length>::sample(
				    0.0*cgs::centimeter, 1.0*cgs::centimeter );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  -0.69314718055995*cgs::centimeter,
				  1e-14 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST( NormalDistribution, sampleAndRecordTrials_static )
{
  std::vector<double> fake_stream( 11 );
  fake_stream[0] = 0.5;
  fake_stream[1] = 0.5;
  fake_stream[2] = 0.9;
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.5;
  fake_stream[5] = 0.2;
  fake_stream[6] = 0.049787;
  fake_stream[7] = 0.449329;
  fake_stream[8] = 0.5;
  fake_stream[9] = 0.5;
  fake_stream[10] = 0.4;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  Utility::DistributionTraits::Counter trials = 0;

  double sample = Utility::NormalDistribution::sampleAndRecordTrials(
							    trials, 0.0, 1.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, 0.69314718055995, 1e-14 );
  FRENSIE_CHECK_EQUAL( 1.0/trials, 1.0 );

  sample = Utility::NormalDistribution::sampleAndRecordTrials(
							    trials, 0.0, 1.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, -0.69314718055995, 1e-14 );
  FRENSIE_CHECK_EQUAL( 2.0/trials, 1.0 );

  sample = Utility::NormalDistribution::sampleAndRecordTrials(
							    trials, 0.0, 1.0 );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample, -0.69314718055995, 1e-14 );
  FRENSIE_CHECK_EQUAL( 3.0/trials, 0.75 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST( UnitAwareNormalDistribution, sampleAndRecordTrials_static )
{
  std::vector<double> fake_stream( 11 );
  fake_stream[0] = 0.5;
  fake_stream[1] = 0.5;
  fake_stream[2] = 0.9;
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.5;
  fake_stream[5] = 0.2;
  fake_stream[6] = 0.049787;
  fake_stream[7] = 0.449329;
  fake_stream[8] = 0.5;
  fake_stream[9] = 0.5;
  fake_stream[10] = 0.4;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  Utility::DistributionTraits::Counter trials = 0;

  quantity<cgs::length> sample =
    Utility::UnitAwareNormalDistribution<cgs::length>::sampleAndRecordTrials(
			    trials, 0.0*cgs::centimeter, 1.0*cgs::centimeter );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  0.69314718055995*cgs::centimeter,
				  1e-14 );
  FRENSIE_CHECK_EQUAL( 1.0/trials, 1.0 );

  sample = Utility::UnitAwareNormalDistribution<cgs::length>::sampleAndRecordTrials(
			    trials, 0.0*cgs::centimeter, 1.0*cgs::centimeter );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  -0.69314718055995*cgs::centimeter,
				  1e-14 );
  FRENSIE_CHECK_EQUAL( 2.0/trials, 1.0 );

  sample = Utility::UnitAwareNormalDistribution<cgs::length>::sampleAndRecordTrials(
			    trials, 0.0*cgs::centimeter, 1.0*cgs::centimeter );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  -0.69314718055995*cgs::centimeter,
				  1e-14 );
  FRENSIE_CHECK_EQUAL( 3.0/trials, 0.75 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST( NormalDistribution, sample )
{
  std::vector<double> fake_stream( 11 );
  fake_stream[0] = 0.5;
  fake_stream[1] = 0.5;
  fake_stream[2] = 0.9;
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.5;
  fake_stream[5] = 0.2;
  fake_stream[6] = 0.049787;
  fake_stream[7] = 0.449329;
  fake_stream[8] = 0.5;
  fake_stream[9] = 0.5;
  fake_stream[10] = 0.4;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

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

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

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

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST( UnitAwareNormalDistribution, sample )
{
  std::vector<double> fake_stream( 11 );
  fake_stream[0] = 0.5;
  fake_stream[1] = 0.5;
  fake_stream[2] = 0.9;
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.5;
  fake_stream[5] = 0.2;
  fake_stream[6] = 0.049787;
  fake_stream[7] = 0.449329;
  fake_stream[8] = 0.5;
  fake_stream[9] = 0.5;
  fake_stream[10] = 0.4;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  quantity<cgs::length> sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  0.69314718055995*cgs::centimeter,
				  1e-14 );

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  -0.69314718055995*cgs::centimeter,
				  1e-14 );

  sample = unit_aware_distribution->sample();
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  -0.69314718055995*cgs::centimeter,
				  1e-14 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the distribution can be sampled
FRENSIE_UNIT_TEST( NormalDistribution, sampleAndRecordTrials )
{
  std::vector<double> fake_stream( 11 );
  fake_stream[0] = 0.5;
  fake_stream[1] = 0.5;
  fake_stream[2] = 0.9;
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.5;
  fake_stream[5] = 0.2;
  fake_stream[6] = 0.049787;
  fake_stream[7] = 0.449329;
  fake_stream[8] = 0.5;
  fake_stream[9] = 0.5;
  fake_stream[10] = 0.4;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  Utility::DistributionTraits::Counter trials = 0;

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

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

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

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the unit-aware distribution can be sampled
FRENSIE_UNIT_TEST( UnitAwareNormalDistribution, sampleAndRecordTrials )
{
  std::vector<double> fake_stream( 11 );
  fake_stream[0] = 0.5;
  fake_stream[1] = 0.5;
  fake_stream[2] = 0.9;
  fake_stream[3] = 0.5;
  fake_stream[4] = 0.5;
  fake_stream[5] = 0.2;
  fake_stream[6] = 0.049787;
  fake_stream[7] = 0.449329;
  fake_stream[8] = 0.5;
  fake_stream[9] = 0.5;
  fake_stream[10] = 0.4;

  Utility::RandomNumberGenerator::setFakeStream( fake_stream );

  Utility::DistributionTraits::Counter trials = 0;

  quantity<cgs::length> sample =
    unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  0.69314718055995*cgs::centimeter,
				  1e-14 );
  FRENSIE_CHECK_EQUAL( 1.0/trials, 1.0 );

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  -0.69314718055995*cgs::centimeter,
				  1e-14 );
  FRENSIE_CHECK_EQUAL( 2.0/trials, 1.0 );

  sample = unit_aware_distribution->sampleAndRecordTrials( trials );
  FRENSIE_CHECK_FLOATING_EQUALITY( sample,
				  -0.69314718055995*cgs::centimeter,
				  1e-14 );
  FRENSIE_CHECK_EQUAL( 3.0/trials, 0.75 );

  Utility::RandomNumberGenerator::unsetFakeStream();
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the distribution independent variable can be
// returned
FRENSIE_UNIT_TEST( NormalDistribution, getUpperBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( distribution->getUpperBoundOfIndepVar(),
		       std::numeric_limits<double>::infinity() );
}

//---------------------------------------------------------------------------//
// Check that the upper bound of the unit-aware distribution independent
// variable can be returned
FRENSIE_UNIT_TEST( UnitAwareNormalDistribution, getUpperBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getUpperBoundOfIndepVar(),
		       Utility::QuantityTraits<quantity<cgs::length> >::inf());
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the distribution independent variable can be
// returned
FRENSIE_UNIT_TEST( NormalDistribution, getLowerBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( distribution->getLowerBoundOfIndepVar(),
		       -std::numeric_limits<double>::infinity() );
}

//---------------------------------------------------------------------------//
// Check that the lower bound of the unit-aware distribution independent
// variable can be returned
FRENSIE_UNIT_TEST( UnitAwareNormalDistribution, getLowerBoundOfIndepVar )
{
  FRENSIE_CHECK_EQUAL( unit_aware_distribution->getLowerBoundOfIndepVar(),
		       -Utility::QuantityTraits<quantity<cgs::length> >::inf());
}

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

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

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

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

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

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

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

  // Create another distribution that is compatible with all interpolation
  // types
  Utility::NormalDistribution test_dist( 10.0, 1.0, 1.0, 19.0 );

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

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

  // Create another distribution that is compatible with all interpolation
  // types
  Utility::UnitAwareNormalDistribution<cgs::length,si::amount>
    test_dist( 10.0*si::meter,
               1.0*si::meter,
               1.0*si::mole,
               1.0*si::meter,
               19.0*si::meter );

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

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

  oss << Utility::NormalDistribution();

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Normal Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["mean"].toDouble(), 0.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["standard dev"].toDouble(), 1.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toDouble(),
                       -Utility::QuantityTraits<double>::inf(), SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toDouble(),
                       Utility::QuantityTraits<double>::inf(), SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0, SHOW_LHS );

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

  oss << Utility::NormalDistribution( 2.0 );

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Normal Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["mean"].toDouble(), 2.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["standard dev"].toDouble(), 1.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toDouble(),
                       -Utility::QuantityTraits<double>::inf(), SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toDouble(),
                       Utility::QuantityTraits<double>::inf(), SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0, SHOW_LHS );

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

  oss << Utility::NormalDistribution( 2.0, 3.0 );

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Normal Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["mean"].toDouble(), 2.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["standard dev"].toDouble(), 3.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toDouble(),
                       -Utility::QuantityTraits<double>::inf(), SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toDouble(),
                       Utility::QuantityTraits<double>::inf(), SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0, SHOW_LHS );

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

  oss << Utility::NormalDistribution( 2.0, 3.0, 4.0 );

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Normal Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["mean"].toDouble(), 2.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["standard dev"].toDouble(), 3.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toDouble(),
                       -Utility::QuantityTraits<double>::inf(), SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toDouble(),
                       Utility::QuantityTraits<double>::inf(), SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 4.0, SHOW_LHS );

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

  oss << Utility::NormalDistribution( -2.0, 3.0, 4.0, -1.0, 1.0 );

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Normal Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["mean"].toDouble(), -2.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["standard dev"].toDouble(), 3.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toDouble(), -1.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toDouble(), 1.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 4.0, SHOW_LHS );

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

  oss << *distribution;

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Normal Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(), "void", SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["mean"].toDouble(), 0.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["standard dev"].toDouble(), 1.0, SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toDouble(),
                       -Utility::QuantityTraits<double>::inf(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toDouble(),
                       Utility::QuantityTraits<double>::inf(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toDouble(), 1.0, SHOW_LHS );
}

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

  oss << Utility::UnitAwareNormalDistribution<cgs::length,si::amount>();

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Normal Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<cgs::length>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["mean"].toType<quantity<cgs::length> >(),
                       0.0*cgs::centimeter,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["standard dev"].toType<quantity<cgs::length> >(),
                       1.0*cgs::centimeter,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toType<quantity<cgs::length> >(),
                       -Utility::QuantityTraits<quantity<cgs::length> >::inf(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toType<quantity<cgs::length> >(),
                       Utility::QuantityTraits<quantity<cgs::length> >::inf(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toType<quantity<si::amount> >(),
                       1.0*si::mole,
                       SHOW_LHS );

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

  oss << Utility::UnitAwareNormalDistribution<cgs::length,si::amount>( 2.0*cgs::centimeter );

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Normal Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<cgs::length>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["mean"].toType<quantity<cgs::length> >(),
                       2.0*cgs::centimeter,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["standard dev"].toType<quantity<cgs::length> >(),
                       1.0*cgs::centimeter,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toType<quantity<cgs::length> >(),
                       -Utility::QuantityTraits<quantity<cgs::length> >::inf(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toType<quantity<cgs::length> >(),
                       Utility::QuantityTraits<quantity<cgs::length> >::inf(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toType<quantity<si::amount> >(),
                       1.0*si::mole,
                       SHOW_LHS );

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

  oss << Utility::UnitAwareNormalDistribution<cgs::length,si::amount>( 2.0*cgs::centimeter, 3.0*cgs::centimeter );

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Normal Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<cgs::length>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["mean"].toType<quantity<cgs::length> >(),
                       2.0*cgs::centimeter,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["standard dev"].toType<quantity<cgs::length> >(),
                       3.0*cgs::centimeter,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toType<quantity<cgs::length> >(),
                       -Utility::QuantityTraits<quantity<cgs::length> >::inf(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toType<quantity<cgs::length> >(),
                       Utility::QuantityTraits<quantity<cgs::length> >::inf(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toType<quantity<si::amount> >(),
                       1.0*si::mole,
                       SHOW_LHS );

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

  oss << Utility::UnitAwareNormalDistribution<cgs::length,si::amount>( 2.0*cgs::centimeter, 3.0*cgs::centimeter, 4.0*si::mole );

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Normal Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<cgs::length>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["mean"].toType<quantity<cgs::length> >(),
                       2.0*cgs::centimeter,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["standard dev"].toType<quantity<cgs::length> >(),
                       3.0*cgs::centimeter,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toType<quantity<cgs::length> >(),
                       -Utility::QuantityTraits<quantity<cgs::length> >::inf(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toType<quantity<cgs::length> >(),
                       Utility::QuantityTraits<quantity<cgs::length> >::inf(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toType<quantity<si::amount> >(),
                       4.0*si::mole,
                       SHOW_LHS );

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

  oss << Utility::UnitAwareNormalDistribution<cgs::length,si::amount>( -2.0*cgs::centimeter, 3.0*cgs::centimeter, 4.0*si::mole, -1.0*cgs::centimeter, 1.0*cgs::centimeter );

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Normal Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<cgs::length>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["mean"].toType<quantity<cgs::length> >(),
                       -2.0*cgs::centimeter,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["standard dev"].toType<quantity<cgs::length> >(),
                       3.0*cgs::centimeter,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toType<quantity<cgs::length> >(),
                       -1.0*cgs::centimeter,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toType<quantity<cgs::length> >(),
                       1.0*cgs::centimeter,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toType<quantity<si::amount> >(),
                       4.0*si::mole,
                       SHOW_LHS );

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

  oss << *unit_aware_distribution;

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

  FRENSIE_CHECK_EQUAL( dist_data["type"].toString(),
                       "Normal Distribution" );
  FRENSIE_CHECK_EQUAL( dist_data["independent unit"].toString(),
                       Utility::UnitTraits<cgs::length>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["dependent unit"].toString(),
                       Utility::UnitTraits<si::amount>::name(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["mean"].toType<quantity<cgs::length> >(),
                       0.0*cgs::centimeter,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["standard dev"].toType<quantity<cgs::length> >(),
                       1.0*cgs::centimeter,
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["lower bound"].toType<quantity<cgs::length> >(),
                       -Utility::QuantityTraits<quantity<cgs::length> >::inf(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["upper bound"].toType<quantity<cgs::length> >(),
                       Utility::QuantityTraits<quantity<cgs::length> >::inf(),
                       SHOW_LHS );
  FRENSIE_CHECK_EQUAL( dist_data["multiplier"].toType<quantity<si::amount> >(),
                       0.5*si::mole,
                       SHOW_LHS );
}

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

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

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

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

    createOArchive( archive_base_name, archive_ostream, oarchive );

    Utility::NormalDistribution dist_a;
    Utility::NormalDistribution dist_b( 2.0 );
    Utility::NormalDistribution dist_c( 2.0, 3.0 );
    Utility::NormalDistribution dist_d( 2.0, 3.0, 4.0 );
    Utility::NormalDistribution dist_e( 2.0, 3.0, 4.0, -1.0 );
    Utility::NormalDistribution dist_f( 2.0, 3.0, 4.0, -1.0, 1.0 );

    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_a ) );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_b ) );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_c ) );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_d ) );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_e ) );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_f ) );
    FRENSIE_REQUIRE_NO_THROW(
                          (*oarchive) << BOOST_SERIALIZATION_NVP( distribution ) );
  }

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

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

  createIArchive( archive_istream, iarchive );

  Utility::NormalDistribution dist_a;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_a ) );
  FRENSIE_CHECK_EQUAL( dist_a, Utility::NormalDistribution() );

  Utility::NormalDistribution dist_b;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_b ) );
  FRENSIE_CHECK_EQUAL( dist_b, Utility::NormalDistribution( 2.0 ) );

  Utility::NormalDistribution dist_c;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_c ) );
  FRENSIE_CHECK_EQUAL( dist_c, Utility::NormalDistribution( 2.0, 3.0 ) );

  Utility::NormalDistribution dist_d;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_d ) );
  FRENSIE_CHECK_EQUAL( dist_d, Utility::NormalDistribution( 2.0, 3.0, 4.0 ) );

  Utility::NormalDistribution dist_e;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_e ) );
  FRENSIE_CHECK_EQUAL( dist_e, Utility::NormalDistribution( 2.0, 3.0, 4.0, -1.0 ) );

  Utility::NormalDistribution dist_f;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_f ) );
  FRENSIE_CHECK_EQUAL( dist_f, Utility::NormalDistribution( 2.0, 3.0, 4.0, -1.0, 1.0 ) );

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

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

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

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

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

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

    createOArchive( archive_base_name, archive_ostream, oarchive );

    Utility::UnitAwareNormalDistribution<cgs::length,si::amount> dist_a;
    Utility::UnitAwareNormalDistribution<cgs::length,si::amount> dist_b( 2.0*cgs::centimeter );
    Utility::UnitAwareNormalDistribution<cgs::length,si::amount> dist_c( 2.0*cgs::centimeter, 3.0*cgs::centimeter );
    Utility::UnitAwareNormalDistribution<cgs::length,si::amount> dist_d( 2.0*cgs::centimeter, 3.0*cgs::centimeter, 4.0*si::mole );
    Utility::UnitAwareNormalDistribution<cgs::length,si::amount> dist_e( 2.0*cgs::centimeter, 3.0*cgs::centimeter, 4.0*si::mole, -1.0*cgs::centimeter );
    Utility::UnitAwareNormalDistribution<cgs::length,si::amount> dist_f( 2.0*cgs::centimeter, 3.0*cgs::centimeter, 4.0*si::mole, -1.0*cgs::centimeter, 1.0*cgs::centimeter );

    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_a ) );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_b ) );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_c ) );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_d ) );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_e ) );
    FRENSIE_REQUIRE_NO_THROW(
                             (*oarchive) << BOOST_SERIALIZATION_NVP( dist_f ) );
    FRENSIE_REQUIRE_NO_THROW(
               (*oarchive) << BOOST_SERIALIZATION_NVP( unit_aware_distribution ) );
  }

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

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

  createIArchive( archive_istream, iarchive );

  Utility::UnitAwareNormalDistribution<cgs::length,si::amount> dist_a;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_a ) );
  FRENSIE_CHECK_EQUAL( dist_a, (Utility::UnitAwareNormalDistribution<cgs::length,si::amount>()) );

  Utility::UnitAwareNormalDistribution<cgs::length,si::amount> dist_b;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_b ) );
  FRENSIE_CHECK_EQUAL( dist_b, (Utility::UnitAwareNormalDistribution<cgs::length,si::amount>( 2.0*cgs::centimeter )) );

  Utility::UnitAwareNormalDistribution<cgs::length,si::amount> dist_c;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_c ) );
  FRENSIE_CHECK_EQUAL( dist_c, (Utility::UnitAwareNormalDistribution<cgs::length,si::amount>( 2.0*cgs::centimeter, 3.0*cgs::centimeter )) );

  Utility::UnitAwareNormalDistribution<cgs::length,si::amount> dist_d;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_d ) );
  FRENSIE_CHECK_EQUAL( dist_d, (Utility::UnitAwareNormalDistribution<cgs::length,si::amount>( 2.0*cgs::centimeter, 3.0*cgs::centimeter, 4.0*si::mole )) );

  Utility::UnitAwareNormalDistribution<cgs::length,si::amount> dist_e;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_e ) );
  FRENSIE_CHECK_EQUAL( dist_e, (Utility::UnitAwareNormalDistribution<cgs::length,si::amount>( 2.0*cgs::centimeter, 3.0*cgs::centimeter, 4.0*si::mole, -1.0*cgs::centimeter )) );

  Utility::UnitAwareNormalDistribution<cgs::length,si::amount> dist_f;

  FRENSIE_REQUIRE_NO_THROW(
                           (*iarchive) >> BOOST_SERIALIZATION_NVP( dist_f ) );
  FRENSIE_CHECK_EQUAL( dist_f, (Utility::UnitAwareNormalDistribution<cgs::length,si::amount>( 2.0*cgs::centimeter, 3.0*cgs::centimeter, 4.0*si::mole, -1.0*cgs::centimeter, 1.0*cgs::centimeter )) );

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

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

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

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

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

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

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

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

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

  IndepQuantityA indep_quantity_a =
    Utility::QuantityTraits<IndepQuantityA>::initializeQuantity( 0.0 );
  InverseIndepQuantityA inv_indep_quantity_a =
    Utility::QuantityTraits<InverseIndepQuantityA>::initializeQuantity( 1.0/sqrt( 2.0*Utility::PhysicalConstants::pi ) );
  DepQuantityA dep_quantity_a =
    Utility::QuantityTraits<DepQuantityA>::initializeQuantity( 1.0 );

  IndepQuantityB indep_quantity_b( indep_quantity_a );
  InverseIndepQuantityB inv_indep_quantity_b( inv_indep_quantity_a );
  DepQuantityB dep_quantity_b( dep_quantity_a );

  FRENSIE_CHECK_FLOATING_EQUALITY(
			   unit_aware_dist_a_copy.evaluate( indep_quantity_a ),
			   dep_quantity_a,
			   1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			unit_aware_dist_a_copy.evaluatePDF( indep_quantity_a ),
			inv_indep_quantity_a,
			1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			   unit_aware_dist_b_copy.evaluate( indep_quantity_b ),
			   dep_quantity_b,
			   1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			unit_aware_dist_b_copy.evaluatePDF( indep_quantity_b ),
			inv_indep_quantity_b,
			1e-15 );

  Utility::setQuantity( indep_quantity_a, 2.0 );
  Utility::setQuantity( inv_indep_quantity_a, exp( -4.0/2.0 )/sqrt( 2.0*Utility::PhysicalConstants::pi ) );
  Utility::setQuantity( dep_quantity_a, exp( -4.0/2.0 ) );

  indep_quantity_b = IndepQuantityB( indep_quantity_a );
  inv_indep_quantity_b = InverseIndepQuantityB( inv_indep_quantity_a );
  dep_quantity_b = DepQuantityB( dep_quantity_a );

  FRENSIE_CHECK_FLOATING_EQUALITY(
			   unit_aware_dist_a_copy.evaluate( indep_quantity_a ),
			   dep_quantity_a,
			   1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			unit_aware_dist_a_copy.evaluatePDF( indep_quantity_a ),
			inv_indep_quantity_a,
			1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			   unit_aware_dist_b_copy.evaluate( indep_quantity_b ),
			   dep_quantity_b,
			   1e-15 );
  FRENSIE_CHECK_FLOATING_EQUALITY(
			unit_aware_dist_b_copy.evaluatePDF( indep_quantity_b ),
			inv_indep_quantity_b,
			1e-15 );
}

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

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

FRENSIE_CUSTOM_UNIT_TEST_SETUP_END();

//---------------------------------------------------------------------------//
// end tstNormalDistribution.cpp
//---------------------------------------------------------------------------//


//
// Created by Dominik Krupke, http://krupke.cc on 12/20/17.
//
#include <vector>

#include <gtest/gtest.h>
#include <boost/graph/properties.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/connected_components.hpp>
#include <boost/graph/directed_graph.hpp>

#include "./boost_graph.h"
#include "./ip_graph.h"

TEST(IpGraph, Foobar){
  using BoostGraph = boost::adjacency_list<boost::vecS,
                                           boost::vecS,
                                           boost::directedS,
                                           boost::property<boost::vertex_name_t,
                                                           int>,
                                           boost::property<boost::edge_weight_t,
                                                           double>>;
  BoostGraph graph;
  auto v1 = boost::add_vertex(graph);
  boost::put(boost::get(boost::vertex_name, graph), v1, 42);
  ASSERT_EQ(boost::get(boost::vertex_name, graph, v1), 42);
  auto v2 = boost::add_vertex(graph);
  auto v3 = boost::add_vertex(graph);
  ASSERT_EQ(boost::num_vertices(graph), 3);
}

TEST(IpGraph, NumVertices){
  using namespace angularfreezetag;
  std::vector<PointWithOrientation> points{{{0.0,0.0},0.0},{{1.0, 0.0}, 0.0}, {{0.5, 0.7}, 0.0}};
  IpGraph ip_graph{points, points.at(0).p};
  ASSERT_EQ(ip_graph.GetNumVertices(), 3+2+2*1);
  ASSERT_EQ(ip_graph.GetNumEdges(), 4+2*1);
}

TEST(IpGraph, NumVerticesCollinear){
  using namespace angularfreezetag;
  std::vector<PointWithOrientation> points{{{0.0,0.0},0.0},{{1.0, 0.0}, 0.0}, {{0.0, 1.0}, 0.0}, {{0.0, -1.0}, 0.0}};
  IpGraph ip_graph{points, points.at(0).p};
  ASSERT_EQ(ip_graph.GetNumVertices(), 4+3+3*2);
  ASSERT_EQ(ip_graph.GetNumEdges(), 9+3*4);
}


#include <iostream>
#include <vector>
#include <cassert>
#include <cstring>
#include <limits>
#include <mpi.h>
//#include <Eigen/Eigenvalues>

enum KernelType {CubicSpline = 0, WendlandC2 = 1, WendlandC4 = 2};

#include "particle_simulator.hpp"
#include "hdr_time.hpp"
#include "hdr_run.hpp"
#ifdef USE_INTRINSICS
#include "vector_x86.hpp"
#endif
#include "hdr_dimension.hpp"
#include "hdr_kernel.hpp"
#include "hdr_sph.hpp"
#include "hdr_hgas.hpp"
#include "hdr_bhns.hpp"

class SPHAnalysis : public HelmholtzGas {
public:
    SPHAnalysis() {
        this->id    = 0;
        this->istar = 0;
        this->mass  = 0.;
        this->pos   = 0.;
        this->vel   = 0.;
        this->uene  = 0.;
        this->alph  = 0.;
        this->alphu = 0.;
        this->ksr   = 0.;
        for(PS::S32 k = 0; k < NR::NumberOfNucleon; k++) {
            this->cmps[k] = 0.;
        }
    }

    PS::F64 calcRadialVelocity() {
        PS::F64 r2 = this->pos * this->pos;
        PS::F64 rv = this->pos * this->vel;
        return (rv / sqrt(r2));
    }

    void readAscii(FILE * fp) {
        fscanf(fp, "%lld%lld%lf", &this->id, &this->istar, &this->mass);      //  3
        fscanf(fp, "%lf%lf%lf", &this->pos[0], &this->pos[1], &this->pos[2]); //  6
        fscanf(fp, "%lf%lf%lf", &this->vel[0], &this->vel[1], &this->vel[2]); //  9
        fscanf(fp, "%lf%lf%lf", &this->acc[0], &this->acc[1], &this->acc[2]); // 12
        fscanf(fp, "%lf%lf%lf", &this->uene, &this->alph, &this->alphu);      // 15
        fscanf(fp, "%lf%lf%6d", &this->dens, &this->ksr,  &this->np);         // 18
        fscanf(fp, "%lf%lf%lf", &this->vsnd, &this->pres, &this->temp);       // 21
        fscanf(fp, "%lf%lf%lf", &this->divv, &this->rotv, &this->bswt);       // 24
        fscanf(fp, "%lf%lf%lf", &this->pot,  &this->abar, &this->zbar);       // 27
        fscanf(fp, "%lf",       &this->enuc);                                 // 28
        fscanf(fp, "%lf%lf%lf", &this->vsmx, &this->udot, &this->dnuc);       // 31
        for(PS::S32 k = 0; k < NuclearReaction::NumberOfNucleon; k++) {       // 32 -- 44
            fscanf(fp, "%lf", &this->cmps[k]);
        }
        fscanf(fp, "%lf", &this->pot3);
        fscanf(fp, "%lf%lf%lf", &this->tempmax[0], &this->tempmax[1], &this->tempmax[2]);
        fscanf(fp, "%lf", &this->entr);
    }

};

class Shell {
public:
    PS::S64 nump; // The number of particles in the shell
    PS::F64 mass; // The total mass in the shell
    PS::F64 ener; // The total internal energy in the shell
    PS::F64 rvel; // Radial velocity in the shell
    NR::Nucleon mele;

    Shell() {
        this->nump = 0;
        this->mass = 0.;
        this->ener = 0.;
        this->rvel = 0.;
        for(PS::S64 k = 0; k < NR::NumberOfNucleon; k++) {
            this->mele[k] = 0.;
        }
    }

    void increment(SPHAnalysis & sph) {
        this->nump += 1;
        this->mass += sph.mass;
        this->ener += sph.mass * sph.uene;
        this->rvel += sph.calcRadialVelocity();
        for(PS::S64 k = 0; k < NR::NumberOfNucleon; k++) {
            this->mele[k] += sph.mass * sph.cmps[k];
        }
    }

    void reduceAddition(Shell sloc) {
        this->nump = PS::Comm::getSum(sloc.nump);
        this->mass = PS::Comm::getSum(sloc.mass);
        this->ener = PS::Comm::getSum(sloc.ener);
        this->rvel = PS::Comm::getSum(sloc.rvel);
        for(PS::S64 k = 0; k < NR::NumberOfNucleon; k++) {
            this->mele[k] = PS::Comm::getSum(sloc.mele[k]);
        }
    }

};

template <class Tsph>
void sphericalizeWhiteDwarf(PS::F64 rmax,
                            PS::F64 drad,
                            char * ofile,
                            Tsph & sph) {

    PS::F64vec cntr_loc = 0.;
    PS::F64vec cvel_loc = 0.;
    PS::F64    dens_loc = 0.;
    for(PS::S64 i = 0; i < sph.getNumberOfParticleLocal(); i++) {
        dens_loc += sph[i].dens;
        cntr_loc += sph[i].dens * sph[i].pos;
        cvel_loc += sph[i].dens * sph[i].vel;
    }
    PS::F64vec cntr_glb = PS::Comm::getSum(cntr_loc);
    PS::F64vec cvel_glb = PS::Comm::getSum(cvel_loc);
    PS::F64    dens_glb = PS::Comm::getSum(dens_loc);
    cntr_glb = (1. / dens_glb) * cntr_glb;
    cvel_glb = (1. / dens_glb) * cvel_glb;

    PS::S64 nbin = (PS::S64)(rmax / drad) + 1;
    Shell * sloc = (Shell *)malloc(sizeof(Shell) * nbin);

    for(PS::S64 i = 0; i < sph.getNumberOfParticleLocal(); i++) {
        sph[i].pos = sph[i].pos - cntr_glb;
        sph[i].vel = sph[i].vel - cvel_glb;
        PS::F64 rad1  = sqrt(sph[i].pos * sph[i].pos);
        PS::S64 ibin = (PS::S64)(rad1 / drad);
        if(ibin >= nbin) {
            continue;
        }
        ///////////// Ad hoc method //////////////
        if(sph[i].istar == 0 && sph[i].cmps[1] > 0.2 && rad1 < 3e10) {
            for(PS::S64 k = 0; k < NR::NumberOfNucleon; k++) {
                if(k != 12) {
                    sph[i].cmps[k] = 0.;
                } else {
                    sph[i].cmps[k] = 1.;
                }
            }
            //continue;
        }
        //////////////////////////////////////////
        sloc[ibin].increment(sph[i]);
    }

    Shell * sglb = (Shell *)malloc(sizeof(Shell) * nbin);
    for(PS::S64 ibin = 0; ibin < nbin; ibin++) {
        sglb[ibin].reduceAddition(sloc[ibin]);
    }

    if(PS::Comm::getRank() == 0) {
        PS::S64 nshl = 0;
        PS::F64 mass = 0.;
        PS::F64 ener = 0.;
        PS::F64 rvel = 0.;
        PS::F64 rad0 = 0.;
        PS::F64 menc = 0.;
        NR::Nucleon mele;
        FILE * fp = fopen(ofile, "w");
        for(PS::S64 ibin = 0; ibin < nbin; ibin++) {
            nshl += sglb[ibin].nump;
            mass += sglb[ibin].mass;
            ener += sglb[ibin].ener;
            rvel += sglb[ibin].rvel;
            for(PS::S64 k = 0; k < NR::NumberOfNucleon; k++) {
                mele[k] += sglb[ibin].mele[k];
            }
            if(nshl < 10000 && ibin != nbin - 1) {
            //if(nshl < 1000 && ibin != nbin - 1) {
                continue;
            }

            PS::F64 rad1   = drad * (ibin + 1);
            PS::F64 radius = ((rad0 != 0.) ? sqrt(rad0 * rad1) : 0.5 * rad1);
            PS::F64 volume = 4. * M_PI / 3. * (rad1 * rad1 * rad1 - rad0 * rad0 * rad0);
            PS::F64 dens   = mass / volume;
            PS::F64 uene   = ener / mass;
            rvel  = rvel / (PS::S64)nshl;
            NR::Nucleon cmps;
            for(PS::S64 k = 0; k < NR::NumberOfNucleon; k++) {
                cmps[k] = mele[k] / mass;
            }
            menc += mass;

            PS::F64 tin = 1e9;
            PS::F64 pout, cout, tout, sout, uout;
            flash_helmholtz_(&dens, &uene, &tin, cmps.getPointer(), &pout, &cout, &tout, &sout);
            tout = (tout < 1e7) ? 1e7 : tout;
            flash_helmholtz_e_(&dens, &tout, cmps.getPointer(), &uout);
            flash_helmholtz_(&dens, &uout, &tout, cmps.getPointer(), &pout, &cout, &tout, &sout);
            fprintf(fp, "%+e %+e %+e %+e %+e %+e %+e",
                    rad1, dens, pout, tout, uout, menc/CodeUnit::SolarMass, rvel);
            for(PS::S64 k = 0; k < NR::NumberOfNucleon; k++) {
                fprintf(fp, " %+e", cmps[k]);
            }
            fprintf(fp, " %+e", sout);
            fprintf(fp, "\n");

            nshl = 0;
            mass = 0.;
            ener = 0.;
            rvel = 0.;
            for(PS::S64 k = 0; k < NR::NumberOfNucleon; k++) {
                mele[k] = 0.;
            }
            rad0 = rad1;
        }
        fclose(fp);
    }

    free(sloc);
    free(sglb);

}

int main(int argc, char ** argv) {
    PS::Initialize(argc, argv);

    PS::ParticleSystem<SPHAnalysis> sph;
    sph.initialize();
    sph.createParticle(0);
    sph.setNumberOfParticleLocal(0);

    char idir[1024], odir[1024];
    PS::F64 rmax, drad;
    PS::S64 ibgn, iend;
    FILE * fp = fopen(argv[1], "r");
    fscanf(fp, "%s", idir);
    fscanf(fp, "%s", odir);
    fscanf(fp, "%lf%lf", &rmax, &drad);
    fscanf(fp, "%lld%lld", &ibgn, &iend);
    fclose(fp);

    init_flash_helmholtz_(&CodeUnit::FractionOfCoulombCorrection);

    for(PS::S64 itime = ibgn; itime <= iend; itime++) {
        char tfile[1024];
        FILE *fp = NULL;
        PS::S64 tdir = 0;
        for(PS::S64 iidir = 0; iidir < 100; iidir++) {
            sprintf(tfile, "%s/t%02d/sph_t%04d_p%06d_i%06d.dat", idir, iidir, itime,
                    PS::Comm::getNumberOfProc(), 0);
            fp = fopen(tfile, "r");
            if(fp != NULL) {
                tdir = iidir;
                break;
            }
        }
        if(fp == NULL) {
            fprintf(stderr, "%s is not found.\n", tfile);
            continue;
        }
        fclose(fp);

        char sfile[1024];
        sprintf(sfile, "%s/t%02d/sph_t%04d", idir, tdir, itime);
        sph.readParticleAscii(sfile, "%s_p%06d_i%06d.dat");

        char ofile[1024];
        sprintf(ofile, "%s/poly.dat", odir);
        sphericalizeWhiteDwarf(rmax, drad, ofile, sph);

    }

    PS::Finalize();

    return 0;
}


#pragma once

#include "utils/matrix.hpp"

#include <boost/optional.hpp>

namespace Aronda::State
{
static const std::size_t number_of_square = 25;

static const std::size_t number_of_state_per_square = 27;

using Square = Utils::Matrix<1, number_of_state_per_square>;

using Board = Utils::Matrix<number_of_square, number_of_state_per_square>;

enum class Player
{
    Black,
    White
};

struct GameState
{
    Player current_player;
    Board board;
    boost::optional<Player> winner;
};
}