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

#ifndef NETKET_GROUNDSTATE_HPP
#define NETKET_GROUNDSTATE_HPP

#include <Eigen/Dense>
#include <Eigen/IterativeLinearSolvers>
#include <complex>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>
#include "Machine/machine.hpp"
#include "Observable/observable.hpp"
#include "Optimizer/optimizer.hpp"
#include "Sampler/sampler.hpp"
#include "Stats/stats.hpp"
#include "Utils/parallel_utils.hpp"
#include "Utils/random_utils.hpp"
#include "matrix_replacement.hpp"

namespace netket {

// Learning schemes for the ground state
// Available methods:
// 1) Stochastic reconfiguration optimizer
//   both direct and sparse version
// 2) Gradient Descent optimizer
class GroundState {
  using GsType = std::complex<double>;

  using VectorT =
      Eigen::Matrix<typename Machine<GsType>::StateType, Eigen::Dynamic, 1>;
  using MatrixT = Eigen::Matrix<typename Machine<GsType>::StateType,
                                Eigen::Dynamic, Eigen::Dynamic>;

  Hamiltonian &ham_;
  Sampler<Machine<GsType>> &sampler_;
  Machine<GsType> &psi_;

  std::vector<std::vector<int>> connectors_;
  std::vector<std::vector<double>> newconfs_;
  std::vector<std::complex<double>> mel_;

  Eigen::VectorXcd elocs_;
  MatrixT Ok_;
  VectorT Okmean_;

  Eigen::MatrixXd vsamp_;

  Eigen::VectorXcd grad_;
  Eigen::VectorXcd gradprev_;

  std::complex<double> elocmean_;
  double elocvar_;
  int npar_;

  double sr_diag_shift_;
  bool sr_rescale_shift_;
  bool use_iterative_;

  int totalnodes_;
  int mynode_;

  std::ofstream filelog_;
  std::string filewfname_;
  int freqbackup_;

  Optimizer &opt_;

  Observables obs_;
  ObsManager obsmanager_;
  json outputjson_;

  bool dosr_;

  int nsamples_;
  int nsamples_node_;
  int ninitsamples_;
  int ndiscardedsamples_;
  int niter_opt_;

 public:
  // JSON constructor
  GroundState(Hamiltonian &ham, Sampler<Machine<GsType>> &sampler,
              Optimizer &opt, const json &pars)
      : ham_(ham),
        sampler_(sampler),
        psi_(sampler.Psi()),
        opt_(opt),
        obs_(ham.GetHilbert(), pars) {
    Init();

    nsamples_ = FieldVal(pars["Learning"], "Nsamples", "Learning");

    nsamples_node_ = int(std::ceil(double(nsamples_) / double(totalnodes_)));

    ninitsamples_ =
        FieldOrDefaultVal(pars["Learning"], "DiscardedSamplesOnInit", 0.);

    ndiscardedsamples_ = FieldOrDefaultVal(pars["Learning"], "DiscardedSamples",
                                           0.1 * nsamples_node_);

    niter_opt_ = FieldVal(pars["Learning"], "NiterOpt", "Learning");

    std::string file_base =
        FieldVal(pars["Learning"], "OutputFile", "Learning");

    int freqbackup = FieldOrDefaultVal(pars["Learning"], "SaveEvery", 50);
    SetOutName(file_base, freqbackup);

    if (pars["Learning"]["Method"] == "Gd") {
      dosr_ = false;
    } else {
      double diagshift = FieldOrDefaultVal(pars["Learning"], "DiagShift", 0.01);
      bool rescale_shift =
          FieldOrDefaultVal(pars["Learning"], "RescaleShift", false);
      bool use_iterative =
          FieldOrDefaultVal(pars["Learning"], "UseIterative", false);

      setSrParameters(diagshift, rescale_shift, use_iterative);
    }

    if (dosr_) {
      InfoMessage() << "Using the Stochastic reconfiguration method"
                    << std::endl;
      if (use_iterative_) {
        InfoMessage() << "With iterative solver" << std::endl;
      }
    } else {
      InfoMessage() << "Using a gradient-descent based method" << std::endl;
    }

    Run();
  }

  void Init() {
    npar_ = psi_.Npar();

    opt_.Init(psi_.GetParameters());

    grad_.resize(npar_);
    Okmean_.resize(npar_);

    freqbackup_ = 0;

    setSrParameters();

    MPI_Comm_size(MPI_COMM_WORLD, &totalnodes_);
    MPI_Comm_rank(MPI_COMM_WORLD, &mynode_);

    InfoMessage() << "Learning running on " << totalnodes_ << " processes"
                  << std::endl;

    MPI_Barrier(MPI_COMM_WORLD);
  }

  void InitSweeps() {
    sampler_.Reset();

    for (int i = 0; i < ninitsamples_; i++) {
      sampler_.Sweep();
    }
  }

  void Sample() {
    sampler_.Reset();

    for (int i = 0; i < ndiscardedsamples_; i++) {
      sampler_.Sweep();
    }

    vsamp_.resize(nsamples_node_, psi_.Nvisible());

    for (int i = 0; i < nsamples_node_; i++) {
      sampler_.Sweep();
      vsamp_.row(i) = sampler_.Visible();
    }
  }

  // Sets the name of the files on which the logs and the wave-function
  // parameters are saved the wave-function is saved every freq steps
  void SetOutName(const std::string &filebase, double freq = 50) {
    filelog_.open(filebase + std::string(".log"));
    freqbackup_ = freq;

    filewfname_ = filebase + std::string(".wf");
  }

  void Gradient() {
    obsmanager_.Reset("Energy");
    obsmanager_.Reset("EnergyVariance");

    for (std::size_t i = 0; i < obs_.Size(); i++) {
      obsmanager_.Reset(obs_(i).Name());
    }

    const int nsamp = vsamp_.rows();
    elocs_.resize(nsamp);
    Ok_.resize(nsamp, psi_.Npar());

    for (int i = 0; i < nsamp; i++) {
      elocs_(i) = Eloc(vsamp_.row(i));
      Ok_.row(i) = psi_.DerLog(vsamp_.row(i));
      obsmanager_.Push("Energy", elocs_(i).real());

      for (std::size_t k = 0; k < obs_.Size(); k++) {
        obsmanager_.Push(obs_(k).Name(), ObSamp(obs_(k), vsamp_.row(i)));
      }
    }

    elocmean_ = elocs_.mean();
    SumOnNodes(elocmean_);
    elocmean_ /= double(totalnodes_);

    Okmean_ = Ok_.colwise().mean();
    SumOnNodes(Okmean_);
    Okmean_ /= double(totalnodes_);

    Ok_ = Ok_.rowwise() - Okmean_.transpose();

    elocs_ -= elocmean_ * Eigen::VectorXd::Ones(nsamp);

    for (int i = 0; i < nsamp; i++) {
      obsmanager_.Push("EnergyVariance", std::norm(elocs_(i)));
    }

    grad_ = 2. * (Ok_.adjoint() * elocs_);

    // Summing the gradient over the nodes
    SumOnNodes(grad_);
    grad_ /= double(totalnodes_ * nsamp);
  }

  std::complex<double> Eloc(const Eigen::VectorXd &v) {
    ham_.FindConn(v, mel_, connectors_, newconfs_);

    assert(connectors_.size() == mel_.size());

    auto logvaldiffs = (psi_.LogValDiff(v, connectors_, newconfs_));

    assert(mel_.size() == std::size_t(logvaldiffs.size()));

    std::complex<double> eloc = 0;

    for (int i = 0; i < logvaldiffs.size(); i++) {
      eloc += mel_[i] * std::exp(logvaldiffs(i));
    }

    return eloc;
  }

  double ObSamp(Observable &ob, const Eigen::VectorXd &v) {
    ob.FindConn(v, mel_, connectors_, newconfs_);

    assert(connectors_.size() == mel_.size());

    auto logvaldiffs = (psi_.LogValDiff(v, connectors_, newconfs_));

    assert(mel_.size() == std::size_t(logvaldiffs.size()));

    std::complex<double> obval = 0;

    for (int i = 0; i < logvaldiffs.size(); i++) {
      obval += mel_[i] * std::exp(logvaldiffs(i));
    }

    return obval.real();
  }

  double ElocMean() { return elocmean_.real(); }

  double Elocvar() { return elocvar_; }

  void Run() {
    opt_.Reset();

    InitSweeps();

    for (int i = 0; i < niter_opt_; i++) {
      Sample();

      Gradient();

      UpdateParameters();

      PrintOutput(i);
    }
  }

  void UpdateParameters() {
    auto pars = psi_.GetParameters();

    if (dosr_) {
      const int nsamp = vsamp_.rows();

      Eigen::VectorXcd b = Ok_.adjoint() * elocs_;
      SumOnNodes(b);
      b /= double(nsamp * totalnodes_);

      if (!use_iterative_) {
        // Explicit construction of the S matrix
        Eigen::MatrixXcd S = Ok_.adjoint() * Ok_;
        SumOnNodes(S);
        S /= double(nsamp * totalnodes_);

        // Adding diagonal shift
        S += Eigen::MatrixXd::Identity(pars.size(), pars.size()) *
             sr_diag_shift_;

        Eigen::FullPivHouseholderQR<Eigen::MatrixXcd> qr(S.rows(), S.cols());
        qr.setThreshold(1.0e-6);
        qr.compute(S);
        const Eigen::VectorXcd deltaP = qr.solve(b);
        // Eigen::VectorXcd deltaP=S.jacobiSvd(ComputeThinU |
        // ComputeThinV).solve(b);

        assert(deltaP.size() == grad_.size());
        grad_ = deltaP;

        if (sr_rescale_shift_) {
          std::complex<double> nor = (deltaP.dot(S * deltaP));
          grad_ /= std::sqrt(nor.real());
        }

      } else {
        Eigen::ConjugateGradient<MatrixReplacement, Eigen::Lower | Eigen::Upper,
                                 Eigen::IdentityPreconditioner>
            it_solver;
        // Eigen::GMRES<MatrixReplacement, Eigen::IdentityPreconditioner>
        // it_solver;
        it_solver.setTolerance(1.0e-3);
        MatrixReplacement S;
        S.attachMatrix(Ok_);
        S.setShift(sr_diag_shift_);
        S.setScale(1. / double(nsamp * totalnodes_));

        it_solver.compute(S);
        auto deltaP = it_solver.solve(b);

        grad_ = deltaP;
        if (sr_rescale_shift_) {
          auto nor = deltaP.dot(S * deltaP);
          grad_ /= std::sqrt(nor.real());
        }

        // if(mynode_==0){
        //   std::cerr<<it_solver.iterations()<<"
        //   "<<it_solver.error()<<std::endl;
        // }
        MPI_Barrier(MPI_COMM_WORLD);
      }
    }

    opt_.Update(grad_, pars);

    SendToAll(pars);

    psi_.SetParameters(pars);
    MPI_Barrier(MPI_COMM_WORLD);
  }

  void PrintOutput(int i) {
    auto Acceptance = sampler_.Acceptance();

    auto jiter = json(obsmanager_);
    jiter["Iteration"] = i;
    outputjson_["Output"].push_back(jiter);

    if (mynode_ == 0) {
      if (jiter["Iteration"] != 0) {
        long pos = filelog_.tellp();
        filelog_.seekp(pos - 3);
        filelog_.write(",  ", 3);
        filelog_ << jiter << "]}" << std::endl;
      } else {
        filelog_ << outputjson_ << std::endl;
      }
    }

    if (mynode_ == 0 && freqbackup_ > 0 && std::fmod(i, freqbackup_) < 0.5) {
      psi_.Save(filewfname_);
    }

    MPI_Barrier(MPI_COMM_WORLD);
  }

  void setSrParameters(double diagshift = 0.01, bool rescale_shift = false,
                       bool use_iterative = false) {
    sr_diag_shift_ = diagshift;
    sr_rescale_shift_ = rescale_shift;
    use_iterative_ = use_iterative;
    dosr_ = true;
  }
};

}  // namespace netket

#endif


//  (C) Copyright Nick Thompson 2018.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_MATH_TOOLS_NUMERICAL_DIFFERENTIATION_HPP
#define BOOST_MATH_TOOLS_NUMERICAL_DIFFERENTIATION_HPP
#include <boost/math/differentiation/finite_difference.hpp>
#include <boost/math/tools/header_deprecated.hpp>

BOOST_MATH_HEADER_DEPRECATED("<boost/math/differentiation/finite_difference.hpp>");

#endif


///////////////////////////////////////////////////////////////////
//  Copyright Eduardo Quintana 2021
//  Copyright Janek Kozicki 2021
//  Copyright Christopher Kormanyos 2021
//  Distributed under the Boost Software License,
//  Version 1.0. (See accompanying file LICENSE_1_0.txt
//  or copy at http://www.boost.org/LICENSE_1_0.txt)

#include "math_unit_test.hpp"
#include <boost/math/fft/bsl_backend.hpp>
#include "fft_test_helpers.hpp"
#include <boost/random.hpp>

bool new_is_on{true};

void * operator new(size_t size)
{
  if(new_is_on==false)
    throw std::bad_alloc{};
  void * p = std::malloc(size);
  return p;
}
void * operator new[](size_t size)
{
  if(new_is_on==false)
   throw std::bad_alloc{};
  void * p = std::malloc(size);
  return p;
}

template<class T>
struct my_allocator
{
    using value_type = T;

    my_allocator()
    {
    }

    template<class U>
    my_allocator(const my_allocator<U>&)
    {
    }

    ~my_allocator()
    {
    }
    T* allocate(size_t n)
    {
      void * p = std::malloc(n*sizeof(T));
      if(p==nullptr)
        throw std::bad_alloc{};
      return reinterpret_cast<T*>(p);
    }
    void deallocate(T* p,size_t /*n*/)
    {
      std::free(p);
    }

    template<class U>
    struct rebind { using other = my_allocator<U>;};
};

using namespace boost::math::fft;

template<class Backend>
void test_inverse(int N, int tolerance)
{
  using allocator_type = typename Backend::allocator_type;
  using Complex = typename Backend::value_type;
  using real_value_type = typename Complex::value_type;
  const real_value_type tol = tolerance*std::numeric_limits<real_value_type>::epsilon();

  // int *_ = new int[45]; //  std::bad_alloc
  // int *_ = new int; // std::bad_alloc

  boost::random::mt19937 rng;
  boost::random::uniform_real_distribution<real_value_type> U(0.0,1.0);
  {
    std::vector<Complex,allocator_type> A(N),B(N),C(N);
    for(auto& x: A)
    {
      x.real( U(rng) );
      x.imag( U(rng) );
    }
    Backend plan(N);
    plan.forward(A.data(),A.data()+A.size(),B.data());
    plan.backward(B.data(),B.data()+B.size(),C.data());

    const real_value_type inverse_N = real_value_type{1.0}/N;
    for(auto &x : C)
      x *= inverse_N;

    real_value_type diff{0.0};

    for(size_t i=0;i<A.size();++i)
    {
      using std::norm;
      diff += norm(A[i]-C[i]);
    }
    using std::sqrt;
    diff = sqrt(diff)*inverse_N;
    using std::max;
    using std::abs;

    const real_value_type expected = 0.0;
    const real_value_type computed = diff;

    real_value_type denom = (max)(abs(expected), real_value_type(1));
    real_value_type mollified_relative_error = abs(expected - computed)/denom;
    if (mollified_relative_error > tol)
        ++::boost::math::test::detail::global_error_count;
    // CHECK_MOLLIFIED_CLOSE(real_value_type{0.0},diff,tol);
  }
}

template<class T>
struct complex_bsl_dft
{
  using Complex = boost::multiprecision::complex<T> ;
  using type = bsl_dft< Complex, my_allocator<Complex> >;
};

int main()
{
  new_is_on=false;
  for(int i=1;i<=100;++i)
  {
    test_inverse<complex_bsl_dft<float>::type>(i,2);
    test_inverse<complex_bsl_dft<double>::type>(i,2);
    test_inverse<complex_bsl_dft<long double>::type>(i,2);
  }
  return boost::math::test::report_errors();
}


/* Lemon stuff */
#include <lemon/list_graph.h>
#include <lemon/concepts/maps.h>
#include <lemon/vf2pp.h>

#include "Snap.h"

#include "utilities.h"
#include "temporalmotifstaticsampler.h"
#include <random>
#include "ctpl.h"
#include <mutex>

#include <vector>
#include <algorithm>
#include <iomanip>

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/vf2_sub_graph_iso.hpp>
#include <boost/functional/hash.hpp>
#include <chrono>
#include <unordered_set>
#include <numeric>

typedef boost::adjacency_list< boost::vecS, boost::vecS, boost::bidirectionalS> graph_type;

///////////////////////////////////////////////////////////////////////////////

TempMotifSamplerStatic::TempMotifSamplerStatic(const TStr& filenameG, const TStr& filenameM) {
	static_motif_ = TSnap::LoadEdgeList<PNGraph>(filenameM, 0, 1, ' ');
	static_undirected_graph_ = TSnap::LoadEdgeList<PUNGraph>(filenameG, 0, 1, ' ');
	static_undirected_motif_ = TSnap::LoadEdgeList<PUNGraph>(filenameM, 0, 1, ' ');
	staticEdgesMotif_m = static_undirected_motif_->GetEdges();
	std::cout << "loaded undirected static MOTIF has: " << static_undirected_motif_->GetNodes() << " nodes and: " << static_undirected_motif_->GetEdges() <<'\n';
	std::cout << "Loaded static undirected graph has: " << static_undirected_graph_->GetMxNId() << " max node id and: " << static_undirected_graph_->GetEdges() << " edges, " << static_undirected_graph_->GetNodes() << " nodes" << '\n' ;
	temporal_degrees_.resize(static_undirected_graph_->GetNodes(), 0);

	ems_ = static_motif_->GetEdges();

	temporal_data_ = TVec< THash<TInt, TVec<TInt64> >>(static_undirected_graph_->GetNodes());
	std::cout << "temporal data instatiated" << std::endl;

	m_ = 0;
	Utils::LoadEdgeList(filenameG.CStr(), temporal_data_, std::ref(temporal_degrees_), m_, timespan_m);
	std::cout << "temporal data filled" << std::endl;

	}

TempMotifSamplerStatic::TempMotifSamplerStatic(std::string filenameG, std::string filenameM, bool parallel)
{
	Utils::LoadEdgeList(filenameG, std::ref(edges_), std::ref(temporal_degrees_));

	//Utils::RemapNodes(std::ref(edges_));
	//Utils::PrintGraph(std::ref(edges_));
	//exit(0);

	//std::cout << "Number on nodes from struct : " << allgraph.max_node_id << '\n';

	std::cout << "READ FILE DONE" << std::endl;
	sort(edges_.begin(), edges_.end());
	std::cout << "Number of temporal edges: " << edges_.size() << '\n';
	StaticGraph sg(std::cref(edges_));
	std::cout << "Number of static edges: " << sg.getNumberOfEdges() << '\n';
	staticandtemporalgraph_ = sg.getFullVector();
	std::cout << "STRUCTURE BUILT CORRECTLY" << std::endl;
	size_t max_temporal_edges = 0;
	for(const auto& edge : staticandtemporalgraph_)
	{
		size_t num = edge.second.size();
		if(num > max_temporal_edges)
			max_temporal_edges = num;
	}
	std::cout << "--Maxedges-- mapped on a static one is: " << max_temporal_edges << std::endl;

	std::ifstream fileM(filenameM);
	if(fileM.is_open())
	{
		std::string line;
		int max_nodes = 0;
		while(getline(fileM, line))
		{
			std::istringstream iss(line);
			std::vector<std::string> results(std::istream_iterator<
						std::string>{iss},std::istream_iterator<std::string>());
			if(results.size() != 3)
			{
				std::cerr << "ERROR, motif is not in format src dst order" << std::endl;
				exit(1);
			}
			else
			{
				// Assume the edges are already ordered
				int src =stoi(results[0]);
				int dst =stoi(results[1]);
				if(src != dst)
				{
      				edgesM_.push_back(std::make_pair(src, dst));
	  				l_++; // counting the number of edges
					max_nodes = std::max(max_nodes, src + 1);
					max_nodes = std::max(max_nodes, dst + 1);
				}
			}
		}
		Vm_ = max_nodes;
	}

	fileM.close();
}

int TempMotifSamplerStatic::setMotif(std::string filenameM)
{
	std::ifstream fileM(filenameM);
	edgesM_.clear();
	l_ = 0;
	if(fileM.is_open())
	{
		std::string line;
		int max_nodes = 0;
		while(getline(fileM, line))
		{
			std::istringstream iss(line);
			std::vector<std::string> results(std::istream_iterator<
						std::string>{iss},std::istream_iterator<std::string>());
			if(results.size() != 3)
			{
				std::cerr << "ERROR, motif is not in format src dst order" << std::endl;
				exit(1);
			}
			else
			{
				// Assume the edges are already ordered
				int src =stoi(results[0]);
				int dst =stoi(results[1]);
				if(src != dst)
				{
      				edgesM_.push_back(std::make_pair(src, dst));
	  				l_++; // counting the number of edges
					max_nodes = std::max(max_nodes, src + 1);
					max_nodes = std::max(max_nodes, dst + 1);
				}
			}
		}
		Vm_ = max_nodes;
	}

	fileM.close();

	return 0;
}

double TempMotifSamplerStatic::get_wall_time()
{
	struct timeval time;
	if (gettimeofday(&time,NULL)){
		//  Handle error
		return 0;
	}
	return (double)time.tv_sec + (double)time.tv_usec * .000001;
}

double TempMotifSamplerStatic::EnumerateOnStaticGraph(int delta, int samp, int seed, double timeLimit, int ell, int triORsq)
{

	l_ = ell;
	std::random_device rd;
	std::mt19937 generator(seed);

	std::vector<double> mass;
	double maxdegst { 0};
	double avgdegst {0};
	double alphamax {0};
	double denom{0};
	TUNGraph::TEdgeI edgeIt = static_undirected_graph_->BegEI();
	std::vector<std::pair<node_t, node_t>> edges_exp;
	TVec< THash<TInt, TInt64 >> probsEdges = TVec< THash<TInt, TInt64 >>(static_undirected_graph_->GetNodes());
	double firsttime = get_wall_time();
	for(long long int i=0; i< static_undirected_graph_->GetEdges(); i++)
	{
		node_t src = edgeIt.GetSrcNId();
		node_t dst = edgeIt.GetDstNId();

		(src < dst) ? edges_exp.push_back(std::make_pair<node_t, node_t>(static_cast<node_t>(src), static_cast<node_t>(dst))) : edges_exp.push_back(std::make_pair<node_t, node_t>(static_cast<node_t>(dst), static_cast<node_t>(src)));
		edgeIt++;
		double currmass { 0 };
		if(temporal_data_[src].IsKey(dst))
		{
			currmass += temporal_data_[src](dst).Len();
		}
		double mval = currmass;
		if(temporal_data_[dst].IsKey(src))
		{
			currmass += temporal_data_[dst](src).Len();
		}

		mass.push_back(static_cast<double>(currmass));
		(src < dst) ? probsEdges[src].AddDat(dst, i) : probsEdges[dst].AddDat(src, i);
	}

	std::discrete_distribution<long long int> importance(mass.begin(), mass.end());
	std::vector<double> sampling_probs{ importance.probabilities() };
	double randomtime = get_wall_time() - firsttime;

	double totestimate{ 0};
	int s{ samp };
	double timeEnumerateTri{ 0 };
	double timeBuildSample{ 0 };
	double timeEnumerateTemporal{ 0 };
	double timenow{ 0 };

	double timeSequence{ 0 };
	double timeIso{ 0 };
	double totInstances{ 0 };
	double totStaticInstances{ 0 };
	std::vector<int> numEdges;
	std::vector<int> staticInsts;
	int pruned{ 0 };
	std::unordered_map<std::uint_fast64_t, double> counts;
	double artifactNorm { 0 };
	long long samples1 = 0;
	double init = get_wall_time();
	while( (get_wall_time() - init) < timeLimit)
	{
			samples1++;
			long long int samplednode { importance(generator) };

			std::vector<std::vector<std::pair<node_t, node_t> > > static_graphlets;

			double timemine = get_wall_time();

			if(triORsq == 0)
				Utils::GetWedgesFromEdge(static_undirected_graph_, edges_exp[samplednode], std::ref(static_graphlets) );
			else if(triORsq == 1)
				Utils::GetTrianglesFromEdge(static_undirected_graph_, edges_exp[samplednode], std::ref(static_graphlets) );
			else if(triORsq == 2)
				Utils::GetSquaresFromEdgeMin(static_undirected_graph_, edges_exp[samplednode], std::ref(static_graphlets) );
			else if(triORsq == 3) // Single edge motif
			{
				std::vector<std::pair<node_t,node_t>> mytmpvec;
				mytmpvec.push_back(std::make_pair(0,1)); //placeholder
				static_graphlets.push_back(mytmpvec);
			}
			timeEnumerateTri += (get_wall_time()-timemine);
			timemine = get_wall_time() - timemine;

			if(static_graphlets.size() == 0)
				continue;

			timemine = get_wall_time();
			std::vector<std::pair<node_t, node_t>> staticEdgesInSample;
			double sampled{ 0 };

			totStaticInstances += static_cast<double>(static_graphlets.size());
			std::vector<std::pair<timestamp_t,size_t>> times; //vector of <timestamp, idx_static_edge_In_sample (which corresponds to the timestamp)>

			//Computing on sampled edge to avoid this repeated computation
			std::vector<std::pair<timestamp_t,size_t>> timesSampledEdge;
			node_t sampledSrc = static_cast<node_t>(edges_exp[samplednode].first);
			node_t sampledDst = static_cast<node_t>(edges_exp[samplednode].second);
			std::pair<node_t, node_t> P1{ std::make_pair(sampledSrc, sampledDst) };
			std::pair<node_t, node_t> P2{ std::make_pair(sampledDst, sampledSrc) };
			bool exsistenceSD {false};
			bool exsistenceDS {false};
			if(temporal_data_[sampledSrc].IsKey(sampledDst))
			{
				exsistenceSD = true;
				const TVec<TInt64>& timestamps = temporal_data_[sampledSrc].GetDat(sampledDst);
				for(size_t j=0; j< timestamps.Len(); j++)
				{
					timesSampledEdge.push_back(std::make_pair<timestamp_t, size_t>(timestamps[j], 0));
				}
			}

			if(temporal_data_[sampledDst].IsKey(sampledSrc))
			{
				exsistenceDS = true;
				const TVec<TInt64>& timestamps = temporal_data_[sampledDst].GetDat(sampledSrc);
				for(size_t j=0; j< timestamps.Len(); j++)
				{
					if(exsistenceSD)
						timesSampledEdge.push_back(std::make_pair<timestamp_t, size_t>(timestamps[j], 1));
					else
						timesSampledEdge.push_back(std::make_pair<timestamp_t, size_t>(timestamps[j], 0));
				}
			}
			timeBuildSample += (get_wall_time() - timemine);

			for(size_t k{ 0 }; k < static_graphlets.size(); k++)
			{
				//timenow = get_wall_time();
				timemine = get_wall_time();
				staticEdgesInSample.clear();
				times.clear();

				if(exsistenceSD)
					staticEdgesInSample.push_back(P1);
				if(exsistenceDS)
					staticEdgesInSample.push_back(P2);

				times.insert(times.begin(), timesSampledEdge.begin(), timesSampledEdge.end());

				//Collecting graphlet edges
				long id{0};

				if(triORsq != 3)
				{
					for(const auto& pair : static_graphlets[k])
					{
						node_t src = pair.first;
						node_t dst = pair.second;
						if(temporal_data_[src].IsKey(dst))
						{
							staticEdgesInSample.push_back(std::make_pair<node_t,node_t>(static_cast<node_t>(src), static_cast<node_t>(dst)));
							size_t currentStaticEdges{ staticEdgesInSample.size() };
							const TVec<TInt64>& timestamps = temporal_data_[src].GetDat(dst);
							for(size_t j=0; j< timestamps.Len(); j++)
							{
								times.push_back(std::make_pair<timestamp_t, size_t>(timestamps[j], currentStaticEdges-1));
							}
						}

						if(temporal_data_[dst].IsKey(src))
						{
							staticEdgesInSample.push_back(std::make_pair<node_t,node_t>(static_cast<node_t>(dst), static_cast<node_t>(src)));
							size_t currentStaticEdges{ staticEdgesInSample.size() };
							const TVec<TInt64>& timestamps = temporal_data_[dst].GetDat(src);
							for(size_t j=0; j< timestamps.Len(); j++)
							{
								times.push_back(std::make_pair<timestamp_t, size_t>(timestamps[j], currentStaticEdges-1));
							}
						}
					}
				}

					std::sort(times.begin(), times.end());
					timeBuildSample += (get_wall_time() - timemine);

					timemine = get_wall_time();
					bool foundCandidate{ false };

					// Pruning
					for(long long int t{ 0 }; t < static_cast<long long int>(times.size())-l_+1; t++)
					{
						if( ((times[t+l_-1].first - times[t].first) <= delta ))
						{
								foundCandidate = true;
								break;
						}
					}
					timeEnumerateTemporal += (get_wall_time() - timemine);
					if(!foundCandidate)
					{
						pruned++;
					}
					else
					{
							timemine = get_wall_time();
							double graphlet_prob{0};
							graphlet_prob += sampling_probs[samplednode];

							double weight { static_cast<double>(1./graphlet_prob) };

							double ressamp  = FastCountSequence(delta, std::ref(times), std::ref(staticEdgesInSample), timeSequence, timeIso, std::ref(counts), graphlet_prob);
							totInstances += ressamp;
							numEdges.push_back(static_cast<int>(times.size()));

							ressamp = ressamp * weight;

							sampled++;

							totestimate += ressamp;
							timeEnumerateTemporal += (get_wall_time() - timemine);
					}

			} // TODO: Delete tmp pointers!!

	}

	double tottime = get_wall_time() - firsttime;
	std::cout.precision(2);
	for(auto& el : counts)
	{
		std::cout << " count is: " <<  std::fixed << el.second/(1.*samples1*static_undirected_motif_->GetEdges()) << " motif is: " << std::hex << el.first << std::dec;
		std::cout << '\n';
	}
	std::cout << "T0 time spent in building prob distrib: " << std::fixed << randomtime  << '\n';
	std::cout << "T1 time spent in static enumeration: " << std::fixed << timeEnumerateTri  << '\n';
	std::cout << "T2 time spent in building sample: " << std::fixed << timeBuildSample  << '\n';
	std::cout << "T3 time spent in temporal enumeration: " << std::fixed << timeEnumerateTemporal  << '\n';
	std::cout << "T3-a time spent in temporal enumeration when enumerating sequences: " << std::fixed << timeSequence  << '\n';
	std::cout << "T3-b time spent in temporal enumeration with isomorphism: " << std::fixed << timeIso  << '\n';
	std::cout << "T4 time spent in whole precedure: " << std::fixed << tottime  << '\n';

	std::cout << "samples s examined: " << samples1 << '\n';
	return totestimate/s;
}

double TempMotifSamplerStatic::EnumerateOnStaticGraphParallel(int delta, int samp, int seed, double timeLimit, int ell, int triORsq, int nthreads)
{

	l_ = ell;
	double firsttime = get_wall_time();
	std::random_device rd;
	std::mt19937 generator(seed);

	std::vector<double> mass;
	std::vector<double> mass2v;
	std::vector<double> mass3v;
	TUNGraph::TEdgeI edgeIt = static_undirected_graph_->BegEI();
	std::vector<std::pair<node_t, node_t>> edges_exp;
	TVec< THash<TInt, TInt64 >> probsEdges = TVec< THash<TInt, TInt64 >>(static_undirected_graph_->GetNodes());
	for(long long int i=0; i< static_undirected_graph_->GetEdges(); i++)
	{
		node_t src = edgeIt.GetSrcNId();
		node_t dst = edgeIt.GetDstNId();
		(src < dst) ? edges_exp.push_back(std::make_pair<node_t, node_t>(static_cast<node_t>(src), static_cast<node_t>(dst))) : edges_exp.push_back(std::make_pair<node_t, node_t>(static_cast<node_t>(dst), static_cast<node_t>(src)));
		edgeIt++;
		double currmass { 0 };
		if(temporal_data_[src].IsKey(dst))
		{
			currmass += temporal_data_[src](dst).Len();
		}
		if(temporal_data_[dst].IsKey(src))
		{
			currmass += temporal_data_[dst](src).Len();
		}

		mass.push_back(static_cast<double>(currmass));
		(src < dst) ? probsEdges[src].AddDat(dst, i) : probsEdges[dst].AddDat(src, i);
	}

	std::discrete_distribution<long long int> importance(mass.begin(), mass.end());
	std::vector<double> sampling_probs{ importance.probabilities() };
	double randomtime = get_wall_time() - firsttime;

	double totestimate{ 0};
	int s{ samp };
	double timeEnumerateTri{ 0 };
	double timeBuildSample{ 0 };
	double timeEnumerateTemporal{ 0 };
	double timenow{ 0 };
	ctpl::thread_pool p(nthreads);
	std::mutex insertSolution;
	std::mutex printLock;
	std::mutex useRandom;
	std::vector<std::unordered_map<std::uint_fast64_t, double>> results;
	std::vector<std::future<bool>> futures;

	std::vector<int> numEdges;
	std::vector<int> staticInsts;
	long long samples1 = 0;
	double init = get_wall_time();

	//DEFINING LAMBDA
	auto sampleAndEstimate{ [&](int ID) mutable -> bool {

	std::unordered_map<std::uint_fast64_t, double> counts;
	useRandom.lock();
	long long int samplednode { importance(generator) };
	useRandom.unlock();

	std::vector<std::vector<std::pair<node_t, node_t> > > static_graphlets;

	if(triORsq == 0)
		Utils::GetWedgesFromEdge(static_undirected_graph_, edges_exp[samplednode], std::ref(static_graphlets) );
	else if(triORsq == 1)
		Utils::GetTrianglesFromEdgeThreads(std::cref(static_undirected_graph_), std::cref(edges_exp[samplednode]), std::ref(static_graphlets) );
	else if(triORsq == 2)
		Utils::GetSquaresFromEdgeMin(static_undirected_graph_, edges_exp[samplednode], std::ref(static_graphlets) );

	if(static_graphlets.size() == 0)
	{

		return true;
	}

	std::vector<std::pair<node_t, node_t>> staticEdgesInSample;
	double sampled{ 0 };

	std::vector<std::pair<timestamp_t,size_t>> times; //vector of <timestamp, idx_static_edge_In_sample (which corresponds to the timestamp)>

	//Computing on sampled edge to avoid this repeated computation
	std::vector<std::pair<timestamp_t,size_t>> timesSampledEdge;
	node_t sampledSrc = static_cast<node_t>(edges_exp[samplednode].first);
	node_t sampledDst = static_cast<node_t>(edges_exp[samplednode].second);
	std::pair<node_t, node_t> P1{ std::make_pair(sampledSrc, sampledDst) };
	std::pair<node_t, node_t> P2{ std::make_pair(sampledDst, sampledSrc) };
	bool exsistenceSD {false};
	bool exsistenceDS {false};
	if(temporal_data_[sampledSrc].IsKey(sampledDst))
	{
		exsistenceSD = true;
		const TVec<TInt64>& timestamps = temporal_data_[sampledSrc].GetDat(sampledDst);
		for(size_t j=0; j< timestamps.Len(); j++)
		{
			timesSampledEdge.push_back(std::make_pair<timestamp_t, size_t>(timestamps[j], 0));
		}
	}

	if(temporal_data_[sampledDst].IsKey(sampledSrc))
	{
		exsistenceDS = true;
		const TVec<TInt64>& timestamps = temporal_data_[sampledDst].GetDat(sampledSrc);
		for(size_t j=0; j< timestamps.Len(); j++)
		{
			if(exsistenceSD)
				timesSampledEdge.push_back(std::make_pair<timestamp_t, size_t>(timestamps[j], 1));
			else
				timesSampledEdge.push_back(std::make_pair<timestamp_t, size_t>(timestamps[j], 0));
		}
	}

	for(size_t k{ 0 }; k < static_graphlets.size(); k++)
	{
		staticEdgesInSample.clear();
		times.clear();

		if(exsistenceSD)
			staticEdgesInSample.push_back(P1);
		if(exsistenceDS)
			staticEdgesInSample.push_back(P2);

		times.insert(times.begin(), timesSampledEdge.begin(), timesSampledEdge.end());

		//Collecting graphlet edges
		long id{0};

			for(const auto& pair : static_graphlets[k])
			{
				node_t src = pair.first;
				node_t dst = pair.second;
				if(temporal_data_[src].IsKey(dst))
				{
					staticEdgesInSample.push_back(std::make_pair<node_t,node_t>(static_cast<node_t>(src), static_cast<node_t>(dst)));
					size_t currentStaticEdges{ staticEdgesInSample.size() };
					const TVec<TInt64>& timestamps = temporal_data_[src].GetDat(dst);
					for(size_t j=0; j< timestamps.Len(); j++)
					{
						times.push_back(std::make_pair<timestamp_t, size_t>(timestamps[j], currentStaticEdges-1));
					}
				}

				if(temporal_data_[dst].IsKey(src))
				{
					staticEdgesInSample.push_back(std::make_pair<node_t,node_t>(static_cast<node_t>(dst), static_cast<node_t>(src)));
					size_t currentStaticEdges{ staticEdgesInSample.size() };
					const TVec<TInt64>& timestamps = temporal_data_[dst].GetDat(src);
					for(size_t j=0; j< timestamps.Len(); j++)
					{
						times.push_back(std::make_pair<timestamp_t, size_t>(timestamps[j], currentStaticEdges-1));
					}
				}
			}

			std::sort(times.begin(), times.end());

			bool foundCandidate{ false };

			for(long long int t{ 0 }; t < static_cast<long long int>(times.size())-l_+1; t++)
			{
				if( ((times[t+l_-1].first - times[t].first) <= delta ))
				{
						foundCandidate = true;
						break;
				}
			}
			if(!foundCandidate)
			{
				continue;
			}
			else
			{
					double timeSequence{0};
					double timeIso{0};
					//Computing probability and checking if the selected vertex is in the graphlet
					double graphlet_prob{0};
					graphlet_prob += sampling_probs[samplednode];

					double ressamp  = FastCountSequence(delta, std::ref(times), std::ref(staticEdgesInSample), timeSequence, timeIso, std::ref(counts), graphlet_prob);
			}

	}
	if(counts.size() != 0)
	{
		insertSolution.lock();
		results.push_back(counts);
		insertSolution.unlock();
	}
	return true;
	} };

	for(long long int ii{ 0 }; ii < s; ii++)
	{
			futures.push_back(p.push(std::ref(sampleAndEstimate)));
	}

	for(int i{ 0}; i < futures.size(); i++)
	{
		bool correct = futures[i].get();
		if(!correct)
			std::cout << "Thread: " << " experienced an error!" << std::endl;
	}
	std::unordered_map<std::uint_fast64_t, double> counts;
	for(auto& map : results)
	{
		for(auto& pair : map)
		{
			if(counts.find(pair.first) != counts.end())
				counts[pair.first] += pair.second;
			else
				counts[pair.first] = pair.second;
		}
	}

	double tottime = get_wall_time() - firsttime;
	std::cout.precision(2);
	std::cout << "Time to build the probability distribution: " << randomtime << '\n';
	std::cout << "Innercycle launching time: " << tottime << '\n';
	for(auto& el : counts)
	{
		std::cout << " count is: " <<  std::fixed << el.second/(1.*s*static_undirected_motif_->GetEdges()) << " motif is: " << std::hex << el.first << std::dec;
		std::cout << '\n';
	}
	return 0;

}

double TempMotifSamplerStatic::FastCountSequence(int delta, std::vector<std::pair<timestamp_t, size_t>>& tmp, std::vector<std::pair<node_t, node_t>>& staticEdgesInSample, double& timeSequence, double& timeIso, std::unordered_map<std::uint_fast64_t, double>& counts, double instanceProbability)
{
	//double startT{ get_wall_time() };
	//std::sort(tmp.begin(), tmp.end());

	/*
	std::cout << "\n--------------------------------\n";
	for(size_t i{ 0 }; i < tmp->size(); i++)
		std::cout << "source: " << (*tmp)[i].src << " dst: " << (*tmp)[i].dst << " tim: " << (*tmp)[i].tim << '\n';
	std::cout << "--------------------------------\n";
	for(auto& pair : staticEdgesInSample)
		std::cout << "source: " << pair.first << " dst: " << pair.second << '\n';
	*/

	//std::unordered_map<std::pair<node_t,node_t>, int, DataStructures::hash_pair> hashID; //hashmap: <edge, edgeID>
	std::unordered_map<std::pair<node_t,node_t>, std::uint_fast64_t, DataStructures::hash_pair> hashID; //hashmap: <edge, edgeID>
	std::vector<std::pair<node_t,node_t>> IDReverse; //Needed to reconstruct the motif graph
	std::uint_fast64_t ID { 1 }; // WARNING: Up to 15 static edges ranging from 1 to 15 otherwise enlarge edgeMask or use integer >64 bits
	for(auto tuple : staticEdgesInSample) // cycling over the static directed edges of the sample
	{
		IDReverse.push_back(tuple);
		hashID[tuple] = ID++;
	}
	std::vector<std::unordered_set<std::uint_fast64_t>> keysLength; // each entry i=0,...,l-1 keeps the keys of length i+1 that have count > 0;
	for(int i{ 0}; i < l_; i++)
	{
		std::unordered_set<std::uint_fast64_t> newset;
		keysLength.push_back(newset);
	}
	std::unordered_map<std::uint_fast64_t, long long> hashCounts; //hasmap["sequence"] = "count of the sequence"
	size_t start { 0 };
	for(const auto& time : tmp )
	{
		while( (time.first - tmp[start].first) > delta )
		{

			std::uint_fast64_t decID { hashID[staticEdgesInSample[tmp[start].second]] } ;
			DecrementCounts(decID, std::ref(hashCounts), std::ref(keysLength));
			start++;
		}

		std::uint_fast64_t incID { hashID[staticEdgesInSample[time.second]] } ;
		IncrementCounts(incID, std::ref(hashCounts), std::ref(keysLength));

	}

	std::vector<std::pair<node_t,node_t>> thisSequence;
	double result{ 0 };
	for(auto& key : keysLength[l_-1])
	{
		thisSequence.clear();
		for(std::uint_fast64_t i{ 0 }; i < static_cast<std::uint_fast64_t>(l_); i++)
		{
			int keyedge = getDigit(key, l_-i);
			std::pair<node_t, node_t> edge{ IDReverse[keyedge-1] };
			thisSequence.push_back(edge);
		}
		int edgeStatic = GetStaticEdgesInstance(std::ref(thisSequence));
		//NOTE: test for isomorphism here is not needed since we sampled only static graphs isomorphic to H
		// thus by the algorithm above each static projection of a motif can have up to |E_H| edges, but we know
		// that we require exactly |E_H| edges otherwise for sure the two motifs are non isomorphic!
		if(edgeStatic == staticEdgesMotif_m) // The instance is isomorphic to the given static graph (?) || here one can implement the constraint on q
		{
			uint_fast64_t encode = ComputeEncoding(std::ref(thisSequence));
			if(counts.find(encode) == counts.end())
			{
				counts[encode] = (hashCounts[key] * 1/instanceProbability) ;
			}
			else
				counts[encode] += (hashCounts[key] * 1/instanceProbability);
		}
		//else
		//	continue;
	}

	return result;
}

bool TempMotifSamplerStatic::testIsomorphism(std::vector<std::pair<node_t, node_t>>& nodeSequence, std::vector<std::pair<int, int>>& motif)
{
	std::vector<node_t> mapSequence(Vm_, -1);
	int currentEdge{ 0 };
	std::unordered_set<node_t> alreadyMapped;
	for(const auto& edge : motif)
	{
		int srcM = edge.first;
		int dstM = edge.second;
		node_t srcSeq = nodeSequence[currentEdge].first;
		node_t dstSeq = nodeSequence[currentEdge].second;
		if(mapSequence[srcM] == -1) // node not mapped
		{
			if(alreadyMapped.find(srcSeq) == alreadyMapped.end())
			{
				mapSequence[srcM] = srcSeq;
				alreadyMapped.insert(srcSeq);
			}
			else // Node in sequence was already assigned
				return false;
		}
		else // node is mapped
		{
			if(!(mapSequence[srcM] == srcSeq))
				return false;
		}
		if(mapSequence[dstM] == -1) // node not mapped
		{
			if(alreadyMapped.find(dstSeq) == alreadyMapped.end())
			{
				mapSequence[dstM] = dstSeq;
				alreadyMapped.insert(dstSeq);
			}
			else // Node in sequence was already assigned
				return false;
		}
		else // node is mapped
		{
			if(!(mapSequence[dstM] == dstSeq))
				return false;
		}
		currentEdge++;
	}
	return true;
}

int TempMotifSamplerStatic::getLength(const long long number)
{
	return (std::floor(log10(number)) + 1);
}

/*
int TempMotifSamplerStatic::getDigit(const long long number,const int digit)
{
	return static_cast<int>( number / static_cast<int>(std::pow(10, digit - 1))) % 10;
}
*/

int TempMotifSamplerStatic::getDigit(const std::uint_fast64_t number,const int digit)
{
	return static_cast<int>( (number >> ((digit-1)*edgeMaskLength_m) ) & edgeMask_m);
}

void TempMotifSamplerStatic::DecrementCounts(const std::uint_fast64_t ID, std::unordered_map<std::uint_fast64_t, long long>& hashCounts, std::vector<std::unordered_set<std::uint_fast64_t>>& keysLength){

	//if(hashCounts.find(ID) == hashCounts.end()) std::cout << "ERRORE SYNCH DECREMENT ID!" << std::endl ;
	hashCounts.at(ID) -= 1;
	if(hashCounts.at(ID) == 0)
	{
		keysLength[0].erase(ID);
		//hashCounts.erase(ID);
	}
	for(int i{ 0 }; i < l_-2; i++)
	{
		for(const auto& key : keysLength[i])
		{
			//long long finalkey = CombineKeys(ID, key);
			std::uint_fast64_t finalkey = CombineKeys(ID, key, i+1);
			//if(hashCounts.find(finalkey) == hashCounts.end()) std::cout << "ERRORE SYNCH DECREMENT FINALKEY!" << std::endl;
			//if(hashCounts.find(key) == hashCounts.end()) std::cout << "ERRORE SYNCH DECREMENT KEY!" << std::endl ;
			hashCounts.at(finalkey) -= hashCounts[key];
			if(hashCounts.at(finalkey) == 0)
			{
				keysLength[i+1].erase(finalkey);
				//hashCounts.erase(finalkey);
			}
		}
	}

	/*
	std::cout << "++++++++++++++++++++++++++++++++\n";
	std::cout << "DECREMENT: \n";
	for(auto& entry : hashCounts)
	{
		std::cout << "key: " << entry.first << " count: " << entry.second << '\n';
	}
	int i{0};
	for(auto list : keysLength)
	{
		std::cout << "i: " << i+1 << " list entries: ";
		for(auto& key : list)
		{
			std::cout << key << ", ";
		}
		i++;
		std::cout << '\n';
	}
	std::cout << "++++++++++++++++++++++++++++++++\n";
	*/

}

void TempMotifSamplerStatic::IncrementCounts(const std::uint_fast64_t ID, std::unordered_map<std::uint_fast64_t, long long>& hashCounts, std::vector<std::unordered_set<std::uint_fast64_t>>& keysLength){

	for(int i{ l_-2 }; i >= 0; i--)
	{
		for(const auto& key : keysLength[i])
		{
			std::uint_fast64_t finalkey = CombineKeys(key, ID, 1);
			long long currentCount{ hashCounts[key] };
			if(keysLength[i+1].find(finalkey) != keysLength[i+1].end()) // key exists
			{
				//if(hashCounts.find(finalkey) == hashCounts.end()) std::cout << "ERRORE SYNCH INCREMENT FINALKEY!" << std::endl ;
				//if(hashCounts.find(key) == hashCounts.end()) std::cout << "ERRORE SYNCH INCREMENT KEY!" << std::endl ;
				//hashCounts.at(finalkey) += hashCounts[key];
				hashCounts.at(finalkey) += currentCount;
			}
			else
			{
				//hashCounts[finalkey] = hashCounts[key];
				hashCounts[finalkey] = currentCount;
				keysLength[i+1].insert(finalkey);
			}
		}
	}
	if(keysLength[0].find(ID) != keysLength[0].end())
	{
		//if(hashCounts.find(ID) == hashCounts.end()) std::cout << "ERRORE SYNCH INCREMENT ID!" << std::endl ;
		hashCounts.at(ID) += 1;// key exists
	}
	else
	{
		hashCounts[ID] = 1;
		keysLength[0].insert(ID);
	}

	/*
	std::cout << "********************************\n";
	std::cout << "INCREMENT: \n";
	for(auto& entry : hashCounts)
	{
		std::cout << "key: " << entry.first << " count: " << entry.second << '\n';
	}
	int i{0};
	for(auto list : keysLength)
	{
		std::cout << "i: " << i+1 << " list entries: ";
		for(auto& key : list)
		{
			std::cout << key << ", ";
		}
		i++;
		std::cout << '\n';
	}
	std::cout << "********************************\n";
	*/

}

int TempMotifSamplerStatic::GetStaticEdgesInstance(std::vector<std::pair<node_t, node_t>>& instance)
{
	std::unordered_set<std::pair<node_t,node_t>, DataStructures::hash_pair> staticEdges;
	//int totalEdges{ 0 };
	for( const auto & edge : instance)
	{
		// Graph orientation -> transforming the instance in an undirected graph, considering only the underlying static structure
		std::pair<node_t, node_t> staticEdge = std::make_pair<node_t,node_t>(static_cast<node_t>(std::min(edge.first, edge.second)), static_cast<node_t>(std::max(edge.first, edge.second)) );
		staticEdges.emplace(staticEdge);
		/*
		if(staticEdges.find(staticEdge) == staticEdges.end())
		{
			staticEdges.insert(staticEdge);
			totalEdges++;
		}
		*/
	}
	//return totalEdges;
	return staticEdges.size();
}

std::uint_fast64_t TempMotifSamplerStatic::ComputeEncoding(std::vector<std::pair<node_t, node_t>>& instance)
{
	int ID { 1};
	std::unordered_map<node_t, node_t> mapping;
	//Remapping nodes to be in 1,...,15 according to their first appeareance
	for(auto & edge : instance)
	{
		node_t src = edge.first;
		node_t dst = edge.second;

		if(mapping.find(src) == mapping.end())
		{
			edge.first = ID;
			mapping[src] = ID++;
		}
		else
			edge.first = mapping[src];

		if(mapping.find(dst) == mapping.end())
		{
			edge.second = ID;
			mapping[dst] = ID++;
		}
		else
			edge.second = mapping[dst];
	}
	//Computing encoding
	std::uint_fast64_t encode { static_cast<std::uint_fast64_t>(instance[0].first) };
	encode =( (encode << edgeMaskLength_m) | (static_cast<std::uint_fast64_t>(instance[0].second) & edgeMask_m));
	for(auto it{instance.begin()+1}; it <  instance.end(); it++)
	{
		encode = ( (encode << edgeMaskLength_m) | (static_cast<std::uint_fast64_t>(it->first) & edgeMask_m));
		encode = ( (encode << edgeMaskLength_m) | (static_cast<std::uint_fast64_t>(it->second) & edgeMask_m));
	}
	return encode;
}

long long TempMotifSamplerStatic::CombineKeys(long long key1, long long key2)
{
	int toshift = getLength(key2);
	return (static_cast<long long>( key1 * pow(10.0, toshift)) + key2);
}

// lengthKey2: number of edges that Key2 represents
std::uint_fast64_t TempMotifSamplerStatic::CombineKeys(const std::uint_fast64_t key1, const std::uint_fast64_t key2, const int lengthKey2)
{
	std::uint_fast64_t combinedKey { key1 << (lengthKey2 * edgeMaskLength_m) };
	return (combinedKey | key2);
}

double TempMotifSamplerStatic::EnumerateOnStaticGraphWithLemon(int delta, int samp, int seed, double timeLimit, int ell, int triORsq)
{
	std::vector<std::pair<node_t, node_t>> motifSeq;
	lemon::ListGraph pattern = lemon::ListGraph();
	std::vector<lemon::ListGraph::Node> nodesPattern;
	for(int i{0}; i < static_undirected_motif_->GetNodes(); i++)
	{
		nodesPattern.push_back(pattern.addNode());
	}
	TUNGraph::TEdgeI edgeItP = static_undirected_motif_->BegEI();
	for(long long int i=0; i< static_undirected_motif_->GetEdges(); i++)
	{
		pattern.addEdge(nodesPattern[edgeItP.GetSrcNId()], nodesPattern[edgeItP.GetDstNId()]);
		motifSeq.push_back(std::make_pair<node_t, node_t>(static_cast<node_t>(edgeItP.GetSrcNId()), static_cast<node_t>(edgeItP.GetDstNId())));
		edgeItP++;
	}

	// Adding colors of the motifs in lemon
	lemon::ListGraph::NodeMap<int> mapColorsPattern(pattern);
	lemon::ListGraph::NodeMap<int> mapIDPattern(pattern);
	for(int i{0}; i< nodesPattern.size(); i++)
	{
		mapColorsPattern[nodesPattern[i]] = 1;
		mapIDPattern[nodesPattern[i]] = i;
	}

	// Adding nodes of the temporal network in lemon
	lemon::ListGraph target = lemon::ListGraph();
	std::vector<lemon::ListGraph::Node> nodesTarget;
	for(int i{0}; i < static_undirected_graph_->GetNodes(); i++)
	{
		nodesTarget.push_back(target.addNode());
	}
	TUNGraph::TEdgeI edgeItT = static_undirected_graph_->BegEI();
	for(long long int i=0; i< static_undirected_graph_->GetEdges(); i++)
	{
		target.addEdge(nodesTarget[edgeItT.GetSrcNId()], nodesTarget[edgeItT.GetDstNId()]);
		edgeItT++;
	}

	// Adding colors of the motifs in lemon
	lemon::ListGraph::NodeMap<int> mapColorsTarget(target);
	lemon::ListGraph::NodeMap<int> mapIDTarget(target);
	for(int i{0}; i< nodesTarget.size(); i++)
	{
		mapColorsTarget[nodesTarget[i]] = 1;
		mapIDTarget[nodesTarget[i]] = i;
	}

	// -- Computing Automorphisms of the motif
	std::vector<int> equivalenceClasses;
	for(int i{0}; i < static_undirected_motif_->GetNodes(); i++)
	{
		equivalenceClasses.push_back(i);
	}

   	lemon::ListGraph::NodeMap<lemon::ListGraph::Node> mm(pattern);
	auto* myVf2ppo = vf2pp(pattern,pattern).mapping(mm).nodeLabels(mapColorsPattern,mapColorsPattern).getPtrToVf2ppObject();
	int countAuto = 0;
   	while(myVf2ppo->find()){
		//process the current mapping mm
		countAuto++;
		for(int ii{0}; ii < static_undirected_motif_->GetNodes(); ii++)
		{
			int nodeCurrMap = equivalenceClasses[ii];
			int nodeMap = equivalenceClasses[mapIDPattern[mm[nodesPattern[ii]]]];
			int newclass = std::min(nodeCurrMap, nodeMap);
			int oldclass = std::max(nodeCurrMap, nodeMap);
			if(newclass == nodeCurrMap)
				equivalenceClasses[mapIDPattern[mm[nodesPattern[ii]]]] = newclass;
			else
 				equivalenceClasses[ii] = newclass;
			// Quadratic cost in the number of edges of the pattern is not a problem since very fast on SMALL pattern graphs
			for(int ij{0}; ij < static_undirected_motif_->GetNodes(); ij++)
			{
				if(equivalenceClasses[ij] == oldclass)
					equivalenceClasses[ij] = newclass;
			}
		}

  	}
	delete myVf2ppo;

	std::unordered_set<std::pair<int, int>, DataStructures::hash_pair> edgesPatternColors;
	std::vector<std::pair<int, int>> edgesToFix;
	for(long long int i=0; i< static_cast<int>(motifSeq.size()); i++)
	{
		int equivSrc = equivalenceClasses[motifSeq[i].first];
		int equivDst = equivalenceClasses[motifSeq[i].second];
		auto currpair = std::make_pair<int, int>(static_cast<int>(std::min(equivSrc, equivDst)), static_cast<int>(std::max(equivSrc, equivDst)) );
		if(edgesPatternColors.find(currpair) == edgesPatternColors.end())
		{
			edgesPatternColors.emplace(currpair);
			edgesToFix.push_back(std::make_pair<int,int>(static_cast<int>(motifSeq[i].first), static_cast<int>(motifSeq[i].second)));
		}
	}

	l_ = ell;
	std::random_device rd;
	std::mt19937 generator(seed);

	std::vector<double> mass;
	double maxdegst { 0};
	double avgdegst {0};
	double alphamax {0};
	double denom{0};
	TUNGraph::TEdgeI edgeIt = static_undirected_graph_->BegEI();
	std::vector<std::pair<node_t, node_t>> edges_exp;
	TVec< THash<TInt, TInt64 >> probsEdges = TVec< THash<TInt, TInt64 >>(static_undirected_graph_->GetNodes());
	double firsttime = get_wall_time();
	for(long long int i=0; i< static_undirected_graph_->GetEdges(); i++)
	{
		node_t src = edgeIt.GetSrcNId();
		node_t dst = edgeIt.GetDstNId();

		(src < dst) ? edges_exp.push_back(std::make_pair<node_t, node_t>(static_cast<node_t>(src), static_cast<node_t>(dst))) : edges_exp.push_back(std::make_pair<node_t, node_t>(static_cast<node_t>(dst), static_cast<node_t>(src)));
		edgeIt++;
		double currmass { 0 };
		if(temporal_data_[src].IsKey(dst))
		{
			currmass += temporal_data_[src](dst).Len();
			denom++;
		}
		double mval = currmass;
		if(temporal_data_[dst].IsKey(src))
		{
			currmass += temporal_data_[dst](src).Len();
			denom++;
		}

		mass.push_back(static_cast<double>(currmass));
		(src < dst) ? probsEdges[src].AddDat(dst, i) : probsEdges[dst].AddDat(src, i);
	}

	std::discrete_distribution<long long int> importance(mass.begin(), mass.end());
	std::vector<double> sampling_probs{ importance.probabilities() };
	double randomtime = get_wall_time() - firsttime;

	double totestimate{ 0};
	int s{ samp };
	double timeEnumerateTri{ 0 };
	double timeBuildSample{ 0 };
	double timeEnumerateTemporal{ 0 };
	double timenow{ 0 };

	double timeSequence{ 0 };
	double timeIso{ 0 };
	double totInstances{ 0 };
	double totStaticInstances{ 0 };
	std::vector<int> numEdges;
	std::vector<int> staticInsts;
	int pruned{ 0 };
	std::unordered_map<std::uint_fast64_t, double> counts;
	double artifactNorm { 0 };
	//std::unordered_map<int, int> lvals;
	long long samples1 = 0;
	double init = get_wall_time();
	while(samples1 < s)
	//while( (get_wall_time() - init) < timeLimit)
	{
			samples1++;
			long long int samplednode { importance(generator) };

			std::vector<std::vector<std::pair<node_t, node_t> > > static_graphlets;

			double initVF2pp = get_wall_time();

			double timemine = get_wall_time();

			int countvf2 = 0;

			mapColorsTarget[nodesTarget[edges_exp[samplednode].first]] = 2;
			mapColorsTarget[nodesTarget[edges_exp[samplednode].second]] = 2;
			node_t srcSampled = std::min(edges_exp[samplednode].first, edges_exp[samplednode].second) ;
			node_t dstSampled = std::max(edges_exp[samplednode].first, edges_exp[samplednode].second) ;
			std::vector<std::vector<std::pair<node_t, node_t> > > static_graphletsVF2pp;

			for(auto toFix : edgesToFix){
			mapColorsPattern[nodesPattern[toFix.first]] = 2;
			mapColorsPattern[nodesPattern[toFix.second]] = 2;
    		lemon::ListGraph::NodeMap<lemon::ListGraph::Node> m(pattern);

			std::unordered_set<std::string> foundEmbeddings;
			auto* myVf2pp = vf2pp(pattern,target).mapping(m).nodeLabels(mapColorsPattern,mapColorsTarget).getPtrToVf2ppObject();
    		while(myVf2pp->find()){
				//process the current mapping m
				countvf2++;

				std::vector<std::pair<node_t, node_t>> embedding;
				std::string encode = "";
				//Get the current embedding without the sampled edge
				for(long long int ix=0; ix< static_cast<long long>(motifSeq.size()); ix++)
				{
					node_t src = mapIDTarget[m[nodesPattern[motifSeq[ix].first]]];
					node_t dst = mapIDTarget[m[nodesPattern[motifSeq[ix].second]]];
					if(src > dst)
					{
						node_t tmp = src;
						src = dst;
						dst = tmp;
					}
					//encode = encode + std::to_string(src) + std::to_string(dst);
					auto currpair = std::make_pair<node_t, node_t>(static_cast<node_t>(src), static_cast<node_t>(dst));

					if((src != srcSampled) || (dst != dstSampled))
						embedding.push_back(currpair);
				}
				// Sort for the encoding to be unique (i.e., permutation invariant)
				std::sort(embedding.begin(), embedding.end(), [](auto &left, auto&right){
					if(left.first == right.first)
						return left.second < right.second;
					return left.first < right.first;
				});
				//Compute encoding
				for(auto e : embedding)
				{
					encode = encode + std::to_string(e.first) + std::to_string(e.second);
				}

				//If not already seen this map under a different permutation then save and remember it.
				if(foundEmbeddings.find(encode) == foundEmbeddings.end())
				{
					static_graphletsVF2pp.push_back(embedding);
					foundEmbeddings.emplace(encode);
				}
    		}
			delete myVf2pp;
			mapColorsPattern[nodesPattern[toFix.first]] = 1;
			mapColorsPattern[nodesPattern[toFix.second]] = 1;
			}

			mapColorsTarget[nodesTarget[edges_exp[samplednode].first]] = 1;
			mapColorsTarget[nodesTarget[edges_exp[samplednode].second]] = 1;
			double endVF2pp = get_wall_time();

			for(auto emb : static_graphletsVF2pp)
				static_graphlets.push_back(emb);

			timeEnumerateTri += (get_wall_time()-timemine);
			if(static_graphlets.size() == 0)
				continue;

			timemine = get_wall_time();
			std::vector<std::pair<node_t, node_t>> staticEdgesInSample;
			double sampled{ 0 };

			totStaticInstances += static_cast<double>(static_graphlets.size());
			std::vector<std::pair<timestamp_t,size_t>> times; //vector of <timestamp, idx_static_edge_In_sample (which corresponds to the timestamp)>

			//Computing on sampled edge to avoid this repeated computation
			std::vector<std::pair<timestamp_t,size_t>> timesSampledEdge;
			node_t sampledSrc = static_cast<node_t>(edges_exp[samplednode].first);
			node_t sampledDst = static_cast<node_t>(edges_exp[samplednode].second);
			std::pair<node_t, node_t> P1{ std::make_pair(sampledSrc, sampledDst) };
			std::pair<node_t, node_t> P2{ std::make_pair(sampledDst, sampledSrc) };
			bool exsistenceSD {false};
			bool exsistenceDS {false};
			if(temporal_data_[sampledSrc].IsKey(sampledDst))
			{
				exsistenceSD = true;
				const TVec<TInt64>& timestamps = temporal_data_[sampledSrc].GetDat(sampledDst);
				for(size_t j=0; j< timestamps.Len(); j++)
				{
					timesSampledEdge.push_back(std::make_pair<timestamp_t, size_t>(timestamps[j], 0));
				}
			}

			if(temporal_data_[sampledDst].IsKey(sampledSrc))
			{
				exsistenceDS = true;
				const TVec<TInt64>& timestamps = temporal_data_[sampledDst].GetDat(sampledSrc);
				for(size_t j=0; j< timestamps.Len(); j++)
				{
					if(exsistenceSD)
						timesSampledEdge.push_back(std::make_pair<timestamp_t, size_t>(timestamps[j], 1));
					else
						timesSampledEdge.push_back(std::make_pair<timestamp_t, size_t>(timestamps[j], 0));
				}
			}
			timeBuildSample += (get_wall_time() - timemine);

			for(size_t k{ 0 }; k < static_graphlets.size(); k++)
			{
				//timenow = get_wall_time();
				timemine = get_wall_time();
				staticEdgesInSample.clear();
				times.clear();

				if(exsistenceSD)
					staticEdgesInSample.push_back(P1);
				if(exsistenceDS)
					staticEdgesInSample.push_back(P2);

				times.insert(times.begin(), timesSampledEdge.begin(), timesSampledEdge.end());

				//Collecting graphlet edges
				long id{0};

				if(triORsq != 3)
				{
					for(const auto& pair : static_graphlets[k])
					{
						node_t src = pair.first;
						node_t dst = pair.second;
						if(temporal_data_[src].IsKey(dst))
						{
							staticEdgesInSample.push_back(std::make_pair<node_t,node_t>(static_cast<node_t>(src), static_cast<node_t>(dst)));
							size_t currentStaticEdges{ staticEdgesInSample.size() };
							const TVec<TInt64>& timestamps = temporal_data_[src].GetDat(dst);
							for(size_t j=0; j< timestamps.Len(); j++)
							{
								times.push_back(std::make_pair<timestamp_t, size_t>(timestamps[j], currentStaticEdges-1));
							}
						}

						if(temporal_data_[dst].IsKey(src))
						{
							staticEdgesInSample.push_back(std::make_pair<node_t,node_t>(static_cast<node_t>(dst), static_cast<node_t>(src)));
							size_t currentStaticEdges{ staticEdgesInSample.size() };
							const TVec<TInt64>& timestamps = temporal_data_[dst].GetDat(src);
							for(size_t j=0; j< timestamps.Len(); j++)
							{
								times.push_back(std::make_pair<timestamp_t, size_t>(timestamps[j], currentStaticEdges-1));
							}
						}
					}
				}

					std::sort(times.begin(), times.end());
					timeBuildSample += (get_wall_time() - timemine);

					timemine = get_wall_time();
					bool foundCandidate{ false };

					// Pruning
					for(long long int t{ 0 }; t < static_cast<long long int>(times.size())-l_+1; t++)
					{
						if( ((times[t+l_-1].first - times[t].first) <= delta ))
						{
								foundCandidate = true;
								break;
						}
					}
					//timeBuildSample += (get_wall_time() - timenow);
					timeEnumerateTemporal += (get_wall_time() - timemine);
					if(!foundCandidate)
					{
						pruned++;
					}
					else
					{

							//Computing probability and checking if the selected vertex is in the graphlet
							timemine = get_wall_time();
							double graphlet_prob{0};
							graphlet_prob += sampling_probs[samplednode];

							double weight { static_cast<double>(1./graphlet_prob) };

							double ressamp  = FastCountSequence(delta, std::ref(times), std::ref(staticEdgesInSample), timeSequence, timeIso, std::ref(counts), graphlet_prob);
							totInstances += ressamp;
							numEdges.push_back(static_cast<int>(times.size()));

							ressamp = ressamp * weight;

							sampled++;

							totestimate += ressamp;
							timeEnumerateTemporal += (get_wall_time() - timemine);
					}

			} // TODO: Delete tmp pointers!!

	}

	double tottime = get_wall_time() - firsttime;
	std::cout.precision(2);
	for(auto& el : counts)
	{
		std::cout << " count is: " <<  std::fixed << el.second/(1.*samples1*static_undirected_motif_->GetEdges()) << " motif is: " << std::hex << el.first << std::dec;
		std::cout << '\n';
	}
	std::cout << "T0 time spent in building prob distrib: " << std::fixed << randomtime  << '\n';
	std::cout << "T1 time spent in static enumeration: " << std::fixed << timeEnumerateTri  << '\n';
	std::cout << "T2 time spent in building sample: " << std::fixed << timeBuildSample  << '\n';
	std::cout << "T3 time spent in temporal enumeration: " << std::fixed << timeEnumerateTemporal  << '\n';
	std::cout << "T3-a time spent in temporal enumeration when enumerating sequences: " << std::fixed << timeSequence  << '\n';
	std::cout << "T3-b time spent in temporal enumeration with isomorphism: " << std::fixed << timeIso  << '\n';
	std::cout << "T4 time spent in whole precedure: " << std::fixed << tottime  << '\n';

	std::cout << "samples s examined: " << samples1 << '\n';
	return totestimate/s;

}


/*
Copyright (C) 2005, 2007, 2009, 2014 Klaus Spanderen
Copyright (C) 2015 CompatibL

This file is part of QuantLib, a free-software/open-source library
for financial quantitative analysts and developers - http://quantlib.org/

QuantLib is free software: you can redistribute it and/or modify it
under the terms of the QuantLib license.  You should have received a
copy of the license along with this program; if not, please email
<quantlib-dev@lists.sf.net>. The license is also available online at
<http://quantlib.org/license.shtml>.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

#ifndef quantlib_test_ad_heston_process_hpp
#define quantlib_test_ad_heston_process_hpp

#include <boost/test/unit_test.hpp>

class AdjointHestonProcessTest {
public:
    static bool testHestonProcessPhi();
    static bool testHestonProcessExpectation();
    static bool testHestonProcessDrift();
    static bool testHestonProcessEvolve();
    static bool testHestonProcessStdDeviation();
    static bool testHestonProcessCovariance();

    static boost::unit_test_framework::test_suite* suite();
};

#endif


// This file is part of the dune-gdt project:
//   https://github.com/dune-community/dune-gdt
// Copyright 2010-2018 dune-gdt developers and contributors. All rights reserved.
// License: Dual licensed as BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)
//      or  GPL-2.0+ (http://opensource.org/licenses/gpl-license)
//          with "runtime exception" (http://www.dune-project.org/license.html)
// Authors:
//   Felix Schindler (2018)
//   Rene Milk       (2017 - 2018)

#define ALUGRIDDEBUG 1

#include <config.h>

#include <string>
#include <vector>
#include <map>
#include <random>
#include <fstream>
#include <limits>

#include <boost/numeric/conversion/cast.hpp>

#include <dune/common/float_cmp.hh>
#include <dune/common/fvector.hh>
#include <dune/common/fmatrix.hh>
#include <dune/common/parallel/mpihelper.hh>

#include <dune/alugrid/common/structuredgridfactory.hh>
#include <dune/xt/common/vector.hh>
#include <dune/xt/common/test/gtest/gtest.h>
#include <dune/xt/common/configuration.hh>
#include <dune/xt/common/exceptions.hh>
#include <dune/xt/common/logging.hh>
#include <dune/xt/common/timedlogging.hh>
#include <dune/xt/common/convergence-study.hh>
#include <dune/xt/common/parallel/threadmanager.hh>

#if HAVE_TBB
#include <tbb/task_scheduler_init.h>
#endif

#include <dune/xt/grid/gridprovider/cube.hh>
#include <dune/xt/common/misc.hh>
#include <dune/xt/grid/grids.hh>
#include <dune/xt/common/timings.hh>

#include <dune/gdt/projections.hh>

#include <dune/gdt/test/linearelliptic/discretizers/block-ipdg.hh>
#include <dune/gdt/test/linearelliptic/discretizers/cg.hh>
#include <dune/gdt/test/linearelliptic/problems/ESV2007.hh>
#include <dune/gdt/test/linearelliptic/problems/ER2007.hh>
#include <dune/gdt/operators/laplace.hh>

using namespace Dune;

static_assert(DUNE_GRID_EXPERIMENTAL_GRID_EXTENSIONS, "grid extensions are mandatory");

//  using GridType = Dune::ALUGrid<3, 3, simplex, nonconforming, ALUGridMPIComm>;
// using GridType = Dune::ALUGrid<2, 2, simplex, nonconforming, ALUGridMPIComm>;
// using GridType = Dune::ALUGrid<2, 2, cube, nonconforming, ALUGridMPIComm>;
using GridType = Yasp2Grid;
using GridProvider = XT::Grid::GridProvider<GridType, XT::Grid::DD::SubdomainGrid<GridType>>;
using Errors = std::pair<double, double>;
constexpr const size_t pol_order = 1;
constexpr const auto la_backend = XT::LA::Backends::istl_sparse;

template <GDT::Backends space_backend>
struct Traits
{
  using CGFactory =
      GDT::LinearElliptic::CGDiscretizer<GridType, XT::Grid::Layers::leaf, space_backend, la_backend, pol_order>;
  using CGDiscretization = typename CGFactory::DiscretizationType;
  using IpdgFactory =
      GDT::LinearElliptic::IpdgDiscretizer<GridType, XT::Grid::Layers::leaf, space_backend, la_backend, pol_order>;
  using IpdgDiscretization = typename IpdgFactory::DiscretizationType;

  static const auto local_method = GDT::LocalEllipticIpdgIntegrands::Method::swipdg;
  static const auto coupling_method = GDT::LocalEllipticIpdgIntegrands::Method::swipdg;
  using BlockIpdgFactory = GDT::LinearElliptic::
      BlockIpdgDiscretizer<GridType, space_backend, la_backend, pol_order, double, 1, local_method, coupling_method>;
  using BlockIpdgDiscretization = typename BlockIpdgFactory::DiscretizationType;
  using Vector = typename BlockIpdgDiscretization::VectorType;
  static_assert(std::is_same<Vector, typename IpdgDiscretization::VectorType>::value, "Vector mismatch");
  using IpdgFunction = GDT::DiscreteFunction<typename IpdgDiscretization::AnsatzSpaceType, Vector>;
  using CGFunction = GDT::DiscreteFunction<typename CGDiscretization::AnsatzSpaceType, Vector>;
  using BlockIpdgFunction = GDT::DiscreteFunction<typename BlockIpdgDiscretization::AnsatzSpaceType, Vector>;
  using ConstBlockIpdgFunction = GDT::ConstDiscreteFunction<typename BlockIpdgDiscretization::AnsatzSpaceType, Vector>;
};

std::string path(std::string filename)
{
  boost::filesystem::path ret(DXTC_CONFIG_GET("global.datadir", "data"));
  return (ret / filename).string();
}

template <GDT::Backends spc, class Space, class TestCase>
Errors exact_error(const TestCase& test_case,
                   const GDT::ConstDiscreteFunction<Space, typename Traits<spc>::Vector>& discrete_solution)
{
  XT::Common::ScopedTiming timing(std::string("analytical_error.") + discrete_solution.name());
  auto layer = discrete_solution.space().grid_layer();
  const auto& exact = test_case.exact_solution();
  auto diff = discrete_solution - exact;
  if (DXTC_CONFIG_GET("global.visualize", false)) {
    diff.visualize(layer, path(discrete_solution.name() + "_diff"), "solution");
  }
  const auto l2_error = Dune::GDT::make_l2_operator(layer, 2)->induced_norm(diff);
  const auto h1s_error = Dune::GDT::make_laplace_operator(layer, 2)->induced_norm(diff);
  return {l2_error, h1s_error};
}

template <GDT::Backends spc, class TestCase>
std::pair<typename Traits<spc>::Vector, typename Traits<spc>::CGDiscretization::AnsatzSpaceType>
cg(TestCase& test_case, GridProvider& grid_provider)
{
  XT::Common::ScopedTiming timer_all("cg.all");
  XT::Common::timings().start("cg.discretize");
  auto cg_disc = Traits<spc>::CGFactory::discretize(grid_provider, test_case.problem());
  auto cg_solution_vector = cg_disc.create_vector();
  XT::Common::timings().stop("cg.discretize");
  const auto solver_options = DXTC_CONFIG.sub("solver");
  XT::Common::timings().start("cg.solve");
  try {
    XT::Common::ScopedTiming solve("cg.solve");
    cg_disc.solve(cg_solution_vector, solver_options);
  } catch (XT::LA::Exceptions::linear_solver_failed_bc_it_did_not_converge) {
    cg_solution_vector += std::nan("failed");
  }
  if (DXTC_CONFIG_GET("global.visualize", false)) {
    cg_disc.visualize(cg_solution_vector, path("cg_solution"), "solution");
  }
  return {cg_solution_vector, cg_disc.ansatz_space()};
}

template <GDT::Backends spc, class BlockTestCase>
std::pair<typename Traits<spc>::Vector, typename Traits<spc>::BlockIpdgDiscretization::AnsatzSpaceType>
block_ipdg(BlockTestCase& test_case, GridProvider& grid_provider)
{
  XT::Common::ScopedTiming timer_all("block_ipdg.all");
  XT::Common::timings().start("block_ipdg.discretize");
  auto block_ipdg_disc = Traits<spc>::BlockIpdgFactory::discretize(grid_provider, test_case.problem(), /*not used*/ 0);
  auto block_ipdg_solution_vector = block_ipdg_disc.create_vector();
  XT::Common::timings().stop("block_ipdg.discretize");
  const auto solver_options = DXTC_CONFIG.sub("solver");
  XT::Common::timings().start("block_ipdg.solve");
  try {
    XT::Common::ScopedTiming solve("block_ipdg.solve");
    block_ipdg_disc.solve(block_ipdg_solution_vector, solver_options);
  } catch (XT::LA::Exceptions::linear_solver_failed_bc_it_did_not_converge) {
    block_ipdg_solution_vector += std::nan("failed");
  }
  if (DXTC_CONFIG_GET("global.visualize", false)) {
    block_ipdg_disc.visualize(block_ipdg_solution_vector, path("block_ipdg_solution"), "solution");
  }
  return {block_ipdg_solution_vector, block_ipdg_disc.ansatz_space()};
}

template <GDT::Backends spc, class TestCase>
std::pair<typename Traits<spc>::IpdgDiscretization, typename Traits<spc>::Vector>
plain_ipdg(TestCase& test_case, GridProvider& grid_provider)
{
  XT::Common::ScopedTiming timer_all("ipdg.all");
  XT::Common::timings().start("ipdg.discretize");
  auto ipdg_disc = Traits<spc>::IpdgFactory::discretize(grid_provider, test_case.problem());
  auto ipdg_solution_vector = ipdg_disc.create_vector();
  const auto solver_options = DXTC_CONFIG.sub("solver");
  XT::Common::timings().stop("ipdg.discretize");
  try {
    XT::Common::ScopedTiming solve("ipdg.solve");
    ipdg_disc.solve(ipdg_solution_vector, solver_options);
  } catch (XT::LA::Exceptions::linear_solver_failed_bc_it_did_not_converge) {
    ipdg_solution_vector += std::nan("failed");
  }
  if (DXTC_CONFIG_GET("global.visualize", false)) {
    ipdg_disc.visualize(ipdg_solution_vector, path("ipdg_solution"), "solution");
  }
  return {std::move(ipdg_disc), std::move(ipdg_solution_vector)};
}

template <GDT::Backends spc, class TestCase>
void error_output(const std::map<std::string, Errors>& error_map, const TestCase& test_case)
{
  auto& out = DXTC_LOG_INFO_0;
  const auto spc_str = GDT::backend_names[spc];
  const auto prob_str = XT::Common::get_template_basename(test_case.problem());
  out << "\n"
      << prob_str << " | " << spc_str << "\n"
      << "----------------------------------\n";
  boost::format row_fmt("%8s %|8t| %15.9f %|8t|%15.9f\n");
  boost::format head_fmt("%8s %|8t| %-15s %|8t|%-15s\n");
  // header
  out << head_fmt % "" % "L2" % "H1s";
  for (auto&& row : error_map) {
    const auto disc = row.first;
    const auto errors = row.second;
    out << row_fmt % disc % errors.first % errors.second;
  }

  out.flush();
}

template <GDT::Backends spc, class TestCase, class BlockTestCase>
void single_run()
{
  XT::Common::ScopedTiming timer_all("single_run");

  std::map<std::string, Errors> error_map;
  XT::Common::Configuration cfg(TestCase::grid_cfg(), std::string("grid"));
  XT::Common::Configuration block_cfg(BlockTestCase::grid_cfg(), std::string("grid"));

  //! get and overwrite grid values from global config
  block_cfg.add(DXTC_CONFIG, "", true);
  cfg.add(DXTC_CONFIG, "", true);

  // Testcases are already grid providers and grid privder can return grid provider
  TestCase test_case{cfg};
  BlockTestCase block_test_case{block_cfg};
  auto& grid_provider = block_test_case.level_provider(0);

  auto ipdg_disc_and_solution = plain_ipdg<spc>(test_case, grid_provider);
  const auto& space = ipdg_disc_and_solution.first.ansatz_space();
  const typename Traits<spc>::IpdgFunction ipdg_solution(space, ipdg_disc_and_solution.second, "ipdg");

  if (DXTC_CONFIG_GET("global.ipdg", true)) {
    error_map["ipdg"] = exact_error<spc>(test_case, ipdg_solution);
  }

  if (DXTC_CONFIG_GET("global.cg", true)) {
    auto cg_disc_and_solution = cg<spc>(test_case, grid_provider);
    typename Traits<spc>::CGFunction cg_func(cg_disc_and_solution.second, cg_disc_and_solution.first, "cg");
    error_map["cg"] = exact_error<spc>(test_case, cg_func);
  }

  if (DXTC_CONFIG_GET("global.block", false)) {
    const auto block_solution_vector_and_space = block_ipdg<spc>(block_test_case, grid_provider);
    typename Traits<spc>::ConstBlockIpdgFunction block_ipdg_solution(
        block_solution_vector_and_space.second, block_solution_vector_and_space.first, "block_ipdg");
    error_map["block"] = exact_error<spc>(test_case, block_ipdg_solution);
  }
  error_output<spc>(error_map, test_case);
}

int main(int argc, char** argv)
{
  using namespace Dune::XT::Common;
  try {

    Dune::MPIHelper::instance(argc, argv);

    DXTC_CONFIG.read_command_line(argc, argv);

    test_create_directory(DXTC_CONFIG_GET("global.datadir", "data/"));

    // LOG_NONE = 1, LOG_ERROR = 2, LOG_INFO = 4,LOG_DEBUG = 8,LOG_CONSOLE =
    // 16,LOG_FILE = 32
    // --> LOG_ERROR | LOG_INFO | LOG_DEBUG | LOG_CONSOLE | LOG_FILE = 62
    Logger().create(DXTC_CONFIG_GET("logging.level", 62),
                    DXTC_CONFIG_GET("logging.file", std::string(argv[0]) + ".log"),
                    DXTC_CONFIG_GET("global.datadir", "data"),
                    DXTC_CONFIG_GET("logging.dir", "log" /*path below datadir*/));
    const int max_level = DXTC_CONFIG_GET("logging.max_nesting", -1);
    //    TimedLogger().create(max_level /*info*/, max_level /*debug*/);
    DXTC_TIMINGS.set_outputdir(DXTC_CONFIG_GET("global.datadir", "data"));

    //    DXTC_LOG_INFO_0 << DXTC_CONFIG.report_string() << std::endl;

    threadManager().set_max_threads(1);
    {
      OutputScopedTiming outs("all", DXTC_LOG_INFO_0);
      DXTC_CONFIG.set("grids.total_macro_cells", 256);

      switch (DXTC_CONFIG_GET("global.problem", 0)) {
        case 0:
          single_run<GDT::Backends::gdt,
                     GDT::LinearElliptic::ER2007TestCase<GridType>,
                     GDT::LinearElliptic::ER2007DdSubdomainsTestCase<GridType>>();
          break;
        case 1:
          single_run<GDT::Backends::gdt,
                     GDT::LinearElliptic::ESV2007TestCase<GridType>,
                     GDT::LinearElliptic::ESV2007DdSubdomainsTestCase<GridType>>();
          break;
        case 2:
          single_run<GDT::Backends::gdt,
                     GDT::LinearElliptic::ER2007TestCase<GridType>,
                     GDT::LinearElliptic::ER2007DdSubdomainsTestCase<GridType>>();
          single_run<GDT::Backends::gdt,
                     GDT::LinearElliptic::ESV2007TestCase<GridType>,
                     GDT::LinearElliptic::ESV2007DdSubdomainsTestCase<GridType>>();
          break;
      }
    }
    DXTC_TIMINGS.output_per_rank("profiler");
    mem_usage();
    //    dump_environment();

  } catch (Dune::Exception& e) {
    std::cerr << "\nDune reported error: " << e.what() << std::endl;
    std::abort();
  } catch (std::exception& e) {
    std::cerr << "\n" << e.what() << std::endl;
    std::abort();
  }
}


/*
 Copyright (C) 2016 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

/*! \file test/scenariogenerator.hpp
    \brief scenario generation test
    \ingroup tests
*/

#pragma once

#include <boost/test/unit_test.hpp>

namespace testsuite {

//! Test scenario generation
/*!
  \ingroup tests
*/
class ScenarioGeneratorTest {
public:
    //! Test consistency of CPI Swap Exposure evolutions with Cpi Floor prices
    static void testCpiSwapExposure();
    //! Test Martingale properties for single currency LGM scenarios with Pseudo Random numbers
    static void testLgmMersenneTwister();
    //! Test Martingale properties for single currency LGM scenarios with Pseudo Random numbers and antithetic sampling
    static void testLgmMersenneTwisterAntithetic();
    //! Test Martingale properties for single currency LGM scenarios with Sobol sequences
    static void testLgmLowDiscrepancy();
    //! Test Martingale properties for single currency LGM scenarios with Sobol sequences and Brownian bridge
    static void testLgmLowDiscrepancyBrownianBridge();
    //! Test Martingale properties for cross asset IR/FX model with Pseudo Random numbers
    static void testCrossAssetMersenneTwister();
    //! Test Martingale properties for cross asset IR/FX model with Pseudo Random numbers and antithetic sampling
    static void testCrossAssetMersenneTwisterAntithetic();
    //! Test Martingale properties for cross asset IR/FX model with Sobol sequences
    static void testCrossAssetLowDiscrepancy();
    //! Test Martingale properties for cross asset IR/FX model with Sobol sequences and Brownian bridge
    static void testCrossAssetLowDiscrepancyBrownianBridge();
    //! Test Martingale properties for cross asset IR/FX model scenarios after applying them to the simulation market
    static void testCrossAssetSimMarket();
    //! Test cross asset IR/FX model scenarios after applying to simulation market pathwise to the direct model output
    static void testCrossAssetSimMarket2();
    //! Test consistency of EUR and USD swap exposure evolutions with European Swaption prices
    static void testVanillaSwapExposure();
    //! Test consistency of FX Forward Exposure evolutions with FX Option prices
    static void testFxForwardExposure();
    //! Test consistency of FX Forward Exposure evolutions with FX Forward Option prices when IR vols vanish
    static void testFxForwardExposureZeroIrVol();

    static boost::unit_test_framework::test_suite* suite();
};
} // namespace testsuite


#include <iostream>
#include <stdexcept>
#include <Eigen/Dense>
#include <Eigen/SparseCore>
#include "mex.h"
#include "ais.h"

#ifdef MX_API_VER
#if MX_API_VER < 0x07030000
typedef int mwIndex;
#endif
#endif

using namespace Eigen;
using namespace std;

void exit_with_help()
{
	mexPrintf(
	"Usage: [kpX] = kron_perm(X, block_size)\n"
	"       [kpX] = kron_perm(X, block_size, row_perm, col_perm)\n"
	"     X is an m-by-n sparse/dense double matrix\n"
	"     block_size is a length-2 array [block_m block_n] for the size of the last block\n"
	"     row_perm is an dense row permutation array\n"
	"     col_perm is an dense column permutation array\n"
	"     kpX is an M-by-N sparse double matrix\n"
	"        M = ceil(m/block_m)*ceil(n/block_n)\n"
	"        N = block_m*block_n\n"
	"Note: \n"
	"     kpX == kron_perm(X(row_perm, col_perm), block_size)\n"
	);
}

static void fake_answer(mxArray *plhs[]) { plhs[0] = mxCreateDoubleMatrix(0, 0, mxREAL); }

/***** MATLAB 2 EIGEN conversion *****/
double matlabScalar2scalar( const mxArray* mp ) {
    double scalar = mxGetScalar(mp);
    return scalar;
}

VectorXd matlabVec2eigenVec( const mxArray* mp ) {
    // Load with a certain size
    const mwSize* size = mxGetDimensions( mp );
    mwSize length;
    if(size[0] > 1) { length = size[0]; } else { length = size[1]; }
    VectorXd x(length);

    // Extract data
    double* data = mxGetPr( mp );
    for(size_t i = 0; i < length; i++) {
        x(i) = data[i];
    }

    return x;
}

mxArray* eigenVec2matlabVec( const VectorXd& x ) {
    mxArray* mp = mxCreateDoubleMatrix( x.size(), 1, mxREAL );
    double* data = mxGetPr( mp );
    // Copy data from vector
    for(size_t i = 0; i < x.size(); ++i) {
        data[i] = x(i);
    }
    return mp;
}

MatrixXd matlabMat2eigenMat( const mxArray* mp ) {
    // Load with a certain size
    const mwSize* size = mxGetDimensions( mp );
    MatrixXd A(size[0],size[1]);

    // Extract data
    double* data = mxGetPr( mp );
    size_t idx = 0;
    for(int j = 0; j < size[1]; j++) {
        for(int i = 0; i < size[0]; i++) {
            A(i,j) = data[ idx++ ];
        }
    }

    return A;
}

SparseMatrix<double> matlabSparse2eigenSparse( const mxArray* mp ) {
    const mwSize* size = mxGetDimensions( mp );
    mwSize nnz = mxGetNzmax( mp );

    // Setup triplets
    vector< Triplet< double > > triplets;
    triplets.reserve( nnz );

    // Extract nonzeros
    size_t *ir = mxGetIr( mp ); /* Row indexing */
    size_t *jc = mxGetJc( mp ); /* Column count */
    double *s = mxGetPr( mp ); /* Non-zero elements */
    for (int ci=0; ci < size[1]; ci++) { /* Loop through columns */
        for (int k = jc[ci]; k < jc[ci+1]; k++) { /* Loop through non-zeros in ith column */
            int ri = ir[k];
            double val = s[k];
            triplets.push_back( Triplet<double>( ri, ci, val ) );
        }
    }

    // Create matrix
    SparseMatrix<double> A(size[0],size[1]);
    A.setFromTriplets( triplets.begin(), triplets.end() );
    //cout << "Loaded sparse matrix:" << endl << A << endl;
    return A;
}

mxArray* eigenSparse2matlabSparse( SparseMatrix<double> A ) {
    mxArray* mp = mxCreateSparse( (mwSize)A.rows(), (mwSize)A.cols(), (mwSize)A.nonZeros(), mxREAL );
    double* sr = mxGetPr( mp );
    mwSize* irs = mxGetIr( mp );
    mwSize* jcs = mxGetJc( mp );

    // Loop through non-zeros
    size_t k = 0;
    for (size_t j = 0; j < A.outerSize(); ++j) {
        mwSize i;
        jcs[j] = k;
        for (SparseMatrix<double>::InnerIterator it(A,j); it; ++it) {
            sr[k] = it.value();
            irs[k] = it.row();
            k++;
        }
    }
    jcs[ A.cols() ] = k;

    return mp;
}

/***** Argument load functions *****/
VectorXd getVector( const mxArray* mp ) {
    const mwSize* size = mxGetDimensions( mp );
    // Error check
    if( !((size[0] == 1 && size[1] > 1) || (size[0] > 1 && size[1] == 1)) ) { throw invalid_argument( "MATLAB argument is not a vector n x 1 or 1 x n" ); }
    if( size[0] > 1 && size[1] > 1 ) { throw invalid_argument( "MATLAB argument is a matrix not a vector" ); }
    if( mxIsSparse(mp) ) { throw invalid_argument( "MATLAB argument is sparse but should be dense" ); }

    // Load vector
    VectorXd x = matlabVec2eigenVec( mp );
    //cout << "Loaded vector:" << endl << x << endl;
    return x;
}

MatrixXd getMatrix( const mxArray* mp ) {
    const mwSize* size = mxGetDimensions( mp );
    // Error check
    if( size[0] <= 1 || size[1] <= 1 ) { throw invalid_argument( "MATLAB argument is a vector rather than a matrix." ); }
    if( mxIsSparse(mp) ) { throw invalid_argument( "MATLAB argument is sparse but should be dense." ); }

    // Load matrix
    MatrixXd A = matlabMat2eigenMat( mp );
    //cout << "Loaded matrix:" << endl << A << endl;
    return A;
}

SparseMatrix< double > getSparseMatrix( const mxArray* mp ) {
    const mwSize* size = mxGetDimensions( mp );
    // Error check
    if( size[0] <= 1 || size[1] <= 1 ) { throw invalid_argument( "MATLAB argument is a vector rather than a matrix." ); }
    if( !mxIsSparse(mp) ) { throw invalid_argument( "MATLAB argument is dense but should be sparse." ); }

    // Load matrix
    SparseMatrix< double > A = matlabSparse2eigenSparse( mp );
    //cout << "Loaded matrix:" << endl << A << endl;
    return A;
}

template <typename T>
double getScalar( const mxArray* mp ) {
    const mwSize* size = mxGetDimensions( mp );
    // Error check
    if( size[0] != 1 || size[1] != 1 ) { throw invalid_argument( "MATLAB argument is not a scalar." ); }
    if( mxIsSparse(mp) ) { throw invalid_argument( "MATLAB argument is sparse but should be dense" ); }

    // Load matrix
    double c = matlabScalar2scalar( mp );
    //cout << "Loaded scalar: " << c << endl;
    return (T) c;
}

/***** MATLAB mex function *****/
void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {
    // ais_mex(thetaNode, thetaEdge, L, nSamples, betaVec, nGibbs, verbosity)
    /*
    printf("\nThere are %d right-hand-side argument(s).\n", nrhs);
    for (int i=0; i<nrhs; i++)  {
        const mwSize* dim = mxGetDimensions(prhs[i]);
        printf("\tInput Arg %i is of type:\t%s size: %d x %d, Sparsity: %d\n",
                i, mxGetClassName(prhs[i]), dim[0], dim[1], mxIsSparse(prhs[i]) );
    }
    */

    //mexPrintf("DEBUG: Before loading mex input");
    /* Verify and load arguments */
    VectorXd thetaNode, betaVec;
    SparseMatrix<double> thetaEdge;
    size_t L, nSamples, nGibbs, verbosity, nThreads;
    try {
        if( nrhs < 7 ) { throw invalid_argument( "Incorrect number of arguments." ); }
        thetaNode = getVector( prhs[0] );
        thetaEdge = getSparseMatrix( prhs[1] );
        L = getScalar<size_t>( prhs[2] );
        nSamples = getScalar<size_t>( prhs[3] );
        betaVec = getVector( prhs[4] );
        nGibbs = getScalar<size_t>( prhs[5] );
        verbosity = getScalar<size_t>( prhs[6] );
        if( nrhs >= 8) {
            nThreads = getScalar<size_t>( prhs[7] );
        } else {
            nThreads = 0;
        }
    } catch ( const std::invalid_argument& e ) {
        cerr << "Error loading arguments: " << e.what() << endl;
        fake_answer(plhs);
        return;
    }
    //mexPrintf("DEBUG: After loading mex input");

    /* Simply call ais function */
    //mexPrintf("DEBUG: Before calling ais function");
    VectorXd logW;
    SparseMatrix<double> X;
    ais( thetaNode, thetaEdge, L, nSamples, betaVec, nGibbs, nThreads, verbosity,
            logW, X );
    //mexPrintf("DEBUG: After calling ais function");

    /* Save final answer into MATLAB output */
    //mexPrintf("DEBUG: Before creating mex output arguments");
    if( nlhs >= 1 ) {
        // Sanity check test
        /*
        VectorXd temp(5);
        temp << 1,2,3,4,5;
        cout << "Vector temp:" << endl << temp << endl;
        plhs[0] = eigenVec2matlabVec( temp );
        */
        plhs[0] = eigenVec2matlabVec( logW );
    }
    if( nlhs >= 2 ) {
        // Sanity check test
        /*
        SparseMatrix<double> temp(4,3);
        std::vector< Triplet<double> > triplets;
        triplets.push_back( Triplet<double>(0,0,1) );
        triplets.push_back( Triplet<double>(0,1,1.5) );
        triplets.push_back( Triplet<double>(1,0,0.5) );
        triplets.push_back( Triplet<double>(1,1,2) );
        triplets.push_back( Triplet<double>(2,2,3) );
        temp.setFromTriplets( triplets.begin(), triplets.end() );
        cout << "Sparse matrix temp:" << endl << temp << endl;
        plhs[1] = eigenSparse2matlabSparse( temp );
        */
        plhs[1] = eigenSparse2matlabSparse( X );
    }
    //mexPrintf("DEBUG: After creating mex output arguments");
}

/*
mxArray* kron_perm_dense(const mxArray *mxX, size_t block_m, size_t block_n, size_t *row_perm, size_t *col_perm) {
	size_t rows = mxGetM(mxX), cols = mxGetN(mxX), size = rows*cols;
	double *X = mxGetPr(mxX);
	kmf_rk1_t rk1;
	rk1.fill_attributes(rows, cols, block_m, block_n, row_perm, col_perm);
	size_t new_rows = rk1.Asize(), new_cols = rk1.Bsize(), new_size = new_rows*new_cols;
	printf("%lu x %lu -> %lu x %lu\n", rows, cols, new_rows, new_cols);
	mxArray *mxkpX = mxCreateDoubleMatrix(new_rows, new_cols, mxREAL);
	double *kpX = mxGetPr(mxkpX);

	for(size_t new_idx = 0; new_idx < new_size; new_idx++)
		kpX[new_idx] = 0;
	for(size_t c = 0, idx = 0; c < cols; c++) {
		for(size_t r = 0; r < rows; r++) {
			size_t i=0, j=0;
			rk1.get_indices(r,c,i,j);
			kpX[j*new_rows+i] = X[idx++];
		}
	}
	return mxkpX;
}
mxArray* kron_perm_sparse(const mxArray *X, size_t block_m, size_t block_n, size_t *row_perm, size_t *col_perm) {
    // ir pointer to row indexes, jc pointer to column starts
	mwIndex *ir = mxGetIr(X), *jc = mxGetJc(X);
	double *val = mxGetPr(X);
	size_t rows = mxGetM(X), cols = mxGetN(X), nnz = jc[cols];

	kmf_rk1_t rk1;
	rk1.fill_attributes(rows, cols, block_m, block_n, row_perm, col_perm);
	size_t new_rows = rk1.Asize(), new_cols = rk1.Bsize();
	mxArray *kpX = mxCreateSparse(new_rows, new_cols, nnz, mxREAL);
	mwIndex *new_ir = mxGetIr(kpX), *new_jc = mxGetJc(kpX);
	double *new_val = mxGetPr(kpX);

    // Set all new column pointers to 0
	for(size_t j = 0; j <= new_cols; j++)
		new_jc[j] = 0;
    // Find sizes of new columns
	for(size_t c = 0; c < cols; c++) {
		for(mwIndex idx = jc[c]; idx != jc[c+1]; idx++) {
			size_t r = ir[idx];
			size_t i, j;
			rk1.get_indices(r,c,i,j);
			new_jc[j+1]++;
		}
	}
    // Make the column pointers be indexes instead of counts
	for(size_t j = 1; j <= new_cols; j++)
		new_jc[j] += new_jc[j-1];
    // Now find update new values
    // Essentially fill in the columns and update the for different j columns
    // Looping through in original order
	for(size_t c = 0; c < cols; c++) {
		for(mwIndex idx = jc[c]; idx != jc[c+1]; idx++) {
			size_t r = ir[idx];
			size_t i, j;
			rk1.get_indices(r,c,i,j);
			new_ir[new_jc[j]] = i;
			new_val[new_jc[j]] = val[idx];
			new_jc[j]++;
		}
	}
    // Reset column pointers
	for(size_t j = new_cols; j > 0; j--)
		new_jc[j] = new_jc[j-1];
	new_jc[0] = 0;
    // Sort column values based on row indices
	for(size_t j = 0; j < new_cols; j++)
		sort(zip_iter(&new_ir[new_jc[j]], &new_val[new_jc[j]]),
				zip_iter(&new_ir[new_jc[j+1]], &new_val[new_jc[j+1]]));
	return kpX;
}
*/


#include<string>
#include "divide_the_problem.h"
#include <boost/algorithm/string.hpp>
#include <boost/range/algorithm.hpp>

using namespace std;

int solve_divide_the_problem(int n, std::string elms) {

    vector<string> strs;
    boost::split(strs, elms, boost::is_space());
    vector<int> nums(n);
    for(int i=0;i<n;i++)
        nums[i] = stoi(strs.at(i));

    boost::sort(nums);

    int mid = (n/2) -1;

    if(nums.at(mid) == nums.at(mid+1)) {
        return 0;
    }

    return nums.at(mid+1) - nums.at(mid);

}


#include <batteries/assert.hpp>
#include <boost/container/small_vector.hpp>

#include <gtest/gtest.h>

namespace {

TEST(Placeholder, Hello) { ASSERT_EQ(2, 1 + 1); }
}


// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2010-2011 Gael Guennebaud <gael.guennebaud@inria.fr>
//
// Eigen is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// Alternatively, you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of
// the License, or (at your option) any later version.
//
// Eigen is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License or the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License and a copy of the GNU General Public License along with
// Eigen. If not, see <http://www.gnu.org/licenses/>.

#include "lapack_common.h"
#include <Eigen/Cholesky>

// POTRF computes the Cholesky factorization of a real symmetric positive definite matrix A.
EIGEN_LAPACK_FUNC(potrf,(char* uplo, int *n, RealScalar *pa, int *lda, int *info))
{
  *info = 0;
        if(UPLO(*uplo)==INVALID) *info = -1;
  else  if(*n<0)                 *info = -2;
  else  if(*lda<std::max(1,*n))  *info = -4;
  if(*info!=0)
  {
    int e = -*info;
    return xerbla_(SCALAR_SUFFIX_UP"POTRF", &e, 6);
  }

  Scalar* a = reinterpret_cast<Scalar*>(pa);
  MatrixType A(a,*n,*n,*lda);
  int ret;
  if(UPLO(*uplo)==UP) ret = internal::llt_inplace<Scalar, Upper>::blocked(A);
  else                ret = internal::llt_inplace<Scalar, Lower>::blocked(A);

  if(ret>=0)
    *info = ret+1;

  return 0;
}

// POTRS solves a system of linear equations A*X = B with a symmetric
// positive definite matrix A using the Cholesky factorization
// A = U**T*U or A = L*L**T computed by DPOTRF.
EIGEN_LAPACK_FUNC(potrs,(char* uplo, int *n, int *nrhs, RealScalar *pa, int *lda, RealScalar *pb, int *ldb, int *info))
{
  *info = 0;
        if(UPLO(*uplo)==INVALID) *info = -1;
  else  if(*n<0)                 *info = -2;
  else  if(*nrhs<0)              *info = -3;
  else  if(*lda<std::max(1,*n))  *info = -5;
  else  if(*ldb<std::max(1,*n))  *info = -7;
  if(*info!=0)
  {
    int e = -*info;
    return xerbla_(SCALAR_SUFFIX_UP"POTRS", &e, 6);
  }

  Scalar* a = reinterpret_cast<Scalar*>(pa);
  Scalar* b = reinterpret_cast<Scalar*>(pb);
  MatrixType A(a,*n,*n,*lda);
  MatrixType B(b,*n,*nrhs,*ldb);

  if(UPLO(*uplo)==UP)
  {
    A.triangularView<Upper>().adjoint().solveInPlace(B);
    A.triangularView<Upper>().solveInPlace(B);
  }
  else
  {
    A.triangularView<Lower>().solveInPlace(B);
    A.triangularView<Lower>().adjoint().solveInPlace(B);
  }

  return 0;
}


/**
 * Copyright (c) 2011-2017 libbitcoin developers (see AUTHORS)
 *
 * This file is part of libbitcoin.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <bitcoin/bitcoin/formats/base_10.hpp>

#include <iomanip>
#include <sstream>
#include <boost/algorithm/string.hpp>
#include <bitcoin/bitcoin/constants.hpp>

namespace libbitcoin {

/**
 * Returns true if a character is one of `[0-9]`.
 *
 * The C standard library function `isdigit` depends on the current locale,
 * and doesn't necessarily correspond to the valid base10 encoding digits.
 * The Microsoft `isdigit` includes superscript characters like '²'
 * in some locales, for example.
 */
static bool is_digit(const char c)
{
    return '0' <= c && c <= '9';
}

template <char C>
bool char_is(const char c)
{
    return c == C;
}

bool decode_base10(uint64_t& out, const std::string& amount,
    uint8_t decimal_places, bool strict)
{
    std::string value(amount);

    // Get rid of the decimal point:
    auto point = std::find(value.begin(), value.end(), '.');
    if (point != value.end())
        point = value.erase(point);

    // Only digits should remain:
    if (!std::all_of(value.begin(), value.end(), is_digit))
        return false;

    // Add digits to the end if there are too few:
    auto actual_places = value.end() - point;
    if (actual_places < decimal_places)
        value.append(decimal_places - actual_places, '0');

    // Remove digits from the end if there are too many:
    bool round = false;
    if (actual_places > decimal_places)
    {
        auto end = point + decimal_places;
        round = !std::all_of(end, value.end(), char_is<'0'>);
        value.erase(end, value.end());
    }
    if (strict && round)
        return false;

    // Convert to an integer:
    std::istringstream stream(value);
    uint64_t number = 0;
    if (value.size() && !(stream >> number))
        return false;

    // Round and return:
    if (round && number == max_uint64)
        return false;
    out = number + round;
    return true;
}

std::string encode_base10(uint64_t amount, uint8_t decimal_places)
{
    std::ostringstream stream;
    stream << std::setfill('0') << std::setw(1 + decimal_places) << amount;

    auto string = stream.str();
    string.insert(string.size() - decimal_places, 1, '.');
    boost::algorithm::trim_right_if(string, char_is<'0'>);
    boost::algorithm::trim_right_if(string, char_is<'.'>);
    return string;
}

bool btc_to_satoshi(uint64_t& satoshi, const std::string& btc)
{
    return decode_base10(satoshi, btc, btc_decimal_places);
}

std::string satoshi_to_btc(uint64_t satoshi)
{
    return encode_base10(satoshi, btc_decimal_places);
}

} // namespace libbitcoin


//
// Created by abakfja on 3/28/21.
//

#ifndef LA_VECTOR_ENGINE_HPP
#define LA_VECTOR_ENGINE_HPP

#include <boost/numeric/ublas/matrix/storage_traits.hpp>

namespace boost::numeric::ublas::experimental::layout {
    struct row_major{};
    struct column_major{};
}

namespace boost::numeric::ublas::experimental {

template<typename T, std::size_t N, class Layout>
struct static_vector_engine {
    using scalar_type = T;
    using array_type = std::array<scalar_type, N>;
    using storage_traits_type = storage_traits<array_type>;
    using size_type = typename storage_traits_type::size_type;
    using resizable_tag = typename storage_traits_type::resizable_tag;
    using layout_tag = Layout;
    static constexpr size_type n = N;
};

template<typename T, class Layout>
struct dynamic_vector_engine {
    using scalar_type = T;
    using array_type = std::vector<T>;
    using storage_traits_type = storage_traits<array_type>;
    using resizable_tag = typename storage_traits_type::resizable_tag;
    using layout_tag = Layout;
};

}

#endif //LA_VECTOR_ENGINE_HPP