// Copyright (c) 2020 Graphcore Ltd. All rights reserved.
#define BOOST_TEST_MODULE LoopTests
#include "popops/Loop.hpp"
#include "poplibs_test/Util.hpp"
#include "popops/ElementWise.hpp"
#include "popops/codelets.hpp"
#include "poputil/TileMapping.hpp"
#include "poputil/exceptions.hpp"
#include <boost/test/unit_test.hpp>
#include <poplibs_support/TestDevice.hpp>

using namespace poplar;
using namespace poplar::program;
using namespace poplibs_test::util;
using namespace popops;
using namespace poplibs_support;
using namespace popops::expr;

static std::size_t loopTest(std::size_t begin, std::size_t end,
                            std::size_t step) {
  auto device = createTestDevice(TEST_TARGET, 1, 4);
  const auto &target = device.getTarget();
  Graph graph(target);
  popops::addCodelets(graph);

  auto acc = graph.addVariable(UNSIGNED_INT, {1}, "acc");
  poputil::mapTensorLinearly(graph, acc);

  Sequence uploadProg, downloadProg;
  std::vector<std::pair<std::string, char *>> tmap;
  auto rawHostAcc = allocateHostMemoryForTensor(acc, "acc", graph, uploadProg,
                                                downloadProg, tmap);

  auto loop = Sequence();
  loop.add(popops::countedLoop(
      graph, begin, end, step,
      [&](Tensor idx) {
        Sequence prog;
        popops::addInPlace(graph, acc, idx, prog, "/accumulate");
        return prog;
      },
      "/countedLoop"));

  Engine engine(graph, Sequence{uploadProg, loop, downloadProg});
  device.bind([&](const Device &d) {
    engine.load(d);
    attachStreams(engine, tmap);
    engine.run(0);
  });

  return *reinterpret_cast<unsigned *>(rawHostAcc.get());
}

BOOST_AUTO_TEST_CASE(LoopTests) {
  BOOST_CHECK_EQUAL(loopTest(1, 2, 1), 1);
  BOOST_CHECK_EQUAL(loopTest(1, 2, 100), 1);
  BOOST_CHECK_EQUAL(loopTest(1, 10, 1), 45); // 1 + 2 + ... + 9
  BOOST_CHECK_EQUAL(loopTest(1, 10, 2), 25); // 1 + 3 + 5 + 7 + 9

  BOOST_CHECK_THROW(loopTest(0, 0, 0), poputil::poplibs_error);
  BOOST_CHECK_THROW(loopTest(0, 1, 0), poputil::poplibs_error);
  BOOST_CHECK_THROW(loopTest(10, 10, 10), poputil::poplibs_error);
}

static std::size_t forLoopTest(int begin, int end, int step,
                               const Type &tensorType,
                               bool createCountTensor = false) {

  auto device = createTestDevice(TEST_TARGET, 1, 4);
  const auto &target = device.getTarget();
  Graph graph(target);
  popops::addCodelets(graph);

  auto limit = graph.addConstant<unsigned>(tensorType, {1}, end, "limit");
  graph.setTileMapping(limit, 0);

  Sequence uploadProg, downloadProg;
  std::vector<std::pair<std::string, char *>> tmap;

  auto prog = Sequence();
  auto loop = Sequence();

  // Make a loop body which always counts 0.... which we use to check the
  // result
  auto bodyVar = graph.addVariable(UNSIGNED_INT, {1}, "bodyVar");
  graph.setTileMapping(bodyVar, 0);
  auto zero = graph.addConstant<unsigned>(UNSIGNED_INT, {1}, 0u, "bodyVar");
  graph.setTileMapping(zero, 0);

  prog.add(Copy(zero, bodyVar));

  popops::mapInPlace(graph, Add(_1, Const(1u)), {bodyVar}, loop,
                     "/bodyFunction");

  auto rawHostbodyVar = allocateHostMemoryForTensor(
      bodyVar, "bodyVar", graph, uploadProg, downloadProg, tmap);
  if (createCountTensor) {
    auto count = graph.addVariable(tensorType, {1}, "loopCount");
    graph.setTileMapping(count, 0);
    prog.add(popops::countedForLoop(graph, count, begin, limit, step, loop,
                                    "/countedForLoop"));
  } else {
    prog.add(popops::countedForLoop(graph, begin, limit, step, loop,
                                    "/countedForLoop"));
  }

  Engine engine(graph, Sequence{uploadProg, prog, downloadProg});
  device.bind([&](const Device &d) {
    engine.load(d);
    attachStreams(engine, tmap);
    engine.run(0);
  });

  return *reinterpret_cast<unsigned *>(rawHostbodyVar.get());
}

BOOST_AUTO_TEST_CASE(ForLoopTests) {
  BOOST_CHECK_EQUAL(forLoopTest(0, 2, 1, UNSIGNED_INT), 2);
  BOOST_CHECK_EQUAL(forLoopTest(0, 2, 1, UNSIGNED_INT, true), 2);
  BOOST_CHECK_EQUAL(forLoopTest(0, 10, 1, UNSIGNED_INT), 10);
  BOOST_CHECK_EQUAL(forLoopTest(2, 12, 1, UNSIGNED_INT), 10);
  BOOST_CHECK_EQUAL(forLoopTest(0, 20, 2, UNSIGNED_INT), 10);
  BOOST_CHECK_EQUAL(forLoopTest(7, 23, 4, UNSIGNED_INT), 4);

  BOOST_CHECK_EQUAL(forLoopTest(10, 0, -2, UNSIGNED_INT), 5);
  BOOST_CHECK_EQUAL(forLoopTest(10, 0, -2, UNSIGNED_INT, true), 5);

  BOOST_CHECK_EQUAL(forLoopTest(-3, 11, 2, INT), 7);
  BOOST_CHECK_EQUAL(forLoopTest(-3, -11, -1, INT), 8);
}


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#ifndef MTL_MAT_VEC_MULT_INCLUDE
#define MTL_MAT_VEC_MULT_INCLUDE

#include <cassert>
// #include <iostream>
#include <boost/mpl/bool.hpp>

#include <boost/numeric/mtl/config.hpp>
#include <boost/numeric/mtl/mtl_fwd.hpp>
#include <boost/numeric/mtl/utility/category.hpp>
#include <boost/numeric/mtl/utility/property_map.hpp>
#include <boost/numeric/mtl/utility/range_generator.hpp>
#include <boost/numeric/mtl/utility/tag.hpp>
#include <boost/numeric/mtl/utility/is_static.hpp>
#include <boost/numeric/mtl/utility/tag.hpp>
#include <boost/numeric/mtl/utility/enable_if.hpp>
#include <boost/numeric/mtl/utility/multi_tmp.hpp>
#include <boost/numeric/mtl/utility/static_assert.hpp>
#include <boost/numeric/mtl/vector/dense_vector.hpp>
#include <boost/numeric/mtl/utility/omp_size_type.hpp>
#include <boost/numeric/mtl/operation/set_to_zero.hpp>
#include <boost/numeric/mtl/operation/update.hpp>
#include <boost/numeric/linear_algebra/identity.hpp>
#include <boost/numeric/meta_math/loop.hpp>
#include <boost/numeric/mtl/interface/vpt.hpp>

namespace mtl { namespace matrix {

namespace impl {

    template <std::size_t Index0, std::size_t Max0, std::size_t Index1, std::size_t Max1, typename Assign>
    struct fully_unroll_mat_cvec_mult
      : public meta_math::loop2<Index0, Max0, Index1, Max1>
    {
	typedef meta_math::loop2<Index0, Max0, Index1, Max1>                              base;
	typedef fully_unroll_mat_cvec_mult<base::next_index0, Max0, base::next_index1, Max1, Assign>  next_t;

	template <typename Matrix, typename VectorIn, typename VectorOut>
	static inline void apply(const Matrix& A, const VectorIn& v, VectorOut& w)
	{
	    Assign::update(w[base::index0], A[base::index0][base::index1] * v[base::index1]);
	    next_t::apply(A, v, w);
	}
    };

    // need specialization here for not going back to column 0 but column 1
    template <std::size_t Index0, std::size_t Max0, std::size_t Max1, typename Assign>
    struct fully_unroll_mat_cvec_mult<Index0, Max0, Max1, Max1, Assign>
      : public meta_math::loop2<Index0, Max0, Max1, Max1>
    {
	typedef meta_math::loop2<Index0, Max0, Max1, Max1>                              base;
	typedef fully_unroll_mat_cvec_mult<base::next_index0, Max0, 2, Max1, Assign>  next_t;

	template <typename Matrix, typename VectorIn, typename VectorOut>
	static inline void apply(const Matrix& A, const VectorIn& v, VectorOut& w)
	{
	    Assign::update(w[base::index0], A[base::index0][base::index1] * v[base::index1]);
	    next_t::apply(A, v, w);
	}
    };

    template <std::size_t Max0, std::size_t Max1, typename Assign>
    struct fully_unroll_mat_cvec_mult<Max0, Max0, Max1, Max1, Assign>
      : public meta_math::loop2<Max0, Max0, Max1, Max1>
    {
	typedef meta_math::loop2<Max0, Max0, Max1, Max1>                              base;

	template <typename Matrix, typename VectorIn, typename VectorOut>
	static inline void apply(const Matrix& A, const VectorIn& v, VectorOut& w)
	{
	    Assign::update(w[base::index0], A[base::index0][base::index1] * v[base::index1]);
	}
    };

    struct noop
    {
	template <typename Matrix, typename VectorIn, typename VectorOut>
	static inline void apply(const Matrix&, const VectorIn&, VectorOut&) {}
    };
} // impl

// Dense matrix vector multiplication with run-time matrix size
template <typename Matrix, typename VectorIn, typename VectorOut, typename Assign>
inline void dense_mat_cvec_mult(const Matrix& A, const VectorIn& v, VectorOut& w, Assign, boost::mpl::true_)
{
    vampir_trace<3017> tracer;
    typedef typename static_num_rows<Matrix>::type size_type;
    static const size_type rows_a= static_num_rows<Matrix>::value, cols_a= static_num_cols<Matrix>::value;

    assert(rows_a > 0 && cols_a > 0);
    // w= A[all][0] * v[0];  N.B.: 1D is unrolled by the compiler faster (at least on gcc)
    for (size_type i= 0; i < rows_a; i++)
	Assign::first_update(w[i], A[i][0] * v[0]);

    // corresponds to w+= A[all][1:] * v[1:]; if necessary
    typedef impl::fully_unroll_mat_cvec_mult<1, rows_a, 2, cols_a, Assign>  f2;
    typedef typename boost::mpl::if_c<(cols_a > 1), f2, impl::noop>::type   f3;
    f3::apply(A, v, w);
}

template <unsigned Index, unsigned Size>
struct init_ptrs
{
    template <typename Matrix, typename Ptrs>
    inline static void apply(const Matrix& A, Ptrs& ptrs)
    {
	ptrs.value= &A[Index][0];
	init_ptrs<Index+1, Size>::apply(A, ptrs.sub);
    }
};

template <unsigned Size>
struct init_ptrs<Size, Size>
{
    template <typename Matrix, typename Ptrs>
    inline static void apply(const Matrix&, Ptrs&) {}
};

template <unsigned Index, unsigned Size>
struct square_cvec_mult_rows
{
    template <typename Tmps, typename Ptrs, typename ValueIn>
    inline static void compute(Tmps& tmps, Ptrs& ptrs, ValueIn vi)
    {
	tmps.value+= *ptrs.value++ * vi;
	square_cvec_mult_rows<Index+1, Size>::compute(tmps.sub, ptrs.sub, vi);
    }

    template <typename As, typename VectorOut, typename Tmps>
    inline static void update(As, VectorOut& w, const Tmps& tmps)
    {
	As::first_update(w[Index], tmps.value);
	square_cvec_mult_rows<Index+1, Size>::update(As(), w, tmps.sub);
    }
};

template <unsigned Size>
struct square_cvec_mult_rows<Size, Size>
{
    template <typename Tmps, typename Ptrs, typename ValueIn>
    inline static void compute(Tmps&, Ptrs&, ValueIn) {}

    template <typename As, typename VectorOut, typename Tmps>
    inline static void update(As, VectorOut&, const Tmps&) {}
};

template <unsigned Index, unsigned Size>
struct square_cvec_mult_cols
{
    template <typename Tmps, typename Ptrs, typename VPtr>
    inline static void compute(Tmps& tmps, Ptrs& ptrs, VPtr vp)
    {
	square_cvec_mult_rows<0, Size>::compute(tmps, ptrs, *vp);
	square_cvec_mult_cols<Index+1, Size>::compute(tmps, ptrs, ++vp);
    }
};

template <unsigned Size>
struct square_cvec_mult_cols<Size, Size>
{
    template <typename Tmps, typename Ptrs, typename VPtr>
    inline static void compute(Tmps&, Ptrs&, VPtr) {}
};

// Dense matrix vector multiplication with run-time matrix size
template <unsigned Size, typename MValue, typename MPara, typename ValueIn, typename ParaIn,
	  typename VectorOut, typename Assign>
inline void square_cvec_mult(const dense2D<MValue, MPara>& A, const mtl::vector::dense_vector<ValueIn, ParaIn>& v, VectorOut& w, Assign)
{
    // vampir_trace<3067> tracer;
    MTL_STATIC_ASSERT((mtl::traits::is_row_major<MPara>::value), "Only row-major matrices supported in this function.");

    typedef typename Collection<VectorOut>::value_type value_type;
    multi_tmp<Size, value_type> tmps(math::zero(w[0]));

    multi_tmp<Size, const MValue*>    ptrs;
    init_ptrs<0, Size>::apply(A, ptrs);

    const ValueIn* vp= &v[0];

    square_cvec_mult_cols<0, Size>::compute(tmps, ptrs, vp); // outer loop over columns
    square_cvec_mult_rows<0, Size>::update(Assign(), w, tmps);         // update rows
}

// Dense matrix vector multiplication with run-time matrix size
template <typename MValue, typename MPara, typename ValueIn, typename ParaIn, typename VectorOut, typename Assign>
typename boost::enable_if<mtl::traits::is_row_major<MPara> >::type
inline dense_mat_cvec_mult(const dense2D<MValue, MPara>& A, const mtl::vector::dense_vector<ValueIn, ParaIn>& v, VectorOut& w, Assign, boost::mpl::false_)
{
    // vampir_trace<3066> tracer;

    using math::zero;
    if (mtl::vector::size(w) == 0) return;

    typedef typename Collection<VectorOut>::value_type value_type;
    // typedef ValueIn                                    value_in_type;
    typedef typename MPara::size_type                  size_type;

    const size_type  nr= num_rows(A), nc= num_cols(A);
    if (nr == nc && nr <= 8)
	switch (nr) {
	  case 1: Assign::first_update(w[0], A[0][0] * v[0]); return;
	  case 2: square_cvec_mult<2>(A, v, w, Assign()); return;
	  case 3: square_cvec_mult<3>(A, v, w, Assign()); return;
	  case 4: square_cvec_mult<4>(A, v, w, Assign()); return;
	  case 5: square_cvec_mult<5>(A, v, w, Assign()); return;
	  case 6: square_cvec_mult<6>(A, v, w, Assign()); return;
	  case 7: square_cvec_mult<7>(A, v, w, Assign()); return;
	  case 8: square_cvec_mult<8>(A, v, w, Assign()); return;
	}

    const size_type  nrb= nr / 4 * 4;
    const value_type z(math::zero(w[0]));

    for (size_type i= 0; i < nrb; i+= 4) {
	value_type      tmp0(z), tmp1(z), tmp2(z), tmp3(z);
	const MValue *p0= &A[i][0], *pe= p0 + nc, *p1= &A[i+1][0], *p2= &A[i+2][0], *p3= &A[i+3][0];
	const ValueIn* vp= &v[0];
	for (; p0 != pe; ) {
	    const ValueIn vj= *vp++;
	    tmp0+= *p0++ * vj;
	    tmp1+= *p1++ * vj;
	    tmp2+= *p2++ * vj;
	    tmp3+= *p3++ * vj;
	}
	Assign::first_update(w[i], tmp0);
	Assign::first_update(w[i+1], tmp1);
	Assign::first_update(w[i+2], tmp2);
	Assign::first_update(w[i+3], tmp3);
    }

    for (size_type i= nrb; i < nr; i++) {
	value_type tmp= z;
	const ValueIn* vp= &v[0];
	for (const MValue *p0= &A[i][0], *pe= p0 + nc; p0 != pe; )
	    tmp+= *p0++ * *vp++;
	Assign::first_update(w[i], tmp);
    }
}

// Dense matrix vector multiplication with run-time matrix size
template <typename Matrix, typename VectorIn, typename VectorOut, typename Assign>
inline void dense_mat_cvec_mult(const Matrix& A, const VectorIn& v, VectorOut& w, Assign, boost::mpl::false_)
{
    vampir_trace<3018> tracer;
    // Naive implementation, will be moved to a functor and complemented with more efficient ones

    using math::zero;
    if (mtl::vector::size(w) == 0) return;
    // std::cout << "Bin in richtiger Funktion\n";

    // if (Assign::init_to_zero) set_to_zero(w); // replace update with first_update insteda

    typedef typename Collection<VectorOut>::value_type value_type;
    typedef typename Collection<VectorIn>::value_type  value_in_type;
    typedef typename Collection<Matrix>::size_type     size_type;

    const value_type z(math::zero(w[0]));
    const size_type nr= num_rows(A), nrb= nr / 4 * 4, nc= num_cols(A);

    for (size_type i= 0; i < nrb; i+= 4) {
	value_type      tmp0(z), tmp1(z), tmp2(z), tmp3(z);
	for (size_type j= 0; j < nc; j++) {
	    const value_in_type vj= v[j];
	    tmp0+= A[i][j] * vj;
	    tmp1+= A[i+1][j] * vj;
	    tmp2+= A[i+2][j] * vj;
	    tmp3+= A[i+3][j] * vj;
	}
	Assign::first_update(w[i], tmp0);
	Assign::first_update(w[i+1], tmp1);
	Assign::first_update(w[i+2], tmp2);
	Assign::first_update(w[i+3], tmp3);
    }

    for (size_type i= nrb; i < nr; i++) {
	value_type tmp= z;
	for (size_type j= 0; j < nc; j++)
	    tmp+= A[i][j] * v[j];
	Assign::first_update(w[i], tmp);
    }
}

// Dense matrix vector multiplication
template <typename Matrix, typename VectorIn, typename VectorOut, typename Assign>
inline void mat_cvec_mult(const Matrix& A, const VectorIn& v, VectorOut& w, Assign, tag::flat<tag::dense>)
{
# ifdef MTL_NOT_UNROLL_FSIZE_MAT_VEC_MULT
    boost::mpl::false_        selector;
# else
	mtl::traits::is_static<Matrix> selector;
# endif
    dense_mat_cvec_mult(A, v, w, Assign(), selector);
}

// Element structure vector multiplication
template <typename Matrix, typename VectorIn, typename VectorOut, typename Assign>
inline void mat_cvec_mult(const Matrix& A, const VectorIn& v, VectorOut& w, Assign, tag::flat<tag::element_structure>)
{
    vampir_trace<3048> tracer;
    if (mtl::vector::size(w) == 0) return;

    typedef typename Collection<VectorOut>::value_type value_type;
    typedef typename Collection<VectorIn>::value_type  value_in_type;

    value_in_type varray[1024];
    value_type    warray[1024];

    if (Assign::init_to_zero) set_to_zero(w);
    for(int elmi= 0; elmi < A.m_total_elements; elmi++){
	const typename Matrix::element_type& elementi= A.m_elements[elmi];
	const typename Matrix::element_type::index_type& indices= elementi.get_indices();
	unsigned int n(size(indices));

	if (n <= 1024) {
	    VectorIn vtmp(n, varray);
	    for (unsigned int i= 0; i < n; i++)
		vtmp[i]= v[indices[i]];
	    VectorOut wtmp(n, warray);
	    wtmp= elementi.get_values() * vtmp;
	    for (unsigned int i= 0; i < n; i++)
		Assign::update(w[indices[i]], wtmp[i]);
	} else {
	    VectorIn vtmp(n);
	    for (unsigned int i= 0; i < n; i++)
		vtmp[i]= v[indices[i]];
	    VectorOut wtmp(elementi.get_values() * vtmp);
	    for (unsigned int i= 0; i < n; i++)
		Assign::update(w[indices[i]], wtmp[i]);
	}
    }
}

// Multi-vector vector multiplication (tag::multi_vector is derived from dense)
template <typename Matrix, typename VectorIn, typename VectorOut, typename Assign>
inline void mat_cvec_mult(const Matrix& A, const VectorIn& v, VectorOut& w, Assign, tag::flat<tag::multi_vector>)
{
    vampir_trace<3019> tracer;
    if (Assign::init_to_zero) set_to_zero(w);
    for (unsigned i= 0; i < num_cols(A); i++)
	Assign::update(w, A.vector(i) * v[i]);
}

// Transposed multi-vector vector multiplication (tag::transposed_multi_vector is derived from dense)
template <typename TransposedMatrix, typename VectorIn, typename VectorOut, typename Assign>
inline void mat_cvec_mult(const TransposedMatrix& A, const VectorIn& v, VectorOut& w, Assign, tag::flat<tag::transposed_multi_vector>)
{
    vampir_trace<3020> tracer;
    typename TransposedMatrix::const_ref_type B= A.ref; // Referred matrix

    if (Assign::init_to_zero) set_to_zero(w);
    for (unsigned i= 0; i < num_cols(B); i++)
	Assign::update(w[i], dot_real(B.vector(i), v));
}

// Hermitian multi-vector vector multiplication (tag::hermitian_multi_vector is derived from dense)
template <typename HermitianMatrix, typename VectorIn, typename VectorOut, typename Assign>
inline void mat_cvec_mult(const HermitianMatrix& A, const VectorIn& v, VectorOut& w, Assign, tag::flat<tag::hermitian_multi_vector>)
{
    vampir_trace<3021> tracer;
    typename HermitianMatrix::const_ref_type B= A.const_ref(); // Referred matrix

    if (Assign::init_to_zero) set_to_zero(w);
    for (unsigned i= 0; i < num_cols(B); i++)
	Assign::update(w[i], dot(B.vector(i), v));
}

// Sparse row-major matrix vector multiplication
template <typename Matrix, typename VectorIn, typename VectorOut, typename Assign>
inline void smat_cvec_mult(const Matrix& A, const VectorIn& v, VectorOut& w, Assign, tag::row_major)
{
    vampir_trace<3022> tracer;
    using namespace tag;
    using mtl::traits::range_generator;
    using math::zero;
    using mtl::vector::set_to_zero;

    typedef typename range_generator<row, Matrix>::type       a_cur_type;
    typedef typename range_generator<nz, a_cur_type>::type    a_icur_type;
    typename mtl::traits::col<Matrix>::type                   col_a(A);
    typename mtl::traits::const_value<Matrix>::type           value_a(A);

    if (Assign::init_to_zero) set_to_zero(w);

    typedef typename Collection<VectorOut>::value_type        value_type;
    a_cur_type ac= begin<row>(A), aend= end<row>(A);
    for (unsigned i= 0; ac != aend; ++ac, ++i) {
	value_type tmp= zero(w[i]);
	for (a_icur_type aic= begin<nz>(ac), aiend= end<nz>(ac); aic != aiend; ++aic)
	    tmp+= value_a(*aic) * v[col_a(*aic)];
	Assign::update(w[i], tmp);
    }
}

// Row-major compressed2D with very few entries (i.e. Very Sparse MATrix) times vector
template <typename MValue, typename MPara, typename VectorIn, typename VectorOut, typename Assign>
inline void vsmat_cvec_mult(const compressed2D<MValue, MPara>& A, const VectorIn& v, VectorOut& w, Assign, tag::row_major)
{
    vampir_trace<3064> tracer;
    using math::zero;

    typedef compressed2D<MValue, MPara>                       Matrix;
    typedef typename Collection<Matrix>::size_type            size_type;
    typedef typename Collection<VectorOut>::value_type        value_type;

    if (size(w) == 0) return;
    const value_type z(math::zero(w[0]));

    // std::cout << "very sparse: nnz = " << A.nnz() << ", num_rows = " << num_rows(A) << '\n';

    size_type nr= num_rows(A);
    for (size_type i1= 0, i2= std::min<size_type>(1024, nr); i1 < i2; i1= i2, i2= std::min<size_type>(i2 + 1024, nr)) {
	// std::cout << "range = " << i1 << " .. " << i2 << "\n";
	if (A.ref_major()[i1] < A.ref_major()[i2])
	    for (size_type i= i1; i < i2; ++i) {
		const size_type cj0= A.ref_major()[i], cj1= A.ref_major()[i+1];
		value_type      tmp0(z);
		for (size_type j0= cj0; j0 != cj1; ++j0)
		    tmp0+= A.data[j0] * v[A.ref_minor()[j0]];
		Assign::first_update(w[i], tmp0);
	    }
    }
}

#ifdef MTL_CRS_CVEC_MULT_TUNING
template <unsigned Index, unsigned BSize, typename SizeType>
struct crs_cvec_mult_block
{
    template <typename Matrix, typename VectorIn, typename CBlock, typename TBlock>
    void operator()(const Matrix& A, const VectorIn& v, const CBlock& cj, TBlock& tmp) const
    {
	for (SizeType j= cj.value; j != cj.sub.value; ++j) // cj is one index larger
	    tmp.value+= A.data[j] * v[A.ref_minor()[j]];
	sub(A, v, cj.sub, tmp.sub);
    }

    template <typename VectorOut, typename TBlock, typename Assign>
    void first_update(VectorOut& w, SizeType i, const TBlock& tmp, Assign as) const
    {
	Assign::first_update(w[i + Index], tmp.value);
	sub.first_update(w, i, tmp.sub, as);
    }

    crs_cvec_mult_block<Index+1, BSize, SizeType> sub;
};

template <unsigned BSize, typename SizeType>
struct crs_cvec_mult_block<BSize, BSize, SizeType>
{
    template <typename Matrix, typename VectorIn, typename CBlock, typename TBlock>
    void operator()(const Matrix& A, const VectorIn& v, const CBlock& cj, TBlock& tmp) const
    {
	for (SizeType j= cj.value; j != cj.sub.value; ++j)// cj is one index larger
	    tmp.value+= A.data[j] * v[A.ref_minor()[j]];
    }

    template <typename VectorOut, typename TBlock, typename Assign>
    void first_update(VectorOut& w, SizeType i, const TBlock& tmp, Assign) const
    {
	Assign::first_update(w[i + BSize], tmp.value);
    }
};

// Row-major compressed2D vector multiplication
template <unsigned BSize, typename MValue, typename MPara, typename VectorIn, typename VectorOut, typename Assign>
inline void smat_cvec_mult(const compressed2D<MValue, MPara>& A, const VectorIn& v, VectorOut& w, Assign as, tag::row_major)
{
    vampir_trace<3049> tracer;
    using math::zero;

    if (A.nnz() < num_rows(A)) {
	vsmat_cvec_mult(A, v, w, as, tag::row_major());
	return;
    }

    typedef compressed2D<MValue, MPara>                       Matrix;
    typedef typename Collection<VectorOut>::value_type        value_type;
    typedef typename mtl::traits::omp_size_type<typename Collection<Matrix>::size_type>::type size_type;

    if (size(w) == 0) return;
    const value_type z(math::zero(w[0]));

    size_type nr= num_rows(A), nrb= nr / BSize * BSize;

    #ifdef MTL_WITH_OPENMP
    #   pragma omp parallel
    #endif
    {
    	#ifdef MTL_WITH_OPENMP
	    vampir_trace<8004> tracer;
    	#   pragma omp for
    	#endif
	for (size_type i= 0; i < nrb; i+= BSize) {
	    multi_constant_from_array<0, BSize+1, size_type> cj(A.ref_major(), i);
	    multi_tmp<BSize, value_type>                     tmp(z);
	    crs_cvec_mult_block<0, BSize-1, size_type>       block;

	    block(A, v, cj, tmp);
	    block.first_update(w, i, tmp, as);
	}
    }

    for (size_type i= nrb; i < nr; ++i) {
	const size_type cj0= A.ref_major()[i], cj1= A.ref_major()[i+1];
	value_type      tmp0(z);
	for (size_type j0= cj0; j0 != cj1; ++j0)
	    tmp0+= A.data[j0] * v[A.ref_minor()[j0]];
	Assign::first_update(w[i], tmp0);
    }
}

template <typename MValue, typename MPara, typename VectorIn, typename VectorOut, typename Assign>
typename mtl::traits::enable_if_scalar<typename Collection<VectorOut>::value_type>::type
inline smat_cvec_mult(const compressed2D<MValue, MPara>& A, const VectorIn& v, VectorOut& w, Assign, tag::row_major)
{
    smat_cvec_mult<crs_cvec_mult_block_size>(A, v, w, Assign(), tag::row_major());
}
#endif

#if !defined(MTL_CRS_CVEC_MULT_NO_ACCEL) && !defined(MTL_CRS_CVEC_MULT_TUNING)

template <typename MValue, typename MPara, typename VectorIn, typename VectorOut, typename Assign>
typename mtl::traits::enable_if_scalar<typename Collection<VectorOut>::value_type>::type
inline adapt_crs_cvec_mult(const compressed2D<MValue, MPara>& A, const VectorIn& v, VectorOut& w, Assign)
{
    vampir_trace<3065> tracer;
    using math::zero;
    assert(!Assign::init_to_zero);

    typedef compressed2D<MValue, MPara>                       Matrix;
    typedef typename Collection<Matrix>::size_type            size_type;
    typedef typename Collection<VectorOut>::value_type        value_type;

    const value_type z(math::zero(w[0]));
    size_type nr= num_rows(A), nrb= nr / 4 * 4, nrb2= nr / 64 * 64;

    for (size_type i1= 0; i1 < nrb2; i1+= 64)
	if (A.ref_major()[i1] != A.ref_major()[i1 + 64])
	    for (size_type i2= i1, i2e= i1+64; i2 < i2e; i2+= 16)
		if (A.ref_major()[i2] != A.ref_major()[i2 + 16])
		    for (size_type i= i2, i3e= i2+16; i < i3e; i+= 4)
			if (A.ref_major()[i] != A.ref_major()[i + 4]) {
			    const size_type cj0= A.ref_major()[i], cj1= A.ref_major()[i+1], cj2= A.ref_major()[i+2],
				cj3= A.ref_major()[i+3], cj4= A.ref_major()[i+4];
			    value_type      tmp0(z), tmp1(z), tmp2(z), tmp3(z);
			    for (size_type j0= cj0; j0 != cj1; ++j0)
				tmp0+= A.data[j0] * v[A.ref_minor()[j0]];
			    for (size_type j1= cj1; j1 != cj2; ++j1)
				tmp1+= A.data[j1] * v[A.ref_minor()[j1]];
			    for (size_type j2= cj2; j2 != cj3; ++j2)
				tmp2+= A.data[j2] * v[A.ref_minor()[j2]];
			    for (size_type j3= cj3; j3 != cj4; ++j3)
				tmp3+= A.data[j3] * v[A.ref_minor()[j3]];

			    Assign::first_update(w[i], tmp0);
			    Assign::first_update(w[i+1], tmp1);
			    Assign::first_update(w[i+2], tmp2);
			    Assign::first_update(w[i+3], tmp3);
			}

    for (size_type i= nrb2; i < nrb; i+= 4)
	if (A.ref_major()[i] != A.ref_major()[i + 4])  {
	    const size_type cj0= A.ref_major()[i], cj1= A.ref_major()[i+1], cj2= A.ref_major()[i+2],
		cj3= A.ref_major()[i+3], cj4= A.ref_major()[i+4];
	    value_type      tmp0(z), tmp1(z), tmp2(z), tmp3(z);
	    for (size_type j0= cj0; j0 != cj1; ++j0)
		tmp0+= A.data[j0] * v[A.ref_minor()[j0]];
	    for (size_type j1= cj1; j1 != cj2; ++j1)
		tmp1+= A.data[j1] * v[A.ref_minor()[j1]];
	    for (size_type j2= cj2; j2 != cj3; ++j2)
		tmp2+= A.data[j2] * v[A.ref_minor()[j2]];
	    for (size_type j3= cj3; j3 != cj4; ++j3)
		tmp3+= A.data[j3] * v[A.ref_minor()[j3]];

	    Assign::first_update(w[i], tmp0);
	    Assign::first_update(w[i+1], tmp1);
	    Assign::first_update(w[i+2], tmp2);
	    Assign::first_update(w[i+3], tmp3);
	}

    for (size_type i= nrb; i < nr; ++i) {
	const size_type cj0= A.ref_major()[i], cj1= A.ref_major()[i+1];
	value_type      tmp0(z);
	for (size_type j0= cj0; j0 != cj1; ++j0)
	    tmp0+= A.data[j0] * v[A.ref_minor()[j0]];
	Assign::first_update(w[i], tmp0);
    }
}

// Row-major compressed2D vector multiplication
template <typename MValue, typename MPara, typename VectorIn, typename VectorOut, typename Assign>
typename mtl::traits::enable_if_scalar<typename Collection<VectorOut>::value_type>::type
inline smat_cvec_mult(const compressed2D<MValue, MPara>& A, const VectorIn& v, VectorOut& w, Assign as, tag::row_major)
{
    vampir_trace<3049> tracer;
    // vampir_trace<5056> tttracer;
    using math::zero;

    if (A.nnz() < num_rows(A) && !as.init_to_zero) {
	vsmat_cvec_mult(A, v, w, as, tag::row_major());
	return;
    }

    typedef compressed2D<MValue, MPara>                       Matrix;
    typedef typename Collection<VectorOut>::value_type        value_type;
    typedef typename mtl::traits::omp_size_type<typename Collection<Matrix>::size_type>::type size_type;

    if (size(w) == 0) return;
    const value_type z(math::zero(w[0]));

    size_type nr= num_rows(A), nrb= nr / 4 * 4;
    if (nr > 10) {
	size_type nh= nr / 2, nq= nr / 4, nt= nr - nq;
	if (!as.init_to_zero &&
	    (A.ref_major()[1] == A.ref_major()[0]
	     || A.ref_major()[nq] == A.ref_major()[nq+1]
	     || A.ref_major()[nh] == A.ref_major()[nh+1]
	     || A.ref_major()[nt] == A.ref_major()[nt+1]
	     || A.ref_major()[nr-1] == A.ref_major()[nr])) {
	    adapt_crs_cvec_mult(A, v, w, as);
	    return;
	}
    }

    #ifdef MTL_WITH_OPENMP
    #   pragma omp parallel
    #endif
    {
    	#ifdef MTL_WITH_OPENMP
	    vampir_trace<8004> tracer;
    	#   pragma omp for
    	#endif
	    for (size_type i= 0; i < nrb; i+= 4) {
		const size_type cj0= A.ref_major()[i], cj1= A.ref_major()[i+1], cj2= A.ref_major()[i+2],
		    cj3= A.ref_major()[i+3], cj4= A.ref_major()[i+4];
		value_type      tmp0(z), tmp1(z), tmp2(z), tmp3(z);
		for (size_type j0= cj0; j0 != cj1; ++j0)
		    tmp0+= A.data[j0] * v[A.ref_minor()[j0]];
		for (size_type j1= cj1; j1 != cj2; ++j1)
		    tmp1+= A.data[j1] * v[A.ref_minor()[j1]];
		for (size_type j2= cj2; j2 != cj3; ++j2)
		    tmp2+= A.data[j2] * v[A.ref_minor()[j2]];
		for (size_type j3= cj3; j3 != cj4; ++j3)
		    tmp3+= A.data[j3] * v[A.ref_minor()[j3]];

		Assign::first_update(w[i], tmp0);
		Assign::first_update(w[i+1], tmp1);
		Assign::first_update(w[i+2], tmp2);
		Assign::first_update(w[i+3], tmp3);
	    }
    }

    for (size_type i= nrb; i < nr; ++i) {
	const size_type cj0= A.ref_major()[i], cj1= A.ref_major()[i+1];
	value_type      tmp0(z);
	for (size_type j0= cj0; j0 != cj1; ++j0)
	    tmp0+= A.data[j0] * v[A.ref_minor()[j0]];
	Assign::first_update(w[i], tmp0);
    }
}
#endif

// Row-major ell_matrix vector multiplication
template <typename MValue, typename MPara, typename VectorIn, typename VectorOut, typename Assign>
typename mtl::traits::enable_if_scalar<typename Collection<VectorOut>::value_type>::type
inline smat_cvec_mult(const ell_matrix<MValue, MPara>& A, const VectorIn& v, VectorOut& w, Assign, tag::row_major)
{
    typedef typename MPara::size_type size_type;

    const size_type stride= A.stride(), slots= A.slots();
    for (size_type r= 0; r < A.dim1(); ++r) {
	MValue s(0);
	for (size_type k= r, i= 0; i < slots; ++i, k+= stride)
	    s+= A.ref_data()[k] * v[A.ref_minor()[k]];
	Assign::first_update(w[r], s);
    }
 }

// Row-major sparse_banded vector multiplication
template <typename MValue, typename MPara, typename VectorIn, typename VectorOut, typename Assign>
typename mtl::traits::enable_if_scalar<typename Collection<VectorOut>::value_type>::type
inline smat_cvec_mult(const sparse_banded<MValue, MPara>& A, const VectorIn& v, VectorOut& w, Assign, tag::row_major)
{
    vampir_trace<3069> tracer;
    typedef sparse_banded<MValue, MPara>                      Matrix;
    typedef typename Collection<VectorOut>::value_type        value_type;
    typedef typename Matrix::band_size_type                   band_size_type;
    typedef typename MPara::size_type                         size_type;
    typedef mtl::vector::dense_vector<band_size_type, vector::parameters<> > vector_type;

    if (size(w) == 0) return;
    const value_type z(math::zero(w[0]));

    size_type nr= num_rows(A), nc= num_cols(A), nb= A.ref_bands().size();
    if (nb == size_type(0) && Assign::init_to_zero) {
	set_to_zero(w);
	return;
    }

    vector_type bands(A.ref_bands()), begin_rows(max(0, -bands)), end_rows(min(nr, nc - bands));
    assert(end_rows[nb-1] > 0);

    // std::cout << "bands = " << bands << ", begin_rows = " << begin_rows << ", end_rows = " << end_rows << "\n";
    size_type begin_pos= 0, end_pos= nb - 1;

    // find lowest diagonal in row 0
    while (begin_pos < nb && begin_rows[begin_pos] > 0) begin_pos++;
    // if at the end, the first rows are empty
    if (begin_pos == nb && Assign::init_to_zero) {
	w[irange(begin_rows[--begin_pos])]= z;
	// std::cout << "w[0.." << begin_rows[begin_pos] << "] <- 0\n";
    }

    band_size_type from= begin_rows[begin_pos];
    // find first entry with same value
    while (begin_pos > 0 && begin_rows[begin_pos - 1] == from) {
	assert(from == 0); // should only happen when multiple bands start in row 0
	begin_pos--;
    }
    for (bool active= true; active; ) {
	// search backwards for the next-largest entry
	band_size_type to= begin_pos > 0 && begin_rows[begin_pos - 1] <= end_rows[end_pos] ? begin_rows[begin_pos - 1] : end_rows[end_pos];

	// std::cout << "rows " << from << ".." << to << ": with bands ";
	// for (size_type i= begin_pos; i <= end_pos; i++)
	//     std::cout << bands[i] << (i < end_pos ? ", " : "\n");

	const MValue* Aps= A.ref_data() + (from * nb + begin_pos);

	const band_size_type blocked_to= ((to - from) & -4) + from;
	assert((blocked_to - from) % 4 == 0 && blocked_to >= band_size_type(from) && blocked_to <= band_size_type(to));
	for (band_size_type r= from; r < blocked_to; r+= 4) {
	    value_type     tmp0(z), tmp1(z), tmp2(z), tmp3(z);
	    const MValue   *Ap0= Aps, *Ap1= Aps + nb, *Ap2= Ap1 + nb, *Ap3= Ap2 + nb;
	    for (size_type b= begin_pos; b <= end_pos; ++b, ++Ap0, ++Ap1, ++Ap2, ++Ap3) {
		tmp0+= *Ap0 * v[r + bands[b]];
		tmp1+= *Ap1 * v[r + bands[b] + 1];
		tmp2+= *Ap2 * v[r + bands[b] + 2];
		tmp3+= *Ap3 * v[r + bands[b] + 3];
	    }
	    Assign::first_update(w[r], tmp0);
	    Assign::first_update(w[r+1], tmp1);
	    Assign::first_update(w[r+2], tmp2);
	    Assign::first_update(w[r+3], tmp3);
	    Aps+= 4 * nb;
	}

	for (band_size_type r= blocked_to; r < band_size_type(to); r++) {
	    value_type     tmp(z);
	    const MValue*  Ap= Aps;
	    for (size_type b= begin_pos; b <= end_pos; ++b, ++Ap)
		tmp+= *Ap * v[r + bands[b]];
	    Assign::first_update(w[r], tmp);
	    Aps+= nb;
	}

	if (begin_pos > 0) {
	    if (begin_rows[begin_pos-1] == to)
		begin_pos--;
	    if (end_rows[end_pos] == to)
		end_pos--;
	} else { // begin == 0 -> decrement end_pos or finish
	    if (end_rows[0] == to) {
		active= false;
		assert(end_rows[0] = end_rows[end_pos]);
	    } else {
		assert(end_pos > 0);
		end_pos--;
	    }
	}
	assert(begin_pos <= end_pos);
	from= to;
    }

    if (size_type(end_rows[0]) < nr  && Assign::init_to_zero) {
	w[irange(end_rows[0], nr)]= z;
	// std::cout << "w[" << end_rows[0] << ".." << nr << "] <- 0\n";
    }
}

// Sparse column-major matrix vector multiplication
template <typename Matrix, typename VectorIn, typename VectorOut, typename Assign>
inline void smat_cvec_mult(const Matrix& A, const VectorIn& v, VectorOut& w, Assign, tag::col_major)
{
    vampir_trace<3023> tracer;
    using namespace tag; namespace traits = mtl::traits;
    using traits::range_generator;
    using mtl::vector::set_to_zero;
    typedef typename range_generator<col, Matrix>::type       a_cur_type;
    typedef typename range_generator<nz, a_cur_type>::type    a_icur_type;

    typename traits::row<Matrix>::type                        row_a(A);
    typename traits::const_value<Matrix>::type                value_a(A);

    if (Assign::init_to_zero) set_to_zero(w);

    unsigned rv= 0; // traverse all rows of v
    for (a_cur_type ac= begin<col>(A), aend= end<col>(A); ac != aend; ++ac, ++rv) {
	typename Collection<VectorIn>::value_type    vv= v[rv];
	for (a_icur_type aic= begin<nz>(ac), aiend= end<nz>(ac); aic != aiend; ++aic)
	    Assign::update(w[row_a(*aic)], value_a(*aic) * vv);
    }
}

// Sparse matrix vector multiplication
template <typename Matrix, typename VectorIn, typename VectorOut, typename Assign>
inline void mat_cvec_mult(const Matrix& A, const VectorIn& v, VectorOut& w, Assign, tag::flat<tag::sparse>)
{
    smat_cvec_mult(A, v, w, Assign(), typename OrientedCollection<Matrix>::orientation());
}

}} // namespace mtl::matrix

#endif // MTL_MAT_VEC_MULT_INCLUDE


/**
 * @file dcmt64.cpp
 *
 * @brief The main function of parameter generator of 64 bit Mersenne Twister.
 *
 * The functions in this file are simple. They parse the command line
 * options and call all_in_one function which does almost all things.
 * Users can change this file so that it fits to their applications
 * and OS.
 *
 * @author Mutsuo Saito
 * @author Makoto Matsumoto (Hiroshima University)
 * @author Takuji Nishimura (Yamagata University)
 *
 * Copyright (C) 2019 Mutsuo Saito, Makoto Matsumoto,
 * Takuji Nishimura and Hiroshima University.
 * All rights reserved.
 *
 * The MIT License is applied to this software, see
 * LICENSE
 */
#include <stdint.h>
#include <inttypes.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
#include <string>
#include <sstream>
#include <fstream>
#include <MTToolBox/AlgorithmRecursionSearch.hpp>
#include <MTToolBox/AlgorithmEquidistribution.hpp>
#include <MTToolBox/MersenneTwister.hpp>
#include <MTToolBox/MersenneTwister.hpp>
#include <NTL/GF2X.h>
#include <getopt.h>
#include "mt64Search.hpp"
#include "search.h"
#include "options.h"

using namespace std;
using namespace MTToolBox;
using namespace NTL;

/**
 * parse command line option, and search parameters
 * @param argc number of arguments
 * @param argv value of arguments
 * @return 0 if this ends normally
 */
int main(int argc, char** argv) {
    options opt;
    bool parse = parse_opt(opt, argc, argv);
    if (!parse) {
        return -1;
    }
    ofstream ofs;
    ofstream log;
    ostream *os;
    ostream *ls;
    if (!opt.outfilename.empty()) {
        ofs.open(opt.outfilename.c_str());
        if (!ofs) {
            cerr << "can't open file:" << opt.outfilename << endl;
            return -1;
        }
        os = &ofs;
    } else {
        os = &cout;
    }
    if (!opt.logfilename.empty()) {
        log.open(opt.logfilename.c_str());
        if (!log) {
            cerr << "can't open file:" << opt.logfilename << endl;
            return -1;
        }
        ls = &log;
    } else {
        ls = os;
    }
    return search(opt, *os, *ls, opt.count);
    //return best_search(opt, *os, *ls, opt.count);
}


#ifndef _CPP_UTILS_BOOSTQUEUE_HEADER__
#define _CPP_UTILS_BOOSTQUEUE_HEADER__

#include <boost/heap/d_ary_heap.hpp>
#include <unordered_map>

#include "exceptions.hpp"
#include "IQueue.hpp"

namespace cpp_utils {

    template <typename ITEM, typename COMPARATOR>
    class BoostQueue;

    namespace internal {

        template <typename ITEM, typename COMPARATOR>
        class HeapNode;

    }

    template <typename ITEM>
    struct HeapNodeDataCompare : public std::binary_function<const ITEM&, const ITEM&, bool> {
        bool operator() (const ITEM& a, const ITEM& b) {
            return (a < b);
        }
    };

    namespace internal {

        template <typename ITEM>
        using BinaryHeap = typename boost::heap::d_ary_heap<
            ITEM,
            boost::heap::arity<2>,
            boost::heap::mutable_<true>
            //implicitly "<" of HeapNode
        >;

        template <typename ITEM>
        using binary_heap_handle_t = typename BinaryHeap<ITEM>::handle_type;

        /**
         * @brief a memory chunk in the queue
         *
         * @tparam ITEM_PTR type of item inside. It's always a pointer
         */
        template <typename ITEM, typename COMPARATOR >
        class HeapNode {
            typedef HeapNode<ITEM, COMPARATOR> This;
        public:
            friend class ::cpp_utils::BoostQueue<ITEM, COMPARATOR>;
        private:
            binary_heap_handle_t<This> priority;
            ITEM* data;
        public:
            friend bool operator <(const This& a, const This& b) {
                //boost queue are max-queue, but I want min-queue
                return COMPARATOR()(*(b.data), *(a.data));
            }
            friend bool operator >(const This& a, const This& b) {
                //boost queue are max-queue, but I want min-queue
                return COMPARATOR()(*(a.data), *(b.data));
            }
        public:
            HeapNode(ITEM* data): data{data}, priority{} {

            }
            virtual ~HeapNode() {
                //we don't own the data, so no deallocating
            }
            HeapNode(const This& o): data{o.data}, priority{o.priority} {

            }
            HeapNode(This&& o): data{o.data}, priority{::std::move(o.priority)} {
                o.data = nullptr;
            }
            This& operator=(const This& o) {
                this->data = o.data;
                this->priority = o.priority;

                return *this;
            }
            This& operator=(This&& o) {
                this->data = o.data;
                this->priority = ::std::move(o.priority);

                o.data = nullptr;
                return *this;
            }

        };

    }

    /**
     * @brief A queue wrapping boost::d_ary_heap
     *
     * The boost api is really flexible, but sometimes we want a simpler interface.
     * By default the comparator used is the binary function "<" on ITEM.
     * However this can be set by changing the second template parameter of BoostQueue. Example of custom
     * comparator:
     *
     * @code
     *  struct NewOrder {
     *      bool operator() (const FooBoost& a, const FooBoost& b) {
     *          //reverse the order of them  heap
     *          return (b < a);
     *      }
     *  };
     *  //usage
     *  BoostQueue<FooBoost, NewOrder> q{};
     * @endcode
     *
     * Note that this queue saves the **pointers** of the data, so to let every work you can't `move` or `clone`
     * the objects in the queue.
     *
     * No precondition needs to be satisfied for ITEM: this implementation does not need HasPriority
     * implemented at all
     *
     * @tparam ITEM the item to store. Note that we will always stopre the popinter of ITEM, not ITEMs themselves
     */
    template <typename ITEM, typename COMPARATOR = HeapNodeDataCompare<ITEM> >
    class BoostQueue: public IQueue<ITEM> {
    public:
        using This = BoostQueue<ITEM, COMPARATOR>;
        using Super = IQueue<ITEM>;
        using heap_node_type = typename internal::HeapNode<ITEM, COMPARATOR>;
        using handle_type = typename internal::BinaryHeap<heap_node_type>::handle_type;
    private:
        std::unordered_map<ITEM*, handle_type> itemToHandleHeap;
        internal::BinaryHeap<heap_node_type> queue;
    public:
        explicit BoostQueue(): queue{}, itemToHandleHeap{} {

        }
        virtual ~BoostQueue() {

        }
        BoostQueue(const This& o): queue{o.queue}, itemToHandleHeap{o.itemToHandleHeap} {

        }
        BoostQueue(This&& o): queue{o.queue}, itemToHandleHeap{::std::move(o.itemToHandleHeap)} {
            o.queue.clear();
        }
        This& operator=(const This& o) {
            this->queue = o.queue;
            this->itemToHandleHeap = o.itemToHandleHeap;
            return *this;
        }
        This& operator=(This&& o) {
            this->queue = ::std::move(o.queue);
            this->itemToHandleHeap = ::std::move(o.itemToHandleHeap);
            return *this;
        }
    public:
        virtual bool isEmpty() const {
            return this->queue.size() == 0;
        }
        virtual void decrease_key(ITEM& val) {
            auto handle = this->itemToHandleHeap[&val];
            this->queue.decrease(handle);
        }

        virtual void increase_key(ITEM& val) {
            auto handle = this->itemToHandleHeap[&val];
            this->queue.increase(handle);
        }

        virtual void push(ITEM& val) {
            auto ha = queue.push(heap_node_type{&val});
            (*ha).priority = ha;
            this->itemToHandleHeap.insert(std::make_pair<>(&val, ha));
        }

        virtual ITEM& pop() {
            ITEM& result = this->peek();
            this->queue.pop();
            this->itemToHandleHeap.erase(&result);
            return result;
        }

        virtual bool contains(const ITEM& n) const {
            return this->itemToHandleHeap.find(const_cast<ITEM*>(&n)) != this->itemToHandleHeap.end();
        }

        virtual ITEM& peek() const {
            heap_node_type x = queue.top();
            return *(x.data);
        }

        virtual size_t size() const {
            return this->queue.size();
        }
    public:
        virtual void cleanup() {
            this->queue.clear();
        }
    public:
        virtual MemoryConsumption getByteMemoryOccupied() const {
            throw cpp_utils::exceptions::NotYetImplementedException{__func__};
        }
    };

}

#endif


#define BOOST_TEST_MODULE TEST_GAMELIB

#include <bq.h>
#include <boost/test/included/unit_test.hpp>

BOOST_AUTO_TEST_CASE(getRandom) {
	constexpr float MIN_RANDOM = 50.0f;
	constexpr float MAX_RANDOM = 80.0f;
	constexpr unsigned NUM_CYCLES = 20000;
	for (unsigned i = 0; i < NUM_CYCLES; ++i) {
		float random = bq::random::getRandom(MIN_RANDOM, MAX_RANDOM);
		BOOST_CHECK(random >= MIN_RANDOM);
		BOOST_CHECK(random <= MAX_RANDOM);
	}
}


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

#include <iostream>
#include <limits>
#include <functional>
#include <array>

#include <boost/core/demangle.hpp>
#include <boost/safe_numerics/checked_result.hpp>
#include <boost/safe_numerics/checked_result_operations.hpp>
#include <boost/safe_numerics/interval.hpp>

template<typename T>
using fptr = T (*)(const T &, const T &);
template<typename T>
using fptr_interval = fptr<boost::safe_numerics::interval<T>>;

template<typename T>
struct op {
    const fptr<T> m_f;
    const fptr_interval<T> m_finterval;
    const char * m_symbol;
    const bool skip_zeros;
};

template<
    typename T,
    unsigned int N
>
bool test_type_operator(
    const T (&value)[N],
    const op<T> & opi
){
    using namespace boost::safe_numerics;

    // for each pair of values p1, p2 (100)
    for(const T & l1 : value)
    for(const T & u1 : value){
        if(l1 > u1) continue; // skip reverse range
        const interval<T> p1(l1, u1);
        for(const T & l2 : value)
        for(const T & u2 : value){
            if(l2 > u2) continue; // skip reverse range
            const interval<T> p2(l2, u2);

            // maybe skip intervals which include zero
            if(opi.skip_zeros){
                if(l2 == safe_numerics_error::range_error
                || l2 == safe_numerics_error::domain_error
                || u2 == safe_numerics_error::range_error
                || u2 == safe_numerics_error::domain_error
                || p2.includes(T(0))
                )
                    continue;
            }

            // create a new interval from the operation
            const interval<T> result_interval = opi.m_finterval(p1, p2);
            std::cout
                << p1 << opi.m_symbol << p2 << " -> " << result_interval << std::endl;

            // if resulting interval is null
            if(result_interval.u < result_interval.l)
                continue;

            // for each pair test values
            for(const T r1 : value)
            for(const T r2 : value){
                // calculate result of operation
                const T result = opi.m_f(r1, r2);
                if(result != safe_numerics_error::range_error
                && result != safe_numerics_error::domain_error ){
                    // note usage of tribool logic here !!!
                    // includes returns indeterminate the conditional
                    // returns false in both cases and this is what we want.
                    // This is very subtle, don't skim over this.
                    // if both r1 and r2 are within they're respective bounds
                    if(p1.includes(r1) && p2.includes(r2)
                    && ! result_interval.includes(result)){
                        return false;
                    }
                }
            }
        }
    }
    return true;
}

// values
// note: need to explicitly specify number of elements to avoid msvc failure
template<typename T>
const boost::safe_numerics::checked_result<T> value[8] = {
    boost::safe_numerics::safe_numerics_error::negative_overflow_error,
    std::numeric_limits<T>::lowest(),
    T(-1),
    T(0),
    T(1),
    std::numeric_limits<T>::max(),
    boost::safe_numerics::safe_numerics_error::positive_overflow_error,
    boost::safe_numerics::safe_numerics_error::domain_error
};

// note: need to explicitly specify number of elements to avoid msvc failure
template<typename T>
const boost::safe_numerics::checked_result<T> unsigned_value[6] = {
    boost::safe_numerics::safe_numerics_error::negative_overflow_error,
    T(0),
    T(1),
    std::numeric_limits<T>::max(),
    boost::safe_numerics::safe_numerics_error::positive_overflow_error,
    boost::safe_numerics::safe_numerics_error::domain_error
};

// invoke for each type
struct test_type {
    unsigned int m_error_count;
    test_type() :
        m_error_count(0)
    {}
    template<typename T>
    bool operator()(const T &){
        using namespace boost::safe_numerics;
        std::cout
            << "** testing "
            << boost::core::demangle(typeid(T).name())
            << std::endl;

        using R = checked_result<T>;
        // pointers to operands for types T
        static const std::array<op<R>, 5> op_table{{
            {operator+, operator+, "+", false},
            {operator-, operator-, "-", false},
            {operator*, operator*, "*", false},
            {operator<<, operator<<, "<<", false},
            {operator>>, operator>>, ">>", false},
        }};

        //for(unsigned int i = 0; i < sizeof(op_table)/sizeof(op) / sizeof(fptr<R>); ++i){
        for(const op<R> & o : op_table){
            if(std::is_signed<T>::value){
                if(! test_type_operator(value<T>, o)){
                    ++m_error_count;
                    return false;
                }
            }
            else{
                if(! test_type_operator(unsigned_value<T>, o)){
                    ++m_error_count;
                    return false;
                }
            }
        }
        return true;
    }
};

#include <boost/mp11/list.hpp>
#include <boost/mp11/algorithm.hpp>

int main(int, char *[]){
    using namespace boost::mp11;
    // list of signed types
    using signed_types = mp_list<std::int8_t, std::int16_t, std::int32_t, std::int64_t>;
    // list of unsigned types
    using unsigned_types = mp_list<std::uint8_t, std::uint16_t, std::uint32_t, std::uint64_t>;

    test_type t;
    mp_for_each<unsigned_types>(t);
    mp_for_each<signed_types>(t);

    std::cout << (t.m_error_count == 0 ? "success!" : "failure") << std::endl;
    return t.m_error_count ;
}


#include <gtest/gtest.h>
#include "test_complex_helper.hpp"
#include "pitts_fixed_tensor3_apply.hpp"
#include <Eigen/Dense>
#include <complex>

template<typename T>
class PITTS_FixedTensor3_apply : public ::testing::Test
{
  public:
    using Type = T;
};

using TestTypes = ::testing::Types<double, std::complex<double>>;
TYPED_TEST_CASE(PITTS_FixedTensor3_apply, TestTypes);

TYPED_TEST(PITTS_FixedTensor3_apply, r2_equals_one)
{
  using Type = TestFixture::Type;
  using FixedTensor3 = PITTS::FixedTensor3<Type,3>;
  using arr3 = std::array<Type,3>;
  constexpr auto eps = 1.e-10;

  FixedTensor3 t3(5,1);
  for(int i = 0; i < 5; i++)
    for(int j = 0; j < 3; j++)
      t3(i,j,0) = 100 + i*10 + j;

  const std::array<std::array<Type,3>,3> M = {arr3{1.,2.,3.},arr3{4.,5.,6.},arr3{7.,8.,9.}};

  using Matrix = Eigen::Matrix<Type,Eigen::Dynamic,Eigen::Dynamic>;
  const auto mapM = Eigen::Map<const Matrix>(&M[0][0], 3, 3);
  auto mapT3 = Eigen::Map<Matrix>(&t3(0,0,0), 5, 3);

  Matrix refResult = mapT3 * mapM.transpose();

  PITTS::apply(t3, M);

  for(int i = 0; i < 5; i++)
    for(int j = 0; j < 3; j++)
    {
      EXPECT_NEAR(refResult(i,j), t3(i,j,0), eps);
    }
}

TYPED_TEST(PITTS_FixedTensor3_apply, r1_equals_one)
{
  using Type = TestFixture::Type;
  using FixedTensor3 = PITTS::FixedTensor3<Type,3>;
  using arr3 = std::array<Type,3>;
  constexpr auto eps = 1.e-10;

  FixedTensor3 t3(1,4);
  for(int i = 0; i < 3; i++)
    for(int j = 0; j < 4; j++)
      t3(0,i,j) = 100 + i*10 + j;

  const std::array<std::array<Type,3>,3> M = {arr3{1.,2.,3.},arr3{4.,5.,6.},arr3{7.,8.,9.}};

  using Matrix = Eigen::Matrix<Type,Eigen::Dynamic,Eigen::Dynamic>;
  const auto mapM = Eigen::Map<const Matrix>(&M[0][0], 3, 3);
  auto mapT3 = Eigen::Map<Matrix>(&t3(0,0,0), 3, 4);

  Matrix refResult = mapM * mapT3;

  PITTS::apply(t3, M);

  for(int i = 0; i < 3; i++)
    for(int j = 0; j < 4; j++)
    {
      EXPECT_NEAR(refResult(i,j), t3(0,i,j), eps);
    }
}

TYPED_TEST(PITTS_FixedTensor3_apply, generic)
{
  using Type = TestFixture::Type;
  using FixedTensor3 = PITTS::FixedTensor3<Type,3>;
  using arr3 = std::array<Type,3>;
  constexpr auto eps = 1.e-10;

  FixedTensor3 t3(5,2);
  for(int i = 0; i < 5; i++)
    for(int j = 0; j < 3; j++)
      for(int k = 0; k < 2; k++)
      t3(i,j,k) = 100 + i*10 + j*2 + k;

  const std::array<std::array<Type,3>,3> M = {arr3{1.,2.,3.},arr3{4.,5.,6.},arr3{7.,8.,9.}};

  using Matrix = Eigen::Matrix<Type,Eigen::Dynamic,Eigen::Dynamic>;
  const auto mapM = Eigen::Map<const Matrix>(&M[0][0], 3, 3);
  auto mapT3_1 = Eigen::Map<Matrix>(&t3(0,0,0), 5, 3);
  auto mapT3_2 = Eigen::Map<Matrix>(&t3(0,0,1), 5, 3);

  Matrix refResult_1 = mapT3_1 * mapM.transpose();
  Matrix refResult_2 = mapT3_2 * mapM.transpose();

  PITTS::apply(t3, M);

  for(int i = 0; i < 5; i++)
    for(int j = 0; j < 3; j++)
    {
      EXPECT_NEAR(refResult_1(i,j), t3(i,j,0), eps);
      EXPECT_NEAR(refResult_2(i,j), t3(i,j,1), eps);
    }
}


// Copyright 2018 The Simons Foundation, Inc. - All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef NETKET_METROPOLISFLIPT_HPP
#define NETKET_METROPOLISFLIPT_HPP

#include <mpi.h>
#include <Eigen/Dense>
#include <iostream>
#include "Utils/parallel_utils.hpp"
#include "Utils/random_utils.hpp"
#include "abstract_sampler.hpp"

namespace netket {

// Metropolis sampling generating local changes
// Parallel tempering is also used
class MetropolisLocalPt : public AbstractSampler {
  AbstractMachine& psi_;

  const AbstractHilbert& hilbert_;

  // number of visible units
  const int nv_;

  // states of visible units
  // for each sampled temperature
  std::vector<Eigen::VectorXd> v_;

  Eigen::VectorXd accept_;
  Eigen::VectorXd moves_;

  int mynode_;
  int totalnodes_;

  // clusters to do updates
  std::vector<std::vector<int>> clusters_;

  // Look-up tables
  std::vector<typename AbstractMachine::LookupType> lt_;

  int nrep_;

  std::vector<double> beta_;

  int nstates_;
  std::vector<double> localstates_;

 public:
  // Constructor with one replica by default
  explicit MetropolisLocalPt(AbstractMachine& psi, int nreplicas = 1)
      : psi_(psi),
        hilbert_(psi.GetHilbert()),
        nv_(hilbert_.Size()),
        nrep_(nreplicas) {
    Init();
  }

  void Init() {
    MPI_Comm_size(MPI_COMM_WORLD, &totalnodes_);
    MPI_Comm_rank(MPI_COMM_WORLD, &mynode_);

    nstates_ = hilbert_.LocalSize();
    localstates_ = hilbert_.LocalStates();

    SetNreplicas(nrep_);

    InfoMessage() << "Metropolis sampler with parallel tempering is ready "
                  << std::endl;
    InfoMessage() << "Nreplicas is equal to " << nrep_ << std::endl;
  }

  void SetNreplicas(int nrep) {
    nrep_ = nrep;
    v_.resize(nrep_);
    for (int i = 0; i < nrep_; i++) {
      v_[i].resize(nv_);
    }

    for (int i = 0; i < nrep_; i++) {
      beta_.push_back(1. - double(i) / double(nrep_));
    }

    lt_.resize(nrep_);

    accept_.resize(2 * nrep_);
    moves_.resize(2 * nrep_);

    Reset(true);
  }

  void Reset(bool initrandom = false) override {
    if (initrandom) {
      for (int i = 0; i < nrep_; i++) {
        hilbert_.RandomVals(v_[i], this->GetRandomEngine());
      }
    }

    for (int i = 0; i < nrep_; i++) {
      psi_.InitLookup(v_[i], lt_[i]);
    }

    accept_ = Eigen::VectorXd::Zero(2 * nrep_);
    moves_ = Eigen::VectorXd::Zero(2 * nrep_);
  }

  // Exchange sweep at given temperature
  void LocalSweep(int rep) {
    std::vector<int> tochange(1);
    std::vector<double> newconf(1);

    std::uniform_real_distribution<double> distu;
    std::uniform_int_distribution<int> distrs(0, nv_ - 1);
    std::uniform_int_distribution<int> diststate(0, nstates_ - 1);

    for (int i = 0; i < nv_; i++) {
      // picking a random site to be changed
      int si = distrs(this->GetRandomEngine());
      assert(si < nv_);
      tochange[0] = si;

      // picking a random state
      int newstate = diststate(this->GetRandomEngine());
      newconf[0] = localstates_[newstate];

      // make sure that the new state is not equal to the current one
      while (std::abs(newconf[0] - v_[rep](si)) <
             std::numeric_limits<double>::epsilon()) {
        newstate = diststate(this->GetRandomEngine());
        newconf[0] = localstates_[newstate];
      }

      const auto lvd = psi_.LogValDiff(v_[rep], tochange, newconf, lt_[rep]);
      double ratio = std::norm(std::exp(beta_[rep] * lvd));

#ifndef NDEBUG
      const auto psival1 = psi_.LogVal(v_[rep]);
      if (std::abs(
              std::exp(psi_.LogVal(v_[rep]) - psi_.LogVal(v_[rep], lt_[rep])) -
              1.) > 1.0e-8) {
        std::cerr << psi_.LogVal(v_[rep]) << "  and LogVal with Lt is "
                  << psi_.LogVal(v_[rep], lt_[rep]) << std::endl;
        std::abort();
      }
#endif
      // Metropolis acceptance test
      if (ratio > distu(this->GetRandomEngine())) {
        accept_(rep) += 1;

        psi_.UpdateLookup(v_[rep], tochange, newconf, lt_[rep]);
        hilbert_.UpdateConf(v_[rep], tochange, newconf);

#ifndef NDEBUG
        const auto psival2 = psi_.LogVal(v_[rep]);
        if (std::abs(std::exp(psival2 - psival1 - lvd) - 1.) > 1.0e-8) {
          std::cerr << psival2 - psival1 << " and logvaldiff is " << lvd
                    << std::endl;
          std::cerr << psival2 << " and LogVal with Lt is "
                    << psi_.LogVal(v_[rep], lt_[rep]) << std::endl;
          std::abort();
        }
#endif
      }
      moves_(rep) += 1;
    }
  }

  void Sweep() override {
    // First we do local sweeps
    for (int i = 0; i < nrep_; i++) {
      LocalSweep(i);
    }

    // Tempearture exchanges
    std::uniform_real_distribution<double> distribution(0, 1);

    for (int r = 1; r < nrep_; r += 2) {
      if (ExchangeProb(r, r - 1) > distribution(this->GetRandomEngine())) {
        Exchange(r, r - 1);
        accept_(nrep_ + r) += 1.;
        accept_(nrep_ + r - 1) += 1;
      }
      moves_(nrep_ + r) += 1.;
      moves_(nrep_ + r - 1) += 1;
    }

    for (int r = 2; r < nrep_; r += 2) {
      if (ExchangeProb(r, r - 1) > distribution(this->GetRandomEngine())) {
        Exchange(r, r - 1);
        accept_(nrep_ + r) += 1.;
        accept_(nrep_ + r - 1) += 1;
      }
      moves_(nrep_ + r) += 1.;
      moves_(nrep_ + r - 1) += 1;
    }
  }

  // computes the probability to exchange two replicas
  double ExchangeProb(int r1, int r2) {
    const double lf1 = 2 * std::real(psi_.LogVal(v_[r1], lt_[r1]));
    const double lf2 = 2 * std::real(psi_.LogVal(v_[r2], lt_[r2]));

    return std::exp((beta_[r1] - beta_[r2]) * (lf2 - lf1));
  }

  void Exchange(int r1, int r2) {
    std::swap(v_[r1], v_[r2]);
    std::swap(lt_[r1], lt_[r2]);
  }

  Eigen::VectorXd Visible() override { return v_[0]; }

  void SetVisible(const Eigen::VectorXd& v) override { v_[0] = v; }

  AbstractMachine& GetMachine() noexcept override { return psi_; }

  const AbstractHilbert& GetHilbert() const noexcept override {
    return hilbert_;
  }

  Eigen::VectorXd Acceptance() const override {
    Eigen::VectorXd acc = accept_;
    for (int i = 0; i < acc.size(); i++) {
      acc(i) /= moves_(i);
    }
    return acc;
  }
};

}  // namespace netket

#endif


///////////////////////////////////////////////////////////////////////////////
// depends_on.hpp                                                            //
//                                                                           //
//  Copyright 2010 Erwann Rogard. Distributed under the Boost                //
//  Software License, Version 1.0. (See accompanying file                    //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)         //
///////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_DETAIL_NON_PARAMETRIC_CONTINGENCY_TABLE_PEARSON_CHISQ_INDEPENDENCE_DEPENDS_ON_HPP_ER_2010
#define BOOST_STATISTICS_DETAIL_NON_PARAMETRIC_CONTINGENCY_TABLE_PEARSON_CHISQ_INDEPENDENCE_DEPENDS_ON_HPP_ER_2010
#include <boost/mpl/push_back.hpp>
#include <boost/accumulators/statistics/detail/weighted_count.hpp>
#include <boost/accumulators/framework/detail/unpack_depends_on.hpp>
#include <boost/statistics/detail/non_parametric/contingency_table/cells/marginal_cells.hpp>

namespace boost {
namespace statistics{
namespace detail{
namespace contingency_table{
namespace independence_between_aux{

    template<typename Keys>
    struct depends_on{
        typedef typename
            contingency_table::marginal_cells<Keys>::type marginal_;
        typedef contingency_table::tag::cells<Keys> joint_;
        typedef boost::accumulators::tag::weighted_count weighted_count_;
        typedef typename boost::mpl::push_back<
            typename boost::mpl::push_back<
                marginal_,
                joint_
            >::type,
            weighted_count_
        >::type dependencies_;

        typedef typename boost::accumulators::detail::unpack_depends_on<
            dependencies_
        >::type type;
    };

}// independence_between_aux
}// contingency_table
}// detail
}// statistics
}// boost

#endif


#ifndef PYTHONIC_TYPES_LONG_HPP
#define PYTHONIC_TYPES_LONG_HPP

#include "pythonic/include/types/long.hpp"

#ifndef USE_GMP
typedef long long pythran_long_t;
#define pythran_long(a) pythran_long_t(a)
#else
#include <gmpxx.h>
typedef mpz_class pythran_long_t;
#define pythran_long(a) pythran_long_t(#a)

namespace pythonic
{

  /* some math overloads { */

  namespace operator_
  {
    template <class T, class U, class T1>
    auto mod(__gmp_expr<T, U> const &t0, T1 const &t1) -> decltype(t0 % t1)
    {
      return t0 % t1;
    }

    template <class T, class U>
    auto floordiv(__gmp_expr<T, U> const &a, long b) -> decltype(a / b)
    {
      return a / b;
    }

    template <class T, class U, class Tp, class Up>
    auto floordiv(__gmp_expr<T, U> const &a, __gmp_expr<Tp, Up> const &b)
        -> decltype(a / b)
    {
      return a / b;
    }
  }
  /* } */
}
/* compute hash of a gmp { */
namespace std
{
  template <class T, class U>
  size_t hash<__gmp_expr<T, U>>::operator()(__gmp_expr<T, U> const &x) const
  {
    return hash<std::string>()(x.get_str());
  }
}

#include <boost/functional/hash_fwd.hpp>
template <class T, class U>
std::size_t hash_value(__gmp_expr<T, U> const &x)
{
  return std::hash<__gmp_expr<T, U>>()(x);
}

/* } */
#ifdef ENABLE_PYTHON_MODULE
#include "pythonic/python/register_once.hpp"

namespace pythonic
{

  python_to_pythran<mpz_class>::python_to_pythran()
  {
    static bool registered = false;
    if (not registered) {
      registered = true;
      boost::python::converter::registry::push_back(
          &convertible, &construct, boost::python::type_id<mpz_class>());
    }
  }

  void *python_to_pythran<mpz_class>::convertible(PyObject *obj_ptr)
  {
    if (!PyLong_Check(obj_ptr))
      return 0;
    return obj_ptr;
  }

  void python_to_pythran<mpz_class>::construct(
      PyObject *obj_ptr,
      boost::python::converter::rvalue_from_python_stage1_data *data)
  {
    void *storage = ((boost::python::converter::rvalue_from_python_storage<
                         mpz_class> *)(data))->storage.bytes;
    auto s = PyObject_Str(obj_ptr);
    new (storage) mpz_class(PyString_AsString(s));
    Py_DECREF(s);
    data->convertible = storage;
  }

  PyObject *custom_mpz_to_long::convert(const mpz_class &v)
  {
    auto s = PyString_FromString(v.get_str().c_str());
    auto l = PyNumber_Long(s);
    Py_DECREF(s);
    return l;
  }

  pythran_to_python<mpz_class>::pythran_to_python()
  {
    register_once<mpz_class, custom_mpz_to_long>();
  }
}

#endif

#endif

#endif