/*********************************************************************
* Software License Agreement (BSD License)
*
*  Copyright (c) 2014, University of Toronto
*  All rights reserved.
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions
*  are met:
*
*   * Redistributions of source code must retain the above copyright
*     notice, this list of conditions and the following disclaimer.
*   * Redistributions in binary form must reproduce the above
*     copyright notice, this list of conditions and the following
*     disclaimer in the documentation and/or other materials provided
*     with the distribution.
*   * Neither the name of the University of Toronto nor the names of its
*     contributors may be used to endorse or promote products derived
*     from this software without specific prior written permission.
*
*  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
*  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
*  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
*  FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
*  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
*  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
*  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
*  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
*  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
*  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
*  ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
*  POSSIBILITY OF SUCH DAMAGE.
*********************************************************************/

/* Author: Jonathan Gammell*/

// The class's header
#include "ompl/util/ProlateHyperspheroid.h"
// For OMPL exceptions
#include "ompl/util/Exception.h"
// For OMPL information
#include "ompl/util/Console.h"
// For geometric equations like prolateHyperspheroidMeasure
#include "ompl/util/GeometricEquations.h"

// For boost::make_shared
#include <boost/make_shared.hpp>

// Eigen core:
#include <Eigen/Core>
// Inversion and determinants
#include <Eigen/LU>
// SVD decomposition
#include <Eigen/SVD>

struct ompl::ProlateHyperspheroid::PhsData
{
    /** \brief The dimension of the prolate hyperspheroid.*/
    unsigned int dim_;
    /** \brief Whether the transformation is up to date */
    bool isTransformUpToDate_;
    /** \brief The minimum possible transverse diameter of the PHS. Defined as the distance between the two foci*/
    double minTransverseDiameter_;
    /** \brief The transverse diameter of the PHS. */
    double transverseDiameter_;
    /** \brief The measure of the PHS. */
    double phsMeasure_;
    /** \brief The first focus of the PHS (i.e., the start state of the planning problem). Unlike other parts of Eigen, variably sized matrices do not require special allocators. */
    Eigen::VectorXd xFocus1_;
    /** \brief The second focus of the PHS (i.e., the goal state of the planning problem). Unlike other parts of Eigen, variably sized matrices do not require special allocators.  */
    Eigen::VectorXd xFocus2_;
    /** \brief The centre of the PHS. Defined as the average of the foci. Unlike other parts of Eigen, variably sized matrices do not require special allocators. */
    Eigen::VectorXd xCentre_;
    /** \brief The rotation from PHS-frame to world frame. Is only calculated on construction. Unlike other parts of Eigen, variably sized matrices do not require special allocators. */
    Eigen::MatrixXd rotationWorldFromEllipse_;
    /** \brief The transformation from PHS-frame to world frame. Is calculated every time the transverse diameter changes. Unlike other parts of Eigen, variably sized matrices do not require special allocators. */
    Eigen::MatrixXd transformationWorldFromEllipse_;
};

ompl::ProlateHyperspheroid::ProlateHyperspheroid(unsigned int n, const double focus1[], const double focus2[])
  : dataPtr_ (boost::make_shared<PhsData>())
{
    //Initialize the data:
    dataPtr_->dim_ = n;
    dataPtr_->transverseDiameter_ = 0.0; // Initialize to something.
    dataPtr_->isTransformUpToDate_ = false;

    // Copy the arrays into their Eigen containers via the Eigen::Map "view"
    dataPtr_->xFocus1_ = Eigen::Map<const Eigen::VectorXd>(focus1, dataPtr_->dim_);
    dataPtr_->xFocus2_ = Eigen::Map<const Eigen::VectorXd>(focus2, dataPtr_->dim_);

    // Calculate the minimum transverse diameter
    dataPtr_->minTransverseDiameter_ = (dataPtr_->xFocus1_ - dataPtr_->xFocus2_).norm();

    // Calculate the centre:
    dataPtr_->xCentre_ = 0.5 * (dataPtr_->xFocus1_ + dataPtr_->xFocus2_);

    // Calculate the rotation
    updateRotation();
}

void ompl::ProlateHyperspheroid::setTransverseDiameter(double transverseDiameter)
{
    if (transverseDiameter < dataPtr_->minTransverseDiameter_)
    {
        OMPL_ERROR("%g < %g", transverseDiameter, dataPtr_->minTransverseDiameter_);
        throw Exception("Transverse diameter cannot be less than the distance between the foci.");
    }

    // Store and update if changed
    if (dataPtr_->transverseDiameter_ != transverseDiameter)
    {
        // Mark as out of date
        dataPtr_->isTransformUpToDate_ = false;

        // Store
        dataPtr_->transverseDiameter_ = transverseDiameter;

        // Update the transform
        updateTransformation();
    }
    // No else, the diameter didn't change
}

void ompl::ProlateHyperspheroid::transform(const double sphere[], double phs[]) const
{
    if (dataPtr_->isTransformUpToDate_ == false)
    {
      throw Exception("The transformation is not up to date in the PHS class. Has the transverse diameter been set?");
    }

    // Calculate the tranformation and offset, using Eigen::Map views of the data
    Eigen::Map<Eigen::VectorXd>(phs, dataPtr_->dim_) = dataPtr_->transformationWorldFromEllipse_*Eigen::Map<const Eigen::VectorXd>(sphere, dataPtr_->dim_);
    Eigen::Map<Eigen::VectorXd>(phs, dataPtr_->dim_) += dataPtr_->xCentre_;
}

bool ompl::ProlateHyperspheroid::isInPhs(const double point[]) const
{
    if (dataPtr_->isTransformUpToDate_ == false)
    {
        // The transform is not up to date until the transverse diameter has been set
        throw Exception ("The transverse diameter has not been set");
    }

    return (getPathLength(point) < dataPtr_->transverseDiameter_);
}

bool ompl::ProlateHyperspheroid::isOnPhs(const double point[]) const
{
    if (dataPtr_->isTransformUpToDate_ == false)
    {
        // The transform is not up to date until the transverse diameter has been set
        throw Exception ("The transverse diameter has not been set");
    }

    return (getPathLength(point) == dataPtr_->transverseDiameter_);
}

unsigned int ompl::ProlateHyperspheroid::getPhsDimension(void) const
{
    return dataPtr_->dim_;
}

double ompl::ProlateHyperspheroid::getPhsMeasure(void) const
{
    if (dataPtr_->isTransformUpToDate_ == false)
    {
        // The transform is not up to date until the transverse diameter has been set, therefore we have no transverse diameter and we have infinite measure
        return std::numeric_limits<double>::infinity();
    }
    else
    {
        // Calculate and return:
        return dataPtr_->phsMeasure_;
    }
}

double ompl::ProlateHyperspheroid::getPhsMeasure(double tranDiam) const
{
    return prolateHyperspheroidMeasure(dataPtr_->dim_, dataPtr_->minTransverseDiameter_, tranDiam);
}

double ompl::ProlateHyperspheroid::getMinTransverseDiameter(void) const
{
    return dataPtr_->minTransverseDiameter_;
}

double ompl::ProlateHyperspheroid::getPathLength(const double point[]) const
{
    return (dataPtr_->xFocus1_ - Eigen::Map<const Eigen::VectorXd>(point, dataPtr_->dim_)).norm() + (Eigen::Map<const Eigen::VectorXd>(point, dataPtr_->dim_) - dataPtr_->xFocus2_).norm();
}

unsigned int ompl::ProlateHyperspheroid::getDimension() const
{
    return dataPtr_->dim_;
}

void ompl::ProlateHyperspheroid::updateRotation(void)
{
    // Mark the transform as out of date
    dataPtr_->isTransformUpToDate_ = false;

    // If the minTransverseDiameter_ is too close to 0, we treat this as a circle.
    double circleTol = 1E-9;
    if (dataPtr_->minTransverseDiameter_ < circleTol)
    {
        dataPtr_->rotationWorldFromEllipse_.setIdentity(dataPtr_->dim_, dataPtr_->dim_);
    }
    else
    {
        // Variables
        // The transverse axis of the PHS expressed in the world frame.
        Eigen::VectorXd transverseAxis(dataPtr_->dim_);
        // The matrix representation of the Wahba problem
        Eigen::MatrixXd wahbaProb(dataPtr_->dim_, dataPtr_->dim_);
        // The middle diagonal matrix in the SVD solution to the Wahba problem
        Eigen::VectorXd middleM(dataPtr_->dim_);

        // Calculate the major axis, storing as the first eigenvector
        transverseAxis = (dataPtr_->xFocus2_ - dataPtr_->xFocus1_ )/dataPtr_->minTransverseDiameter_;

        // Calculate the rotation that will allow us to generate the remaining eigenvectors
        // Formulate as a Wahba problem, first forming the matrix a_j*a_i' where a_j is the transverse axis if the ellipse in the world frame, and a_i is the first basis vector of the world frame (i.e., [1 0 .... 0])
        wahbaProb = transverseAxis * Eigen::MatrixXd::Identity(dataPtr_->dim_, dataPtr_->dim_).col(0).transpose();

        // Then run it through the  SVD solver
        Eigen::JacobiSVD<Eigen::MatrixXd, Eigen::NoQRPreconditioner> svd(wahbaProb, Eigen::ComputeFullV | Eigen::ComputeFullU);

        // Then calculate the rotation matrix from the U and V components of SVD
        // Calculate the middle diagonal matrix
        middleM = Eigen::VectorXd::Ones(dataPtr_->dim_);
        // Make the last value equal to det(U)*det(V) (zero-based indexing remember)
        middleM(dataPtr_->dim_ - 1) = svd.matrixU().determinant() * svd.matrixV().determinant();

        // Calculate the rotation
        dataPtr_->rotationWorldFromEllipse_ = svd.matrixU() * middleM.asDiagonal() * svd.matrixV().transpose();
    }
}

void ompl::ProlateHyperspheroid::updateTransformation(void)
{
    // Variables
    // The radii of the ellipse
    Eigen::VectorXd diagAsVector(dataPtr_->dim_);
    // The conjugate diameters:
    double conjugateDiamater;

    // Calculate the conjugate radius
    conjugateDiamater = std::sqrt(dataPtr_->transverseDiameter_*dataPtr_->transverseDiameter_ - dataPtr_->minTransverseDiameter_*dataPtr_->minTransverseDiameter_);

    // Store into the diagonal matrix
    // All the elements but one are the conjugate radius
    diagAsVector.fill(conjugateDiamater/2.0);

    // The first element in diagonal is the transverse radius
    diagAsVector(0) = 0.5 * dataPtr_->transverseDiameter_;

    // Calculate the transformation matrix
    dataPtr_->transformationWorldFromEllipse_ = dataPtr_->rotationWorldFromEllipse_ * diagAsVector.asDiagonal();

    // Calculate the measure:
    dataPtr_->phsMeasure_ = prolateHyperspheroidMeasure(dataPtr_->dim_, dataPtr_->minTransverseDiameter_, dataPtr_->transverseDiameter_);

    // Mark as up to date
    dataPtr_->isTransformUpToDate_ = true;
}


#include "MUMPS.hpp"
#include "exceptions.hpp"

#include <Eigen/Sparse>

#include <chrono>
#include <iostream>

namespace neon
{
MUMPS::MUMPS(MatrixProperty const symmetric_flag)
{
    info.job = Job::Initialisation;

    // Par determines parallel data location
    // 0  : Host is not involved with computes
    // 1  : Host is involved
    info.par = 1;
    info.comm_fortran = -987654;

    // Symmetric matrix flag
    info.sym = symmetric_flag;

    MUMPSAdapter::mumps_c(info);

    // Verbosity levels
    info.icntl[0] = 1;
    info.icntl[1] = 1;
    info.icntl[2] = 1;
    info.icntl[3] = 1;

    // Ordering algorithm
    info.icntl[6] = Ordering::Automatic;

    // Iterative refinement
    info.icntl[9] = 100;

    // Compute the residual
    info.icntl[10] = Residual::Cheap;
}

MUMPS::~MUMPS()
{
    // Take out the trash
    info.job = Job::Terminate;
    MUMPSAdapter::mumps_c(info);
}

void MUMPS::internal_solve(sparse_matrix const& A, vector& x, vector const& b)
{
    auto start = std::chrono::high_resolution_clock::now();

    x = b;

    info.n = A.rows();
    info.nz = coefficients.size();

    info.a = coefficients.data();
    info.irn = rows.data();
    info.jcn = cols.data();

    if (build_sparsity_pattern)
    {
        // Analysis phase
        info.job = Job::Analysis;
        MUMPSAdapter::mumps_c(info);

        if (info.info[0] < 0)
        {
            throw computational_error("Error in analysis phase of MUMPS solver\n");
        }
        build_sparsity_pattern = false;
    }

    // Factorization phase
    info.job = Job::Factorisation;
    MUMPSAdapter::mumps_c(info);

    if (info.info[0] < 0)
    {
        throw computational_error("Error in factorisation phase of MUMPS solver\n");
    }

    info.rhs = x.data();
    info.nrhs = 1;
    info.lrhs = info.n;

    info.job = Job::BackSubstitution;
    MUMPSAdapter::mumps_c(info);

    if (info.info[0] < 0)
    {
        throw computational_error("Error in back substitution phase of MUMPS solver\n");
    }

    auto end = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double> elapsed_seconds = end - start;

    std::cout << std::string(6, ' ') << "MUMPS solver took " << elapsed_seconds.count() << "s\n";
}

void MUMPSLLT::allocate_coordinate_format_storage(sparse_matrix const& A)
{
    coefficients.clear();
    coefficients.reserve(A.nonZeros());

    if (build_sparsity_pattern)
    {
        rows.reserve(A.nonZeros());
        cols.reserve(A.nonZeros());

        // Decompress the upper part of the sparse matrix
        for (auto k = 0; k < A.outerSize(); ++k)
        {
            for (sparse_matrix::InnerIterator it(A, k); it; ++it)
            {
                if (it.col() >= it.row())
                {
                    coefficients.emplace_back(it.value());
                    rows.emplace_back(it.row() + 1);
                    cols.emplace_back(it.col() + 1);
                }
            }
        }
        rows.shrink_to_fit();
        cols.shrink_to_fit();
    }
    else
    {
        // Only update the non-zero numerical values
        for (auto k = 0; k < A.outerSize(); ++k)
        {
            for (sparse_matrix::InnerIterator it(A, k); it; ++it)
            {
                if (it.col() >= it.row())
                {
                    coefficients.emplace_back(it.value());
                }
            }
        }
    }
}

void MUMPSLLT::solve(sparse_matrix const& A, vector& x, vector const& b)
{
    this->allocate_coordinate_format_storage(A);
    internal_solve(A, x, b);
}

void MUMPSLU::allocate_coordinate_format_storage(sparse_matrix const& A)
{
    rows.clear();
    cols.clear();
    coefficients.clear();

    rows.reserve(A.nonZeros());
    cols.reserve(A.nonZeros());
    coefficients.reserve(A.nonZeros());

    for (auto k = 0; k < A.outerSize(); ++k)
    {
        for (sparse_matrix::InnerIterator it(A, k); it; ++it)
        {
            coefficients.emplace_back(it.value());
            rows.emplace_back(it.row() + 1);
            cols.emplace_back(it.col() + 1);
        }
    }
}

void MUMPSLU::solve(sparse_matrix const& A, vector& x, vector const& b)
{
    this->allocate_coordinate_format_storage(A);
    internal_solve(A, x, b);
}
}


#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<int, Eigen::Dynamic, 5> test_int_D_5(const Eigen::Matrix<int, Eigen::Dynamic, 5> & M)
{
	return M;
}
void export_int_D_5()
{
	boost::python::def("test_int_D_5",test_int_D_5);
}


#include "RaytracingEngine.h"
#include "GlobalConst.h"

#include <Eigen/Dense>

using namespace Eigen;

RaytracingEngine* RaytracingEngine::Get()
{
	static RaytracingEngine deepRT;
	return &deepRT;
}

void RaytracingEngine::Initialize()
{
	Eigen::initParallel();
}

RaytracingEngine::RaytracingEngine()
{
}

RaytracingEngine::~RaytracingEngine()
{
}

void RaytracingEngine::operator=(const RaytracingEngine& raytracingEngine)
{
	UNUSED(raytracingEngine);
}


// MIT License
//
// Copyright (c) 2020 Edward D. Lee, Bryan C. Daniels
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#ifndef samplers_hpp
#define samplers_hpp

#include <stdio.h>
#include <array>
#include <vector>
#include <random>
#include <iostream>
#include <math.h>
#include <assert.h>
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
#include <iterator>
#include <algorithm>

#endif /* samplers_hpp */

namespace py = boost::python;
namespace np = boost::python::numpy;

// Base sampling class
class Sampler {
public:
    int n;                                  // system size
    std::vector<std::vector<double>> couplingMat;
    std::vector<double> multipliers;        // fields and couplings
    std::vector<std::vector<int>> sample;   // stored sample
    int seed;
    std::mt19937_64 rd;
    std::uniform_real_distribution<double> unitrng;

    Sampler();
    Sampler(int, std::vector<double>, int=-1);  // init with multipliers
    Sampler(int, np::ndarray, int=-1);

    virtual double calc_e(std::vector<int> const&) = 0;
    virtual double sample_metropolis(std::vector<int>&, int const) = 0;
    void generate_sample(int const,
                         int const,
                         int const,
                         bool const=false);
    np::ndarray fetch_sample();
    std::vector<double> means();
    void print(int const);

private:
    virtual std::vector<int> init_sample() = 0;
};

// sampling for Ising model
class Ising : public Sampler {
public:
    Ising();
    Ising(int, std::vector<double>, int=-1);
    Ising(int, np::ndarray, int=-1);

    double calc_e(std::vector<int> const&);
    double sample_metropolis(std::vector<int>&, int const);
    void readin_multipliers(np::ndarray);
private:
    std::vector<int> init_sample();
};

// 3-state Potts sampling
class Potts3 : public Sampler {
public:
    Potts3();
    Potts3(int, std::vector<double>, int=-1);
    Potts3(int, np::ndarray, int=-1);

    double calc_e(std::vector<int> const&);
    double sample_metropolis(std::vector<int>&, int const);
    py::tuple get_value();
    py::tuple get_state();
    void set_state(py::tuple);
    np::ndarray multipliers2ndarray();
private:
    std::uniform_int_distribution<int> staterng;
    std::vector<int> init_sample();
    void readin_multipliers(np::ndarray);
};


#include <frovedis.hpp>
#include <frovedis/matrix/sliced_matrix.hpp>
#include <frovedis/matrix/blas_wrapper.hpp>

#define BOOST_TEST_MODULE FrovedisTest
#include <boost/test/unit_test.hpp>

using namespace frovedis;
using namespace std;

BOOST_AUTO_TEST_CASE( frovedis_test )
{
    int argc = 1;
    char** argv = NULL;
    use_frovedis use(argc, argv);

    colmajor_matrix_local<float> cm (
             make_rowmajor_matrix_local_load<float>("./sample_4x4"));

    // creating slice colmajor matrix from entire matrix
    sliced_colmajor_matrix_local<float> sm (cm);

    // scaling entire slice with 2
    auto col = make_col_vector(sm,0);
    scal<float> (col, 2);

    // scaling entire slice with 2
    auto row = make_row_vector(sm,3);
    scal<float> (row, 5);

    // checking whether operation can be done on sliced matrix correctly
    cm.to_rowmajor().save("./out_4x4");
    BOOST_CHECK (system("diff ./out_4x4 ./ref_4x4") == 0);
    system("rm -f ./out_4x4");
}


// Boost.Geometry Index
//
// R-tree linear split algorithm implementation
//
// Copyright (c) 2008 Federico J. Fernandez.
// Copyright (c) 2011-2013 Adam Wulkiewicz, Lodz, Poland.
//
// This file was modified by Oracle on 2020.
// Modifications copyright (c) 2020, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle
//
// 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_NSPHERE_INDEX_DETAIL_RTREE_LINEAR_REDISTRIBUTE_ELEMENTS_HPP
#define BOOST_GEOMETRY_EXTENSIONS_NSPHERE_INDEX_DETAIL_RTREE_LINEAR_REDISTRIBUTE_ELEMENTS_HPP

#include <type_traits>

#include <boost/core/ignore_unused.hpp>

#include <boost/geometry/index/detail/rtree/linear/redistribute_elements.hpp>

namespace boost { namespace geometry { namespace index {

namespace detail { namespace rtree { namespace linear {

template <typename Elements, typename Parameters, typename Translator, size_t DimensionIndex>
struct find_greatest_normalized_separation<Elements, Parameters, Translator, nsphere_tag, DimensionIndex>
{
    typedef typename Elements::value_type element_type;
    typedef typename rtree::element_indexable_type<element_type, Translator>::type indexable_type;
    typedef typename coordinate_type<indexable_type>::type coordinate_type;

    typedef std::conditional_t
        <
            std::is_integral<coordinate_type>::value,
            double,
            coordinate_type
        > separation_type;

    static inline void apply(Elements const& elements,
                             Parameters const& parameters,
                             Translator const& translator,
                             separation_type & separation,
                             size_t & seed1,
                             size_t & seed2)
    {
        const size_t elements_count = parameters.get_max_elements() + 1;
        BOOST_GEOMETRY_INDEX_ASSERT(elements.size() == elements_count, "unexpected number of elements");
        BOOST_GEOMETRY_INDEX_ASSERT(2 <= elements_count, "unexpected number of elements");

        indexable_type const& indexable = rtree::element_indexable(elements[0], translator);

        // find the lowest low, highest high
        coordinate_type lowest_low = geometry::get<DimensionIndex>(indexable) - geometry::get_radius<0>(indexable);
        coordinate_type highest_high = geometry::get<DimensionIndex>(indexable) + geometry::get_radius<0>(indexable);
        // and the lowest high
        coordinate_type lowest_high = highest_high;
        size_t lowest_high_index = 0;
        for ( size_t i = 1 ; i < elements_count ; ++i )
        {
            indexable_type const& indexable = rtree::element_indexable(elements[i], translator);

            coordinate_type min_coord = geometry::get<DimensionIndex>(indexable) - geometry::get_radius<0>(indexable);
            coordinate_type max_coord = geometry::get<DimensionIndex>(indexable) + geometry::get_radius<0>(indexable);

            if ( max_coord < lowest_high )
            {
                lowest_high = max_coord;
                lowest_high_index = i;
            }

            if ( min_coord < lowest_low )
                lowest_low = min_coord;

            if ( highest_high < max_coord )
                highest_high = max_coord;
        }

        // find the highest low
        size_t highest_low_index = lowest_high_index == 0 ? 1 : 0;
        indexable_type const& highest_low_indexable = rtree::element_indexable(elements[highest_low_index], translator);
        coordinate_type highest_low = geometry::get<DimensionIndex>(highest_low_indexable) - geometry::get_radius<0>(highest_low_indexable);
        for ( size_t i = highest_low_index ; i < elements_count ; ++i )
        {
            indexable_type const& indexable = rtree::element_indexable(elements[i], translator);

            coordinate_type min_coord = geometry::get<DimensionIndex>(indexable) - geometry::get_radius<0>(indexable);
            if ( highest_low < min_coord &&
                 i != lowest_high_index )
            {
                highest_low = min_coord;
                highest_low_index = i;
            }
        }

        coordinate_type const width = highest_high - lowest_low;

        // highest_low - lowest_high
        separation = difference<separation_type>(lowest_high, highest_low);
        // BOOST_GEOMETRY_INDEX_ASSERT(0 <= width);
        if ( std::numeric_limits<coordinate_type>::epsilon() < width )
            separation /= width;

        seed1 = highest_low_index;
        seed2 = lowest_high_index;

        ::boost::ignore_unused(parameters);
    }
};

}}} // namespace detail::rtree::linear

}}} // namespace boost::geometry::index

#endif // BOOST_GEOMETRY_EXTENSIONS_NSPHERE_INDEX_DETAIL_RTREE_LINEAR_REDISTRIBUTE_ELEMENTS_HPP


#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;

#include <fmt/format.h>
#include <fmt/format.cc>
#include <fmt/string.h>
#include <fmt/ostream.h>

#include <vector>
#include <cmath>
#include <random>
#include <math.h>

#include <Eigen/Dense>
using namespace Eigen;

// --------------------------------------------------
// #include "definitions.h"
const double pi = M_PI;
typedef std::array<double, 3> vec;

using std::sin;
using std::cos;
using std::exp;
using std::acos;
using std::asin;
using std::pow;
using std::sqrt;
using std::log;

// --------------------------------------------------
namespace mcmc {

    class photon {

        public:
            std::array<double, 4> data;
            photon( double, double, double, double );

            /// photon four-velocity components
            const double hv(){ return data[0]; };
            const double vx(){ return data[1]; };
            const double vy(){ return data[2]; };
            const double vz(){ return data[3]; };

            /// Location
            // const double x(){ return data[4]; };
            // const double y(){ return data[5]; };
            // const double z(){ return data[6]; };

    };

    photon::photon(double E, double vx, double vy, double vz) {
        this->data = {{E, vx, vy, vz}};
    };

    class electron {

        public:
            std::array<double, 4> data;
            electron( double, double, double, double );

            // four-velocity of electron
            const double v0(){ return data[0]; };
            const double vx(){ return data[1]; };
            const double vy(){ return data[2]; };
            const double vz(){ return data[3]; };

            /// 3-velocity in units of c, i.e. v/c
            const double v() {
                return std::sqrt(
                          pow(vx(), 2.0)
                        + pow(vy(), 2.0)
                        + pow(vz(), 2.0)
                                ); };

            /// gamma factor
            const double gamma() { return 1.0 / std::sqrt( 1.0 - std::pow( v(), 2.0 ) ); };

            /// proper momentum
            const double p() { return gamma() * v(); };

    };

    electron::electron(double v0, double vx, double vy, double vz) {
        this->data = {{v0, vx, vy, vz}};
    };

    // --------------------------------------------------
    class photonBucket {

        /// size of the bucket (in terms of photons)
        size_t nPhotons = 0;

        /// Photon container
        std::vector<std::array<double, 4>> bucket;

        public:
            void push_back( photon ph );

            //const size_t size( ) { return this->nPhotons; };
            const size_t size( ) { return bucket.size(); };

            void replace( const size_t indx, photon ph );

            photon get( const size_t indx );

            std::array<double, 4> get_data( const size_t indx );

            void resize(const size_t N);

            void swap(const size_t i1, const size_t i2);

            std::vector<double> vecE();
            std::vector<double> vecZ();
    };

    /// Append photon into bucket
    void photonBucket::push_back( photon ph ) {
        bucket.push_back( ph.data );
        nPhotons++;
    };

    /// Replace i:th photon with the given one
    void photonBucket::replace( const size_t indx, photon ph ) {
        bucket[indx] = ph.data;
    };

    /// Swap i1 and i2 photons in the bucket
    void photonBucket::swap(const size_t i1, const size_t i2) {
        std::swap( bucket[i1], bucket[i2] );
    };

    /// return i:th photon data
    photon photonBucket::get( const size_t indx ) {
        std::array<double, 4> data = bucket[indx];
        photon ph(data[0], data[1], data[2], data[3]);

        return ph;
    };

    /// return i:th photon raw data
    std::array<double, 4> photonBucket::get_data( const size_t indx ) {
        return bucket[indx];
    };

    /// Resize bucket
    void photonBucket::resize(const size_t N) {
        // TODO error check if N < N
        bucket.resize(N);
        nPhotons = N;

        return;
    };

    /// collect energy components of velocity
    std::vector<double> photonBucket::vecE() {
        std::vector<double> ve;
        ve.resize( size() );

        for (size_t i=0; i<size(); i++) {
            auto d = bucket[i];
            ve[i] = d[0];
        }
        return ve;
    };

    /// collect z components of velocity
    std::vector<double> photonBucket::vecZ() {
        std::vector<double> vz;
        vz.resize( size() );

        for (size_t i=0; i<size(); i++) {
            auto d = bucket[i];
            vz[i] = d[3];
        }
        return vz;
    };

    // --------------------------------------------------
    class electronBucket {

        /// size of the bucket (in terms of photons)
        size_t nElectrons = 0;

        /// Photon container
        std::vector<std::array<double, 4>> bucket;

        public:
            void push_back( electron e );
            const size_t size( ) { return this->nElectrons; };

            void replace( size_t indx, electron e );

            electron get( size_t indx );

    };

    void electronBucket::push_back( electron e ) {
        bucket.push_back( e.data );
        nElectrons++;
    };

    void electronBucket::replace( size_t indx, electron e ) {
        bucket[indx] = e.data;
    };

    electron electronBucket::get( size_t indx ) {
        std::array<double, 4> data = bucket[indx];
        electron e(data[0], data[1], data[2], data[3]);

        return e;
    };

    //--------------------------------------------------
    class Slab {

        /// Box sizes
        double xmin, xmax, ymin, ymax, zmin, zmax;

        /// RNG seed
        uint32_t rngSeed = 1;

        /// RNG engine (Mersenne twister)
        std::mt19937 rng;

        /// Ready-made distribution for flat variables in [0, 2pi[
        std::uniform_real_distribution<double> randPhi{0.0, 2.0*pi};

        /// Ready-made distribution for flat variables in [0,1[
        std::uniform_real_distribution<double> randmu{0.0, 1.0};

        /// Random spherical direction (r, theta, phi) with Lamberts law
        vec randSph() {
            return {{ 1.0, std::acos(std::pow(randmu(rng),0.5)), randPhi(rng) }};
        };

        /// Random direction in Cartesian (vx, vy, vx) coordinates
        // done via spherical coordinates (r, theta, phi) and then transforming back
        vec randHalfSphere() {
            vec sphDirs = randSph();
            vec ret =
            {{
                sphDirs[0] * std::sin(sphDirs[1]) * std::cos(sphDirs[2]),
                sphDirs[0] * std::sin(sphDirs[1]) * std::sin(sphDirs[2]),
                sphDirs[0] * std::cos(sphDirs[1])
            }};

            return ret;
        };

        /// Draw samples from Planck function using series sampling
        double Planck(double kT) {

            double z1 = randmu(rng);
            double z2 = randmu(rng);
            double z3 = randmu(rng);
            double x = -1.0*log(z1*z2*z3);

            double j = 1.0;
            double a = 1.0;
            z1 = randmu(rng);

            // 90/pi^4 (rounded down to get finite loop)
            while (1.08232*z1 > a) {
                j += 1.0;
                a += 1.0 / std::pow(j, 4.0);
            }
            return kT * x/j;
        };

        /// Sampling relativistic Maxwellian using the Sobol method
        // u = v*gamma
        double relMaxwellianVel(double Te) {
            double x4 = randmu(rng);
            double x5 = randmu(rng);
            double x6 = randmu(rng);
            double x7 = randmu(rng);

            double u = -Te*log(x4*x5*x6);
            double n = -Te*log(x4*x5*x6*x7);

            if (n*n - u*u < 1.0) { return relMaxwellianVel(Te); };

            return u;
        };

        /// Sampling from non-relativistic Maxwellian with rejection sampling
        // NOTE: these are coordinate velocities
        double MaxwellianVel(double Te) {
            double vmin = -5.0*Te;
            double vmax =  5.0*Te;
            double vf = vmin + (vmax-vmin)*randmu(rng);

            double f = vf*vf*std::exp(-(vf*vf)/(2.0*Te));
            double x = randmu(rng);

            if (x > f) { return MaxwellianVel(Te); };

            return vf;
        };

        /// Isotropic velocity components
        // using u = abs(u_i) to get cartesian (ux, uy, uz)
        vec velXYZ(double u ) {
            double x1 = randmu(rng);
            double x2 = randmu(rng);

            vec ret =
            {{
                    u*(2.0*x1 - 1.0),
                    2.0*u*sqrt(x1*(1.0-x1))*cos(2.0*pi*x2),
                    2.0*u*sqrt(x1*(1.0-x1))*sin(2.0*pi*x2)
            }};

            return ret;
        };

        public:
        /// General boosted non-rel/rel Maxwellian
        // Te: electron temperature in m_e c^2
        // G:  Bulk Lorentz vector
        //
        // Ref: Zenitani 2015
        // NOTE: these are proper velocities and gamma = sqrt(1+u^2)
        vec boostedMaxwellian(double Te, vec G) {

            double u;
            if (Te > 0.2) { // relativistic
                u = relMaxwellianVel(Te);
            } else { // non-relativistic
                u = MaxwellianVel(Te);
            }

            // get isotropic velocity components
            vec ui = velXYZ(u);

            // check if bulk velocity
            if (G[0] == 0.0 && G[1] == 0.0 && G[2] == 0.0) {
                return ui;
            }

            // next boost in X dir; TODO generalize
            vec beta =
            {{
                1.0/sqrt(1.0 + G[0]*G[0]),
                1.0/sqrt(1.0 + G[1]*G[1]),
                1.0/sqrt(1.0 + G[2]*G[2])
            }};

            double x8 = randmu(rng);
            if (-beta[0]*ui[0] > x8) { ui[0] = -ui[0]; };
            ui[0] = G[0]*(ui[0] + beta[0]*std::sqrt(1.0 + u*u));
            // u = std::sqrt(ui[0]*ui[0] + ui[1]*ui[1] + ui[2]*ui[2]);

            return ui;
        };

        /// Compton scattering using Sobol's algorithm
        std::pair<photon, electron> comptonScatter(photon ph, electron el) {

            // fmt::print("v: {}\n", el.v());
            // fmt::print("E: {}\n", ph.hv());

            Vector3d ve( el.vx(), el.vy(), el.vz() );
            Vector3d beta = ve.normalized();
            Vector3d omeg(ph.vx(), ph.vy(), ph.vz());

            double theta = acos( beta.dot(omeg) );

            // Create base vectors and matrix
            //--------------------------------------------------
            // k
            Vector3d kvec(0.0, -1.0, 0.0);

            // j
            Vector3d jvec = beta.cross(omeg);
            jvec = jvec/jvec.norm();

            // i
            Vector3d ivec = kvec.cross(jvec);
            ivec = ivec/ivec.norm();

            Matrix3d M;
            M << ivec, jvec, kvec;

            // --------------------------------------------------

            // unit vector of electron in scattering coordinates
            Vector3d v0 = M.transpose() * ve; // rotate electron velocity to scattering plane (i,k)
            double mu = v0(0)*sin(theta) + v0(2)*cos(theta);
            double rho = sqrt( pow(v0(0),2.0) + pow(v0(1),2.0) );

            // Compton parameter
            double y = ph.hv() * el.gamma() * (1.0 - mu*el.v() );

            // Additional scattering angles (v0, w0, t0) that define a frame of reference
            Vector3d w0(v0(1)/rho,      -v0(0)/rho,        0.0);
            Vector3d t0(v0(0)*v0(2)/rho, v0(1)*v0(2)/rho, -rho);

            // --------------------------------------------------
            // scatter
            double OOp, z1, z2, z3, mup, phip, yp, Y;
            while (true) {
                z1 = randmu(rng);
                z2 = randmu(rng);
                z3 = randmu(rng);

                mup  = (el.v() + 2.0*z1 - 1.0)/(1.0 + el.v()*(2.0*z1 - 1.0));
                phip = 2.0*pi*z2;

                OOp = mu*mup - sqrt(1.0-mup*mup) * (rho*sin(phip)*cos(theta)
                      - (1.0/rho)*(v0(1)*cos(phip) + v0(0)*v0(2)*sin(phip))*sin(theta));

                yp = y/(1.0 + ph.hv()*(1.0 - OOp))/(el.gamma() * (1.0-mup*el.v()));
                Y = yp/y + pow(yp/y,3) + pow(yp/y,2)*
                    ( pow(1.0/yp - 1.0/y, 2) - 2.0*( 1.0/yp - 1.0/y) );

                if (Y>2.0*z3) { break; };
            }

            // --------------------------------------------------
            // we have now scattered successfully

            // new energy
            double hvp = yp/( el.gamma()*(1.0 - mup*el.v()) );

            // new direction from ijk base to xyz base
            Vector3d Op_ijk = mup*v0
                          + sqrt(1.0-mup*mup)*( w0*cos(phip) + t0*sin(phip) );
            Vector3d Op = (M.transpose().inverse() * Op_ijk).normalized();

            // pack everything to classes and return
            photon phs(hvp, Op(0), Op(1), Op(2));

            return std::make_pair(phs, el);
        };

        public:

            /// photon container
            photonBucket bucket;

            /// Overflow bucket
            photonBucket overflow;

            /// Simulation time step (in units of c)
            double dt = 0.1;

            /// Slab height
            double height = 1.0;

            /// electron number density
            double ne = 0.0;

            /// Thomson optical depth
            double tau = 0.0;

            /// location containers
            std::vector<double> xloc, yloc, zloc;

            /// Constructor
            Slab(photonBucket b) {
                this->bucket = b;

                // prepare location containers
                xloc.resize( bucket.size() );
                yloc.resize( bucket.size() );
                zloc.resize( bucket.size() );

                // Finally seed the rng
                rng.seed( rngSeed );
            };

            /// Number of photons in the slab
            const size_t size( ) {return this->bucket.size(); };

            /// Set slab dimensions; z is implicitly assumed as the height
            void set_dimensions(double _xmin, double _xmax,
                                double _ymin, double _ymax,
                                double _zmin, double _zmax) {

                this->xmin = _xmin;
                this->xmax = _xmax;
                this->ymin = _ymin;
                this->ymax = _ymax;
                this->zmin = _zmin;
                this->zmax = _zmax;

                this->height = zmax - zmin;
            };

            /// Set number density and compute Thomson optical depth based on it
            void set_numberDensity(double _ne) {
                this->ne = _ne;

                // compute Thomson tau = sigma_T * n_e * H
                tau = 1.0 * ne * height;
            };

            /// Step in time performing the full radiation interactions
            void step() {

                push();
                // wrap();
                // emergingFlux();

                // check_scatter();
                // scatter();
                // inject();

            };

            /// Push photons
            // TODO: Properly vectorize although this probably implicitly works
            // already on compiler level
            void push() {

                size_t N = this->size();
                std::vector<double> vx, vy, vz;
                vx.resize(N);
                vy.resize(N);
                vz.resize(N);

                // get velocities from bucket
                for (size_t i=0; i<N; i++) {
                    auto vel = this->bucket.get_data(i);
                    vx[i] = vel[1];
                    vy[i] = vel[2];
                    vz[i] = vel[3];
                }

                // step forward in time
                for (size_t i=0; i<N; i++) {
                    xloc[i] += vx[i]*dt;
                    yloc[i] += vy[i]*dt;
                    zloc[i] += vz[i]*dt;
                }
            };

            /// Inject more from the floor
            void inject(double flux) {

                // size of the floor
                double area = (xmax-xmin)*(ymax-ymin);

                // how many to inject based on the flux
                size_t Ninj = (size_t)flux*area*dt;

                // fmt::print("Injecting {} photons...\n", Ninj);

                // resize beforehand
                size_t Ns = size();
                bucket.resize(Ns + Ninj);
                xloc.resize(Ns + Ninj);
                yloc.resize(Ns + Ninj);
                zloc.resize(Ns + Ninj);

                for (size_t i=0; i<Ninj; i++) {

                    // create random photon
                    double E = Planck(1.0);

                    vec dir = randHalfSphere();
                    photon ph(E, dir[0], dir[1], dir[2]);

                    bucket.replace( Ns + i, ph );

                    // set location
                    xloc[Ns + i] = 0.0; // TODO random loc
                    yloc[Ns + i] = 0.0; // TODO random loc
                    zloc[Ns + i] = 0.0; // bottom
                }

            };

            /// Scrape photons that are overflowing from the slab
            void scrape() {

                size_t i = 0;
                size_t Nspills = 0;

                while ( i<size()-Nspills) {
                    if (zloc[i] >= height) {
                        // swamp everything to the end and remove later
                        // fmt::print("{} swapping {} to {} \n", i, yloc[i], yloc[size()-Nspills-1]);

                        std::swap( xloc[i], xloc[size() - Nspills - 1] );
                        std::swap( yloc[i], yloc[size() - Nspills - 1] );
                        std::swap( zloc[i], zloc[size() - Nspills - 1] );
                        bucket.swap( i, size()-Nspills - 1 );

                        Nspills++;
                    } else {
                        i++;
                    }
                }

                // collecting spills
                // fmt::print("Scraping spills: {} // total size: {}\n", Nspills, size());
                for (size_t i=size()-Nspills; i<size(); i++) {
                    photon ph = bucket.get(i);
                    // fmt::print("  spill {}\n", ph.hv() );

                    overflow.push_back(ph);
                }

                // and finally remove
                xloc.resize(size() - Nspills);
                yloc.resize(size() - Nspills);
                zloc.resize(size() - Nspills);
                bucket.resize(size() - Nspills);

                //fmt::print("after size {}\n", size());
            };

            /// inject everything from point in bottom
            void floor() {
                for (size_t i=0; i<size(); i++) {
                    zloc[i] = zmin;

                    // center of floor
                    xloc[i] = 0.0;
                    yloc[i] = 0.0;
                }
            };

            /// Wrap into xy box bounded by [xmin, xmax] x [ymin, ymax]
            void wrap() {

                for (size_t i=0; i<size(); i++) {
                    if (xloc[i] < xmin) { xloc[i] += xmax; }
                    if (xloc[i] > xmax) { xloc[i] -= xmax; }

                    if (yloc[i] < ymin) { yloc[i] += ymax; }
                    if (yloc[i] > ymax) { yloc[i] -= ymax; }
                }

            };

            // Check the optical distance and then scatter
            void scatter(double Te) {

                double x, y, z, z0;

                double etau = std::exp(-dt/ne);

                for (size_t i=0; i<size(); i++) {
                    z = zloc[i];

                    // scatter if e^-d\tau < rand()
                    z0 = randmu(rng);
                    if (etau < z0) {
                        photon ph = bucket.get(i);

                        // isotropic mono-energetic electron
                        // vec ve = velXYZ(0.8); // (beta)
                        // electron el(1.0, ve[0], ve[1], ve[2]);

                        // isotropic Maxwellian electrons
                        fmt::print("sampling from Maxwellian...\n");
                        vec ve = boostedMaxwellian(Te, {{0.0, 0.0, 0.0}});
                        electron el(1.0, ve[0], ve[1], ve[2]);

                        fmt::print("vx {} / vy {} / vz {}", el.vx(), el.vy(), el.vz());
                        fmt::print("target electron gamma: {} \n", el.gamma() );
                        fmt::print("target electron beta: {} \n", el.v() );

                        auto ret = comptonScatter(ph, el);
                        bucket.replace(i, ret.first );
                    }

                }

            };

    };

}

// --------------------------------------------------
PYBIND11_MODULE(mcmc, m) {

    py::class_<mcmc::photon>(m, "photon" )
        .def(py::init<double, double, double, double >())
        .def_readwrite("data",      &mcmc::photon::data)
        .def("hv",  &mcmc::photon::hv)
        .def("vx",  &mcmc::photon::vx)
        .def("vy",  &mcmc::photon::vy)
        .def("vz",  &mcmc::photon::vz);

    py::class_<mcmc::electron>(m, "electron" )
        .def(py::init<double, double, double, double >())
        .def_readwrite("data",      &mcmc::electron::data)
        .def("v0",    &mcmc::electron::v0)
        .def("vx",    &mcmc::electron::vx)
        .def("vy",    &mcmc::electron::vy)
        .def("vz",    &mcmc::electron::vz)
        .def("v",     &mcmc::electron::v)
        .def("gamma", &mcmc::electron::gamma);

    py::class_<mcmc::photonBucket>(m, "photonBucket" )
        .def(py::init<>())
        .def("size",      &mcmc::photonBucket::size)
        .def("replace",   &mcmc::photonBucket::replace)
        .def("get",       &mcmc::photonBucket::get)
        .def("vecE",      &mcmc::photonBucket::vecE)
        .def("vecZ",      &mcmc::photonBucket::vecZ)
        .def("push_back", &mcmc::photonBucket::push_back);

    py::class_<mcmc::electronBucket>(m, "electronBucket" )
        .def(py::init<>())
        .def("size",      &mcmc::electronBucket::size)
        .def("replace",   &mcmc::electronBucket::replace)
        .def("get",       &mcmc::electronBucket::get)
        .def("push_back", &mcmc::electronBucket::push_back);

    py::class_<mcmc::Slab>(m, "Slab" )
        .def(py::init< mcmc::photonBucket >())
        .def_readwrite("xloc",    &mcmc::Slab::xloc)
        .def_readwrite("yloc",    &mcmc::Slab::yloc)
        .def_readwrite("zloc",    &mcmc::Slab::zloc)
        .def_readwrite("tau",     &mcmc::Slab::tau)
        .def_readwrite("height",  &mcmc::Slab::height)
        .def_readwrite("ne",      &mcmc::Slab::ne)
        .def_readonly("bucket",   &mcmc::Slab::bucket)
        .def_readonly("overflow", &mcmc::Slab::overflow)
        .def("size",              &mcmc::Slab::size)
        .def("push",              &mcmc::Slab::push)
        .def("inject",            &mcmc::Slab::inject)
        .def("set_dimensions",    &mcmc::Slab::set_dimensions)
        .def("set_numberDensity", &mcmc::Slab::set_numberDensity)
        .def("scrape",            &mcmc::Slab::scrape)
        .def("wrap",              &mcmc::Slab::wrap)
        .def("scatter",           &mcmc::Slab::scatter)
        .def("boostedMaxwellian", &mcmc::Slab::boostedMaxwellian)
        .def("comptonScatter",    &mcmc::Slab::comptonScatter)
        .def("floor",             &mcmc::Slab::floor);

        // --------------------------------------------------
        // Bare bones array interface
        /*
        .def("__getitem__", [](const Sequence &s, size_t i) {
            if (i >= s.size()) throw py::index_error();
            return s[i];
        })
        .def("__setitem__", [](Sequence &s, size_t i, float v) {
            if (i >= s.size()) throw py::index_error();
            s[i] = v;
        })
        // Slices [optional]
        .def("__getitem__", [](const Sequence &s, py::slice slice) -> Sequence* {
            size_t start, stop, step, slicelength;
            if (!slice.compute(s.size(), &start, &stop, &step, &slicelength))
                throw py::error_already_set();
            Sequence *seq = new Sequence(slicelength);
            for (size_t i = 0; i < slicelength; ++i) {
                (*seq)[i] = s[start]; start += step;
            }
            return seq;
        })
        .def("__setitem__", [](Sequence &s, py::slice slice, const Sequence &value) {
            size_t start, stop, step, slicelength;
            if (!slice.compute(s.size(), &start, &stop, &step, &slicelength))
                throw py::error_already_set();
            if (slicelength != value.size())
                throw std::runtime_error("Left and right hand size of slice assignment have different sizes!");
            for (size_t i = 0; i < slicelength; ++i) {
                s[start] = value[i]; start += step;
            }
        })
        .def("__getitem__", [](const mcmc::vMesh &s, uint64_t i) {
                return s.__getitem__(i);
        })
        .def("__setitem__", [](mcmc::vMesh &s, uint64_t i, vblock_t v) {
                s.__setitem__(i, v);
        })
        // i,j,k indexing based interface
        .def("__getitem__", [](const mcmc::vMesh &s, py::tuple indx) {
                size_t i = indx[0].cast<size_t>();
                size_t j = indx[1].cast<size_t>();
                size_t k = indx[2].cast<size_t>();
                return s.__getitem2__( i,j,k );
        })
        .def("__setitem__", [](mcmc::vMesh &s, py::tuple indx, vblock_t v) {
                size_t i = indx[0].cast<size_t>();
                size_t j = indx[1].cast<size_t>();
                size_t k = indx[2].cast<size_t>();
                return s.__setitem2__( i,j,k, v);
        })

        // .def("__setitem__", [](mcmc::vMesh &s, uint64_t i, vblock_t v) {
        //         s.__setitem__(i, v);
        // })
        */

}


//===----------------------------------------------------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is dual licensed under the MIT and the University of Illinois Open
// Source Licenses. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//  Adaptation to Boost of the libcxx
//  Copyright 2010 Vicente J. Botet Escriba
//  Distributed under the Boost Software License, Version 1.0.
//  See http://www.boost.org/LICENSE_1_0.txt

#include <boost/chrono/duration.hpp>
#include <boost/detail/lightweight_test.hpp>

int main()
{
    {
    boost::chrono::seconds s1(3);
    boost::chrono::seconds s2(3);
    BOOST_TEST(s1 == s2);
    BOOST_TEST(!(s1 != s2));
    }
    {
    boost::chrono::seconds s1(3);
    boost::chrono::seconds s2(4);
    BOOST_TEST(!(s1 == s2));
    BOOST_TEST(s1 != s2);
    }
    {
    boost::chrono::milliseconds s1(3);
    boost::chrono::microseconds s2(3000);
    BOOST_TEST(s1 == s2);
    BOOST_TEST(!(s1 != s2));
    }
    {
    boost::chrono::milliseconds s1(3);
    boost::chrono::microseconds s2(4000);
    BOOST_TEST(!(s1 == s2));
    BOOST_TEST(s1 != s2);
    }
    {
    boost::chrono::duration<int, boost::ratio<2, 3> > s1(9);
    boost::chrono::duration<int, boost::ratio<3, 5> > s2(10);
    BOOST_TEST(s1 == s2);
    BOOST_TEST(!(s1 != s2));
    }
    {
    boost::chrono::duration<int, boost::ratio<2, 3> > s1(10);
    boost::chrono::duration<int, boost::ratio<3, 5> > s2(9);
    BOOST_TEST(!(s1 == s2));
    BOOST_TEST(s1 != s2);
    }
    {
    boost::chrono::duration<int, boost::ratio<2, 3> > s1(9);
    boost::chrono::duration<double, boost::ratio<3, 5> > s2(10);
    BOOST_TEST(s1 == s2);
    BOOST_TEST(!(s1 != s2));
    }
    {
    boost::chrono::seconds s1(3);
    boost::chrono::seconds s2(3);
    BOOST_TEST(!(s1 < s2));
    BOOST_TEST(!(s1 > s2));
    BOOST_TEST( (s1 <= s2));
    BOOST_TEST( (s1 >= s2));
    }
    {
    boost::chrono::seconds s1(3);
    boost::chrono::seconds s2(4);
    BOOST_TEST( (s1 < s2));
    BOOST_TEST(!(s1 > s2));
    BOOST_TEST( (s1 <= s2));
    BOOST_TEST(!(s1 >= s2));
    }
    {
    boost::chrono::milliseconds s1(3);
    boost::chrono::microseconds s2(3000);
    BOOST_TEST(!(s1 < s2));
    BOOST_TEST(!(s1 > s2));
    BOOST_TEST( (s1 <= s2));
    BOOST_TEST( (s1 >= s2));
    }
    {
    boost::chrono::milliseconds s1(3);
    boost::chrono::microseconds s2(4000);
    BOOST_TEST( (s1 < s2));
    BOOST_TEST(!(s1 > s2));
    BOOST_TEST( (s1 <= s2));
    BOOST_TEST(!(s1 >= s2));
    }
    {
    boost::chrono::duration<int, boost::ratio<2, 3> > s1(9);
    boost::chrono::duration<int, boost::ratio<3, 5> > s2(10);
    BOOST_TEST(!(s1 < s2));
    BOOST_TEST(!(s1 > s2));
    BOOST_TEST( (s1 <= s2));
    BOOST_TEST( (s1 >= s2));
    }
    {
    boost::chrono::duration<int, boost::ratio<2, 3> > s1(10);
    boost::chrono::duration<int, boost::ratio<3, 5> > s2(9);
    BOOST_TEST(!(s1 < s2));
    BOOST_TEST( (s1 > s2));
    BOOST_TEST(!(s1 <= s2));
    BOOST_TEST( (s1 >= s2));
    }
    {
    boost::chrono::duration<int, boost::ratio<2, 3> > s1(9);
    boost::chrono::duration<double, boost::ratio<3, 5> > s2(10);
    BOOST_TEST(!(s1 < s2));
    BOOST_TEST(!(s1 > s2));
    BOOST_TEST( (s1 <= s2));
    BOOST_TEST( (s1 >= s2));
    }
    return boost::report_errors();
}


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_IEEE_FUNCTIONS_SCALAR_COPYSIGN_HPP_INCLUDED
#define BOOST_SIMD_IEEE_FUNCTIONS_SCALAR_COPYSIGN_HPP_INCLUDED

#include <boost/simd/ieee/functions/copysign.hpp>
#include <boost/simd/include/functions/scalar/abs.hpp>
#include <boost/simd/include/functions/scalar/signnz.hpp>
#include <boost/simd/sdk/math.hpp>
#include <boost/math/special_functions/sign.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_SIMD_FUNCTOR_IMPLEMENTATION( boost::simd::tag::copysign_, tag::cpu_
                            , (A0)
                            , (scalar_< arithmetic_<A0> >)(scalar_< arithmetic_<A0> >)
                            )
  {
    typedef A0 result_type;
    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      return boost::simd::abs(a0)*boost::simd::signnz(a1);
    }
  };

  BOOST_SIMD_FUNCTOR_IMPLEMENTATION( boost::simd::tag::copysign_, tag::cpu_
                            , (A0)
                            , (scalar_< double_<A0> >)(scalar_< double_<A0> >)
                            )
  {
    typedef A0 result_type;
    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
    #ifdef BOOST_SIMD_HAS_COPYSIGN
      return ::copysign(a0, a1);
    // _copysign appears to be bogus for a1 = -0 in old MSVCRT
    #elif defined(BOOST_SIMD_HAS__COPYSIGN) && !defined(__MSVCRT__)
      return ::_copysign(a0, a1);
    #else
      return boost::math::copysign(a0, a1);
    #endif
    }
  };

  BOOST_SIMD_FUNCTOR_IMPLEMENTATION( boost::simd::tag::copysign_, tag::cpu_
                            , (A0)
                            , (scalar_< single_<A0> >)(scalar_< single_<A0> >)
                            )
  {
    typedef A0 result_type;
    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
    #ifdef BOOST_SIMD_HAS_COPYSIGNF
      return ::copysignf(a0, a1);
    #elif defined(BOOST_SIMD_HAS__COPYSIGNF)
      return ::_copysignf(a0, a1);
    #else
        return boost::math::copysign(a0, a1);
    #endif
    }
  };
} } }

#endif


// Distributed under the MIT License.
// See LICENSE.txt for details.

#include "Evolution/Systems/NewtonianEuler/Limiters/CharacteristicHelpers.hpp"

#include <array>
#include <boost/functional/hash.hpp>  // IWYU pragma: keep
#include <cstddef>
#include <limits>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <utility>

#include "DataStructures/Tags.hpp"
#include "DataStructures/Tensor/EagerMath/DotProduct.hpp"
#include "DataStructures/Tensor/Tensor.hpp"
#include "DataStructures/Variables.hpp"
#include "Domain/Structure/Element.hpp"  // IWYU pragma: keep
#include "Domain/Tags.hpp"               // IWYU pragma: keep
#include "ErrorHandling/Assert.hpp"
#include "Evolution/Systems/NewtonianEuler/Characteristics.hpp"
#include "Evolution/Systems/NewtonianEuler/SoundSpeedSquared.hpp"
#include "Evolution/Systems/NewtonianEuler/Tags.hpp"
#include "NumericalAlgorithms/Spectral/Mesh.hpp"
#include "Utilities/GenerateInstantiations.hpp"
#include "Utilities/Gsl.hpp"
#include "Utilities/TMPL.hpp"
#include "Utilities/TaggedTuple.hpp"

namespace NewtonianEuler::Limiters {

template <size_t VolumeDim, size_t ThermodynamicDim>
std::pair<Matrix, Matrix> right_and_left_eigenvectors(
    const Scalar<double>& mean_density,
    const tnsr::I<double, VolumeDim>& mean_momentum,
    const Scalar<double>& mean_energy,
    const EquationsOfState::EquationOfState<false, ThermodynamicDim>&
        equation_of_state,
    const tnsr::i<double, VolumeDim>& unit_normal) noexcept {
  // Compute fluid primitives from mean conserved state
  const auto velocity = [&mean_density, &mean_momentum]() noexcept {
    auto result = mean_momentum;
    for (size_t i = 0; i < VolumeDim; ++i) {
      result.get(i) /= get(mean_density);
    }
    return result;
  }();
  const auto specific_internal_energy = [&mean_density, &mean_energy,
                                         &mean_momentum]() noexcept {
    auto result = mean_energy;
    get(result) /= get(mean_density);
    get(result) -= 0.5 * get(dot_product(mean_momentum, mean_momentum)) /
                   square(get(mean_density));
    return result;
  }();

  Scalar<double> pressure{};
  Scalar<double> kappa_over_density{};
  if constexpr (ThermodynamicDim == 1) {
    pressure = equation_of_state.pressure_from_density(mean_density);
    get(kappa_over_density) =
        get(equation_of_state.kappa_times_p_over_rho_squared_from_density(
            mean_density)) *
        get(mean_density) / get(pressure);
  } else if constexpr (ThermodynamicDim == 2) {
    pressure = equation_of_state.pressure_from_density_and_energy(
        mean_density, specific_internal_energy);
    get(kappa_over_density) =
        get(equation_of_state
                .kappa_times_p_over_rho_squared_from_density_and_energy(
                    mean_density, specific_internal_energy)) *
        get(mean_density) / get(pressure);
  }

  const Scalar<double> specific_enthalpy{
      {{(get(mean_energy) + get(pressure)) / get(mean_density)}}};
  const Scalar<double> sound_speed_squared =
      NewtonianEuler::sound_speed_squared(
          mean_density, specific_internal_energy, equation_of_state);

  return std::make_pair(right_eigenvectors<VolumeDim>(
                            velocity, sound_speed_squared, specific_enthalpy,
                            kappa_over_density, unit_normal),
                        left_eigenvectors<VolumeDim>(
                            velocity, sound_speed_squared, specific_enthalpy,
                            kappa_over_density, unit_normal));
}

template <size_t VolumeDim>
void characteristic_fields(
    const gsl::not_null<tuples::TaggedTuple<
        ::Tags::Mean<NewtonianEuler::Tags::VMinus>,
        ::Tags::Mean<NewtonianEuler::Tags::VMomentum<VolumeDim>>,
        ::Tags::Mean<NewtonianEuler::Tags::VPlus>>*>
        char_means,
    const tuples::TaggedTuple<
        ::Tags::Mean<NewtonianEuler::Tags::MassDensityCons>,
        ::Tags::Mean<NewtonianEuler::Tags::MomentumDensity<VolumeDim>>,
        ::Tags::Mean<NewtonianEuler::Tags::EnergyDensity>>& cons_means,
    const Matrix& left) noexcept {
  auto& char_v_minus =
      get<::Tags::Mean<NewtonianEuler::Tags::VMinus>>(*char_means);
  auto& char_v_momentum =
      get<::Tags::Mean<NewtonianEuler::Tags::VMomentum<VolumeDim>>>(
          *char_means);
  auto& char_v_plus =
      get<::Tags::Mean<NewtonianEuler::Tags::VPlus>>(*char_means);

  const auto& cons_mass_density =
      get<::Tags::Mean<NewtonianEuler::Tags::MassDensityCons>>(cons_means);
  const auto& cons_momentum_density =
      get<::Tags::Mean<NewtonianEuler::Tags::MomentumDensity<VolumeDim>>>(
          cons_means);
  const auto& cons_energy_density =
      get<::Tags::Mean<NewtonianEuler::Tags::EnergyDensity>>(cons_means);

  get(char_v_minus) = left(0, 0) * get(cons_mass_density);
  for (size_t j = 0; j < VolumeDim; ++j) {
    char_v_momentum.get(j) = left(j + 1, 0) * get(cons_mass_density);
  }
  get(char_v_plus) = left(VolumeDim + 1, 0) * get(cons_mass_density);

  for (size_t i = 0; i < VolumeDim; ++i) {
    get(char_v_minus) += left(0, i + 1) * cons_momentum_density.get(i);
    for (size_t j = 0; j < VolumeDim; ++j) {
      char_v_momentum.get(j) +=
          left(j + 1, i + 1) * cons_momentum_density.get(i);
    }
    get(char_v_plus) +=
        left(VolumeDim + 1, i + 1) * cons_momentum_density.get(i);
  }

  get(char_v_minus) += left(0, VolumeDim + 1) * get(cons_energy_density);
  for (size_t j = 0; j < VolumeDim; ++j) {
    char_v_momentum.get(j) +=
        left(j + 1, VolumeDim + 1) * get(cons_energy_density);
  }
  get(char_v_plus) +=
      left(VolumeDim + 1, VolumeDim + 1) * get(cons_energy_density);
}

template <size_t VolumeDim>
void characteristic_fields(
    const gsl::not_null<Scalar<DataVector>*> char_v_minus,
    const gsl::not_null<tnsr::I<DataVector, VolumeDim>*> char_v_momentum,
    const gsl::not_null<Scalar<DataVector>*> char_v_plus,
    const Scalar<DataVector>& cons_mass_density,
    const tnsr::I<DataVector, VolumeDim>& cons_momentum_density,
    const Scalar<DataVector>& cons_energy_density,
    const Matrix& left) noexcept {
  get(*char_v_minus) = left(0, 0) * get(cons_mass_density);
  for (size_t j = 0; j < VolumeDim; ++j) {
    char_v_momentum->get(j) = left(j + 1, 0) * get(cons_mass_density);
  }
  get(*char_v_plus) = left(VolumeDim + 1, 0) * get(cons_mass_density);

  for (size_t i = 0; i < VolumeDim; ++i) {
    get(*char_v_minus) += left(0, i + 1) * cons_momentum_density.get(i);
    for (size_t j = 0; j < VolumeDim; ++j) {
      char_v_momentum->get(j) +=
          left(j + 1, i + 1) * cons_momentum_density.get(i);
    }
    get(*char_v_plus) +=
        left(VolumeDim + 1, i + 1) * cons_momentum_density.get(i);
  }

  get(*char_v_minus) += left(0, VolumeDim + 1) * get(cons_energy_density);
  for (size_t j = 0; j < VolumeDim; ++j) {
    char_v_momentum->get(j) +=
        left(j + 1, VolumeDim + 1) * get(cons_energy_density);
  }
  get(*char_v_plus) +=
      left(VolumeDim + 1, VolumeDim + 1) * get(cons_energy_density);
}

template <size_t VolumeDim>
void characteristic_fields(
    const gsl::not_null<
        Variables<tmpl::list<NewtonianEuler::Tags::VMinus,
                             NewtonianEuler::Tags::VMomentum<VolumeDim>,
                             NewtonianEuler::Tags::VPlus>>*>
        char_vars,
    const Variables<tmpl::list<NewtonianEuler::Tags::MassDensityCons,
                               NewtonianEuler::Tags::MomentumDensity<VolumeDim>,
                               NewtonianEuler::Tags::EnergyDensity>>& cons_vars,
    const Matrix& left) noexcept {
  characteristic_fields(
      make_not_null(&get<NewtonianEuler::Tags::VMinus>(*char_vars)),
      make_not_null(
          &get<NewtonianEuler::Tags::VMomentum<VolumeDim>>(*char_vars)),
      make_not_null(&get<NewtonianEuler::Tags::VPlus>(*char_vars)),
      get<NewtonianEuler::Tags::MassDensityCons>(cons_vars),
      get<NewtonianEuler::Tags::MomentumDensity<VolumeDim>>(cons_vars),
      get<NewtonianEuler::Tags::EnergyDensity>(cons_vars), left);
}

template <size_t VolumeDim>
void conserved_fields_from_characteristic_fields(
    const gsl::not_null<Scalar<DataVector>*> cons_mass_density,
    const gsl::not_null<tnsr::I<DataVector, VolumeDim>*> cons_momentum_density,
    const gsl::not_null<Scalar<DataVector>*> cons_energy_density,
    const Scalar<DataVector>& char_v_minus,
    const tnsr::I<DataVector, VolumeDim>& char_v_momentum,
    const Scalar<DataVector>& char_v_plus, const Matrix& right) noexcept {
  get(*cons_mass_density) = right(0, 0) * get(char_v_minus);
  for (size_t j = 0; j < VolumeDim; ++j) {
    cons_momentum_density->get(j) = right(j + 1, 0) * get(char_v_minus);
  }
  get(*cons_energy_density) = right(VolumeDim + 1, 0) * get(char_v_minus);

  for (size_t i = 0; i < VolumeDim; ++i) {
    get(*cons_mass_density) += right(0, i + 1) * char_v_momentum.get(i);
    for (size_t j = 0; j < VolumeDim; ++j) {
      cons_momentum_density->get(j) +=
          right(j + 1, i + 1) * char_v_momentum.get(i);
    }
    get(*cons_energy_density) +=
        right(VolumeDim + 1, i + 1) * char_v_momentum.get(i);
  }

  get(*cons_mass_density) += right(0, VolumeDim + 1) * get(char_v_plus);
  for (size_t j = 0; j < VolumeDim; ++j) {
    cons_momentum_density->get(j) +=
        right(j + 1, VolumeDim + 1) * get(char_v_plus);
  }
  get(*cons_energy_density) +=
      right(VolumeDim + 1, VolumeDim + 1) * get(char_v_plus);
}

#define DIM(data) BOOST_PP_TUPLE_ELEM(0, data)
#define THERMODIM(data) BOOST_PP_TUPLE_ELEM(1, data)

#define INSTANTIATE(_, data)                                            \
  template std::pair<Matrix, Matrix> right_and_left_eigenvectors(       \
      const Scalar<double>&, const tnsr::I<double, DIM(data)>&,         \
      const Scalar<double>&,                                            \
      const EquationsOfState::EquationOfState<false, THERMODIM(data)>&, \
      const tnsr::i<double, DIM(data)>&) noexcept;

GENERATE_INSTANTIATIONS(INSTANTIATE, (1, 2, 3), (1, 2))

#undef THERMODIM
#undef INSTANTIATE

#define INSTANTIATE(_, data)                                               \
  template void characteristic_fields(                                     \
      const gsl::not_null<tuples::TaggedTuple<                             \
          ::Tags::Mean<NewtonianEuler::Tags::VMinus>,                      \
          ::Tags::Mean<NewtonianEuler::Tags::VMomentum<DIM(data)>>,        \
          ::Tags::Mean<NewtonianEuler::Tags::VPlus>>*>,                    \
      const tuples::TaggedTuple<                                           \
          ::Tags::Mean<NewtonianEuler::Tags::MassDensityCons>,             \
          ::Tags::Mean<NewtonianEuler::Tags::MomentumDensity<DIM(data)>>,  \
          ::Tags::Mean<NewtonianEuler::Tags::EnergyDensity>>&,             \
      const Matrix&) noexcept;                                             \
  template void characteristic_fields(                                     \
      const gsl::not_null<Scalar<DataVector>*>,                            \
      const gsl::not_null<tnsr::I<DataVector, DIM(data)>*>,                \
      const gsl::not_null<Scalar<DataVector>*>, const Scalar<DataVector>&, \
      const tnsr::I<DataVector, DIM(data)>&, const Scalar<DataVector>&,    \
      const Matrix&) noexcept;                                             \
  template void characteristic_fields(                                     \
      const gsl::not_null<                                                 \
          Variables<tmpl::list<NewtonianEuler::Tags::VMinus,               \
                               NewtonianEuler::Tags::VMomentum<DIM(data)>, \
                               NewtonianEuler::Tags::VPlus>>*>,            \
      const Variables<                                                     \
          tmpl::list<NewtonianEuler::Tags::MassDensityCons,                \
                     NewtonianEuler::Tags::MomentumDensity<DIM(data)>,     \
                     NewtonianEuler::Tags::EnergyDensity>>&,               \
      const Matrix&) noexcept;                                             \
  template void conserved_fields_from_characteristic_fields(               \
      const gsl::not_null<Scalar<DataVector>*>,                            \
      const gsl::not_null<tnsr::I<DataVector, DIM(data)>*>,                \
      const gsl::not_null<Scalar<DataVector>*>, const Scalar<DataVector>&, \
      const tnsr::I<DataVector, DIM(data)>&, const Scalar<DataVector>&,    \
      const Matrix&) noexcept;

GENERATE_INSTANTIATIONS(INSTANTIATE, (1, 2, 3))

#undef DIM
#undef INSTANTIATE

}  // namespace NewtonianEuler::Limiters


/*******************************************************************************
 *
 * MIT License
 *
 * Copyright (c) 2017 Advanced Micro Devices, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 *******************************************************************************/
#ifndef MIOPEN_UTIL_HPP_
#define MIOPEN_UTIL_HPP_

#include <miopen/common.hpp>
#include <miopen/miopen.h>

#include <boost/range/adaptors.hpp>

namespace miopen {

struct Handle;

float Im2ColGPU(
    const Handle& handle,
    std::size_t spatial_dim,
    ConstData_t im,
    std::size_t im_offset,
    std::size_t in_c,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& in_spatial,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& wei_spatial,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& out_spatial,
    const std::vector<int>& pad_spatial,
    const std::vector<int>& stride_spatial,
    const std::vector<int>& dilation_spatial,
    Data_t col,
    miopenDataType_t type);

float Col2ImGPU(
    const Handle& handle,
    std::size_t spatial_dim,
    ConstData_t col,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& out_spatial,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& wei_spatial,
    const std::vector<int>& pad_spatial,
    const std::vector<int>& stride_spatial,
    const std::vector<int>& dilation_spatial,
    std::size_t in_c,
    const decltype(boost::adaptors::slice(std::vector<std::size_t>(), 0, 1))& in_spatial,
    Data_t im,
    std::size_t im_offset,
    miopenDataType_t type);

float transpose_NCHW2CNHW(const Handle& handle,
                          int n,
                          int c,
                          int h_in,
                          int w_in,
                          int h_out,
                          int w_out,
                          ConstData_t in,
                          Data_t out,
                          int in_offset,
                          int out_offset,
                          int h_stride,
                          int w_stride,
                          miopenDataType_t type);

float transpose_CNHW2NCHW(const Handle& handle,
                          int n,
                          int c,
                          int h_out,
                          int w_out,
                          int h_in,
                          int w_in,
                          ConstData_t in,
                          Data_t out,
                          int in_offset,
                          int out_offset,
                          int h_stride,
                          int w_stride,
                          miopenDataType_t type);

float transpose_NCHW2Vec(const Handle& handle,
                         const std::vector<std::size_t>& lens,
                         ConstData_t in,
                         Data_t out,
                         std::size_t vec_size,
                         bool trans,
                         bool forward,
                         const void* alpha,
                         const void* beta);

float transpose_packed_MN2NM(const Handle& handle,
                             int m,
                             int n,
                             int in_offset,
                             int out_offset,
                             ConstData_t in,
                             Data_t out,
                             miopenDataType_t type);
} // namespace miopen

#endif // _MIOPEN_UTIL_HPP_


#define BOOST_TEST_MODULE "test_external_distance_interaction"

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

#include <test/util/utility.hpp>

#include <mjolnir/core/BoundaryCondition.hpp>
#include <mjolnir/core/SimulatorTraits.hpp>
#include <mjolnir/forcefield/external/ExternalDistanceInteraction.hpp>
#include <mjolnir/forcefield/external/ExcludedVolumeWallPotential.hpp>
#include <mjolnir/core/AxisAlignedPlane.hpp>
#include <mjolnir/util/make_unique.hpp>
#include <random>

BOOST_AUTO_TEST_CASE(ExternalDistanceInteraction_numerical_difference)
{
    namespace test = mjolnir::test;
    mjolnir::LoggerManager::set_default_logger(
            "test_external_distance_interaction.log");

    using traits_type      = mjolnir::SimulatorTraits<double, mjolnir::UnlimitedBoundary>;
    using real_type        = traits_type::real_type;
    using coordinate_type  = traits_type::coordinate_type;
    using boundary_type    = traits_type::boundary_type;
    using system_type      = mjolnir::System<traits_type>;
    using potential_type   = mjolnir::ExcludedVolumeWallPotential<real_type>;
    using shape_type       = mjolnir::AxisAlignedPlane<traits_type, mjolnir::PositiveZDirection<traits_type>>;
    using interaction_type = mjolnir::ExternalDistanceInteraction<traits_type, potential_type, shape_type>;

    interaction_type interaction(shape_type(0.0, 0.5),
        potential_type(/*epsilon = */1.0, /* cutoff = */2.0, {
            {0, 1.0}, {1, 1.0}
        }));

    system_type sys(2, boundary_type{});
    test::clear_everything(sys);

    std::mt19937 mt(123456789);

    for(int i = 0; i < 10000; ++i)
    {
        test::clear_everything(sys);

        sys.position(0) = coordinate_type(0.0, 0.0, 1.0);
        sys.position(1) = coordinate_type(0.0, 0.0, 1.0);

        test::apply_random_perturbation(sys, mt, 0.01);

        // compare between numerical diff and force implementation
        constexpr real_type tol = 1e-4;
        constexpr real_type dr  = 1e-5;

        test::check_force(sys, interaction, tol, dr, false);
        test::check_force_and_energy(sys, interaction, tol);
    }
}


#include <envire_core/Item.hpp>
#include <Eigen/Geometry>

namespace envire
{
    ENVIRE_PLUGIN(VectorPlugin, Eigen::Vector3d)
}