#pragma once

#include <deal.II/base/exceptions.h>
#include <deal.II/base/index_set.h>
#include <deal.II/base/utilities.h>
#include <deal.II/dofs/dof_handler.h>
#include <deal.II/lac/trilinos_vector.h>

#include "collision_tensor.hpp"
#include "collision_tensor_galerkin.hpp"
#include "dense/collision_tensor_zlastAM.hpp"
#include "dense/collision_tensor_zlastAM_eigen.hpp"
#include "spectral/basis/spectral_basis_factory_ks.hpp"

namespace boltzmann {
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
class CollisionTensorOperatorBase
{
 protected:
  typedef typename SpectralBasisFactoryKS::basis_type spectral_basis_t;
  typedef dealii::TrilinosWrappers::MPI::Vector trilinos_vector_t;

 protected:
  CollisionTensorOperatorBase(MPI_Comm& communicator)
      : comm(communicator)
  { /* empty */
  }

 public:
  /// explicit Euler step
  virtual void apply(trilinos_vector_t& out, double dt) const = 0;
  /// load tensor from HDF5-file
  virtual void load_tensor(std::string fname) = 0;
  void set_truncation_threshold(double tre);

 protected:
  MPI_Comm comm;
  double truncate_treshold_ = 0;
  bool use_treshold_ = false;

};

// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
class CollisionTensorOperatorPG : public CollisionTensorOperatorBase
{
 public:
  CollisionTensorOperatorPG(const dealii::DoFHandler<2>& dh,
                            const spectral_basis_t& basis,
                            const dealii::IndexSet& parallel_partitioning,
                            MPI_Comm communicator = MPI_COMM_WORLD)
      : CollisionTensorOperatorBase(communicator)
      , n_phys_dofs(dh.n_dofs())
      , n_velo_dofs(basis.n_dofs())
      , Q(basis.n_dofs())
      , vtmp(parallel_partitioning, communicator)
      , local_buffer(parallel_partitioning.n_elements())
  { /* empty */ }

  virtual void apply(trilinos_vector_t& out, double dt) const;
  virtual void load_tensor(std::string fname);

 private:
  const unsigned int n_phys_dofs;
  const unsigned int n_velo_dofs;
  CollisionTensor Q;
  mutable trilinos_vector_t vtmp;
  mutable std::vector<double> local_buffer;
};

// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
class CollisionTensorOperatorG : public CollisionTensorOperatorBase
{
 public:
  CollisionTensorOperatorG(const dealii::DoFHandler<2>& dh,
                           const spectral_basis_t& basis,
                           const dealii::IndexSet& parallel_partitioning,
                           MPI_Comm communicator = MPI_COMM_WORLD)
      : CollisionTensorOperatorBase(communicator)
      , n_phys_dofs(dh.n_dofs())
      , n_velo_dofs(basis.n_dofs())
      , n_local_phys_dofs_(parallel_partitioning.n_elements() / basis.n_dofs())
      , Q(basis)
      , vtmp(parallel_partitioning, communicator)
  {
    lambda.resize(4, n_local_phys_dofs_);
    lambda_prev.resize(4, n_local_phys_dofs_);
  }

  virtual void apply(trilinos_vector_t& out, double dt) const;
  virtual void load_tensor(std::string fname);

 private:
  const unsigned int n_phys_dofs;
  const unsigned int n_velo_dofs;
  const unsigned int n_local_phys_dofs_;
  CollisionTensorGalerkin Q;
  mutable trilinos_vector_t vtmp;
  /// conservation of momentum
  typedef Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor> carray_t;
  mutable carray_t lambda;
  mutable carray_t lambda_prev;

 public:
  EIGEN_MAKE_ALIGNED_OPERATOR_NEW

};

#ifdef USE_MPI
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
template <typename CT_DENSE = ct_dense::CollisionTensorZLastAM>
class CollisionTensorOperatorDense : public CollisionTensorOperatorBase
{
 public:
  CollisionTensorOperatorDense(const dealii::DoFHandler<2>& dh,
                               const spectral_basis_t& basis,
                               const dealii::IndexSet& parallel_partitioning,
                               int vblksize = 1,
                               MPI_Comm communicator = MPI_COMM_WORLD)
      : CollisionTensorOperatorBase(communicator)
      , basis_(basis)
      , n_phys_dofs_(dh.n_dofs())
      , n_velo_dofs_(basis.n_dofs())
      , vblksize_(vblksize)
      , n_local_phys_dofs_(parallel_partitioning.n_elements() / basis.n_dofs())
      , Q_(basis, parallel_partitioning.n_elements() / basis.n_dofs())
      , vtmp(parallel_partitioning, communicator)
  {
    /* empty */
  }

 private:
  const spectral_basis_t& basis_;
  const unsigned int n_phys_dofs_;
  const unsigned int n_velo_dofs_;
  const int vblksize_;
  const int n_local_phys_dofs_;
  CT_DENSE Q_;

 private:
  mutable trilinos_vector_t vtmp;
  typedef Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::ColMajor> in_array_t;
  /// padded input array
  mutable in_array_t padded_in;
  /// conservation of momentum
  mutable in_array_t lambda;
  mutable in_array_t lambda_prev;

 public:
  virtual void apply(trilinos_vector_t& out, double dt) const override;
  virtual void load_tensor(std::string fname) override;

 public:
  EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};

template <typename CT_DENSE>
void
CollisionTensorOperatorDense<CT_DENSE>::apply(trilinos_vector_t& out, double dt) const
{
  BOOST_ASSERT(out.local_size()/n_velo_dofs_ == n_local_phys_dofs_);
  BOOST_ASSERT(vtmp.local_size()/n_velo_dofs_ == n_local_phys_dofs_);

  const double* data = out.begin();

  Eigen::Map<const in_array_t> vin(data, n_velo_dofs_, n_local_phys_dofs_);
  int imax = -1;
  if (use_treshold_) {
    for (int i = n_velo_dofs_ -1; i >= 0; --i) {
      if ((vin.row(i).abs() > truncate_treshold_).any()) {
        imax = i;
        break;
      }
    }
  }
  // pad & copy to padded array
  Q_.pad(padded_in, vin);
  Eigen::Map<in_array_t> vout(out.begin(), n_velo_dofs_, n_local_phys_dofs_);
  // get lambda (moment conservation)
  Q_.get_lambda(lambda_prev, vin);
  // apply collision tensor write directly to output array
  Eigen::Map<in_array_t> vtmp_eigen(vtmp.begin(), n_velo_dofs_, n_local_phys_dofs_);
  Q_.apply(vtmp_eigen, padded_in, use_treshold_ ? imax : -1);
  // explicit Euler timestep
  out.sadd(1.0, dt, vtmp);
  // get contribution to lambda from next timestep
  Q_.get_lambda(lambda, vout);
  lambda -= lambda_prev;
  // conserve moments
  Q_.project_lambda(vout, lambda);
}

template <typename CT_DENSE>
void
CollisionTensorOperatorDense<CT_DENSE>::load_tensor(std::string fname)
{
  Q_.import_entries_mpishmem(fname, vblksize_);

  // initialize buffer arrays used during ::apply
  int npadded = Q_.padded_vector_length();
  padded_in.resize(npadded, n_local_phys_dofs_);
  // this needs to be done only once, the parts used to
  // store the input vector are overwritten, and the elements in between
  // are multiplied by zero during Q_.apply, just make sure they are not NaN's by chance.
  padded_in.setZero();

  // temporary arrays for conservation of momentum
  lambda_prev.resize(4, n_local_phys_dofs_);
  lambda.resize(4, n_local_phys_dofs_);
}

#endif

}  // namespace boltzmann


#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<boost::int64_t, 6, 1> test_long_6_1(const Eigen::Matrix<boost::int64_t, 6, 1> & M)
{
	return M;
}
void export_long_6_1()
{
	boost::python::def("test_long_6_1",test_long_6_1);
}


#include "assign_bilinear_bases_dist.hxx"
#include "../../SDP.hxx"
#include "../../read_vector.hxx"

#include <El.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>

void read_bilinear_bases(
  const boost::filesystem::path &sdp_directory, const Block_Info &block_info,
  const El::Grid &grid,
  std::vector<El::Matrix<El::BigFloat>> &bilinear_bases_local,
  std::vector<El::DistMatrix<El::BigFloat>> &bilinear_bases_dist)
{
  auto &block_indices(block_info.block_indices);
  bilinear_bases_local.resize(2 * block_indices.size());

  for(size_t file_rank(0); file_rank < block_info.file_num_procs; ++file_rank)
    {
      const boost::filesystem::path bilinear_path(
        sdp_directory / ("bilinear_bases." + std::to_string(file_rank)));
      boost::filesystem::ifstream bilinear_stream(bilinear_path);
      if(!bilinear_stream.good())
        {
          throw std::runtime_error("Could not open '" + bilinear_path.string()
                                   + "'");
        }
      size_t file_num_bases;
      bilinear_stream >> file_num_bases;
      if(!bilinear_stream.good())
        {
          throw std::runtime_error("Corrupted or empty file: "
                                   + bilinear_path.string());
        }

      // The index of bilinear_bases in each file is described by
      // file_block_indices.  However, block_indices is not in
      // numerical order.  So we have to take care when placing blocks
      for(size_t block = 0; block < file_num_bases; ++block)
        for(size_t parity = 0; parity < 2; ++parity)
          {
            size_t height, width;
            bilinear_stream >> height >> width;
            if(!bilinear_stream.good())
              {
                throw std::runtime_error("Corrupted header in file: "
                                         + bilinear_path.string());
              }

            auto block_iter(std::find(
              block_indices.begin(), block_indices.end(),
              block_info.file_block_indices.at(file_rank).at(block)));

            if(block_iter != block_indices.end())
              {
                size_t block_index(
                  2 * std::distance(block_indices.begin(), block_iter)
                  + parity);
                auto &local(bilinear_bases_local.at(block_index));
                local.Resize(height, width);
                for(size_t row = 0; row < height; ++row)
                  for(size_t column = 0; column < width; ++column)
                    {
                      bilinear_stream >> local(row, column);
                    }
              }
            else
              {
                // Add one to get the initial newline after 'width'.
                for(size_t line = 0; line < height * width + 1; ++line)
                  {
                    bilinear_stream.ignore(
                      std::numeric_limits<std::streamsize>::max(), '\n');
                  }
              }
          }
      if(!bilinear_stream.good())
        {
          throw std::runtime_error("Corrupted data in file: "
                                   + bilinear_path.string());
        }
    }

  assign_bilinear_bases_dist(bilinear_bases_local, grid, bilinear_bases_dist);
}


/*
@copyright Louis Dionne 2015
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#include <boost/hana/ext/std/pair.hpp>

#include <boost/hana/assert.hpp>
#include <boost/hana/tuple.hpp>

#include <laws/base.hpp>
#include <laws/comparable.hpp>
#include <laws/foldable.hpp>
#include <laws/orderable.hpp>
#include <laws/product.hpp>

#include <utility>
using namespace boost::hana;

using test::ct_eq;
using test::ct_ord;

int main() {
    auto eq_elems = make<tuple_tag>(ct_eq<3>{}, ct_eq<4>{});

    auto eqs = make<tuple_tag>(
          std::make_pair(ct_eq<3>{}, ct_eq<3>{})
        , std::make_pair(ct_eq<3>{}, ct_eq<4>{})
        , std::make_pair(ct_eq<4>{}, ct_eq<3>{})
        , std::make_pair(ct_eq<4>{}, ct_eq<4>{})
    );

    auto ords = make<tuple_tag>(
          std::make_pair(ct_ord<3>{}, ct_ord<3>{})
        , std::make_pair(ct_ord<3>{}, ct_ord<4>{})
        , std::make_pair(ct_ord<4>{}, ct_ord<3>{})
        , std::make_pair(ct_ord<4>{}, ct_ord<4>{})
    );

    //////////////////////////////////////////////////////////////////////////
    // Comparable, Orderable, Foldable
    //////////////////////////////////////////////////////////////////////////
    test::TestComparable<ext::std::pair_tag>{eqs};
    test::TestOrderable<ext::std::pair_tag>{ords};
    test::TestFoldable<ext::std::pair_tag>{eqs};

    //////////////////////////////////////////////////////////////////////////
    // Product
    //////////////////////////////////////////////////////////////////////////
    {
        // first
        {
            BOOST_HANA_CONSTANT_CHECK(equal(
                first(std::make_pair(ct_eq<0>{}, ct_eq<1>{})),
                ct_eq<0>{}
            ));
        }

        // second
        {
            BOOST_HANA_CONSTANT_CHECK(equal(
                second(std::make_pair(ct_eq<0>{}, ct_eq<1>{})),
                ct_eq<1>{}
            ));
        }

        // make
        {
            BOOST_HANA_CONSTANT_CHECK(equal(
                make<ext::std::pair_tag>(ct_eq<0>{}, ct_eq<1>{}),
                std::make_pair(ct_eq<0>{}, ct_eq<1>{})
            ));
        }

        // laws
        test::TestProduct<ext::std::pair_tag>{eq_elems};
    }
}


#ifndef _INCLUDED_BOUNDS_HPP_
#define _INCLUDED_BOUNDS_HPP_

/**
 * @file   bounds.hpp
 * @author Aron Ahmadia <aron@valkyrie.appmath.columbia.edu>
 * @date   Mon Mar  3 14:44:08 2008
 *
 * @brief  Bounds - an object for handling boundaries of a partition
 *
 *
 */

#include <boost/numeric/ublas/vector.hpp>
#include <boost/version.hpp>
#include "hyper_sample/utility/geometry.hpp"

namespace smo {

  class Bounds
  {
  public:
    Bounds() {
      depth = -1;
    }

    Bounds(const Region &inBoundAngles) {
      boundAngles = inBoundAngles;
      depth = boundAngles.size1() - 1;
    }

    inline
    PolarAngle lower() const {
      return boundAngles(depth,0);
    }

    inline
    PolarAngle upper() const {
      return boundAngles(depth,1);
    }

    inline
    void sub() {
      depth--;
    }

    inline
    void super() {
      depth++;
    }

    inline
    void reset() {
      depth = boundAngles.size1() -1;
    }

  private:
    DimCount depth;
    Region boundAngles;
  };

}

#endif


//
// Created by Alex Beccaro on 05/12/2019.
//

#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>
#include "../../src/problems/101-150/135/problem135.hpp"

BOOST_AUTO_TEST_SUITE( Problem135 )

    BOOST_AUTO_TEST_CASE( Solution ) {
        auto res = problems::problem135::solve();
        BOOST_CHECK_EQUAL(res, 4989);
    }

BOOST_AUTO_TEST_SUITE_END()


/* Copyright (C) 2012-2019 IBM Corp.
 * This program is 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. See accompanying LICENSE file.
 */
#include <iostream>
#include <fstream>
#include <vector>
#include <cmath>
#include <algorithm>
#include <tuple>
#include <NTL/BasicThreadPool.h>

#include <helib.h>

#include <intraSlot.h>
#include <binaryArith.h>
#include <binaryCompare.h>

#include "gtest/gtest.h"
#include "test_common.h"

#ifdef DEBUG_PRINTOUT
#include <debugging.h>
#endif
// define flags FLAG_PRINT_ZZX, FLAG_PRINT_POLY, FLAG_PRINT_VEC, functions
//        decryptAndPrint(ostream, ctxt, sk, ea, flags)
//        decryptAndCompare(ctxt, sk, ea, pa);

namespace {

struct Parameters {
    Parameters(long prm, long bitSize, bool bootstrap, long seed, long nthreads) :
        prm(prm),
        bitSize(bitSize),
        bootstrap(bootstrap),
        seed(seed),
        nthreads(nthreads)
    {};

    long prm; // parameter size (0-tiny,...,4-huge)
    long bitSize; // bitSize of input integers (<=32)
    bool bootstrap; // test comparison with bootstrapping
    long seed; // PRG seed
    long nthreads; // number of threads

    // Tell google test how to print Parameters
    friend std::ostream& operator<<(std::ostream& os, const Parameters& params) {
        return os << "{" <<
           "prm=" << params.prm << "," <<
           "bitSize=" << params.bitSize << "," <<
           "bootstrap=" << params.bootstrap << "," <<
           "seed=" << params.seed << "," <<
           "nthreads=" << params.nthreads <<
           "}";
    };
};

class GTest_binaryCompare : public ::testing::TestWithParam<std::tuple<Parameters, int>> {
    protected:
        static std::vector<helib::zzX> unpackSlotEncoding;
        constexpr static long mValues[][15] = {
            // { p, phi(m),   m,   d, m1, m2, m3,    g1,   g2,   g3, ord1,ord2,ord3, B,c}
            {  2,    48,   105, 12,  3, 35,  0,    71,    76,    0,   2,  2,   0, 25, 2},
            {  2 ,  600,  1023, 10, 11, 93,  0,   838,   584,    0,  10,  6,   0, 25, 2},
            {  2,  2304,  4641, 24,  7,  3,221,  3979,  3095, 3760,   6,  2,  -8, 25, 3},
            {  2, 15004, 15709, 22, 23,683,  0,  4099, 13663,    0,  22, 31,   0, 25, 3},
            {  2, 27000, 32767, 15, 31,  7, 151, 11628, 28087,25824, 30,  6, -10, 28, 4}
        };

        static long correctBitSize(long minimum, long oldBitSize)
        {
            long newBitSize;
            if(oldBitSize <= 0) newBitSize = minimum;
            else if(oldBitSize > 32) newBitSize = 32;
            else newBitSize = oldBitSize;
            return newBitSize;
        };

        // Validates the prm value, throwing if invalid
        static long validatePrm(long prm)
        {
            if(prm < 0 || prm >= 5)
                throw std::invalid_argument("prm must be in the interval [0, 4]");
            return prm;
        };

        static NTL::Vec<long> calculateMvec(const long *vals)
        {
            NTL::Vec<long> mvec;
            append(mvec, vals[4]);
            if (vals[5]>1) append(mvec, vals[5]);
            if (vals[6]>1) append(mvec, vals[6]);
            return mvec;
        };

        static std::vector<long> calculateGens(const long *vals)
        {
            std::vector<long> gens;
            gens.push_back(vals[7]);
            if (vals[8]>1) gens.push_back(vals[8]);
            if (vals[9]>1) gens.push_back(vals[9]);
            return gens;
        };

        static std::vector<long> calculateOrds(const long *vals)
        {
            std::vector<long> ords;
            ords.push_back(vals[10]);
            if (abs(vals[11])>1) ords.push_back(vals[11]);
            if (abs(vals[12])>1) ords.push_back(vals[12]);
            return ords;
        };

        static long calculateLevels(bool bootstrap, long bitSize)
        {
            return bootstrap ? 900 : 30*(7 + NTL::NumBits(bitSize + 2)); // that should be enough
        };

        helib::FHEcontext& prepareContext(helib::FHEcontext &context)
        {
            if (helib_test::verbose) {
                std::cout <<"input bitSize="<<bitSize << std::endl;
                if (nthreads>1) std::cout << "  using "<<NTL::AvailableThreads()<<" threads\n";
                std::cout << "computing key-independent tables..." << std::flush;
            }
            buildModChain(context, L, c,/*willBeBootstrappable=*/bootstrap);
            if (bootstrap) {
                context.makeBootstrappable(mvec, /*t=*/0);
            }
            buildUnpackSlotEncoding(unpackSlotEncoding, *context.ea);

            if (helib_test::verbose) {
                std::cout << " done.\n";
                context.zMStar.printout();
            }

            return context;
        }

        void prepareSecKey(helib::FHESecKey& secKey)
        {
            if (helib_test::verbose) {
                std::cout << "\ncomputing key-dependent tables..." << std::flush;
            }
            secKey.GenSecKey();
            addSome1DMatrices(secKey); // compute key-switching matrices
            addFrbMatrices(secKey);
            if (bootstrap) secKey.genRecryptData();
            if (helib_test::verbose) std::cout << " done\n";
        };

        const long prm;
        const long bitSize;
        const bool bootstrap;
        const long seed;
        const long nthreads;
        const long *vals;
        const long p;
        const long m;
        const NTL::Vec<long> mvec;
        const std::vector<long> gens;
        const std::vector<long> ords;
        const long c;
        const long L;
        helib::FHEcontext context;
        helib::FHESecKey secKey;

        GTest_binaryCompare() :
            prm(validatePrm(std::get<0>(GetParam()).prm)),
            bitSize(correctBitSize(5, std::get<0>(GetParam()).bitSize)),
            bootstrap(std::get<0>(GetParam()).bootstrap),
            seed(std::get<0>(GetParam()).seed),
            nthreads(std::get<0>(GetParam()).nthreads),
            vals(mValues[prm]),
            p(vals[0]),
            m(vals[2]),
            mvec(calculateMvec(vals)),
            gens(calculateGens(vals)),
            ords(calculateOrds(vals)),
            c(vals[14]),
            L(calculateLevels(bootstrap, bitSize)),
            context(m, p, /*r=*/1, gens, ords),
            secKey(prepareContext(context))
    {};

        void SetUp() override
        {
            if (seed) NTL::SetSeed(NTL::ZZ(seed));
            if (nthreads>1) NTL::SetNumThreads(nthreads);

            prepareSecKey(secKey);

            helib::activeContext = &context; // make things a little easier sometimes
#ifdef DEBUG_PRINTOUT
            helib::dbgEa = (helib::EncryptedArray*) context.ea;
            helib::dbgKey = &secKey;
#endif
        };

        virtual void TearDown() override
        {
#ifdef DEBUG_PRINTOUT
            helib::cleanupGlobals();
#endif
        }

    public:
        static void TearDownTestCase ()
        {
            if (helib_test::verbose)
                helib::printAllTimers(std::cout);
        };
};

std::vector<helib::zzX> GTest_binaryCompare::unpackSlotEncoding;
constexpr long GTest_binaryCompare::mValues[5][15];

TEST_P(GTest_binaryCompare, comparison)
{
  const helib::EncryptedArray& ea = *context.ea;

  // Choose two random n-bit integers
  long pa = NTL::RandomBits_long(bitSize);
  long pb = NTL::RandomBits_long(bitSize+1);
  long pMax = std::max(pa,pb);
  long pMin = std::min(pa,pb);
  bool pMu = pa>pb;
  bool pNi = pa<pb;

  // Encrypt the individual bits
  NTL::Vec<helib::Ctxt> eMax, eMin, enca, encb;

  helib::Ctxt mu(secKey), ni(secKey);
  resize(enca, bitSize, mu);
  resize(encb, bitSize+1, ni);
  for (long i=0; i<=bitSize; i++) {
    if (i<bitSize) secKey.Encrypt(enca[i], NTL::ZZX((pa>>i)&1));
    secKey.Encrypt(encb[i], NTL::ZZX((pb>>i)&1));
    if (bootstrap) { // put them at a lower level
      if (i<bitSize) enca[i].bringToSet(context.getCtxtPrimes(5));
      encb[i].bringToSet(context.getCtxtPrimes(5));
    }
  }
#ifdef DEBUG_PRINTOUT
  decryptAndPrint((std::cout<<" before comparison: "), encb[0], secKey, ea,0);
#endif

  std::vector<long> slotsMin, slotsMax, slotsMu, slotsNi;
  //cmp only
  compareTwoNumbers(mu, ni, helib::CtPtrs_VecCt(enca), helib::CtPtrs_VecCt(encb),
                      &unpackSlotEncoding);
  ea.decrypt(mu, secKey, slotsMu);
  ea.decrypt(ni, secKey, slotsNi);
  EXPECT_EQ(std::make_pair(slotsMu[0], slotsNi[0]),
            std::make_pair((long) pMu, (long) pNi))
      << "Comparison (without min max) error: a=" << pa << ", b=" << pb
      << ", mu=" << slotsMu[0] << ", ni=" << slotsNi[0] << std::endl;
  if (helib_test::verbose) {
    std::cout << "Comparison (without min max) succeeded: ";
    std::cout << '(' << pa << ',' << pb <<
                 ")=> mu=" << slotsMu[0] << ", ni=" << slotsNi[0] <<std::endl;
  }

  {helib::CtPtrs_VecCt wMin(eMin), wMax(eMax); // A wrappers around output vectors
  //cmp with max and min
  compareTwoNumbers(wMax, wMin, mu, ni,
                    helib::CtPtrs_VecCt(enca), helib::CtPtrs_VecCt(encb),
                    &unpackSlotEncoding);
  decryptBinaryNums(slotsMax, wMax, secKey, ea);
  decryptBinaryNums(slotsMin, wMin, secKey, ea);
  } // get rid of the wrapper
  ea.decrypt(mu, secKey, slotsMu);
  ea.decrypt(ni, secKey, slotsNi);

  EXPECT_EQ(
          std::make_tuple(       pMax,        pMin,        pMu,        pNi),
          std::make_tuple(slotsMax[0], slotsMin[0], slotsMu[0], slotsNi[0])
          )
         << "Comparison (with min max) error: a=" << pa << ", b=" << pb
         << ", but min=" << slotsMin[0] << ", max=" << slotsMax[0]
         << ", mu=" << slotsMu[0] << ", ni=" << slotsNi[0] << std::endl;

  if (helib_test::verbose) {
    std::cout << "Comparison (with min max) succeeded: ";
    std::cout << '('<<pa<<','<<pb<<")=>("<<slotsMin[0]<<','<<slotsMax[0]
         <<"), mu="<<slotsMu[0]<<", ni="<<slotsNi[0]<<std::endl;
  }

#ifdef DEBUG_PRINTOUT
  const helib::Ctxt* minLvlCtxt = nullptr;
  long minLvl=1000;
  for (const helib::Ctxt& c: eMax) {
    long lvl = c.logOfPrimeSet();
    if (lvl < minLvl) {
      minLvlCtxt = &c;
      minLvl = lvl;
    }
  }
  decryptAndPrint((std::cout<<" after comparison: "), *minLvlCtxt, secKey, ea,0);
  std::cout << std::endl;
#endif
}

INSTANTIATE_TEST_SUITE_P(small_paramater_sizes_repeated, GTest_binaryCompare, ::testing::Combine(
            ::testing::Values(
                //SLOW
                Parameters(1, 5, false, 0, 1)
                //FAST
                //Parameters(0, 5, false, 0, 1)
            ), ::testing::Range(0,3)) // The range is for repeats
        );

} // namespace

#if 0
e2=a2+b2+1
e1=a1+b1+1
e0=a0+b0+1, ne0 = a0+b0

e*2 = e2
e*1 = e2 e1
e*0 = e*1 e0, ne*0 = e*1 ne0

a*2 = a2    , b*2 = b2
a*1 = e2 a1 , b*1 = e*1 -e2 - a*1
a*0 = e*1 a0, b*0 = ne*0 -a*0

c2 = a2 b2
c1 = a1 b1
c0 = a0 b0

c*2 = c2
c*1 = e2 c1
c*0 = e*1 c0

A2 = a2,      B2 = b2,      C2 = c2
A1 = a2 +a*1, B1 = b2 +b*1, C1 = c2 + c*1
A0 = A1 +a*0, B0 = B1 +b*0, C0 = C1 + c*0

(a>b) = A0+C0
(a<b) = B0+C0

mx2 = a2+b2+c2
mx1 = a*1+b*1+c1 + a1(a2+c2) + b1(b2+c)

X ab01 = b1 a0
X b10 = b1 b0

mx0 = a0+b0+c*0
     + a0(A1+C1) = a0 A1 + a0(c*2 +a*1 b1) = a0(A1+c*2) + a*1 ab01
     + b0(B1+C1) = b0 B1 + b0(c*2 +a*1 b1) = b0(B1+c*2) + a*1 b10
    = a0(1+A1+c*2) + b0(1+B1+c*2) + a*1(b10+ab01) + c*0

mn0 = c0
     + a0(B1+C1) = a0 B1 + a0(c*2 +a*1 b1) = a0(B1+c*2) + a*1 ab01
     + b0(A1+C1) = b0 A1 + b0(c*2 +a*1 b1) = b0(A1+c*2) + a*1 b10
    = a0(B1+c*2) + b0(A1+C*2) + a*1(b10 + ab01) + c*0

e20 = e2 ne0

      a*1(b10+ab01)=(a2+b2+1)a1 b1(a0+b0) = e20 c1

mx0 = a0(1+A1+c*2) + b0(1+B1+c*2) + e20 c1 + c*0
mn0 = a0(B1+c*2) + b0(A1+c*2) + e20 c1 + c*0

#endif


//==================================================================================================
/*!

  Copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#include <boost/simd/constant/mtwo.hpp>
#include <boost/simd/as.hpp>
#include <scalar_test.hpp>

STF_CASE_TPL( "Check mtwo behavior for integral types"
            , (std::uint8_t)(std::uint16_t)(std::uint32_t)(std::uint64_t)
              (std::int8_t)(std::int16_t)(std::int32_t)(std::int64_t)
            )
{
  using boost::simd::as;
  using boost::simd::detail::mtwo;
  using boost::simd::Mtwo;

  STF_TYPE_IS(decltype(Mtwo<T>()), T);
  STF_EQUAL(Mtwo<T>(), T(-2));
  STF_EQUAL(mtwo( as(T{}) ),T(-2));
}

STF_CASE_TPL( "Check mtwo behavior for floating types"
            , (double)(float)
            )
{
  using boost::simd::as;
  using boost::simd::detail::mtwo;
  using boost::simd::Mtwo;

  STF_TYPE_IS(decltype(Mtwo<T>()), T);
  STF_IEEE_EQUAL(Mtwo<T>(), T(-2));
  STF_IEEE_EQUAL(mtwo( as(T{}) ), T(-2));
}


#include <pcl/point_types.h>
#include <pcl/point_cloud.h>
#include <pcl/common/common.h>
#include <pcl/common/transforms.h>
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/console/parse.h>
#include <pcl/console/print.h>
#include <pcl/io/pcd_io.h>
#include <iostream>
#include <flann/flann.h>
#include <flann/io/hdf5.h>
#include <boost/filesystem.hpp>

typedef std::pair<std::string, std::vector<float> > vfh_model;

/** \brief Loads an n-D histogram file as a VFH signature
  * \param path the input file name
  * \param vfh the resultant VFH model
  */
bool
loadHist (const boost::filesystem::path &path, vfh_model &vfh)
{
  int vfh_idx;
  // Load the file as a PCD
  try
  {
    sensor_msgs::PointCloud2 cloud;
    int version;
    Eigen::Vector4f origin;
    Eigen::Quaternionf orientation;
    pcl::PCDReader r;
    int type; unsigned int idx;
    r.readHeader (path.string (), cloud, origin, orientation, version, type, idx);

    vfh_idx = pcl::getFieldIndex (cloud, "vfh");
    if (vfh_idx == -1)
      return (false);
    if ((int)cloud.width * cloud.height != 1)
      return (false);
  }
  catch (pcl::InvalidConversionException e)
  {
    return (false);
  }

  // Treat the VFH signature as a single Point Cloud
  pcl::PointCloud <pcl::VFHSignature308> point;
  pcl::io::loadPCDFile (path.string (), point);
  vfh.second.resize (308);

  std::vector <sensor_msgs::PointField> fields;
  getFieldIndex (point, "vfh", fields);

  for (size_t i = 0; i < fields[vfh_idx].count; ++i)
  {
    vfh.second[i] = point.points[0].histogram[i];
  }
  vfh.first = path.string ();
  return (true);
}

/** \brief Search for the closest k neighbors
  * \param index the tree
  * \param model the query model
  * \param k the number of neighbors to search for
  * \param indices the resultant neighbor indices
  * \param distances the resultant neighbor distances
  */
inline void
nearestKSearch (flann::Index<flann::ChiSquareDistance<float> > &index, const vfh_model &model,
                int k, flann::Matrix<int> &indices, flann::Matrix<float> &distances)
{
  // Query point
  flann::Matrix<float> p = flann::Matrix<float>(new float[model.second.size ()], 1, model.second.size ());
  memcpy (&p.ptr ()[0], &model.second[0], p.cols * p.rows * sizeof (float));

  indices = flann::Matrix<int>(new int[k], 1, k);
  distances = flann::Matrix<float>(new float[k], 1, k);
  index.knnSearch (p, indices, distances, k, flann::SearchParams (512));
  delete[] p.ptr ();
}

/** \brief Load the list of file model names from an ASCII file
  * \param models the resultant list of model name
  * \param filename the input file name
  */
bool
loadFileList (std::vector<vfh_model> &models, const std::string &filename)
{
  ifstream fs;
  fs.open (filename.c_str ());
  if (!fs.is_open () || fs.fail ())
    return (false);

  std::string line;
  while (!fs.eof ())
  {
    getline (fs, line);
    if (line.empty ())
      continue;
    vfh_model m;
    m.first = line;
    models.push_back (m);
  }
  fs.close ();
  return (true);
}

int
main (int argc, char** argv)
{
  int k = 6;

  double thresh = DBL_MAX;     // No threshold, disabled by default

  if (argc < 2)
  {
    pcl::console::print_error
      ("Need at least three parameters! Syntax is: %s <query_vfh_model.pcd> [options] {kdtree.idx} {training_data.h5} {training_data.list}\n", argv[0]);
    pcl::console::print_info ("    where [options] are:  -k      = number of nearest neighbors to search for in the tree (default: ");
    pcl::console::print_value ("%d", k); pcl::console::print_info (")\n");
    pcl::console::print_info ("                          -thresh = maximum distance threshold for a model to be considered VALID (default: ");
    pcl::console::print_value ("%f", thresh); pcl::console::print_info (")\n\n");
    return (-1);
  }

  std::string extension (".pcd");
  transform (extension.begin (), extension.end (), extension.begin (), (int(*)(int))tolower);

  // Load the test histogram
  std::vector<int> pcd_indices = pcl::console::parse_file_extension_argument (argc, argv, ".pcd");
  vfh_model histogram;
  if (!loadHist (argv[pcd_indices.at (0)], histogram))
  {
    pcl::console::print_error ("Cannot load test file %s\n", argv[pcd_indices.at (0)]);
    return (-1);
  }

  pcl::console::parse_argument (argc, argv, "-thresh", thresh);
  // Search for the k closest matches
  pcl::console::parse_argument (argc, argv, "-k", k);
  pcl::console::print_highlight ("Using "); pcl::console::print_value ("%d", k); pcl::console::print_info (" nearest neighbors.\n");

  std::string kdtree_idx_file_name = "kdtree.idx";
  std::string training_data_h5_file_name = "training_data.h5";
  std::string training_data_list_file_name = "training_data.list";

  std::vector<vfh_model> models;
  flann::Matrix<int> k_indices;
  flann::Matrix<float> k_distances;
  flann::Matrix<float> data;
  // Check if the data has already been saved to disk
  if (!boost::filesystem::exists ("training_data.h5") || !boost::filesystem::exists ("training_data.list"))
  {
    pcl::console::print_error ("Could not find training data models files %s and %s!\n",
        training_data_h5_file_name.c_str (), training_data_list_file_name.c_str ());
    return (-1);
  }
  else
  {
    loadFileList (models, training_data_list_file_name);
    flann::load_from_file (data, training_data_h5_file_name, "training_data");
    pcl::console::print_highlight ("Training data found. Loaded %d VFH models from %s/%s.\n",
        (int)data.rows, training_data_h5_file_name.c_str (), training_data_list_file_name.c_str ());
  }

  // Check if the tree index has already been saved to disk
  if (!boost::filesystem::exists (kdtree_idx_file_name))
  {
    pcl::console::print_error ("Could not find kd-tree index in file %s!", kdtree_idx_file_name.c_str ());
    return (-1);
  }
  else
  {
    flann::Index<flann::ChiSquareDistance<float> > index (data, flann::SavedIndexParams ("kdtree.idx"));
    index.buildIndex ();
    nearestKSearch (index, histogram, k, k_indices, k_distances);
  }

  // Output the results on screen
  pcl::console::print_highlight ("The closest %d neighbors for %s are:\n", k, argv[pcd_indices[0]]);
  for (int i = 0; i < k; ++i)
    pcl::console::print_info ("    %d - %s (%d) with a distance of: %f\n",
        i, models.at (k_indices[0][i]).first.c_str (), k_indices[0][i], k_distances[0][i]);

  // Load the results
  pcl::visualization::PCLVisualizer p (argc, argv, "VFH Cluster Classifier");
  int y_s = (int)floor (sqrt ((double)k));
  int x_s = y_s + (int)ceil ((k / (double)y_s) - y_s);
  double x_step = (double)(1 / (double)x_s);
  double y_step = (double)(1 / (double)y_s);
  pcl::console::print_highlight ("Preparing to load ");
  pcl::console::print_value ("%d", k);
  pcl::console::print_info (" files (");
  pcl::console::print_value ("%d", x_s);
  pcl::console::print_info ("x");
  pcl::console::print_value ("%d", y_s);
  pcl::console::print_info (" / ");
  pcl::console::print_value ("%f", x_step);
  pcl::console::print_info ("x");
  pcl::console::print_value ("%f", y_step);
  pcl::console::print_info (")\n");

  int viewport = 0, l = 0, m = 0;
  for (int i = 0; i < k; ++i)
  {
    std::string cloud_name = models.at (k_indices[0][i]).first;
    boost::replace_last (cloud_name, "_vfh", "");

    p.createViewPort (l * x_step, m * y_step, (l + 1) * x_step, (m + 1) * y_step, viewport);
    l++;
    if (l >= x_s)
    {
      l = 0;
      m++;
    }

    sensor_msgs::PointCloud2 cloud;
    pcl::console::print_highlight (stderr, "Loading "); pcl::console::print_value (stderr, "%s ", cloud_name.c_str ());
    if (pcl::io::loadPCDFile (cloud_name, cloud) == -1)
      break;

    // Convert from blob to PointCloud
    pcl::PointCloud<pcl::PointXYZ> cloud_xyz;
    pcl::fromROSMsg (cloud, cloud_xyz);

    if (cloud_xyz.points.size () == 0)
      break;

    pcl::console::print_info ("[done, ");
    pcl::console::print_value ("%d", (int)cloud_xyz.points.size ());
    pcl::console::print_info (" points]\n");
    pcl::console::print_info ("Available dimensions: ");
    pcl::console::print_value ("%s\n", pcl::getFieldsList (cloud).c_str ());

    // Demean the cloud
    Eigen::Vector4f centroid;
    pcl::compute3DCentroid (cloud_xyz, centroid);
    pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_xyz_demean (new pcl::PointCloud<pcl::PointXYZ>);
    pcl::demeanPointCloud<pcl::PointXYZ> (cloud_xyz, centroid, *cloud_xyz_demean);
    // Add to renderer*
    p.addPointCloud (cloud_xyz_demean, cloud_name, viewport);

    // Check if the model found is within our inlier tolerance
    std::stringstream ss;
    ss << k_distances[0][i];
    if (k_distances[0][i] > thresh)
    {
      p.addText (ss.str (), 20, 30, 1, 0, 0, ss.str (), viewport);  // display the text with red

      // Create a red line
      pcl::PointXYZ min_p, max_p;
      pcl::getMinMax3D (*cloud_xyz_demean, min_p, max_p);
      std::stringstream line_name;
      line_name << "line_" << i;
      p.addLine (min_p, max_p, 1, 0, 0, line_name.str (), viewport);
      p.setShapeRenderingProperties (pcl::visualization::PCL_VISUALIZER_LINE_WIDTH, 5, line_name.str (), viewport);
    }
    else
      p.addText (ss.str (), 20, 30, 0, 1, 0, ss.str (), viewport);

    // Increase the font size for the score*
    p.setShapeRenderingProperties (pcl::visualization::PCL_VISUALIZER_FONT_SIZE, 18, ss.str (), viewport);

    // Add the cluster name
    p.addText (cloud_name, 20, 10, cloud_name, viewport);
  }
  // Add coordianate systems to all viewports
  p.addCoordinateSystem (0.1, 0);

  p.spin ();
  return (0);
}


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

  @copyright 2016 NumScale SAS

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

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{
  /*!
    @ingroup group-constant

    Generate a mask with the lone most significand bit set to one
    (even if the type is unsigned).

    @par Semantic:

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

    is similar to:

    @code
    T r = bitwise_cast<T>(1 << sizeof(T)*8-1);
    @endcode

    @return The Signmask constant for the proper type
  **/
  template<typename T> T Signmask();

  namespace functional
  {
    /*!
      @ingroup group-callable-constant
      Generate the  constant signmask.

      @return The Signmask constant for the proper type
    **/
    Value Signmask();
  }
} }
#endif

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

#endif


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

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


// Copyright 2015-2022 The ALMA Project Developers
//
// 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.

#pragma once

/// @file
/// Classes and functions used for sampling from several distributions
/// used in the MC scheme.

#include <vector>
#include <utility>
#include <tuple>
#include <map>
#include <random>
#include <Eigen/Dense>
#include <pcg_random.hpp>
#include <structures.hpp>
#include <qpoint_grid.hpp>
#include <dos.hpp>
#include <processes.hpp>
#include <deviational_particle.hpp>
#include <mutex>

namespace alma {
/// Generate a random time delta from an exponential distribution
/// @param[in] w - a scattering rate
/// @param[in] rng - a random number generator
/// @return an exponential deviate
inline double random_dt(double w, pcg64& rng) {
    if (w == 0.)
        throw value_error("invalid scattering rate");
    ;
    double u = std::uniform_real_distribution(0., 1.)(rng);
    return -std::log(u) / w;
}

/// Base class for discrete distributions over a q-point grid.
class Grid_distribution {
protected:
    /// Distribution function.
    std::vector<double> cumulative;
    /// Random number generator.
    pcg64 rng;
    /// Vector with particle sign
    std::vector<alma::particle_sign> signs;
    /// The sum of cumulative
    double cumulsum = -1.0;
public:
    /// Number of q points.
    std::size_t nqpoints;
    /// Number of phonon modes at each q point.
    std::size_t nmodes;
    /// Empty constructor
    Grid_distribution() = default;

    /// Constructor.
    /// @param[in] grid - phonon spectrum on a regular grid
    /// @param[in] _rng - a random number generator
    Grid_distribution(const Gamma_grid& grid, pcg64& _rng)
        : rng(_rng), nqpoints(grid.nqpoints),
          nmodes(grid.get_spectrum_at_q(0).omega.size()) {
    }

    /// Trivial virtual destructor.
    virtual ~Grid_distribution() {
    }

    /// Fill the 'cumulative' vector.
    ///
    /// @param[in] p - vector with the unnormalized probabilities
    /// of each q point
    void fill_cumulative(const std::vector<double>& p);

    /// Draw a sample from the distribution.
    ///
    /// @return an array containing the mode number and the
    /// q-point number.
    std::array<std::size_t, 2> sample() {
        double u = std::uniform_real_distribution(0., 1.)(this->rng);
        auto pos = std::lower_bound(
                       this->cumulative.begin(), this->cumulative.end(), u) -
                   this->cumulative.begin();

        std::array<std::size_t, 2> nruter;
        nruter[0] = pos % this->nmodes;
        nruter[1] = pos / this->nmodes;
        return nruter;
    }

    /// @return a tuple containing the mode number, the
    /// q-point number and particle sign
    std::tuple<std::size_t, std::size_t, alma::particle_sign>
    sample_with_sign() {
        double u = std::uniform_real_distribution(0., 1.)(this->rng);
        auto pos = std::lower_bound(
                       this->cumulative.begin(), this->cumulative.end(), u) -
                   this->cumulative.begin();
        return std::make_tuple(
            pos % this->nmodes, pos / this->nmodes, this->signs[pos]);
    }
};

/// Objects of this class allow us to sample from a discrete
/// distribution over a q-point grid with a PMF proportional
/// to cv / tau, where cv is the contribution to the specific heat
/// and tau is the relaxation time.
class BE_derivative_distribution : public Grid_distribution {
public:
    /// Empty constructor

    BE_derivative_distribution() = default;

    /// Constructor.
    ///
    /// @param[in] grid - phonon spectrum on a regular grid
    /// @param[in] w - scattering rates
    /// @param[in] T - the temperature in K
    /// @param[in, out] _rng - a random number generator
    BE_derivative_distribution(const Gamma_grid& grid,
                               const Eigen::Ref<const Eigen::ArrayXXd>& w,
                               double T,
                               pcg64& _rng);
};

/// Objects of this class allow us to sample from a discrete
/// distribution over a q-point grid with a PMF proportional to one
/// component of the group velocity and to each mode's contribution
/// to the specific heat.
class Nabla_T_distribution : public Grid_distribution {
public:
    /// Empty constructor
    Nabla_T_distribution() = default;
    /// Constructor.
    ///
    /// @param[in] grid - phonon spectrum on a regular grid
    /// @param[in] nablaT - any vector parallel to the temperature
    /// gradient.
    /// @param[in] T - the temperature in K
    /// @param[in, out] _rng - a random number generator
    /// @param[in] theta - rotation of material with respect
    /// to x-axis
    Nabla_T_distribution(const Gamma_grid& grid,
                         const Eigen::Ref<const Eigen::Vector3d>& nablaT,
                         double T,
                         pcg64& _rng,
                         double theta = 0.);

    /// Get number of generated particles for given conditions
    ///
    ///@param[in] time - in ps
    ///@param[in] vol  - box_volume/unitcell_volume
    ///@param[in] Eff  - deviational particle energy
    std::size_t Ntogenerate(const double time,
                            const double vol,
                            const double Eff);

    /// Returns energy introduced by the generator per unit of time
    ///@param[in] vol  - box_volume/unitcell_volume
    double get_energy(const double vol);
};

/// Objects of this class allow us to sample from a discrete
/// distribution over a q-point grid given from a vector
class ref_distribution : public Grid_distribution {
private:
    std::mutex shield;
public:
    ///Empty constructor
    ref_distribution() = default;

    ref_distribution(const ref_distribution& that) : Grid_distribution(that) {
    }

    ref_distribution& operator=(const ref_distribution& that) {
        this->cumulative = that.cumulative;
        this->rng        = that.rng;
        this->signs      = that.signs;
        this->cumulsum   = that.cumulsum;
        this->nqpoints   = that.nqpoints;
        this->nmodes     = that.nmodes;
        return *this;
    }

    /// Constructor.
    ///
    /// @param[in] grid - phonon spectrum on a regular grid
    /// @param[in] fd   - the distribution
    /// @param[in, out] _rng - a random number generator
    ref_distribution(const Gamma_grid& grid,
                     const Eigen::VectorXd&  fd,
                     pcg64& _rng);

    /// Get number of generated particles for given conditions
    ///@param [in] vol - vol_box / volume_unit_cell
    ///@param [in] Eff - packet energy
    std::size_t Ntogenerate(const double vol,
                            const double Eff);

    std::tuple<std::size_t,std::size_t,
        alma::particle_sign> sample_dist() {

        shield.lock();
        auto res = this->sample_with_sign();
        shield.unlock();
        return res;
    }

};

/// Objects of this class allow us to sample from a discrete
/// distribution over a q-point grid with a PMF corresponding to an
/// isothermal wall of given orientation and equilibrium
/// temperature.  They are also useful for simple periodic systems
/// if Teq is set accordingly.
class Isothermal_wall_distribution : public Grid_distribution {
public:
    /// Empty constructor
    Isothermal_wall_distribution() = default;
    /// Constructor.
    ///
    /// @param[in] grid - phonon spectrum on a regular grid
    /// @param[in] Twall - the temperature of the wall in K
    /// @param[in] Teq - the simulation temperature in K
    /// @param[in] normal - a normal vector pointing out
    /// of the wall
    /// @param[in, out] _rng - a random number generator
    /// @param[in] theta - rotation of material with respect
    /// to x-axis
    Isothermal_wall_distribution(
        const Gamma_grid& grid,
        double Twall,
        double Teq,
        const Eigen::Ref<const Eigen::Vector3d>& normal,
        pcg64& _rng,
        double theta = 0.);

    /// Get number of generated particles for given conditions
    ///
    ///@param[in] time - in ps
    ///@param[in] spf  - area_boundary/unitcell_volume
    ///@param[in] Eff  - deviational particle energy
    std::size_t Ntogenerate(const double time,
                            const double spf,
                            const double Eff);

    /// Get the flux:
    ///@param[in] vuc - unitcell_volume
    double get_flux(const double vuc);
};

/// Objects of this class allow us to sample from a discrete
/// distribution over a q-point grid with a temperature
/// different from that of reference
class outTref_distribution : public Grid_distribution {
public:
    /// Empty constructor
    outTref_distribution() = default;
    /// Constructor.
    ///
    /// @param[in] grid - phonon spectrum on a regular grid
    /// @param[in] Tint - the temperature in K
    /// @param[in] Tref - reference temperature
    /// @param[in, out] _rng - a random number generator
    outTref_distribution(const Gamma_grid& grid,
                         double Tinit,
                         double Tref,
                         pcg64& _rng);

    /// Get number of generated particles for given conditions
    ///
    ///@param[in] vol  - box_volume/unitcell_volume
    ///@param[in] Eff  - deviational particle energy
    std::size_t Ntogenerate(const double vol, const double Eff);

    /// Returns energy introduced by the generator
    ///@param[in] vol  - box_volume/unitcell_volume
    double get_energy(const double vol);
};

/// planar_source_distribution
/// Emission probability for outgoing modes is proportional to
/// heat capacity * normal velocity.
class planar_source_distribution : public Grid_distribution {
public:
    /// Constructor.
    ///
    /// @param[in] grid - phonon spectrum on a regular grid
    /// @param[in] Tref - temperature in K at which to compute heat capacities
    /// @param[in] normal - a normal vector pointing out of the source
    /// @param[in, out] _rng - a random number generator
    planar_source_distribution(const Gamma_grid& grid,
                               double Tref,
                               const Eigen::Ref<const Eigen::Vector3d>& normal,
                               pcg64& _rng);
};

/// For layered systems, this class
/// defines the layers of each state
/// and use them to calculate the coupling
/// of two states in two stacks by using localization
/// distribution and Jensen-Shanon divergence

class layer_coupling {
private:
    /// Jensen shanon diveregence plus -1
    /// the function is defined for both STD maps
    /// and STD vectors
    inline double JSDm1(std::map<std::string, double>& P,
                        std::map<std::string, double>& Q) const {
        double PM = 0., QM = 0.;

        for (auto& [layer_id, p] : P) {
            double q = Q.at(layer_id);

            double M = 0.5 * (p + q);

            if (alma::almost_equal(M, 0.)) {
                continue;
            }

            if (!alma::almost_equal(q, 0.)) {
                QM += q * std::log2(q / M);
            }

            if (!alma::almost_equal(p, 0.)) {
                PM += p * std::log2(p / M);
            }
        }

        return std::max({1.0 - (0.5 * QM + 0.5 * PM), 0.});
    }

    inline double JSDm1(std::vector<double>& P, std::vector<double>& Q) const {
        double PM = 0., QM = 0.;

        for (std::size_t ii = 0; ii < P.size(); ii++) {
            double p = P[ii];
            double q = Q[ii];

            double M = 0.5 * (p + q);

            if (alma::almost_equal(M, 0.)) {
                continue;
            }

            if (!alma::almost_equal(q, 0.)) {
                QM += q * std::log2(q / M);
            }

            if (!alma::almost_equal(p, 0.)) {
                PM += p * std::log2(p / M);
            }
        }

        return std::max({1.0 - (0.5 * QM + 0.5 * PM), 0.});
    }

public:
    /// To store for each material the layers
    std::map<std::string, std::map<std::string, std::vector<int>>> layers;

    std::map<std::string, std::string> connection;
    bool stack_injection = false;

    /// Default constructor
    layer_coupling() = default;

    /// Calculates the coupling rate between two states
    double get_injection_rate(Eigen::VectorXcd& wfin_,
                              Eigen::VectorXcd& wfout_,
                              std::string material_in,
                              std::string material_out) const;
};

/// Diffuse mismatch distribution

class Diffuse_mismatch_distribution {
private:
    /// Number of branches, indexed via 'A' and 'B'.
    std::map<char, std::size_t> Nbranches;
    /// Number of phonon modes, indexed via 'A' and 'B'.
    std::map<char, std::size_t> Ntot;
    /// Unit cell volumes, indexed via 'A' and 'B'.
    std::map<char, double> volume;

    /// Material names:
    std::string material_A, material_B;

    /// Tuple describing the origin of each available mode in A or B and its
    /// contribution to the DOS.
    /// The first element is the material side
    /// The second element is a branch index
    /// The third element is a q-point index
    /// The fourth element is the projection of its velocity on the normal to
    /// the surface. The fifth element is the heat capacity The sixth element is
    /// a contribution to the DOS
    using dos_tuple = std::
        tuple<char, std::size_t, std::size_t, double, double, Gaussian_for_DOS>;
    /// Short notation for tuple specifying the material side, mode index, and
    /// cumulative probability
    typedef std::tuple<char, std::size_t, double> lookup_entry;
    /// Lookup table for incident A modes
    std::vector<std::vector<lookup_entry>> lookup_incidentA;
    /// Lookup table for incident B modes
    std::vector<std::vector<lookup_entry>> lookup_incidentB;
    /// Random number generator.
    pcg64 rng;
    /// Reference temperature (for computing heat capacities)
    double Tref;
    /// Gather information about all available phonon modes.
    ///
    /// @param[in] gridA - phonon spectrum of material A
    /// @param[in] gridB - phonon spectrum of material B
    /// @param[in] normal - a normal vector pointing from A to B.
    /// @param[in] scalebroad - factor modulating all the broadenings
    /// @return a vector of dos_tuples describing all modes
    std::vector<dos_tuple> get_modes(
        const Gamma_grid& gridA,
        const Gamma_grid& gridB,
        const Eigen::Ref<const Eigen::Vector3d>& normal,
        double scalebroad);

public:
    /// Empty Constructor.
    ///
    ///
    Diffuse_mismatch_distribution() = default;
    ///
    ///
    /// Constructor.
    ///
    /// @param[in] gridA - phonon spectrum of material A
    /// @param[in] poscarA - crystal structure of material A
    /// @param[in] gridB - phonon spectrum of material B
    /// @param[in] poscarB - crystal structure of material B
    /// @param[in] normal - a normal vector pointing from A to B.
    /// @param[in] scalebroad - factor modulating all the broadenings
    /// @param[in, out] _rng - a random number generator
    /// @param[in] Tref - reference temperature [K]
    /// @param[in] thicknessA - material A thickness for 2D simulations [nm]
    /// @param[in] thicknessB - material B thickness for 2D simulations [nm]
    /// @param[in] coupling - coupling between layers
    Diffuse_mismatch_distribution(
        const Gamma_grid& gridA,
        const Crystal_structure& poscarA,
        const Gamma_grid& gridB,
        const Crystal_structure& poscarB,
        const Eigen::Ref<const Eigen::Vector3d>& normal,
        double scalebroad,
        pcg64& _rng,
        double Tref = 300.0,
        double thicknessA = -1.,
        double thicknessB = -1.,
        layer_coupling coupling = layer_coupling(),
        std::string materialA = "None",
        std::string materialB = "None");

    /// Draw a final state for a particle incident from A or B.
    ///
    /// @param[in] incidence - 'A' or 'B'
    /// @param[in, out] particle - information about the particle, before and
    /// after the interaction.
    /// @param[in] account_for_velocity: if true, emission probability is
    /// proportional to abs(projected velocity).
    /// @return 'A' or 'B', depending on the direction of emission
    char reemit(char incidence, D_particle& particle);
};

/// Objects of this class allow us to simulate completely diffusive
/// interfaces between two media. An incident phonon undergoes
/// elastic diffusion and exits the interface in a mode chosen at
/// random.
///
/// In contrast with Diffuse_mismatch_distribution, in this case
/// the normal vector is not fixed at construction time.
class Elastic_interface_distribution {
private:
    /// Number of q points in grid A.
    std::size_t nqA;
    /// Number of q points in grid B.
    std::size_t nqB;
    /// Number of branches in grid A.
    std::size_t nmodesA;
    /// Number of branches in grid B.
    std::size_t nmodesB;
    /// Volume of the unit cell in A.
    double VA;
    /// Volume of the unit cell in B.
    double VB;
    /// Tuple describing the origin of each available mode
    /// and its contribution to the DOS.
    /// The first element is either 'A' or 'B'
    /// The second element is a branch index
    /// The third element is a q-point index
    /// The fourth element is a contribution to the DOS
    using dos_tuple =
        std::tuple<char, std::size_t, std::size_t, Gaussian_for_DOS>;
    /// Mode-to-mode cumulative transition probabilities.
    /// Only allowed transitions are considered.
    std::vector<std::vector<double>> cumulative;
    /// Modes to which the elements of cumulative refer.
    std::vector<std::vector<std::size_t>> allowed;
    /// Unit vectors parallel to each of the group velocities
    /// from the original grid. Each column is a 3-vector.
    Eigen::MatrixXd directions;
    /// Random number generator.
    pcg64& rng;
    /// Gather information about all available modes.
    ///
    /// @param[in] gridA - phonon spectrum of material A
    /// @param[in] gridB - phonon spectrum of material B
    /// @param[in] scalebroad - factor modulating all
    /// the broadenings
    /// @return a vector of tuples describing all modes
    std::vector<dos_tuple> get_all_modes(const Gamma_grid& gridA,
                                         const Gamma_grid& gridB,
                                         double scalebroad) const;

public:
    /// Constructor.
    ///
    /// @param[in] gridA - phonon spectrum of material A
    /// @param[in] gridB - phonon spectrum of material B
    /// @param[in] scalebroad - factor modulating all
    /// the broadenings
    /// @param[in, out] _rng - a random number generator
    Elastic_interface_distribution(const Gamma_grid& gridA,
                                   const Gamma_grid& gridB,
                                   double scalebroad,
                                   pcg64& _rng);
    /// Draw a final state for a particle incident from A or B or
    /// viceversa.
    ///
    /// @param[in] incidence - 'A' or 'B'
    /// @param[in] normal - normal to the interface, pointing
    /// from A to B.
    /// @param[in, out] particle - information about the particle,
    /// before and after the interaction.
    /// @return 'A' or 'B', depending on the direction of emission
    char reemit(char incidence,
                const Eigen::Ref<const Eigen::Vector3d>& normal,
                D_particle& particle);
};

// Simple and general class that allows us to simulate completely
// random elastic scattering.
class Elastic_distribution {
private:
    /// Number of q points in the grid.
    std::size_t nq;
    /// Number of branches in the grid..
    std::size_t nmodes;
    /// Tuple describing the origin of each available mode
    /// and its contribution to the DOS.
    /// The first element is a branch index
    /// The second element is a q-point index
    /// The third element is a contribution to the DOS
    using dos_tuple = std::tuple<std::size_t, std::size_t, Gaussian_for_DOS>;
    /// Mode-to-mode cumulative transition probabilities.
    /// Only allowed transitions are considered.
    std::vector<std::vector<double>> cumulative;
    /// Modes to which the elements of cumulative refer.
    std::vector<std::vector<std::size_t>> allowed;
    /// Unit vectors parallel to each of the group velocities
    /// from the original grid. Each column is a 3-vector.
    Eigen::MatrixXd directions;
    /// Random number generator.
    pcg64& rng;
    /// Gather information about all available modes.
    ///
    /// @param[in] grid - description of the phonon spectrum
    /// @param[in] scalebroad - factor modulating all
    /// the broadenings
    /// @return a vector of tuples describing all modes
    std::vector<dos_tuple> get_all_modes(const Gamma_grid& grid,
                                         double scalebroad) const;

public:
    /// Constructor.
    ///
    /// @param[in] grid - phonon spectrum of the material
    /// @param[in] scalebroad - factor modulating all
    /// the broadenings
    /// @param[in, out] _rng - a random number generator
    Elastic_distribution(const Gamma_grid& grid,
                         double scalebroad,
                         pcg64& _rng);
    /// Draw a final state for a particle.
    ///
    /// @param[in, out] particle - information about the particle,
    /// before and after the interaction.
    void scatter(D_particle& particle);

    /// Draw a final state for a particule with the constraint that
    /// one component of its group velocity has a predefined sign.
    ///
    /// @param[in] normal - direction of the projection
    /// @param[in] refsign - an integer with the desired sign
    /// @param[in, out] particle - information about the particle,
    /// before and after the interaction.
    void scatter(const Eigen::Ref<const Eigen::Vector3d>& normal,
                 const int refsign,
                 D_particle& particle);
};
} // namespace alma


//#include "math.hpp"
#include "array.hpp"
//#include "globals.hpp"
//#include "lcao_wavefunction.hpp"
//#include "scf.hpp"
//#include "timer.hpp"
//#include <boost/timer/timer.hpp>
#include <omp.h>
#include <cstdlib>
#include <iostream>
//
//
//
inline void print_array(const array<double, 1> &C, const std::string name)
{
	std::cout << std::endl;
	for(unsigned int i = 1; i <= C.array::first_rank(); ++i)
	{
		std::cout << name << "(" << i << ") = " << C(i) << std::endl;
	}
	std::cout << "Rank = " << C.array::rank() << std::endl;
	std::cout << "Rank 1 = " << C.array::first_rank() << std::endl;
	std::cout << "Status = " << (C.array::is_ready()? "created" : "not created") << std::endl;
	std::cout << "Is null = " << (C.array::is_null()? "yes" : "no") << std::endl;
	std::cout << "Is positive = " << (C.array::is_positive()? "yes" : "no") << std::endl;
	std::cout << "Is negative = " << (C.array::is_negative()? "yes" : "no") << std::endl;
	std::cout << "Min = " << C.array::min() << std::endl;
	std::cout << "Max = " << C.array::max() << std::endl;
};

template <typename data_type0>
inline data_type0 func_test()
{
	return data_type0(2.334);
};
//
//
//
inline void print_array(const array<double, 2> &C, const std::string name)
{
	std::cout << std::endl;
	for(unsigned int i = 1; i <= C.array::first_rank(); ++i)
	{
		for(unsigned int j = 1; j <= C.array::second_rank(); ++j)
		{
			std::cout << name << "(" << i << ", " << j << ") = " << C(i, j) << std::endl;
		}
	}
	std::cout << "Rank = " << C.array::rank() << std::endl;
	std::cout << "Rank 1 = " << C.array::first_rank() << std::endl;
	std::cout << "Rank 2 = " << C.array::second_rank() << std::endl;
	std::cout << "Status = " << (C.array::is_ready()? "created" : "not created") << std::endl;
	std::cout << "Is null = " << (C.array::is_null()? "yes" : "no") << std::endl;
	std::cout << "Is positive = " << (C.array::is_positive()? "yes" : "no") << std::endl;
	std::cout << "Is negative = " << (C.array::is_negative()? "yes" : "no") << std::endl;
	std::cout << "Min = " << C.array::min() << std::endl;
	std::cout << "Max = " << C.array::max() << std::endl;
};
//
//
//
int main()
{
/*
	size =      0, construction wall time = 0.000000657001 s
	size =     50, construction wall time = 0.0001215   s
	size =    100, construction wall time = 0.000122804 s
	size =    200, construction wall time = 0.000120979 s
	size =    400, construction wall time = 0.000126246 s
	size =    800, construction wall time = 0.000152721 s
	size =   1000, construction wall time = 0.000131989 s
	size =   5000, construction wall time = 0.000164553 s
//
//
//
int main()
{
	size =      0, construction wall time = 0.000000657001 s
	size =     50, construction wall time = 0.0001215   s
	size =    100, construction wall time = 0.000122804 s
	size =    200, construction wall time = 0.000120979 s
	size =    400, construction wall time = 0.000126246 s
	size =    800, construction wall time = 0.000152721 s
	size =   1000, construction wall time = 0.000131989 s
	size =   5000, construction wall time = 0.000164553 s
	size =  10000, construction wall time = 0.000211936 s
	size =  20000, construction wall time = 0.000308194 s
	size = 100000, construction wall time = 0.00103987  s
*/
	unsigned int row = 20;
	double start_time = 0.0, end_time = 0.0;
	start_time = omp_get_wtime();
	array<double, 1> A(row);
	array<double, 1> B(row);
	array<double, 1> C(row);
	array<double, 1> D(10);
	array<double, 1> E(10);
	array<double, 1> F(1000001);
	array<double, 1> G(1000001);
	array<double, 1> H(50);
	array<double, 1> I(50);
	array<double, 1> Z;
	array<double, 1> T;
	array<double, 1> W1(10);
	array<double, 1> W2(10);
	array<double, 2> a;
	array<double, 2> b;
	array<double, 2> c;
	array<double, 2> d;
	a.create(6, 6);
	b.create(2, 3);
	c.create(3, 2);
	d.create(2, 2);
	W1 = 2.5;
	W2 = 0.5;
	H = 2.5;
	I = T + Z;
	A.array::build_randomly();
	F.array::build_randomly();
	//end_time = omp_get_wtime();
	print_array(A, "A");
	double test[100];
	unsigned int i = 100;
	start_time = omp_get_wtime();
	std::cout << sizeof(test)/sizeof(double);
	end_time = omp_get_wtime();
	std::cout << "\nwall time for a/b = " << end_time - start_time << " s" << std::endl;
	start_time = omp_get_wtime();
	std::cout << i;
	end_time = omp_get_wtime();
	std::cout << "\nwall time for i = " << end_time - start_time << " s" << std::endl;
	unsigned int element = 1;
	A(element) = 34.0;
	std::cout << "\nnovo elemento A(1) = " << A(element) << std::endl;
	B = A;
	print_array(B, "B");
	B = 180.1;
	print_array(B, "B");
	A << B;
	///*array<double, 1>*/ C += B;
	print_array(B, "B");
	print_array(A, "A");
	//D = 25.5;
	//F = 25.5;
	start_time = omp_get_wtime();
	//#pragma omp parallel
	//{
	//	#pragma omp master
		//E = D + 0.5;
	//}
	end_time = omp_get_wtime();
	//print_array(E, "E");
	//std::cout << "wall time for 100000 = " << end_time - start_time << " s" << std::endl;
	print_array(D, "D");
	print_array(E, "E");
	print_array(H, "H");
	print_array(Z, "Z");
	print_array(I, "I");
	print_array(T, "T");
	////W2.clear();
	T = W1 + W2;
	print_array(T, "T");
	I.create(30);
	a.create(17, 10);
	print_array(I, "I");
	print_array(a, "a");
	a.build_randomly();
	print_array(a, "a");
	std::cout << "a(2, 3) = " << a(2, 3)  << std::endl;
	a.transpose();
	print_array(a, "a");
	a.build_identity_form();
	print_array(a, "a");
	a.set_diagonal(2.5);
	print_array(a, "a");
	a.set_zero();
	a.set_anti_diagonal(3.5);
	print_array(a, "a");
	start_time = omp_get_wtime();
	{
		G << F;
	}
	end_time = omp_get_wtime();
	//print_array(G, "G");
	std::cout << "G = F no inline = " << end_time - start_time << " s" << std::endl;
	std::cout << "T(9) = " << T.get(9) << std::endl;
	a.build_randomly();
	std::ofstream file("teste.txt");
	a.write(file, "hahahah!", 8);
	a.clear();
	start_time = omp_get_wtime();
	{
		switch(a.is_ready())
		{
			case true: std::cout << "from switch: is ready!" << std::endl; break;
			case false: std::cout << "from switch: is not ready!" << std::endl; break;
		}
	}
	end_time = omp_get_wtime();
	std::cout << "switch time = " << end_time - start_time << std::endl;
	start_time = omp_get_wtime();
	{
		if(a.is_ready())
		{
			std::cout << "from if: is ready!" << std::endl;
		}
		else
		{
			std::cout << "from if: is not ready!" << std::endl;
		}
	}
	end_time = omp_get_wtime();
	std::cout << "if time = " << end_time - start_time << std::endl;
	b(1, 1) = 1.0;
	b(1, 2) = 2.0;
	b(1, 3) = 3.0;
	b(2, 1) = 4.0;
	b(2, 2) = 5.0;
	b(2, 3) = 6.0;
	c(1, 1) = 7.0;
	c(1, 2) = 8.0;
	c(2, 1) = 9.0;
	c(2, 2) = 10.0;
	c(3, 1) = 11.0;
	c(3, 2) = 12.0;
	print_array(b, "b");
	print_array(c, "c");
	print_array(d, "d");
	d = b*c;
	print_array(d, "d");
	b *= c;
	print_array(b, "b");
	b.write();
	b.debug_check();
	//std::cout << "return type test = " << c.func_t<double>() << std::endl;
	//std::cout << "return type test = " << c.func_t<int>() << std::endl;
	//std::cout << "return type test = " << c.func_t<float>() << std::endl;
	/*
	double matrix_a[2*3] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
	double matrix_b[3*2] = {7.0, 8.0, 9.0, 10.0, 11.0, 12.0};
	double matrix_c[2*2] = {0.0, 0.0, 0.0, 0.0};
	cblas_dgemm(CblasRowMajor,
                CblasNoTrans,
                CblasNoTrans,
                2, // A and C
                2, // B and C
                3, // A and B
                1.0,
                matrix_a,
                3,
                matrix_b,
                2,
                1.0,
                matrix_c,
                2);
	std::cout << matrix_c[0] << std::endl;
	std::cout << matrix_c[1] << std::endl;
	std::cout << matrix_c[2] << std::endl;
	std::cout << matrix_c[3] << std::endl;
	*/
	//a.get(1);
	/*
	double* vet1 = NULL;
	vet1 = new double[0]();
	std::cout << "ve1 is NULL = " << (vet1 == NULL? "yes" : "no") << std::endl;
	vet1[9] = 1.5;
	vet1[10] = 2.5;
	vet1[11] = 3.5;
	std::cout << "ve1 = " << vet1[9] << std::endl;
	std::cout << "ve1 = " << vet1[10] << std::endl;
	std::cout << "ve1 = " << vet1[11] << std::endl;
	*/
	/*
	#pragma omp parallel
	{
	start_time = omp_get_wtime();
	G = F + 0.5;
	end_time = omp_get_wtime();
	}
	print_array(G, "G");
	*/
	//C = (A )
	//std::cout << "wall time = " << end_time - start_time << " s" << std::endl;
	//A.array::set_all(0.5);
	//print_array(A, "A");
	//array<double, 1> B(row);
	/*
	//print_array(A, row);
	std::cout << "[Summ 2.0!!]" << std::endl;
	B = A - 2.0;
	print_array(B, row);
	std::cout << "[Using set all!!]" << std::endl;
	A.array::set_all(2.8574);
	//B = A;
	//std::cout << "[B!!]" << std::endl;
	//print_array(B, row);
	std::cout << "[A!!]" << std::endl;
	for(unsigned int i = 1; i <= row; ++i)
	{
		std::cout << "A(" << i << ") = " << A(i) << std::endl;
	}
	A(1, 1.5432);
	A(3, 9.5432);
	std::cout << "[Changing the variables!!]" << std::endl;
	for(unsigned int i = 1; i <= row; ++i)
	{
		std::cout << "A(" << i << ") = " << A(i) << std::endl;
	}
	std::cout << "[Sort!!]" << std::endl;
	A.array::sort();
	for(unsigned int i = 1; i <= row; ++i)
	{
		std::cout << "A(" << i << ") = " << A(i) << std::endl;
	}
	std::cout << "Summ = " << A.array::summation() << std::endl;
	std::cout << "Norm = " << A.array::norm() << std::endl;
	std::cout << "[Reseting!!]" << std::endl;
	A.array::reset();
	std::cout << "[Recreating!!]" << std::endl;
	A.array::create(row);
	for(unsigned int i = 1; i <= row; ++i)
	{
		std::cout << "A(" << i << ") = " << A(i) << std::endl;
	}
	*/
	return 0;
}


/*
 * MutableBooleanNetwork.cpp
 *
 *  Created on: Nov 18, 2010
 *      Author: stewie
 */

#include <utility>
#include <algorithm>
#include <fstream>
#include <iostream>

#include <boost/tuple/tuple.hpp>

#include <BnSimulator/util/printing.hpp>
#include <BnSimulator/util/JaggedArray.hpp>
#include <BnSimulator/core/MutableBooleanNetwork.hpp>

using namespace std;
using namespace boost;

namespace bn {

void MutableBooleanNetwork::Updater::update() {
	using std::swap;
	State next(size());
	MutableBooleanNetwork::Network::vertex_iterator vi, vend;
	for (tie(vi, vend) = vertices(bn->net); vi != vend; ++vi) {
		size_t index = 0;
		size_t j = 0;
		MutableBooleanNetwork::Network::inv_adjacency_iterator it, end;
		for (tie(it, end) = inv_adjacent_vertices(*vi, bn->net); it != end; ++it, ++j) {
			index += (1 << j) * state[*it];
		}
		next[*vi] = bn->net[*vi][index];
	}
	swap(state, next);
}

void MutableBooleanNetwork::Updater::update(State& s) {
	using std::swap;
	State next(size());
	MutableBooleanNetwork::Network::vertex_iterator vi, vend;
	for (tie(vi, vend) = vertices(bn->net); vi != vend; ++vi) {
		size_t index = 0;
		size_t j = 0;
		MutableBooleanNetwork::Network::inv_adjacency_iterator it, end;
		for (tie(it, end) = inv_adjacent_vertices(*vi, bn->net); it != end; ++it, ++j) {
			index += (1 << j) * s[*it];
		}
		next[*vi] = bn->net[*vi][index];
	}
	swap(s, next);
}

/**
 * Triggers a simulation step.
 *
 * @note
 * From an implementation point of view, this is @e the most expensive piece
 * of code in the whole library.
 */
void MutableBooleanNetwork::update() {
	using std::swap;
	State next(size());
	Network::vertex_iterator vi, vend;
	for (tie(vi, vend) = vertices(net); vi != vend; ++vi) {
		size_t index = 0; // index computation is the most expensive operation
		size_t j = 0;
		Network::inv_adjacency_iterator it, end;
		for (tie(it, end) = inv_adjacent_vertices(*vi, net); it != end; ++it, ++j) {
			index += (1 << j) * state[*it];
		}
		next[*vi] = net[*vi][index]; // vertex descriptors are integers
	}
	swap(state, next);
}

void MutableBooleanNetwork::update(State& s) {
	using std::swap;
	State next(size());
	Network::vertex_iterator vi, vend;
	for (tie(vi, vend) = vertices(net); vi != vend; ++vi) {
		size_t index = 0; // index computation is the most expensive operation
		size_t j = 0;
		Network::inv_adjacency_iterator it, end;
		for (tie(it, end) = inv_adjacent_vertices(*vi, net); it != end; ++it, ++j) {
			index += (1 << j) * s[*it];
		}
		next[*vi] = net[*vi][index]; // vertex descriptors are integers
	}
	swap(s, next);
}

BooleanFunction MutableBooleanNetwork::getFunction(const size_t i) const {
	assert(i < size());
	return BooleanFunction(net[vertex(i, net)]);
}

vector<BooleanFunction> MutableBooleanNetwork::getFunctions() const {
	vector<BooleanFunction> res;
	for (size_t i = 0; i < size(); ++i) {
		res.push_back(getFunction(i));
	}
	return res;
}

/**
 * Convenient function to initialize a boolean network from two files containing
 * a textual description of its topology and node functions.
 *
 * Topology file must contain, for each node, a whitespace separated list of
 * node indices which describe the inputs of a node.
 * Thus, for the i-th node the i-th row reads:\n
 * \<first input\> \<second input\> ... \<k-th input\>
 *
 * Function file must contain, for each node, a whitespace separated list of
 * '0' or '1' characters which define the ouputs of the boolean function for that
 * node. Thus, if the i-th node has associated a function @e f, in the
 * i-th row we read:\n
 * \f$\underbrace{f(\overbrace{00 \ldots 00}^k) \quad (\overbrace{00 \ldots 01}^k)
 * \quad \ldots \quad f(\overbrace{11 \ldots 11}^k)}_{2^k}\f$\n
 * which totals to \f$2^k\f$ elements if i-th node has @e k inputs.
 *
 * Input order is specified by topology file with the @e leftmost integer
 * indicating the least significant bit.
 *
 * It is advisable to use this method instead of the constuctor.
 * @param n number of nodes in the network
 * @param topologyFilename file name of topology description
 * @param functionFilename file name of function definitions
 * @return a new boolean network
 *
 * @todo
 * Parameter @a n is actually useless since it can be inferred by input files.
 * It might be kept as a double check.
 */
MutableBooleanNetwork MutableBooleanNetwork::makeNetwork(
		const char topologyFilename[], const char functionFilename[]) {
	MutableBooleanNetwork res;
	ifstream topoFile(topologyFilename);
	util::JaggedArray<size_t> topo;
	topoFile >> topo;
	topoFile.close();
	ifstream funcFile(functionFilename);
	util::JaggedArray<bool> func;
	funcFile >> func;
	funcFile.close();
	for (size_t i = 0; i < func.numRows(); ++i) // add vertices and functions
		add_vertex(TruthTable(func.begin(i), func.end(i)), res.net);
	for (size_t i = 0; i < topo.numRows(); ++i) { // add edges
		Network::vertex_descriptor v = vertex(i, res.net);
		for (util::JaggedArray<size_t>::const_iterator j = topo.begin(i), end =
				topo.end(i); j != end; ++j) { //
			add_edge(vertex(*j, res.net), v, res.net);
		}
	}
	res.state = State(res.size());
	return res;
}

/**
 * Prints a network to a stream.
 *
 * It prints only the number of nodes. It is not very informative indeed.
 * @param out an output stream
 * @param rbn a random boolean network
 * @return the stream passed as first argument
 */
ostream& operator<<(ostream& out, const MutableBooleanNetwork& bn) {
	MutableBooleanNetwork::Network::vertex_iterator vi, vend;
	for (tie(vi, vend) = vertices(bn.net); vi != vend; ++vi) {
		out << *vi << ") ";
		util::print(out, inv_adjacent_vertices(*vi, bn.net));
		out << " : " << util::dump(bn.net[*vi]) << '\n';
	}
	return out;
}

void swap(MutableBooleanNetwork& a, MutableBooleanNetwork& b) {
	a.swap(b);
}

}


#include "test_Prefix.h"
#include <boost/range/distance.hpp>
#include <CGAL/boost/graph/Euler_operations.h>

template <typename T>
void
join_face_test()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);

  Surface_fixture_1<T> f;

  bool found;
  halfedge_descriptor e;
  boost::tie(e, found) = halfedge(f.w, f.v, f.m);
  assert(found);
  // manually set the halfedge of f.f1 to the edge that is to be
  // removed to provoke a special case
  set_halfedge(f.f1, e, f.m);
  CGAL::Euler::join_face(e,f.m);

  assert(CGAL::internal::exact_num_faces(f.m) == 2);
  assert(CGAL::internal::exact_num_edges(f.m) == 6);

  CGAL::Halfedge_around_face_iterator<T> begin, end;
  boost::tie(begin, end) = CGAL::halfedges_around_face(halfedge(f.f1, f.m), f.m);
  assert(std::distance(begin, end) == 4);
  for(; begin != end; ++begin)
  {

    halfedge_descriptor hd = *begin;
    assert(face(hd, f.m) == f.f1);

  }

  face_iterator fit, fend;
  for(boost::tie(fit, fend) = faces(f.m); fit != fend; ++fit) {
    assert(*fit == f.f1 || *fit == f.f3);
  }

  assert(degree(f.w, f.m) == 2);
  assert(degree(f.v, f.m) == 3);
  assert(CGAL::is_valid(f.m));

}

template <typename T>
void
remove_face_test_1()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);
  Surface_fixture_1<T> f;

  // find the edge between x and y
  bool found;
  halfedge_descriptor e;
  boost::tie(e, found) = halfedge(f.x, f.y, f.m);
  assert(found);
  assert(face(e, f.m) == f.f3);

  CGAL::Euler::remove_face(e,f.m);

  assert(CGAL::is_valid(f.m));

  assert_EQUAL(degree(f.v, f.m) == 3);
  assert_EQUAL(degree(f.x, f.m) == 2);
  assert_EQUAL(CGAL::internal::exact_num_faces(f.m) == 2);
  assert_EQUAL(CGAL::internal::exact_num_edges(f.m) == 5);
  assert_EQUAL(CGAL::internal::exact_num_vertices(f.m) == 4);
  halfedge_iterator eb, ee;
  int count = 0;
  for(boost::tie(eb, ee) = halfedges(f.m); eb != ee; ++eb) {
    if(face(*eb,f.m) == boost::graph_traits<T>::null_face())
      ++count;
  }
  assert(count == 4);
}

template <typename T>
void
remove_face_test_2()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);

  Surface_fixture_2<T> f;

  // find the edge between x and v
  bool found;
  halfedge_descriptor e;

  boost::tie(e, found) = halfedge(f.x, f.w, f.m);
  assert(found);
  boost::tie(e, found) = halfedge(f.x, f.v, f.m);
  assert(found);
  assert(face(e, f.m) == f.f1);
  CGAL::Euler::remove_face(e,f.m);
  assert(CGAL::is_valid(f.m));

  assert(CGAL::internal::exact_num_faces(f.m) == 3);
  assert(CGAL::internal::exact_num_edges(f.m) == 7);
  assert(CGAL::internal::exact_num_vertices(f.m) == 5);

  boost::tie(e, found) = halfedge(f.x, f.w, f.m);
  assert(found);
  assert(face(e,f.m) == boost::graph_traits<T>::null_face());

  // check the boundary

  halfedge_descriptor n = next(e, f.m);
  while(n != e) {
    assert(face(n,f.m) == boost::graph_traits<T>::null_face() );
    n = next(n, f.m);
  }
}

template <typename T>
void
add_face_to_border_test()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);

  Surface_fixture_5<T> f;

  CGAL::Euler::add_face_to_border(f.h1, f.h2, f.m);

  assert(CGAL::is_valid(f.m));

}

template <typename T>
void
add_vertex_and_face_to_border_test()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);

  Surface_fixture_5<T> f;

  CGAL::Euler::add_vertex_and_face_to_border(f.h1, f.h2, f.m);

  assert(CGAL::is_valid(f.m));

}

template <typename T>
void
join_vertex_interior_test()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);

  Surface_fixture_3<T> f;
  halfedge_descriptor e;

  bool found;
  boost::tie(e, found) = halfedge(f.w, f.x, f.m);
  assert(found);
  CGAL::Euler::join_vertex(e,f.m);
  assert(CGAL::internal::exact_num_faces(f.m) == 2);
  assert(CGAL::internal::exact_num_vertices(f.m) == 5);
  assert(CGAL::internal::exact_num_edges(f.m) == 6);
  assert(boost::distance(CGAL::halfedges_around_face(halfedge(f.f1, f.m), f.m)) == 3);
  assert(boost::distance(CGAL::halfedges_around_face(halfedge(f.f2, f.m), f.m)) == 3);
  assert(degree(f.x, f.m) == 4);
  assert(CGAL::is_valid(f.m));
}

template <typename T>
void
join_vertex_exterior_test()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);

  {
    // exterior edge is collapsed
    Surface_fixture_3<T> f;
    halfedge_descriptor e;
    bool found;
    boost::tie(e, found) = halfedge(f.w, f.y, f.m);
    assert(source(e,f.m) == f.w);
    assert(target(e,f.m) == f.y);
    assert(found);
    CGAL::Euler::join_vertex(e,f.m);
    assert(CGAL::internal::exact_num_faces(f.m) == 2);
    assert(CGAL::internal::exact_num_vertices(f.m) == 5);
    assert(CGAL::internal::exact_num_edges(f.m) == 6);
    assert(boost::distance(CGAL::halfedges_around_face(halfedge(f.f1, f.m), f.m)) == 4);
    assert(boost::distance(CGAL::halfedges_around_face(halfedge(f.f2, f.m), f.m)) == 3);
    assert(degree(f.y, f.m) == 3);
    assert(CGAL::is_valid(f.m));
  }

  {
    Surface_fixture_3<T> f;
    halfedge_descriptor e;
    bool found;
    boost::tie(e, found) = halfedge(f.y, f.w, f.m);

    assert(source(e,f.m) == f.y);
    assert(target(e,f.m) == f.w);
    assert(found);
    CGAL::Euler::join_vertex(e,f.m);
    assert(CGAL::internal::exact_num_faces(f.m) == 2);
    assert(CGAL::internal::exact_num_vertices(f.m) == 5);
    assert(CGAL::internal::exact_num_edges(f.m) == 6);
    assert(boost::distance(CGAL::halfedges_around_face(halfedge(f.f1, f.m), f.m)) == 4);
    assert(boost::distance(CGAL::halfedges_around_face(halfedge(f.f2, f.m), f.m)) == 3);

    assert(CGAL::is_valid(f.m));
    assert(degree(f.w, f.m) == 3);

  }
}

template <typename T>
void
split_vertex()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);

  Surface_fixture_3<T> f;
  halfedge_descriptor h1, h2;
  bool found;
  boost::tie(h1, found) = halfedge(f.w, f.y, f.m);
  assert(found);
  boost::tie(h2, found) = halfedge(f.z, f.y, f.m);
  assert(found);
  assert(face(h2, f.m) == Traits::null_face());

  // split border vertex y
  CGAL::Euler::split_vertex(h1, h2,f.m);
  assert(CGAL::is_valid(f.m));
  assert(CGAL::internal::exact_num_vertices(f.m) == 7);
  assert(CGAL::internal::exact_num_edges(f.m) == 8);
  assert(boost::distance(CGAL::halfedges_around_face(h1, f.m)) == 5);
  assert(boost::distance(CGAL::halfedges_around_face(h2, f.m)) == 7);
}

template <typename T>
void
split_join_vertex_inverse()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);
  Surface_fixture_3<T> f;
  halfedge_descriptor h, h1, h2;
  bool found;
  boost::tie(h, found) = halfedge(f.w, f.x, f.m);
  assert(found);
  CGAL::Euler::join_vertex(h,f.m);
  assert(CGAL::is_valid(f.m));
  boost::tie(h1, found) = halfedge(f.z, f.x, f.m);
  assert(found);
  boost::tie(h2, found) = halfedge(f.v, f.x, f.m);
  assert(found);
  CGAL::Euler::join_vertex(CGAL::Euler::split_vertex(h1, h2,f.m),f.m);
  assert(CGAL::is_valid(f.m));

  assert(CGAL::internal::exact_num_vertices(f.m)== 5);
  assert(CGAL::internal::exact_num_faces(f.m) == 2);
  assert(CGAL::internal::exact_num_edges(f.m) == 6);
  assert(CGAL::internal::exact_num_halfedges(f.m) == 12);
  assert(boost::distance(CGAL::halfedges_around_face(h1, f.m)) == 3);
  assert(boost::distance(CGAL::halfedges_around_face(h2, f.m)) == 3);
}

template <typename T>
void
join_loop_test()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);
  Surface_fixture_4<T> f;

  CGAL::Euler::join_loop(f.h1, f.h2, f.m);

  assert(CGAL::is_valid(f.m));
}

template <typename T>
void
split_loop_test()
{
  CGAL_GRAPH_TRAITS_MEMBERS(T);
  Surface_fixture_8<T> f;

  CGAL::Euler::split_loop(f.h1, f.h2, f.h3, f.m);
  assert(CGAL::internal::exact_num_vertices(f.m) == 8);
  assert(CGAL::internal::exact_num_faces(f.m) == 8);
  assert(CGAL::internal::exact_num_halfedges(f.m) == 24);
  assert(CGAL::is_valid(f.m));
}

template <typename T>
void
split_face_test()
{
 Surface_fixture_6<T> f;
 CGAL::Euler::split_face(f.h1, f.h2,f.m);
 assert(num_vertices(f.m) == 4);
 assert(num_faces(f.m) == 2);
 assert(num_halfedges(f.m) == 10);
}

template <typename T>
void
 make_hole_test()
{
 Surface_fixture_7<T> f;
 std::size_t nv = num_vertices(f.m);
 std::size_t nf = num_faces(f.m);
 std::size_t nh = num_halfedges(f.m);

 CGAL::Euler::make_hole(f.h, f.m);

 assert(CGAL::internal::exact_num_vertices(f.m) == nv);
 assert(CGAL::internal::exact_num_faces(f.m) == nf-1 );
 assert(CGAL::internal::exact_num_halfedges(f.m) == nh);
}

template <typename T>
void
remove_center_vertex_test()
{
 Surface_fixture_7<T> f;
 std::size_t nv = num_vertices(f.m);
 std::size_t nf = num_faces(f.m);
 std::size_t nh = num_halfedges(f.m);

 typename boost::graph_traits<T>::degree_size_type deg = degree(target(f.h,f.m),f.m);
 CGAL::Euler::remove_center_vertex(f.h,f.m);

 assert(CGAL::internal::exact_num_vertices(f.m) == nv-1);
 assert(CGAL::internal::exact_num_faces(f.m) == (nf-deg)+1);
 assert(CGAL::internal::exact_num_halfedges(f.m) == nh-(2*deg));
}

template <typename T>
void
join_split_inverse()
{

}

template <typename T>
void
does_satisfy_link_condition()
{
  Surface_fixture_7<T> f;

  assert(CGAL::Euler::does_satisfy_link_condition(*edges(f.m).first,f.m));
}

template <typename Graph>
void
test_Euler_operations()
{
  join_face_test<Graph>();
  add_vertex_and_face_to_border_test<Graph>();
  add_face_to_border_test<Graph>();
  join_vertex_interior_test<Graph>();
  join_vertex_exterior_test<Graph>();
  split_vertex<Graph>();
  split_join_vertex_inverse<Graph>();
  join_loop_test<Graph>();
  split_loop_test<Graph>();
  split_face_test<Graph>();
  make_hole_test<Graph>();
  remove_center_vertex_test<Graph>();
  join_split_inverse<Graph>();
  does_satisfy_link_condition<Graph>();
}

int main()
{
  test_Euler_operations<Polyhedron>();
  test_Euler_operations<SM>();

#ifdef CGAL_USE_OPENMESH
  test_Euler_operations<OMesh>();
#endif

  std::cerr << "done\n";
  return 0;
}


// Author(s): Wieger Wesselink
// Copyright: see the accompanying file COPYING or copy at
// https://github.com/mCRL2org/mCRL2/blob/master/COPYING
//
// 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)
//
/// \file pp_test.cpp
/// \brief Test for parser + pretty printer

#include "mcrl2/lps/linearise.h"
#include "mcrl2/lps/parse.h"
#include "mcrl2/lps/specification.h"
#include <boost/test/included/unit_test_framework.hpp>
#include <string>

using namespace mcrl2;
using namespace mcrl2::lps;

BOOST_AUTO_TEST_CASE(rational)
{
  std::string input(
      "% This is an mCRL2 process specification which can be used for testing\n"
      "% operations on rational numbers.\n"
      "\n"
      "% Tests for unary operations floor, ceil, round, succ, pred, -, abs, succ, pred\n"
      "act  unary_operations : Real # Int # Int # Int # Real # Real # Real # Real;\n"
      "proc Test_unary_operations =\n"
      "       sum x: Int, p: Pos, r: Real. (x >= -5 && x <= 5 && p <= 10 && r == x/p)\n"
      "       -> unary_operations(r, floor(r), ceil(r), round(r), abs(r), -r, succ(r), pred(r))\n"
      "       <> delta;\n"
      "\n"
      "% Tests for binary operations <, <=, >=, >, max, min, +, -, *, /\n"
      "act  binary_operations: Real # Real # Bool # Bool # Bool # Bool # Real # Real # Real # Real # Real # Real;\n"
      "     left_operand: Real;\n"
      "proc Test_binary_operations =\n"
      "       sum x: Int, p: Pos, r: Real. (x >= -5 && x <= 5 && p <= 10 && r == x/p) ->\n"
      "         left_operand(r) .\n"
      "         (sum y: Int, q: Pos, s: Real. (y >= -5 && y <= 5 && q <= 10 && s == y/q)\n"
      "         -> binary_operations(r, s, r < s, r <= s, r >= s, r > s, min(r, s), max(r, s), r + s, r - s, r * s, r / s)\n"
      "         <> delta\n"
      "         )\n"
      "       <> delta;\n"
      "\n"
      "% Tests for exponentation\n"
      "act  base: Real;\n"
      "     exponent: Real # Int # Real;\n"
      "proc Test_exponentation =\n"
      "       sum x: Int, p: Pos, r: Real. (x >= -5 && x <= 5 && p <= 10 && r == x/p)\n"
      "       -> (base(r) . (sum y: Int. (y >= -5 && y <= 5) -> exponent(r, y, exp(r, y)) <> delta))\n"
      "       <> delta;\n"
      "\n"
      "init Test_unary_operations + Test_binary_operations + Test_exponentation;\n"
  );

  specification s=remove_stochastic_operators(linearise(input));

  std::string output;
  output = lps::pp(s);

  // Check whether the symbol @ occurs in the pretty printed output. If this is
  // the case, still some internal symbol is exposed. As a result, our parsers
  // will not be able to handle the specification as input.
  BOOST_CHECK(output.find('@') == std::string::npos);
}

BOOST_AUTO_TEST_CASE(no_summands)
{
  specification spec;
  std::string s = lps::pp(spec);
  std::cout << "--- no_summands ---\n" << s << std::endl;
  BOOST_CHECK(s.find("delta @ 0") != std::string::npos);
}

#ifdef MCRL2_PRINT_PROBLEM_CASES
void test_specification(const std::string& spec_text)
{
  specification x = parse_linear_process_specification(spec_text);

  std::string s1 = lps::pp(x);
  std::string s2 = lps::print(x);
  if (s1 != s2)
  {
    std::clog << "--- testing spec ---" << std::endl;
    std::clog << "<pp>   " << s1 << std::endl;
    std::clog << "<print>" << s2 << std::endl;
    BOOST_CHECK(s1 == s2);
  }
}

BOOST_AUTO_TEST_CASE(problem_cases)
{
  std::string SPEC;

  SPEC =
    "act  a: Bool;         \n"
    "proc P =  a(true) . P;\n"
    "init P;               \n"
    ;
  test_specification(SPEC);
}
#endif

BOOST_AUTO_TEST_CASE(ticket_1208)
{
  std::string text =
    "act a:Nat;                  \n"
    "init a(n whr n=m, m=3 end); \n"
    ;
  try
  {
    specification x = parse_linear_process_specification(text);
  }
  catch (const mcrl2::runtime_error& e)
  {
    std::string s = e.what();
    BOOST_CHECK(s.find("n whr n=m, m=3 end") != std::string::npos);
  }
}

BOOST_AUTO_TEST_CASE(ticket_1267)
{
  std::string text =
    "act a: Nat;                                      \n"
    "proc loop(n: Nat) = a(n) . loop((n + 1) mod 10); \n"
    "init loop(0);                                    \n"
    ;
  specification x = parse_linear_process_specification(text);
  auto const& a = x.process().action_summands().front().multi_action();
  std::string s = lps::pp(a);
  std::cout << "s = " << s << std::endl;
  BOOST_CHECK(s == "a(n)");
}

boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[])
{
  return nullptr;
}


#include "caustic_observer.hpp"
#include "interpolation.hpp"
#include "fileIO.hpp"
#include "master_observer.hpp"
#include "initial_conditions/initial_conditions.hpp"
#include <boost/numeric/ublas/io.hpp>
#include <boost/lexical_cast.hpp>
#include "potential.hpp"

// forward declarations:
// helper functions that determine the area/volume spanned by the particle state and
// the monodromy deltas
double getSignedArea2D(const State& particle, const InitialCondition& IC);
double getSignedArea3D(const State& particle, const InitialCondition& IC);

CausticObserver::CausticObserver( std::size_t dimension, bool breakOnFirst, std::string file_name ) :
        ThreadLocalObserver( std::move(file_name) ),
        mBreakOnFirst( breakOnFirst ), mDimension(dimension) {
    if(mDimension < 2 || mDimension > 3)
        THROW_EXCEPTION(std::invalid_argument, "Dimension for caustic observer must be 2 or 3 but got %1%", mDimension);
}

CausticObserver::~CausticObserver()
{
}

void CausticObserver::combine(ThreadLocalObserver& other)
{
    auto& data = dynamic_cast<CausticObserver&>( other );
    // move all caustic positions to root observer
    std::move( data.mCausticPositions.begin(),
               data.mCausticPositions.end(),
               std::back_inserter(mCausticPositions)    );

    // calculate total particle number
    mParticleNumber = std::max(mParticleNumber, data.mParticleNumber);
}

const CausticObserver::container_type& CausticObserver::getCausticPositions() const
{
    return mCausticPositions;
}

void CausticObserver::startTrajectory( const InitialCondition& start, std::size_t trajectory )
{
    mOldArea = 0;
    mCachedInitialCondition = &start;
    mCausticCount = 0;
    mParticleNumber = trajectory;
}

bool CausticObserver::watch( const State& state, double t )
{
    // calculate enclosed volume
    double signed_area = 0;
    if( mDimension == 2)
         signed_area = getSignedArea2D( state, *mCachedInitialCondition );
    else if(mDimension == 3)
        signed_area = getSignedArea3D( state, *mCachedInitialCondition );
    else
        assert(0);

    // check for sign change
    // we need to check that we are not in the initial frame, because this would cause
    // problems for spherical ICs (NaN; div by zero)
    if(t > 0 && (signed_area * mOldArea < 0 || signed_area == 0 ) )
    {
        // linear interpolate to find position where signed_area was 0
        const auto& pos = state.getPosition();
        // a = (signed_area - mOldArea)
        // A = a * p + mOldArea = 0 => p = -mOldArea / a
        double p = - mOldArea / (signed_area - mOldArea);
        mCausticCount++;

        // create new caustic object and save in data vector
        // only thread-local, so no protection required
        auto position = interpolate_linear_1d(mOldPosition, pos, p);
        auto velocity = interpolate_linear_1d(mOldVelocity, state.getVelocity(), p);
        mCausticPositions.emplace_back( mParticleNumber,
                                        position,
                                        mCachedInitialCondition->getState().getPosition(),
                                        velocity,
                                        mCachedInitialCondition->getState().getVelocity(),
                                        interpolate_linear_1d( mOldTime, t, p ),
                                        mCausticCount );

        // break if we are only looking for first caustic
        if( mBreakOnFirst )
            return false;
    }

    // update cache variables for old state
    mOldArea = signed_area;
    mOldPosition = state.getPosition();
    mOldVelocity = state.getVelocity();
    mOldTime = t;

    return true;
}

void CausticObserver::save(std::ostream& target)
{
    // file header
    target << "caus001\n";
    writeInteger(target, mParticleNumber );
    writeInteger(target, mDimension );
    writeInteger(target, mCausticPositions.size() );

    for(const auto& c : mCausticPositions)
        c.write(target);
}

//

// helper function template to get area between particle velocity and deltas
double getSignedArea2D(const State& particle, const InitialCondition& IC)
{
    auto& d = particle.getMatrix();
    auto delta = IC.getDelta(0).getPhaseSpaceVector();

    boost::numeric::ublas::c_vector<double, 4> result = prod(d, delta);

    return result[0] * particle.getVelocity()[1] - result[1] * particle.getVelocity()[0];
}

double getSignedArea3D(const State& particle, const InitialCondition& IC)
{
    typedef boost::numeric::ublas::c_vector<double, 6> vec_t;
    /// \todo check for 2d initial condition
    auto& d = particle.getMatrix();
    auto delta = IC.getDelta(0).getPhaseSpaceVector();

    /// \todo use 3D vectors here
    vec_t v1 = prod(d, delta);

    delta = IC.getDelta(1).getPhaseSpaceVector();

    vec_t v2 = prod(d, delta);

    std::array<double, 3> v1xv2;
    /// \todo right now we cannot use cross product here, because v1, v2 are 6 element vectors
    v1xv2[0] =  v1[1]*v2[2] - v1[2]*v2[1];
    v1xv2[1] = -v1[0]*v2[2] + v1[2]*v2[0];
    v1xv2[2] = v1[0]*v2[1] - v1[1]*v2[0];
    //crossProduct(v1xv2, v1, v2);

    return std::inner_product(v1xv2.begin(), v1xv2.end(), particle.getVelocity().begin(), 0.0);
}

std::shared_ptr<ThreadLocalObserver> CausticObserver::clone() const
{
    return std::make_shared<CausticObserver>( mDimension, mBreakOnFirst, filename() );
}