//   Copyright 2015 Patrick Putnam
//
//   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 ASSORTATIVE_SELECTOR_HPP_
#define ASSORTATIVE_SELECTOR_HPP_

#include <boost/random/discrete_distribution.hpp>
#include <boost/random/uniform_real_distribution.hpp>
#include <boost/math/constants/constants.hpp>
#include <cmath>

#include <iterator>
#include <cmath>
#include <cassert>
#include <iostream>

template < class URNG >
struct assortative_selector {
    typedef URNG rng_type;
    typedef unsigned int result_type;

    typedef boost::random::discrete_distribution< result_type, double > dist_type;

    typedef std::vector< std::pair< double, unsigned int > > population_fitness_type;
    typedef typename population_fitness_type::iterator      fitness_iterator;

    static const unsigned int BIN_SIZE = 100;
    static const unsigned int BIN_COUNT = 100;

    rng_type * m_rng;
    dist_type m_dist;
    boost::random::uniform_real_distribution< double > m_uniform;

    unsigned int m_next_bin;

    population_fitness_type m_pop_fit;
    std::vector< size_t >   m_bin_sizes;

    struct simple_comp {
        bool operator()( const std::pair< double, unsigned int > & lhs, const std::pair< double, unsigned int > & rhs ) {
            return (lhs.first < rhs.first);
        }
    };

    assortative_selector( const assortative_selector< URNG > & other ) :
        m_rng( other.m_rng)
        , m_dist( other.m_dist )
        , m_uniform()
        , m_next_bin( other.m_next_bin )
        , m_pop_fit( other.m_pop_fit )
        , m_bin_sizes( other.m_bin_sizes )
    {}

    template < class Iter >
    assortative_selector( rng_type & rng, Iter first, Iter last ) :
        m_rng( &rng )
        , m_dist()
        , m_uniform()
        , m_next_bin( -1 )
        , m_pop_fit()
    {
        init_method2( first, last );
    }

    /// allows selfing
    unsigned int operator()() {
        unsigned int bstart = 0, bsize = 0;
        if( m_next_bin == -1 ) {
            unsigned int bin_idx = m_dist( *m_rng );
            unsigned int bin_y = (int) ((sqrt( 8 * bin_idx + 1) - 1) / 2);
            m_next_bin = bin_idx - (( bin_y + 1 ) * (bin_y) / 2);

            bstart = (( bin_y == 0 ) ? 0 : m_bin_sizes[ bin_y - 1] );
            bsize  = m_bin_sizes[ bin_y ] - bstart;
//            if( bsize == 0 ) {
//                std::cerr << bin_y << ", " << m_bin_sizes[bin_y] << std::endl;
//                std::cerr << "Bin Start: " << bstart << "; Bin Size: " << bsize << std::endl;
//                std::cerr << m_bin_sizes[bin_y] << std::endl;
//                std::cerr << m_dist.probabilities()[ bin_y ] << std::endl;
//                assert( false );
//            }
        } else {
            bstart = (( m_next_bin == 0 ) ? 0 : m_bin_sizes[ m_next_bin - 1] );
            bsize =  m_bin_sizes[m_next_bin] - bstart;
            m_next_bin = -1;
        }

        if( bsize == 0 ) {
            std::cerr << "Bin Start: " << bstart << "; Bin Size: " << bsize << std::endl;
            assert( false );
        }

        double r = m_uniform( *m_rng );
        return m_pop_fit[ bstart + r * bsize ].second;
    }

    virtual ~assortative_selector() {}
protected:

    template < class Iter >
    void init_method1( Iter first, Iter last ) {
        unsigned int N = 0;
        while( first != last ) {
            m_pop_fit.push_back( std::make_pair( *first, N++ ) );
            ++first;
        }

        simple_comp comp;

        std::sort( m_pop_fit.begin(), m_pop_fit.end(), comp );

        std::vector< double > bin_mean;
        if( m_pop_fit.size() % BIN_SIZE ) {
            bin_mean.reserve( m_pop_fit.size() / BIN_SIZE + 1 );
        } else {
            bin_mean.reserve( m_pop_fit.size() / BIN_SIZE );
        }

        fitness_iterator pit = m_pop_fit.begin();
        double accum = 0.0;
        unsigned int b = BIN_SIZE;

        unsigned int bin_size_accum = 0;
        while( pit != m_pop_fit.end() ) {
            accum += pit->first;
            if( ! --b ) {
                bin_mean.push_back( accum / (double) BIN_SIZE );
                bin_size_accum += BIN_SIZE;
                m_bin_sizes.push_back( bin_size_accum );
                accum = 0.0;
                b = BIN_SIZE;
            }
            ++pit;
        }

        if( b != BIN_SIZE ) {
            bin_mean.push_back( accum / (BIN_SIZE - b) );
            bin_size_accum += (BIN_SIZE - b);
            m_bin_sizes.push_back( bin_size_accum );
        }
        assert(m_bin_sizes.back() == N );

        init_discrete_distribution( bin_mean );
    }

    template < class Iter >
    void init_method2( Iter first, Iter last ) {
//        std::cerr << "Init method 2 " << std::endl;
        unsigned int N = 0;
        double min_fit = 2.0, max_fit = 0.0;
        while( first != last ) {
            m_pop_fit.push_back( std::make_pair( *first, N++ ) );

            if( *first > max_fit ) {
                max_fit = *first;
            }

            if( *first < min_fit ) {
                min_fit = *first;
            }
            ++first;
        }

        simple_comp comp;
        std::sort( m_pop_fit.begin(), m_pop_fit.end(), comp );

        std::vector< double > bin_mean;
        bin_mean.reserve( BIN_COUNT );
        m_bin_sizes.reserve( BIN_COUNT );

//        double bin_size = (max_fit - min_fit) / (double) BIN_COUNT;
        if( max_fit == min_fit) {
            // all individuals have the same fitness
            // therefore all bins have same average fitness
            // divide population into BIN_COUNT bins
                                                            // if N = 101 && BIN_COUNT = 100 then
            unsigned int ind_per_bin = (N / BIN_COUNT);     // ind_per_bin = 1
            unsigned int b = BIN_COUNT;                     // b = 100
            unsigned int n = N % BIN_COUNT;                 // n = 1
            unsigned int baccum = 0;

            while( n-- ) {                                  // single round
                // N - (BIN_COUNT - 1) * ind_per_bin
                bin_mean.push_back( max_fit );
                baccum += (ind_per_bin + 1);
                m_bin_sizes.push_back( baccum ); // push_back(1 + 1)
                --b;                                        // b = 99
            }

            while( b-- ) {                                  // 99 rounds
                bin_mean.push_back( max_fit );              //
                baccum += (ind_per_bin);                    //
                m_bin_sizes.push_back( baccum );            // push_back( )
            }
                                                            // | m_bin_sizes | = | < 2, 3, 4, 5, ..., 100, 101 > | = 100
        } else {
            double bin_step = (1.0000001 * max_fit - min_fit) / (double)(BIN_COUNT);
            fitness_iterator pit = m_pop_fit.begin();
            double accum = 0.0, bin_max = min_fit + bin_step;

//            std::cerr << "Bin Range: <" << min_fit << ", " << max_fit << ", "  << bin_step << " >" << std::endl;

            unsigned int count = 0, bin_size_accum = 0;
            while( pit != m_pop_fit.end() ) {
                if( pit->first >= bin_max ) {

                    bin_mean.push_back( accum / (double) count );
                    bin_size_accum += count;
                    m_bin_sizes.push_back( bin_size_accum );

                    accum = 0.0;
                    count = 0;
                    bin_max += bin_step;

                    // advance to next containing bin
                    while( pit->first >= bin_max ) {
                        bin_mean.push_back( 0.0 );
                        m_bin_sizes.push_back( bin_size_accum );
                        bin_max += bin_step;
                    }
                }
                accum += pit->first;
                ++count;

                ++pit;
            }
            if( bin_size_accum < (double) N ) {
                bin_mean.push_back( accum / (double) count );
                bin_size_accum += count;
                assert( bin_size_accum == (double) N );

                m_bin_sizes.push_back( bin_size_accum );
            }
        }

//        std::cerr << "Last two bin sizes: " << m_bin_sizes[ m_bin_sizes.size() - 2] << ", " << m_bin_sizes.back() << std::endl;
        assert( m_bin_sizes.back() == N);
//        std::cerr << "bin count: " << bin_mean.size() << std::endl;
        assert( bin_mean.size() == BIN_COUNT );

        init_discrete_distribution( bin_mean );
    }

    void init_discrete_distribution( std::vector< double > & bin_mean ) {
        // N * (N + 1) / 2
//        std::cerr << "Bin Mean Size: " << bin_mean.size() << std::endl;
        unsigned int bin_pairs = bin_mean.size() * (bin_mean.size() + 1)  / 2;
        assert( bin_pairs != -1 );

        std::vector< double > pairs( bin_pairs, 0.0);
        std::vector< double >::iterator it = pairs.begin();
        std::vector< double >::iterator y = bin_mean.begin();
        while( y != bin_mean.end() ) {
            double fit = *y;
            ++y;
            std::vector<double>::iterator x = bin_mean.begin();
            while( x != y ) {
                (*it) = prob2( fit, (*x) );
                ++it;
                ++x;
            }
        }

        assert( it == pairs.end() );

        typename dist_type::param_type p(pairs.begin(), pairs.end());
        m_dist.param(p);
    }

    double prob1( double x, double y ) {
        return x * y;
    }

    double prob2( double x, double y, double mu = 0.0, double sigma = 1.0 ) {
        double res = x * y;
        if( res == 0.0 ) return 0.0;

        double coeff = (1.0/sigma) * boost::math::double_constants::one_div_root_two_pi;
        double var = sigma * sigma;

        res = ( (1.0 - res) - mu); // (1 - xy) - mu
        res *= res;
        res /= (2.0 * var);

        return coeff * exp(-res);
    }
};

#endif  // ASSORTATIVE_SELECTOR_HPP_


///////////////////////////////////////////////////////////////////////////////
// lost_df_accumulator.hpp                                                   //
//                                                                           //
//  Copyright 2010 Erwann Rogard. Distributed under the Boost                //
//  Software License, Version 1.0. (See accompanying file                    //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)         //
///////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_DETAIL_NON_PARAMETRIC_CONTINGENCY_TABLE_PEARSON_CHISQ_INDEPENDENCE_AUX_LOST_DF_ACCUMULATOR_HPP_ER_2010
#define BOOST_STATISTICS_DETAIL_NON_PARAMETRIC_CONTINGENCY_TABLE_PEARSON_CHISQ_INDEPENDENCE_AUX_LOST_DF_ACCUMULATOR_HPP_ER_2010
#include <boost/mpl/detail/wrapper.hpp>
#include <boost/statistics/detail/non_parametric/contingency_table/factor/levels.hpp>

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

    template<typename A,typename N>
    struct lost_df_accumulator{

        lost_df_accumulator(const A& acc, N& df) : acc_(acc), df_(df){}

        template<typename Key>
        void operator()(const boost::mpl::detail::wrapper<Key>& wrapper)const
        {
            namespace ct = contingency_table;
            this->df_ += (ct::extract::levels<Key>( this->acc_ ).size() - 1);
        }

        private:
        const A& acc_;
        mutable N& df_;
    };

    template<typename A,typename N>
    independence_between_aux::lost_df_accumulator<A,N>
    make_lost_df_accumulator(const A& acc, N& df)
    {
        namespace aux = independence_between_aux;
        return aux::lost_df_accumulator<A,N>(acc, df);
    }

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

#endif


#ifndef __HMM_HPP__
#define __HMM_HPP__

#include "utility.hpp"
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <fstream>
#include <boost/functional/hash.hpp>
#include <boost/bimap.hpp>
#include <math.h>

	auto total = std::chrono::duration<double, std::milli>{0};

template <typename viterbi_T>
class HMM {
public:
	using Viterbi_type = viterbi_T;
	using Model_type = typename Viterbi_type::Model;

public:
	HMM() = delete;
	HMM(const std::string& filename) : m_viterbi(mlTrain(filename)) {}

	HMM(const HMM& other) = default;
	HMM& operator=(const HMM& rhs) = default;
	std::vector<std::string> infer(const std::vector<std::string>& ws) {
		auto is =
			std::vector<typename Viterbi_type::Emission_type>(ws.size(), 0);

		std::transform(ws.cbegin(), ws.cend(), is.begin(),
					   [this](const std::string& w) {
						   auto i = emissionBijection.left.find(w);
						   if (i == emissionBijection.left.end()) {
							   std::cerr << w << " not covered.\n";
							   return 0;
						   }
						   return i->second;
					   });

		auto before = std::chrono::high_resolution_clock::now();

		auto iouts = m_viterbi.infer(is);

		total += std::chrono::high_resolution_clock::now() - before;

		auto outs = std::vector<std::string>(ws.size(), "");

		std::transform(iouts.cbegin(), iouts.cend(), outs.begin(),
					   [this](const typename Viterbi_type::Label_type& l) {
						   return labelBijection.right.find(l)->second;
					   });

		return outs;
	}  // end infer

private:
	using stringmap = std::unordered_map<std::string, int>;
	using stringpair = std::pair<std::string, std::string>;
	using pairmap =
		std::unordered_map<stringpair, int, boost::hash<stringpair>>;
	using bijection = boost::bimap<std::string, int>;
	using bijectionPair = bijection::value_type;

private:
	Model_type mlTrain(const std::string& filename) {
		auto file = std::ifstream{filename};

		bool isNewSent = true;
		stringmap initialLabelCount;
		stringmap labelCount;
		pairmap transitionCount;
		pairmap emissionCount;
		std::string prevLabel;
		int nextLabel = 0;
		int nextEmission = 0;
		int numSents = 0;

		for (std::string label, emission = "";
			 std::getline(file, emission) && std::getline(file, label);) {
			// if its two new lines in the file emission will be empty
			// note: we require two newlines at end of file (or numSents is
			// wrong)
			if (emission == "") {
				isNewSent = true;
				numSents += 1;
				emission = label;
				if (!std::getline(file, label)) break;
//			} else {
			}

				// bijections
				if (labelBijection.left.find(label) ==
					labelBijection.left.end()) {
					labelBijection.insert(bijectionPair(label, nextLabel++));
				}

				if (emissionBijection.left.find(emission) ==
					emissionBijection.left.end()) {
					emissionBijection.insert(
						bijectionPair(emission, nextEmission++));
				}

				if (isNewSent) {
					isNewSent = false;
					initialLabelCount[label] += 1;
					labelCount[label] += 1;
					prevLabel = label;
				} else {
					// it is not a new sentence, so prevLabel is valid
					transitionCount[std::make_pair(prevLabel, label)] += 1;
					labelCount[label] += 1;
				}

				// we can always count emissions
				emissionCount[std::make_pair(label, emission)] += 1;
				prevLabel = label;
		}  // end for

		// now comes the ml-estimate step
		auto maxLabel = nextLabel;
		auto maxEmission = nextEmission;
/*		std::cout << "Labels: " << maxLabel << "\nEmissions: " << maxEmission
				  << "\nSentences: " << numSents << std::endl;
*/
		auto m = Model_type{maxLabel, maxEmission, std::log(0.0f)};

/*		for (auto i = 0; i < maxLabel; ++i) {
			m.start[i] =
				log(initialLabelCount[labelBijection.right.find(i)->second]) -
				log(numSents);
		}
*/

		for(const auto& l : initialLabelCount) {
			m.setStart(labelBijection.left.find(l.first)->second, log(l.second) - log(numSents));
		}

		// transitions
		for(const auto& t : transitionCount) {
			auto i = labelBijection.left.find(t.first.first)->second;
			auto j = labelBijection.left.find(t.first.second)->second;
			m.setTransition(i, j, log(t.second) - log(labelCount[t.first.first]));
		}

/*
		for (auto i = 0; i < maxLabel; ++i) {
			for (auto j = 0; j < maxLabel; ++j) {
				m.setTransition(i, j,
					log(transitionCount[std::make_pair(
						labelBijection.right.find(i)->second,
						labelBijection.right.find(j)->second)]) -
					log(labelCount[labelBijection.right.find(i)->second]));
			}
		}
*/
		// emissions
/*
		for(auto i = 0; i < maxLabel; ++i) {
		for(auto j = 0; j < nextEmission-1; ++j) {
			m.emissions(i, j) = log(emissionCount[std::make_pair(labelBijection.right.find(i)->second, emissionBijection.right.find(j)->second)]) - log(labelCount[labelBijection.right.find(i)->second]);
		}
		}
*/
		for ( auto e = emissionCount.begin(); e != emissionCount.end(); ++e) {
			m.setEmission(labelBijection.left.find(e->first.first)->second,
						emissionBijection.left.find(e->first.second)->second,
						log(e->second) - log(labelCount[e->first.first]));
		}

		return std::move(m);
	}

private:
	bijection labelBijection;
	bijection emissionBijection;
	Viterbi_type m_viterbi;
};  // end class HMM

#endif


/*
 * Copyright 2020 Ryan Levy, Xiongjie Yu, and Bryan K. Clark
 *
 * 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.
 */

#include "../util/types_and_headers.h"
#include "../linalg/lapack_wrapper.h"
#include "../dtensor/dtensor_all.h"
#include "../qtensor/qtensor_all.h"
#include "../mps/mps_all.h"

#include "../models/sites/spinhalf.h"
#include "../models/sites/electron.h"
#include "../models/hams/Heisenberg.h"

#include "../algos/dmrg/dmrg.h"

#include "../util/timer.h"

#include "../models/hams/AutoMPO.h"

#include "../models/lattice/square.h"
#include "../models/lattice/triangular.h"

#include <ctf.hpp>
#include <sys/stat.h>
using namespace std;
inline bool check_existence(const string& name);

//#include <Eigen/Dense>
#include "hdf5.h"

filebuf fbuf;
ostream perr(&fbuf);
filebuf fbufo;
ostream pout(&fbufo);

template <typename T, template <typename,unsigned> class TensorType>
void fixTensors(TensorType<T,1>& psi,TensorType<T,2>& H){
  uint_vec perm;
  string Link_name_pref = "ID"+to_string(psi._id)+"Link";
  string Site_name_pref = "Site";
  for(unsigned l=0;l<psi.length;l++){
    string left_link_name  = Link_name_pref+to_string(l);
    string right_link_name = Link_name_pref+to_string(l+1);
    string site_name       = Site_name_pref+to_string(l);
    vector<qtensor_index> inds(3);
    for(auto& ix : psi.A[l].idx_set){
      if(ix.name()==left_link_name) inds[0] = ix;
      else if(ix.name()==site_name) inds[1] = ix;
      else if(ix.name()==right_link_name) inds[2] = ix;
    }
    find_index_permutation(psi.A[l].idx_set,inds, perm);
    psi.A[l].permute(perm);
  }
  //check if we need to dagger H
  string site_name = Site_name_pref+to_string(0);
  auto& psiSet = psi.A[0].idx_set;
  auto& HSet   = H.A[0].idx_set;
  auto it  = find_if(psiSet.begin(), psiSet.end(), [&](qtensor_index& qi){ return qi.name()==site_name;});
  auto itH = find_if(HSet.begin(),   HSet.end(),   [&](qtensor_index& qi){ return (qi.name()==site_name) && (qi.level()==0);});
  assert(it!=psiSet.end() && itH != HSet.end());
  if( it->arrow() == itH->arrow()){
    for(auto& Ai: H.A) Ai.dag();
  }

}

int main(int argc, char **argv)
{
  MPI_Init(&argc, &argv);
  {
    CTF::World world(argc,argv);
    if (world.rank == 0){
      perr.rdbuf(cerr.rdbuf());
      pout.rdbuf(cout.rdbuf());
      perr.precision(12);
      pout.precision(12);
    }
    if(argc==1) {
      perr<<"Error: Need filename!"<<endl;
      assert(1==2);
    }
    string inputName = string(argv[1]);
    ifstream in;
    in.open(inputName);
    assert(in);
    string line; stringstream linest;

    /*
     *              input file format
     * type
     * Nx Ny
     * J1 J2
     * fname
     * prefix
     * nsweeps
     * maxm maxm maxm
     * cutoff cutoff cutoff
     * restart restart restart
     *
     */

    string type; getline(in,type);
    getline(in,line); linest.clear(); linest.str(line);
    int Nx,Ny;
    linest >> Nx >> Ny;
    auto N = Nx*Ny;
    double J1,J2;
    getline(in,line); linest.clear(); linest.str(line);
    perr << "J1 line=" << line << endl;
    linest >> J1 >> J2;
    string fname="";
    getline(in,line); if(!line.empty()) fname=line;
    string pref="";
    getline(in,line); if(!line.empty()) pref=line;
    int nsweeps = 0;
    getline(in,line); linest.clear(); linest.str(line);
    linest >> nsweeps;

    vector<int> maxm;
    vector<double> cutoff;
    vector<int> max_restart;
    getline(in,line); linest.clear(); linest.str(line);
    while(getline(linest,line,' ')){
      maxm.push_back(std::stoi(line));
    }
    getline(in,line); linest.clear(); linest.str(line);
    while(getline(linest,line,' ')){
      cutoff.push_back(std::stof(line));
    }
    getline(in,line); linest.clear(); linest.str(line);
    while(getline(linest,line,' ')){
      max_restart.push_back(std::stoi(line));
    }
    in.close();

    if(world.rank==0)
      printf("type=%s N=(%i,%i) J1=%f J2=%f\nfile=%s prefix=%s\nnsweeps=%i \n",
          type.c_str(),Nx,Ny,J1,J2,fname.c_str(),pref.c_str(),nsweeps);
    perr<<"maxm:";        for(auto m: maxm)        perr<<m<<" "; perr<<"\n";
    perr<<"cutoff:";      for(auto c: cutoff)      perr<<c<<" "; perr<<"\n";
    perr<<"max_restart:"; for(auto r: max_restart) perr<<r<<" "; perr<<"\n";
    //spinhalf sites(N);
    electron sites(N);
    str_vec ps;
    for (size_t i = 0; i < sites.N(); i++) {
      if(i%2==0)
        ps.push_back("Dn");
      else
        ps.push_back("Up");
    }

    AutoMPO ampo(sites);
    bool yperiodic = true;
    auto lattice = triangularLattice(Nx,Ny,yperiodic);
    auto t = 1.0;
    auto U = J2;

    for(auto bnd : lattice){
      //hopping terms in the 1DEG
      ampo+=-t,"Cdagup",bnd.s1-1,"Cup",bnd.s2-1;
      ampo+=-t,"Cdagdn",bnd.s1-1,"Cdn",bnd.s2-1;
      ampo+=-t,"Cdagup",bnd.s2-1,"Cup",bnd.s1-1;
      ampo+=-t,"Cdagdn",bnd.s2-1,"Cdn",bnd.s1-1;
    }
    //-------------------------
    for(int i=0;i<N;i++)
      ampo+=U,"Nupdn",i;

    if(type=="d"){
      pout << "\n" << "Dense MPS DMRG (Linear Heisenberg)" << '\n';

      MPS<double> psi(&sites,ps);
      //psi.load(fname); //TODO: fix pref
      psi.print();
      MPO< double > H;
      Heisenberg< double > HB(&sites);
      HB.buildHam(H);
      dmrg(psi, H, nsweeps, maxm, cutoff, max_restart);
      psi.print();

    }
    if(type=="q"){
      pout << "\n" << "qMPS Fermionic DMRG" << '\n';

      qMPS< double > psi(&sites,ps);
      bool yperiodic=true;
      auto lattice = triangularLattice(Nx,Ny,yperiodic);

      auto t = 1.0;
      auto U = J2;

      AutoMPO ampo(sites);
      for(auto bnd : lattice){
        //hopping terms in the 1DEG
        ampo+=-t,"Cdagup",bnd.s1-1,"Cup",bnd.s2-1;
        ampo+=-t,"Cdagdn",bnd.s1-1,"Cdn",bnd.s2-1;
        ampo+=-t,"Cdagup",bnd.s2-1,"Cup",bnd.s1-1;
        ampo+=-t,"Cdagdn",bnd.s2-1,"Cdn",bnd.s1-1;
      }
      //-------------------------
      for(int i=0;i<N;i++)
        ampo+=U,"Nupdn",i;

      string postfix = to_string(Nx)+"x"+to_string(Ny)+"_U"+to_string(U);
      auto sp = "psi_"+postfix;
      qMPO< double > H;
      Heisenberg< double > HB(&sites);
      perr<<"making H"<<endl;
      HB.buildHam(ampo,H);

      psi.load(fname,pref);
      psi.print();

      assert(H._id != psi._id);
      fixTensors(psi,H);

      dmrg(psi, H, nsweeps, maxm, cutoff, max_restart);

    }
    if(type=="qs"){
      pout << "\n" << "qsMPS Fermionic DMRG" << '\n';

      qsMPS<double> psi(&sites,ps);
      psi.load(fname,pref);
      psi.print();
      qMPO< double > H;
      Heisenberg< double > HB(&sites);
      HB.buildHam(ampo,H);
      qsMPO< double > Hq = H;

      assert(H._id != psi._id);
      fixTensors(psi,Hq);

      dmrg(psi, Hq, nsweeps, maxm, cutoff, max_restart);

      psi.print();
      for(int l=0;l<N;l++){
        auto& Al = psi.A[l];
        perr<<Al._T.nnz_tot<<","<<(double)Al._T.nnz_tot/(Al._T.get_tot_size(false))<<endl;
        for(size_t i=0;i<Al.block_index_qd.size();i++)
          perr<<"   ("<<Al.block_index_qd[i][0]<<","
              << Al.block_index_qd[i][1]<<","<<Al.block_index_qd[i][2]<<")\n";
      }
    }
    if(type=="qToqs"){
      pout << "\n" << "q to qsMPS Fermionic DMRG" << '\n';

      qMPO< double > H;
      Heisenberg< double > HB(&sites);
      HB.buildHam(ampo,H);
      H.load("H_36_1.h5");
      H.print();
      qsMPO< double > Hq = H;

      qMPS<double> psi(&sites);
      psi.load(fname,pref);
      psi.print();
      qsMPS<double> psiq = psi;
      psiq.print();

      assert(H._id != psi._id);
      fixTensors(psiq,Hq);

      dmrg(psiq, Hq, nsweeps, maxm, cutoff, max_restart);

      psiq.print();
      /*for(int l=0;l<N;l++){
        auto& Al = psiq.A[l];
        perr<<Al._T.nnz_tot<<","<<(double)Al._T.nnz_tot/(Al._T.get_tot_size(false))<<endl;
        perr<<"   ";
        for(size_t i=0;i<Al.block_index_qd.size();i++)
          perr<<"("<<Al.block_index_qd[i][0]<<","
              << Al.block_index_qd[i][1]<<","<<Al.block_index_qd[i][2]<<") ";
        perr<<'\n';
      }*/
    }
  }
  MPI_Finalize();
  return 0;
  exit(1);
  //------------------------------------
  return 0;
}

inline bool check_existence(const std::string& name){
  struct stat buffer;
  return (stat (name.c_str(),&buffer) == 0);
}


/*    Copyright (c) 2010-2015, Delft University of Technology
 *    All rights reserved.
 *
 *    Redistribution and use in source and binary forms, with or without modification, are
 *    permitted provided that the following conditions are met:
 *      - Redistributions of source code must retain the above copyright notice, this list of
 *        conditions and the following disclaimer.
 *      - Redistributions in binary form must reproduce the above copyright notice, this list of
 *        conditions and the following disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *      - Neither the name of the Delft University of Technology nor the names of its contributors
 *        may be used to endorse or promote products derived from this software without specific
 *        prior written permission.
 *
 *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 *    OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *    MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *    COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 *    GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 *    AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 *    OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *    Changelog
 *      YYMMDD    Author            Comment
 *      100902    K. Kumar          File header and footer added.
 *      100916    D. Dirkx          Added minor comments during checking.
 *      100928    K. Kumar          Small comment modifications.
 *      100929    K. Kumar          Small comment modifications.
 *      110202    K. Kumar          Added overload for map with State* for
 *                                  computeNearestLeftNeighborUsingBinarySearch( ).
 *      110803    J. Leloux         Added convertStringToTemplate.
 *      110805    J. Leloux         Added outputCurrentRunningTime( ).
 *      110807    K. Kumar          Minor comment modifications.
 *      110810    J. Leloux         Minor comment modifications.
 *      110913    K. Kumar          Implemented automatic root-path functions based on
 *                                  suggestions by M. Persson.
 *      111117    K. Kumar          Added listAllFilesInDirectory( ) function.
 *
 *    References
 *      Press W.H., et al. Numerical Recipes in C++: The Art of Scientific Computing. Cambridge
 *          University Press, February 2002.
 *
 *    Notes
 *      Need to add bounds checking with respect to targetValue for
 *      computeNearestLeftNeighborUsingBinarySearch( ).
 */

#include <map>
#include <iterator>
#include <Eigen/Core>

namespace tudat
{
namespace basic_mathematics
{

//! Nearest left neighbor binary search.
int computeNearestLeftNeighborUsingBinarySearch(
        const Eigen::VectorXd& vectorOfSortedData,
        const double targetValueInVectorOfSortedData )
{
    // Declare local variables.
    // Declare bounds of vector of sorted data and current position.
    int leftLimitOfVectorOfSortedData = 0;
    int rightLimitOfVectorOfSortedData = vectorOfSortedData.rows( ) - 1;
    int currentPositionInVectorOfSortedData;

    // Check if data is sorted in ascending order.
    // ( true if ascending, else false ).
    bool isVectorOfSortedDataAscending
            = ( vectorOfSortedData[ rightLimitOfVectorOfSortedData ]
                >= vectorOfSortedData[ leftLimitOfVectorOfSortedData ] );

    // Loop through vector of sorted data until left and right limits
    // are neighbours.
    while ( rightLimitOfVectorOfSortedData
            - leftLimitOfVectorOfSortedData > 1 )
    {
        // Compute midpoint ( bitshift is same as division by 2.0 ).
        currentPositionInVectorOfSortedData
                = ( rightLimitOfVectorOfSortedData
                    + leftLimitOfVectorOfSortedData ) >> 1;

        // Check which limit to replace ( if ascending and target datum
        // is in right half, replace left limit ).
        if ( targetValueInVectorOfSortedData
             >= vectorOfSortedData[ currentPositionInVectorOfSortedData ]
             && isVectorOfSortedDataAscending )
        {
            // Set left limit to current position in vector of sorted data.
            leftLimitOfVectorOfSortedData = currentPositionInVectorOfSortedData;
        }

        else
        {
            // Set right limit to current position in vector of sorted data.
            rightLimitOfVectorOfSortedData = currentPositionInVectorOfSortedData;
        }
    }

    // Set current position to left limit.
    currentPositionInVectorOfSortedData = leftLimitOfVectorOfSortedData;

    // Return current position in vector.
    return currentPositionInVectorOfSortedData;
}

//! Nearest left neighbor binary search.
int computeNearestLeftNeighborUsingBinarySearch(
        const std::map < double, Eigen::VectorXd >& sortedIndepedentAndDependentVariables,
        const double targetValueInMapOfData )
{
    // Declare local variables.
    // Declare bounds of key of map of data and current position.
    int leftLimitOfKeyOfMapOfData = 0;
    int rightLimitOfKeyOfMapOfData = sortedIndepedentAndDependentVariables
                                     .size( ) - 1;
    int currentPositionInKeyOfMapOfData;

    // Declare map iterator
    std::map < double, Eigen::VectorXd >::const_iterator mapIterator;

    // Loop through vector of sorted data until left and right limits
    // are neighbours
    while ( rightLimitOfKeyOfMapOfData - leftLimitOfKeyOfMapOfData > 1 )
    {
        // Compute midpoint ( bitshift is same as division by 2.0 ).
        currentPositionInKeyOfMapOfData
                = ( rightLimitOfKeyOfMapOfData
                   + leftLimitOfKeyOfMapOfData ) >> 1;

        // Set map iterator to begin begin of map of sorted independent and
        // dependent variables.
        mapIterator = sortedIndepedentAndDependentVariables.begin( );

        // Advance iterator to location of current position in key of map of
        // data.
        advance( mapIterator, currentPositionInKeyOfMapOfData );

        // Check that target value lies to the right of lower bound.
        if ( targetValueInMapOfData
             >= mapIterator->first )
        {
            // Set left limit to current position in map of data.
            leftLimitOfKeyOfMapOfData = currentPositionInKeyOfMapOfData;
        }

        else
        {
            // Set right limit to current position in map of data.
            rightLimitOfKeyOfMapOfData = currentPositionInKeyOfMapOfData;
        }
    }

    // Set current position to left limit.
    currentPositionInKeyOfMapOfData = leftLimitOfKeyOfMapOfData;

    // Return current position in map of data.
    return currentPositionInKeyOfMapOfData;
}

} // namespace basic_mathematics
} // namespace tudat


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

#include "permutation.hpp"

#include <numeric>

#include <boost/algorithm/string/join.hpp>
#include <boost/assign/std/vector.hpp>
#include <boost/dynamic_bitset.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/algorithm_ext/push_back.hpp>

#include <core/utils/range_utils.hpp>
#include <reversible/functions/circuit_to_truth_table.hpp>
#include <reversible/simulation/simple_simulation.hpp>

using namespace boost::assign;
using boost::adaptors::transformed;

namespace cirkit
{

permutation_t identity_permutation( unsigned size )
{
  permutation_t perm( size );
  std::iota( perm.begin(), perm.end(), 0u );
  return perm;
}

permutation_t truth_table_to_permutation( const binary_truth_table& spec )
{
  permutation_t perm;

  for ( const auto& row : index( spec ) )
  {
    auto from = row.value.first;
    auto to   = row.value.second;

    assert( truth_table_cube_to_number( binary_truth_table::cube_type( from.first, from.second ) ) == row.index );
    perm += truth_table_cube_to_number( binary_truth_table::cube_type( to.first, to.second ) );
  }

  return perm;
}

permutation_t circuit_to_permutation( const circuit& circ )
{
  binary_truth_table spec;
  circuit_to_truth_table( circ, spec, simple_simulation_func() );
  return truth_table_to_permutation( spec );
}

cycles_t permutation_to_cycles( const permutation_t& perm, bool sort )
{
  cycles_t cycles;

  boost::dynamic_bitset<> vismask = ~boost::dynamic_bitset<>( perm.size() );
  unsigned start, current;

  while ( vismask.any() )
  {
    start = vismask.find_first();
    permutation_t cycle;
    cycle += start;
    current = perm[start];
    vismask.reset( current );

    while ( current != start ) {
      cycle += current;
      current = perm[current];
      vismask.reset( current );
    }

    cycles += cycle;
  }

  assert( vismask.none() );

  if ( sort )
  {
    boost::sort( cycles, []( const std::vector<unsigned>& x1, const std::vector<unsigned>& x2 ) { return x1.size() > x2.size(); } );
  }

  return cycles;
}

std::vector<std::pair<unsigned, unsigned>> permutation_to_transpositions( const permutation_t& perm )
{
  auto perm_copy = perm;
  std::vector<std::pair<unsigned, unsigned>> transpositions;

  for ( int i = perm_copy.size() - 1; i > 0; --i )
  {
    if ( static_cast<int>( perm_copy[i] ) == i ) { continue; }

    /* where is i? */
    auto other = std::distance( perm_copy.begin(), std::find( perm_copy.begin(), perm_copy.begin() + i, i ) );
    transpositions.push_back( {i, other} );

    std::swap( perm_copy[i], perm_copy[other] );
  }

  return transpositions;
}

unsigned permutation_inv( const permutation_t& perm )
{
  unsigned inv = 0u;

  for ( unsigned i = 0u; i < perm.size() - 1u; ++i )
  {
    for ( unsigned j = i + 1u; j < perm.size(); ++j )
    {
      if ( perm[i] > perm[j] )
      {
        ++inv;
      }
    }
  }

  return inv;
}

int permutation_sign( const permutation_t& perm )
{
  return ( permutation_inv( perm ) % 2 == 0u ) ? 1 : -1;
}

permutation_t permutation_multiply( const permutation_t& a, const permutation_t& b )
{
  assert( a.size() == b.size() );
  permutation_t result( a.size() );

  for ( auto i = 0u; i < a.size(); ++i )
  {
    result[i] = b[a[i]];
  }

  return result;
}

permutation_t permutation_invert( const permutation_t& perm )
{
  permutation_t result( perm.size() );

  for ( auto i = 0u; i < perm.size(); ++i )
  {
    result[perm[i]] = i;
  }

  return result;
}

std::vector<unsigned> cycles_type( const cycles_t& cycles )
{
  std::vector<unsigned> type( cycles.size() );
  boost::transform( cycles, type.begin(), []( const permutation_t& cycle ) { return cycle.size(); } );
  boost::sort( type );
  return type;
}

bool is_involution( const permutation_t& perm )
{
  auto c = permutation_to_cycles( perm );
  return boost::find_if( c, []( const std::vector<unsigned>& cycle ) { return cycle.size() > 2u; } ) == c.end();
}

inline unsigned pos( unsigned i, unsigned j, unsigned n )
{
  return i * n + ( i * ( i + 1 ) ) / 2 + j;
}

/* dynamic programming algorithm to check whether a permutation is
   simple.

   [M.H. Albert, M.D. Atkinson, M. Klazar, Journal of Integer Sequences 6 (2003), 03.4.4]
*/
bool is_simple( const permutation_t& perm )
{
  const unsigned n = perm.size();
  const auto sumn  = ( n * ( n + 1 ) ) / 2;

  std::vector<unsigned> m_min( sumn );
  std::vector<unsigned> m_max( sumn );

  auto p = 0u;
  for ( auto i = 0u; i < n; ++i )
  {
    m_min[p] = m_max[p] = perm.at( i );

    for ( auto j = ( i + 1u ); j < n; ++j )
    {
      m_min[p + 1u] = std::min( m_min[p], perm.at( j ) );
      m_max[p + 1u] = std::max( m_max[p], perm.at( j ) );
      ++p;
    }

    ++p;
  }

  p = 0u;
  for ( auto i = 0u; i < ( n - 1u ); ++i )
  {
    ++p;
    for ( auto j = ( i + 1u ); j < n; ++j )
    {
      if ( ( m_max[p] - m_min[p] ) == ( j - i ) && !( ( i == 0u ) && ( j == ( n - 1u ) ) ) )
      {
        return false;
      }
      ++p;
    }
  }

  return true;
}

std::string permutation_to_string( const permutation_t& perm )
{
  return "[" + any_join( perm, " " ) + "]";
}

std::string cycles_to_string( const cycles_t& cycles, bool print_fixpoints )
{
  return boost::join( cycles | transformed( [&print_fixpoints]( const permutation_t& cycle ) {
        return ( cycle.size() > 1u || print_fixpoints ) ? ( "(" + any_join( cycle, " " ) + ")" ) : std::string();
      } ), "" );
}

std::string cycles_to_string( const permutation_t& perm, bool print_fixpoints )
{
  return cycles_to_string( permutation_to_cycles( perm ), print_fixpoints );
}

std::string type_to_string( const std::vector<unsigned>& type )
{
  return "(" + any_join( type, ", " ) + ")";
}

}

// Local Variables:
// c-basic-offset: 2
// eval: (c-set-offset 'substatement-open 0)
// eval: (c-set-offset 'innamespace 0)
// End:


//  Copyright (c) 2019 National Technology & Engineering Solutions of Sandia,
//                     LLC (NTESS).
//  Copyright (c) 2014 Hartmut Kaiser
//  Copyright (c) 2014 Patricia Grubel
//  Copyright (c) 2019 Nikunj Gupta
//
//  SPDX-License-Identifier: BSL-1.0
//  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)

// This is the fourth in a series of examples demonstrating the development of
// a fully distributed solver for a simple 1D heat distribution problem.
//
// This example builds on example three. It futurizes the code from that
// example. Compared to example two this code runs much more efficiently. It
// allows for changing the amount of work executed in one HPX thread which
// enables tuning the performance for the optimal grain size of the
// computation. This example is still fully local but demonstrates nice
// scalability on SMP machines.

#include <hpx/local/algorithm.hpp>
#include <hpx/local/init.hpp>
#include <hpx/modules/resiliency.hpp>
#include <hpx/modules/synchronization.hpp>
#include <boost/range/irange.hpp>

#include <atomic>
#include <cstddef>
#include <cstdint>
#include <exception>
#include <iostream>
#include <memory>
#include <random>
#include <utility>
#include <vector>

struct validate_exception : std::exception
{
};

///////////////////////////////////////////////////////////////////////////////
double const pi = std::acos(-1.0);

// Variable to count the number of failed attempts
std::atomic<int> counter(0);

// Variables to generate errors
std::random_device rd;
std::mt19937 gen(rd());

///////////////////////////////////////////////////////////////////////////////
// Our partition data type
struct partition_data
{
public:
    partition_data(std::size_t size)
      : data_(size)
      , size_(size)
    {
    }

    partition_data(std::size_t subdomain_width, double subdomain_index,
        std::size_t subdomains)
      : data_(subdomain_width + 1)
      , size_(subdomain_width + 1)
    {
        for (std::size_t k = 0; k != subdomain_width + 1; ++k)
        {
            data_[k] = std::sin(2 * pi *
                ((0.0 + subdomain_width * subdomain_index + k) /
                    static_cast<double>(subdomain_width * subdomains)));
        }
    }

    partition_data(partition_data&& other)
      : data_(std::move(other.data_))
      , size_(other.size_)
    {
    }

    double& operator[](std::size_t idx)
    {
        return data_[idx];
    }
    double operator[](std::size_t idx) const
    {
        return data_[idx];
    }

    friend std::vector<double>::const_iterator begin(const partition_data& v)
    {
        return begin(v.data_);
    }
    friend std::vector<double>::const_iterator end(const partition_data& v)
    {
        return end(v.data_);
    }

    std::size_t size() const
    {
        return size_;
    }

    void resize(std::size_t size)
    {
        data_.resize(size);
        size_ = size;
    }

private:
    std::vector<double> data_;
    std::size_t size_;
};

std::ostream& operator<<(std::ostream& os, partition_data const& c)
{
    os << "{";
    for (std::size_t i = 0; i != c.size() - 1; ++i)
    {
        if (i != 0)
            os << ", ";
        os << c[i];
    }
    os << "}";
    return os;
}

///////////////////////////////////////////////////////////////////////////////
struct stepper
{
    // Our data for one time step
    typedef hpx::shared_future<partition_data> partition;
    typedef std::vector<partition> space;

    // Our operator
    static double stencil(double left, double center, double right)
    {
        return 0.5 * (0.75) * left + (0.75) * center - 0.5 * (0.25) * right;
    }

    static double left_flux(double left, double center)
    {
        return (0.625) * left - (0.125) * center;
    }

    static double right_flux(double center, double right)
    {
        return 0.5 * (0.75) * center + (1.125) * right;
    }

    // The partitioned operator, it invokes the heat operator above on all
    // elements of a partition.
    static partition_data heat_part(std::size_t sti, double error,
        partition_data const& left_input, partition_data const& center_input,
        partition_data const& right_input)
    {
        static thread_local std::exponential_distribution<> dist_(error);

        double num = dist_(gen);
        bool error_flag = false;

        // Probability of error occurrence is proportional to exp(-error_rate)
        if (num > 1.0)
        {
            error_flag = true;
            ++counter;
        }

        std::size_t const size = center_input.size() - 1;
        partition_data workspace(size + 2 * sti + 1);

        std::copy(
            end(left_input) - sti - 1, end(left_input) - 1, &workspace[0]);
        std::copy(begin(center_input), end(center_input) - 1, &workspace[sti]);
        std::copy(begin(right_input), begin(right_input) + sti + 1,
            &workspace[size + sti]);

        for (std::size_t t = 0; t != sti; ++t)
        {
            for (std::size_t k = 0; k != size + 2 * sti - 1 - 2 * t; ++k)
                workspace[k] =
                    stencil(workspace[k], workspace[k + 1], workspace[k + 2]);
        }

        workspace.resize(size + 1);

        // Artificial error injection to get replay in action
        if (error_flag)
            throw validate_exception();

        return workspace;
    }

    hpx::future<space> do_work(std::size_t subdomains,
        std::size_t subdomain_width, std::size_t iterations, std::size_t sti,
        std::uint64_t nd, std::uint64_t n_value, double error,
        hpx::lcos::local::sliding_semaphore& sem)
    {
        using hpx::resiliency::experimental::dataflow_replicate;
        using hpx::util::unwrapping;

        // U[t][i] is the state of position i at time t.
        std::vector<space> U(2);
        for (space& s : U)
            s.resize(subdomains);

        std::size_t b = 0;
        auto range = boost::irange(b, subdomains);
        hpx::ranges::for_each(hpx::execution::par, range,
            [&U, subdomain_width, subdomains](std::size_t i) {
                U[0][i] = hpx::make_ready_future(
                    partition_data(subdomain_width, double(i), subdomains));
            });

        auto Op = unwrapping(&stepper::heat_part);

        // Actual time step loop
        for (std::size_t t = 0; t != iterations; ++t)
        {
            space const& current = U[t % 2];
            space& next = U[(t + 1) % 2];

            for (std::size_t i = 0; i != subdomains; ++i)
            {
                next[i] = dataflow_replicate(n_value, Op, sti, error,
                    current[(i - 1 + subdomains) % subdomains], current[i],
                    current[(i + 1) % subdomains]);
            }

            // every nd time steps, attach additional continuation which will
            // trigger the semaphore once computation has reached this point
            if ((t % nd) == 0)
            {
                next[0].then([&sem, t](partition&&) {
                    // inform semaphore about new lower limit
                    sem.signal(t);
                });
            }

            // suspend if the tree has become too deep, the continuation above
            // will resume this thread once the computation has caught up
            sem.wait(t);
        }

        // Return the solution at time-step 'iterations'.
        return hpx::when_all(U[iterations % 2]);
    }
};

///////////////////////////////////////////////////////////////////////////////
int hpx_main(hpx::program_options::variables_map& vm)
{
    std::uint64_t n_value =
        vm["n-value"].as<std::uint64_t>();    // Number of partitions.
    std::uint64_t subdomains =
        vm["subdomains"].as<std::uint64_t>();    // Number of partitions.
    std::uint64_t subdomain_width =
        vm["subdomain-width"].as<std::uint64_t>();    // Number of grid points.
    std::uint64_t iterations =
        vm["iterations"].as<std::uint64_t>();    // Number of steps.
    std::uint64_t nd =
        vm["nd"].as<std::uint64_t>();    // Max depth of dep tree.
    std::uint64_t sti =
        vm["steps-per-iteration"]
            .as<std::uint64_t>();    // Number of time steps per iteration
    double error = vm["error-rate"].as<double>();

    // Create the stepper object
    stepper step;

    std::cout << "Starting 1d stencil with dataflow replicate" << std::endl;

    // Measure execution time.
    std::uint64_t t = hpx::chrono::high_resolution_clock::now();

    {
        // limit depth of dependency tree
        hpx::lcos::local::sliding_semaphore sem(nd);

        hpx::future<stepper::space> result = step.do_work(subdomains,
            subdomain_width, iterations, sti, nd, n_value, error, sem);

        stepper::space solution = result.get();
        hpx::wait_all(solution);
    }

    std::cout << "Time elapsed: "
              << static_cast<double>(
                     hpx::chrono::high_resolution_clock::now() - t) /
            1e9
              << std::endl;
    std::cout << "Errors occurred: " << counter << std::endl;

    // for (std::size_t i = 0; i != subdomains; ++i)
    //     std::cout << solution[i].get() << " ";
    // std::cout << std::endl;

    return hpx::local::finalize();
}

int main(int argc, char* argv[])
{
    using namespace hpx::program_options;

    // Configure application-specific options.
    options_description desc_commandline;

    desc_commandline.add_options()(
        "results", "print generated results (default: false)");

    desc_commandline.add_options()("n-value",
        value<std::uint64_t>()->default_value(5), "Number of allowed replays");

    desc_commandline.add_options()("error-rate",
        value<double>()->default_value(5), "Error rate for injecting errors");

    desc_commandline.add_options()("subdomain-width",
        value<std::uint64_t>()->default_value(128),
        "Local x dimension (of each partition)");

    desc_commandline.add_options()("iterations",
        value<std::uint64_t>()->default_value(10), "Number of time steps");

    desc_commandline.add_options()("steps-per-iteration",
        value<std::uint64_t>()->default_value(16),
        "Number of time steps per iterations");

    desc_commandline.add_options()("nd",
        value<std::uint64_t>()->default_value(10),
        "Number of time steps to allow the dependency tree to grow to");

    desc_commandline.add_options()("subdomains",
        value<std::uint64_t>()->default_value(10), "Number of partitions");

    // Initialize and run HPX
    hpx::local::init_params init_args;
    init_args.desc_cmdline = desc_commandline;

    return hpx::local::init(hpx_main, argc, argv, init_args);
}


#include <iostream>
//#include <iomanip>
//#include <fstream>

//#include <boost/numeric/ublas/matrix.hpp>
//#include <boost/numeric/ublas/vector.hpp>
//#include <boost/numeric/ublas/io.hpp>

#include <boost/timer.hpp>

#include <vector>
#include <cmath>
#include <cstring>

#include "NRGclasses.hpp"
#include "NRGfunctions.hpp"
#include "SpecFuncClass.hpp"
#include "DM_NRG.hpp"
#include "NRGOpMatRules.hpp"

#ifndef pi
#define pi 3.141592653589793238462643383279502884197169
#endif

int main (int argc, char* argv[]){

  CNRGCodeHandler ThisCode;

  //CNRGbasisarray AcutN;
  CNRGbasisarray* AcutN;
  CNRGbasisarray AbasisN;

  //CNRGbasisarray AcutNp1;
  CNRGbasisarray AbasisNp1;

  CNRGbasisarray SingleSite;

  CNRGmatrix** OpArrayN;

  CNRGmatrix* RhoN;
  vector<double> ParamsTemp;
  double betabar=0.727;
    double DM,TM;
  // 0 - betabar; 1 - TM; 2 - DM ?
  double Temp; // In units of the bandwidth (fixed!)
               // Temp/DN = TempBar = 1/betabar
  int Mtemp=1001;

  double twindow=2.0;

  double broadtemp=0.8; // Good for CFS

  char CNsites[8];
  char CNmat[8];
  char arqname[32];
  char ext[32];

  int NshellMax=3;
  int NFermiOps=2;

  // Check time
  boost::timer MyTime;
  double time_elapsed;

  int UseCFS=0;
  int Nw=0; // No of omegas in each shell

  // FDM calculations
  bool UseFDM=false;
  double auxwN=0.0;
  double ZN=0.0;
  double ZTot=0.0;
  double ZNdisc=0.0;
  double TempBar;
  vector<double> wN;

  // Read code parameters, including z_twist
  ThisCode.ReadGenPars(true);

  double bbroad=0.5*log(ThisCode.Lambda);

  // New (2016)
  bool UseGap=false;

  // Command-line: Set Temp
  DM_NRG_CommandLineRead(argc,argv,Mtemp,betabar,twindow,broadtemp,bbroad,UseCFS,UseFDM,UseGap,Nw);

  // Set Temp
  cout << " Mtemp = " << Mtemp
       << " betabar = " << betabar
       << " twindow = " << twindow
       << " dBroad = " << bbroad
       << " dBroadTemp = " << broadtemp << endl
       << " NomegasEachShell = " << Nw << " (if =0 then use standard interpolation) "
       << endl;
  //betabar=0.727;
  Temp=0.0;  // Real Temperature
  //ParamsTemp.push_back(betabar); // Need to fix this today...
  if (Mtemp<ThisCode.Nsitesmax-1) {
    if (!UseFDM) ThisCode.Nsitesmax=Mtemp+1;
    DM=CalcDN(ThisCode.Lambda,Mtemp);
    Temp=DM/betabar;
  }
  else{ // OK, Let me try this. Instead of 0.0, "Temp" will be DM(Nsites+200)/betabar.
    //Temp=0.0;
    Temp=CalcDN(ThisCode.Lambda,ThisCode.Nsitesmax+199)/betabar;
    DM=CalcDN(ThisCode.Lambda,ThisCode.Nsitesmax-1);
  }
  cout << " Temp = "<<Temp
       << " DMtemp = " << DM
       << endl;
  //DM=CalcDN(ThisCode.Lambda,NshellMax);
  ParamsTemp.push_back(Temp/DM);
  // Save in Temp_Mtemp.dat
  ofstream OutFile;
  strcpy(arqname,"Temp_Mtemp.dat");
  OutFile.open(arqname);
  OutFile << Temp << " " << Mtemp << endl;
  OutFile.close();

  cout << "Lambda    = " << ThisCode.Lambda << endl;
  cout << "Nsites0   = " << ThisCode.Nsites0 << endl;
  cout << "Nsitesmax = " << ThisCode.Nsitesmax << endl;
  cout << "NFermiOps = " << ThisCode.NopsSaved << endl;
  cout << "Ext arq   = " << ThisCode.SaveArraysFileName << endl;
  cout << "Symmetry  = " << ThisCode.SymNo << endl;
  if ( (ThisCode.SymNo != 2)&&(ThisCode.SymNo != 3) ){
    cout << " DM-NRG: Only 1chQSz and 1chQ symmetries supported at this point. Testing QS symmetry now..." << endl;
  }
  if (ThisCode.totalS){cout << " SU(2) symmetry detected. " << endl;}
  cout << "Oliveira z = " << ThisCode.code_z_twist << endl;

  if (UseGap){ThisCode.ReadParams((char *)"input_nrg.dat",1);} // Need the gap

  NshellMax=ThisCode.Nsitesmax-1;
  NFermiOps= ThisCode.NopsSaved;

  // April 2010
  // TotS QNs in ThisCode
  ThisCode.SetTotS();
  // Setting SingleSite (depends only on SymNo)
  ThisCode.SetSingleSite(&SingleSite);

  // Allocate matrices: Tricky

  OpArrayN=new CNRGmatrix* [NFermiOps];
  for (int iop=0;iop<NFermiOps;iop++){
    OpArrayN[iop]=new CNRGmatrix [NshellMax+1];
  }

  AcutN = new CNRGbasisarray [NshellMax+1];
  RhoN = new CNRGmatrix [NshellMax+1];

  // Read last Nshell

  cout << " Reading bin Files for NshellMax = " << NshellMax << endl;
  AcutN[NshellMax].Nshell=NshellMax;
  AbasisNp1.Nshell=NshellMax;
  //ThisCode.ReadArrays("test"); // ReadsInto pAcutN
  // Not too handy...

  sprintf(CNsites,"%d",NshellMax);
  //strcpy(ext,"test_N");
  strcpy(ext,ThisCode.SaveArraysFileName);
  strcat(ext,"_N");
  strcat(ext,CNsites);
  strcat(ext,".bin");

  // Read Abasis
  strcpy(arqname,"Abasis_");
  strcat(arqname,ext);
  AbasisNp1.ReadBin(arqname);

  // Read Acut
  strcpy(arqname,"Acut_");
  strcat(arqname,ext);
  AcutN[NshellMax].ReadBin(arqname);

  // Read Operators at NshellMax
  for (int iop=0;iop<NFermiOps;iop++){
    sprintf(CNmat,"%d",iop);
    strcpy(arqname,"Mat");
    strcat(arqname,CNmat);
    strcat(arqname,"_");
    strcat(arqname,ext);
    OpArrayN[iop][NshellMax].ReadBin(arqname);
    // Need a better way to do this but for now it will do:
    // Actually, from Mat block it works!
    //OpArrayN[iop][NshellMax].CheckForMatEl=OneChQSz_cd_check;

  }
  // end read operators

  cout << " NshellMax Files read "<< endl;

  // FDM-NRG: Setting temperature
  DM=CalcDN(ThisCode.Lambda,NshellMax-1); // Why????
  TempBar=Temp/DM;
  ZN=AcutN[NshellMax].PartitionFunc(1.0/TempBar);
  if (!UseFDM)
    ParamsTemp.push_back(ZN); // for NshellMax

  wN.push_back(ZN);
  ZTot=ZN;
  cout << " Temp = " << Temp << " at N= " << NshellMax
       << " TempBar = " << TempBar
       << " DN = " << DM
       << " ZNdisc = ZN = " << ZN
       << " ZwNN = " << ZN*pow(4.0,NshellMax-NshellMax)
       << endl;
  /////////

  // Read AcutN
  for (int Nshell=NshellMax-1;Nshell>=ThisCode.Nsites0;Nshell--){

    cout << " Reading AcutN in Nshell = " << Nshell << endl;

    //AcutN[Nshell].ClearAll();
    //AcutN[Nshell].Nshell=Nshell;

    sprintf(CNsites,"%d",Nshell);
    strcpy(ext,ThisCode.SaveArraysFileName);
    strcat(ext,"_N");
    strcat(ext,CNsites);
    strcat(ext,".bin");

    // Read Acut
    strcpy(arqname,"Acut_");
    strcat(arqname,ext);
    AcutN[Nshell].ReadBin(arqname);

    // FDM-NRG: Setting temperature
    DM=CalcDN(ThisCode.Lambda,Nshell-1);
    TempBar=Temp/DM;
    // Partition function
    ZN=AcutN[Nshell].PartitionFunc(1.0/TempBar);
    // Partition function for discarded states
    ZNdisc=AcutN[Nshell].PartitionFuncDisc(1.0/TempBar);
    wN.push_back(ZNdisc*pow(4.0,NshellMax-Nshell));
    ZTot+=ZNdisc*pow(4.0,NshellMax-Nshell);
    cout << " Temp = " << Temp << " at N= " << Nshell
  	 << " TempBar = " << TempBar
  	 << " DM = " << DM
  	 << " ZN = " << ZN
  	 << " ZNdisc = " << ZNdisc
  	 << " ZwNN = " << ZNdisc*pow(4.0,NshellMax-Nshell)
  	 << endl;
    /////////
  }
  //end loop in Nshell

  cout << "Ztot = " << ZTot << endl;

  // Calculate wN
  double NormwN=0.0;
  for (int ii=0; ii<wN.size(); ii++){
    wN[ii]/=ZTot;
    cout << " wN[" << NshellMax-ii << "] = " << wN[ii] << endl;
    NormwN+=wN[ii];
  }
  cout << " Sum_N wN = " << NormwN << endl;

  // end calculate wn

  if (UseFDM)
    ParamsTemp.push_back(ZTot); // for NshellMax

  // For 1-channel only!! d=4

  // Set density matrix at the LAST NRG iteration

  // Either CalcRhoN AND save it OR read it from file
  // Read RhoNmax
  sprintf(CNsites,"%d",NshellMax);
  strcpy(ext,ThisCode.SaveArraysFileName);
  strcat(ext,"_N");
  strcat(ext,CNsites);
  strcat(ext,".bin");

  strcpy(arqname,"rhoDM_");
  strcat(arqname,ext);

  if (ThisCode.CheckFileExists(arqname)){
    cout << " DM_NRG: Found file " << arqname << endl;
    RhoN[NshellMax].ReadBin(arqname);
  }else{
    DM_NRG_SetRhoNmax(ParamsTemp,&AcutN[NshellMax],&RhoN[NshellMax]);
    RhoN[NshellMax].SaveBin(arqname);
  }

  cout << " trace(Rho[Nshell= " << NshellMax << " ]) = " << RhoN[NshellMax].CalcTrace() << endl;

  //if (NshellMax==48){
  //cout << " Rho Nshell = " << NshellMax << endl;
  //RhoN[NshellMax].PrintAllBlocks();}
  // Debugging
  double qnums[2];
  int iBl=0;

  // Calculate reduced density matrices

  for (int Nshell=NshellMax-1;Nshell>=ThisCode.Nsites0;Nshell--){

    cout << "DM-NRG: working on Nshell = " << Nshell << endl;

    AbasisN.ClearAll();
    AbasisN.Nshell=Nshell;

    // AcutN[Nshell].ClearAll();
    // AcutN[Nshell].Nshell=Nshell;

    sprintf(CNsites,"%d",Nshell);
    //strcpy(ext,"test_N");
    strcpy(ext,ThisCode.SaveArraysFileName);
    strcat(ext,"_N");
    strcat(ext,CNsites);
    strcat(ext,".bin");

    // Read Abasis
    strcpy(arqname,"Abasis_");
    strcat(arqname,ext);
    AbasisN.ReadBin(arqname);

    // Read Acut (read already)
    // strcpy(arqname,"Acut_");
    // strcat(arqname,ext);
    // AcutN[Nshell].ReadBin(arqname);

    // Set ChildStates in AcutN

    DM_NRG_SetChildSt(&AcutN[Nshell],&AbasisNp1);

    // Set RhoN from RhoNp1

    MyTime.restart();

    // FDM-NRG: Setting temperature
    DM=CalcDN(ThisCode.Lambda,Nshell-1);
    TempBar=Temp/DM;
    // Partition function
    ZN=AcutN[Nshell].PartitionFunc(1.0/TempBar);
    // Partition function for discarded states
    ZNdisc=AcutN[Nshell].PartitionFuncDisc(1.0/TempBar);
    cout << " Temp = " << Temp << " at N= " << Nshell
  	 << " TempBar = " << TempBar
  	 << " DN = " << DM
  	 << " ZN = " << ZN
  	 << " ZNdisc = " << ZNdisc
  	 << endl;

    ParamsTemp.clear();
    ParamsTemp.push_back(TempBar);
    //ParamsTemp.push_back(ZN); // NOT ZNdisc!!
    ParamsTemp.push_back(ZTot/pow(4.0,NshellMax-Nshell)); // =ZNdisc/wN !!
    // For 1-channel only!! d=4
    //wN.push_back(ZNdisc*pow(4.0,NshellMax-Nshell));
    /////////

    // Either CalcRhoN AND save it OR read it from file

    // Read RhoN
    strcpy(arqname,"rhoDM_");
    strcat(arqname,ext);

    if (ThisCode.CheckFileExists(arqname)){
      cout << " DM_NRG: Found file " << arqname << endl;
      RhoN[Nshell].ReadBin(arqname);
    }else{
      if (ThisCode.totalS){
	DM_NRG_CalcRhoN_withSU2(ParamsTemp,UseFDM,
				&AcutN[Nshell],&AcutN[Nshell+1],
				&AbasisNp1,&SingleSite,
				&RhoN[Nshell],&RhoN[Nshell+1]);

      }
      else{
	DM_NRG_CalcRhoN(ParamsTemp,UseFDM,
			&AcutN[Nshell],&AcutN[Nshell+1],&AbasisNp1,
			&RhoN[Nshell],&RhoN[Nshell+1]);
      }
      // if SU(2) symmetry
      RhoN[Nshell].SaveBin(arqname);
    }

    time_elapsed=MyTime.elapsed();
    cout << " Rho Nshell = " << Nshell
	 << " completed in " << time_elapsed << " secs " << endl;

    cout << " trace(Rho[Nshell= " << Nshell << " ]) = " << RhoN[Nshell].CalcTrace() << endl;
    // Debugging
//      if (Nshell==1){
//        RhoN[Nshell].PrintAllBlocks();
//     qnums[0]=-2.000;
//     qnums[1]=1.000;
//     iBl=RhoN[Nshell].GetBlockFromQNumbers(qnums);
//     RhoN[Nshell].PrintMatBlock(iBl,iBl);
//     qnums[0]=2.000;
//     qnums[1]=1.000;
//     iBl=RhoN[Nshell].GetBlockFromQNumbers(qnums);
//     RhoN[Nshell].PrintMatBlock(iBl,iBl);
//      }

    // Update AbasisNp1
    AbasisNp1.ClearAll();
    AbasisNp1=AbasisN;

    // Read Operators
    for (int iop=0;iop<NFermiOps;iop++){
      sprintf(CNmat,"%d",iop); // Get all SavedMatrices
      strcpy(arqname,"Mat");
      strcat(arqname,CNmat);
      strcat(arqname,"_");
      strcat(arqname,ext);
      OpArrayN[iop][Nshell].ReadBin(arqname);
      // Need a better way to do this but for now it will do:
      // Not needed!
      //OpArrayN[iop][Nshell].CheckForMatEl=OneChQSz_cd_check;

    }
    // end read operators

    // Debug
//     if ((Nshell==0)||(Nshell==1)){
//       cout << " Op1 : " << endl;
//       OpArrayN[0][Nshell].PrintAllBlocks();
//       cout << " Op2 : " << endl;
//       OpArrayN[1][Nshell].PrintAllBlocks();
//       cout << " DM-NRG: Abasis(Nshell="<<Nshell<<"): " << endl;
//       AbasisN.PrintBasisAll();
//       cout << " Op1 (Nshell="<< Nshell<<"): " << endl;
//       OpArrayN[0][Nshell].PrintAllBlocks();
//     }
    // end debug

  }
  // end loop in Nshell

  // Save RhoN matrices

  // Read RhoN matrices

  // Given AcutN, rhoN and the Operators, calculate the spectral density

  // Calculate rho_0_0 and rho_Costi. Good for debugging
//   DM_NRG_CalcSpecFuncs(&ThisCode,AcutN,RhoN,OpArrayN,0,0);
//   DM_NRG_CalcSpecFuncs(&ThisCode,AcutN,RhoN,OpArrayN,1,1);
//   DM_NRG_CalcSpecFuncs(&ThisCode,AcutN,RhoN,OpArrayN,2,2);

   CSpecFunction spec1;

   spec1.Lambda=ThisCode.Lambda;
   spec1.z_twist=ThisCode.code_z_twist;
   spec1.NshellMax=ThisCode.Nsitesmax;
   spec1.NshellMin=ThisCode.Nsites0;
   spec1.AcutN=AcutN;
   spec1.RhoN=RhoN;
   spec1.UseCFS=UseCFS;

   // Spec Dens
   if (UseCFS==2) spec1.BDelta=LogGaussDelta; // FDM calculation
   else spec1.BDelta=BroadDelta;
   //spec1.dBroad=0.5*log(ThisCode.Lambda);
   spec1.dBroad=bbroad;

   // Finite Temp stuff (need to test this!)
   if (UseCFS==1) spec1.BDeltaTemp=LorentzDeltaAnders;
   else if (UseCFS==2) spec1.BDeltaTemp=GaussDelta;
   else spec1.BDeltaTemp=LorentzDelta;

   //spec1.Temp=0.0;
   //spec1.Mtemp=1000;
   spec1.Temp=Temp;
   spec1.Mtemp=Mtemp;
   spec1.Betabar=betabar;

   spec1.TwindowFac=twindow;
   spec1.dBroadTemp=broadtemp;

   if (UseGap){
     spec1.Gap=ThisCode.dInitParams[3];
     cout << " Using gap = " << spec1.Gap << endl;
   }
   // end setting up the gap]

   // Debug (Mar 2016)
   //DM_NRG_CalcSpecFunc_ij(&spec1,OpArrayN,1,1,UseCFS,UseGap,Nw);

   // Calculate ALL spectral functions!!
   // Jun 2015: Only diagonal functions for now...
   for (int iop=0; iop<NFermiOps; iop++){
     DM_NRG_CalcSpecFunc_ij(&spec1,OpArrayN,iop,iop,UseGap,Nw);
//     if (UseCFS!=0){
//       DM_NRG_CalcSpecFunc_ij(&spec1,OpArrayN,iop,iop,UseCFS,Nw);
//     } else {
//       for (int jop=0; jop<NFermiOps; jop++){
//	 DM_NRG_CalcSpecFunc_ij(&spec1,OpArrayN,iop,jop,UseCFS,Nw);
//       }
//     }
//     // end if CFS
   }
   // end loop in Fermi Ops

   //int iop=0;
   //int jop=0;
   //CFS_CalcSpecFunc_ij(&spec1,OpArrayN,iop,jop);

  delete[] RhoN;
  delete[] AcutN;
  for (int iop=NFermiOps-1;iop>=0;iop--){
    delete[] OpArrayN[iop];
  }
  delete[] OpArrayN;

}
// end MAIN