// Author: Daisuke Kanaizumi
// Affiliation: Department of Applied Mathematics, Waseda University

// verification program for q-Bessel functions
// April 12th, 2018

#ifndef QBESSEL_HPP
#define QBESSEL_HPP

#include <kv/interval.hpp>
#include <kv/rdouble.hpp>
#include <kv/constants.hpp>
#include <kv/complex.hpp>
#include <kv/convert.hpp>
#include <kv/defint.hpp>
#include <kv/Heine.hpp>
#include <kv/Pochhammer.hpp>
#include <kv/QHypergeometric.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <limits>
namespace ub = boost::numeric::ublas;
namespace kv{
template <class T> interval<T>Jackson1(const interval<T>& z,const interval<T>& nu,const interval<T>& q){
// verification program for Jackson`s 1st q-Bessel function
interval<T>res,a,b,c,series;
int j,K;
T rad;
if(abs(z)>=2){
throw std::domain_error("Jackson`s 1st q-Bessel function is not defined");
}
if(abs(q)>=1){
throw std::domain_error("absolute value of q must be under 1");
}
 if(z<0){
   throw std::domain_error("z must be positive");
 }
 if (pow(q,nu)<1){
j=1;
K=500;
a=1.;
b=1.;
c=1.;
// Leibniz criterion
 for (int k=1; k<=K; k++){
    j = -1*j;
    b=b*(1-pow(q,k));
    c=c*(1-pow(q,k+nu));
    a = a+j*pow(z/2,2*k)/(b*c);
  }
b=b*(1-pow(q,K+1));
c=c*(1-pow(q,K+nu+1));

rad=abs(pow(z/2,2*K+2)/(b*c)).upper();
series=a+rad*interval<T>(-1.,1.);
res=pow(z/2,nu)*series/Karpelevich(interval<T>(pow(q,nu+1)),interval<T>(q));
}
 else{
res=pow(z/2,nu)*infinite_qPochhammer(interval<T>(pow(q,nu+1)),interval<T>(q))*
  Heine(interval<T>(0),interval<T>(0),interval<T>(pow(q,nu+1)),interval<T>(q),interval<T>(-z*z/4))/Euler(interval<T>(q));
}

 return res;
}
template <class T> complex<interval<T> >Jackson1(const complex<interval<T> >& z,const complex<interval<T> >& nu,const interval<T>& q){
  complex<interval<T> >res,i;

if(abs(q)>=1){
throw std::domain_error("absolute value of q must be under 1");
}
 if(abs(z).upper()>=2){
throw std::domain_error("Jackson`s 1st q-Bessel function is not defined");
}

res=pow(z/2,nu)*infinite_qPochhammer(complex<interval<T> >(pow(q,nu+1)),interval<T>(q))*
  Heine(complex<interval<T> >(0),complex<interval<T> >(0),complex<interval<T> >(pow(q,nu+1)),interval<T>(q),complex<interval<T> >(-z*z/4))/Euler(interval<T>(q));

 return res;
}

template <class T> complex<interval<T> >modified_qBesselI1(const complex<interval<T> >& z,const complex<interval<T> >& nu,const interval<T>& q){
  // verification program for 1st modified q-Bessel I1
  complex<interval<T> >res,i;
  if(abs(q)>=1){
    throw std::domain_error("absolute value of q must be under 1");
  }
  if(abs(z).upper()>=2){
    throw std::domain_error("1st modified q-Bessel function is not defined");
  }
  interval<T>pi;
  i=complex<interval<T> >::i();
  pi=constants<interval<T> >::pi();
  res=exp(-i*nu*pi/2)*Jackson1(complex<interval<T> >(i*z),complex<interval<T> >(nu),interval<T> (q));
  return res;
}

  template <class T> complex<interval<T> >modified_qBesselK1(const complex<interval<T> >& z,const complex<interval<T> >& nu,const interval<T>& q){
    // verification program for 1st modified q-Bessel K1, nu should not be an integer
    complex<interval<T> >res;
    if(abs(q)>=1){
      throw std::domain_error("absolute value of q must be under 1");
    }
    if(abs(z).upper()>=2){
      throw std::domain_error("1st modified q-Bessel function is not defined");
    }
    interval<T>pi;
    pi=constants<interval<T> >::pi();
    res=pi*0.5/sin(pi*nu)*
      (modified_qBesselI1(complex<interval<T> > (z), complex<interval<T> > (-nu), interval<T> (q))-modified_qBesselI1(complex<interval<T> > (z), complex<interval<T> > (nu), interval<T> (q)));
    return res;
  }
  template <class T> interval<T>Jackson2(const interval<T>& z,const interval<T>& nu,const interval<T>& q){
    // verification program for Jackson`s 2nd q-Bessel function
  interval<T>res,a,b,c,series,pq;
int j,K;
T rad;
if(abs(q)>=1){
throw std::domain_error("absolute value of q must be under 1");
}

// if (pow(q,nu)<1 && z*z<4*q){
/*j=1;
K=500;
a=1.;
b=1.;
c=1.;
// Leibniz criterion
 for (int k=1; k<=K; k++){
    j = -1*j;
    b=b*(1-pow(q,k));
    c=c*(1-pow(q,k+nu));
    a = a+j*pow(q,k*(k-1))*pow(pow(q,nu+1)*z*z/4,k)/(b*c);
  }
  b=b*(1-pow(q,K+1));
  c=c*(1-pow(q,K+nu+1));

 rad=abs(pow(q,K*(K+1))*pow(pow(q,nu+1)*z*z/4,K+1)/(b*c)).upper();
 series=a+rad*interval<T>(-1.,1.);
 res=pow(z/2,nu)*series/Karpelevich(interval<T>(pow(q,nu+1)),interval<T>(q));
*/ //}
// else{
   /* implementation by original definition*/
// res=pow(z*0.5,nu)*infinite_qPochhammer(interval<T>(pow(q,nu+1)),interval<T>(q))*
//  _0phi_1(interval<T>(pow(q,nu+1)),interval<T>(q),interval<T>(-z*z*pow(q,nu+1)/4))/Euler(interval<T>(q));

 // alternative implementaion
 // reference
 // H. T Koelink, Hansen-Lommel orthogonality Relations for Jackson`s q-Bessel functions, formula 3.2
 // Journal of Mathematical Analysis and Applications 175, 425-437 (1993)
 pq=pow(q,nu+1);
 res=pow(z/2,nu)*_1phi_1(interval<T> (-z*z/4),interval<T> (0),interval<T>(q), interval<T> (pq))/Euler(interval<T>(q));
 // }

  return res;
}
template <class T> complex<interval<T> >Jackson2(const complex<interval<T> >& z,const complex<interval<T> >& nu,const interval<T>& q){
  complex<interval<T> >res,pq,i;
  i=complex<interval<T> >::i();
  if(abs(q)>=1){
    throw std::domain_error("absolute value of q must be under 1");
  }
  pq=pow(q,nu+1);
  /* implementation by original definition*/

  //  res=pow(z/2,nu)*infinite_qPochhammer(complex<interval<T> >(pq),interval<T>(q))*
  //  _0phi_1(complex<interval<T> >(pq),interval<T>(q),complex<interval<T> >(-z*z*pq/4))/Euler(interval<T>(q));

  // alternative implementaion
  // reference
  // H. T Koelink, Hansen-Lommel Orthogonality Relations for Jackson`s q-Bessel functions, formula 3.2
  // Journal of Mathematical Analysis and Applications 175, 425-437 (1993)
   try{
  res=pow(z/2,nu)*_1phi_1(complex<interval<T> >(-z*z/4),complex<interval<T> >(0),interval<T>(q), complex<interval<T> >(pq))/Euler(interval<T>(q));
  }
  // alternative implementaion
  // reference
  // Y. Chen , M. E. Ismail, K. A. Muttalib. Asymptotics of basic Bessel functions and q-Laguerre polynomials, Lemma 2
  // Journal of Computational and Applied Mathematics, 54(3), 263-272 (1994).
  catch(std::domain_error){
    ub::vector< complex<interval<T> > > a(3);
    ub::vector< complex<interval<T> > > b(2);
    ub::vector< complex<interval<T> > > c(2);
    a(0)=pow(q,(nu+0.5)*0.5);
    a(1)=-pow(q,(nu+0.5)*0.5);
    a(2)=0.;
    b(0)=-sqrt(q);
    b(1)=i*a(0)*z*0.5;
    c(0)=-sqrt(q);
    c(1)=-b(1);

    res=pow(z*0.5,nu)*infinite_qPochhammer(interval<T>(sqrt(q)),interval<T>(q))/Euler(interval<T>(q))*0.5
      *(infinite_qPochhammer(complex<interval<T> >(b(1)),interval<T>(sqrt(q)))
	*QHypergeom(ub::vector<complex<interval<T> > >(a),ub::vector<complex<interval<T> > >(b),interval<T>(sqrt(q)),complex<interval<T> >(sqrt(q),0))+
	infinite_qPochhammer(complex<interval<T> >(c(1)),interval<T>(sqrt(q)))
	*QHypergeom(ub::vector<complex<interval<T> > >(a),ub::vector<complex<interval<T> > >(c),interval<T>(sqrt(q)),complex<interval<T> >(sqrt(q),0)));
    	}

    if((abs(res)).upper()==std::numeric_limits<T>::infinity()){
    ub::vector< complex<interval<T> > > a(3);
    ub::vector< complex<interval<T> > > b(2);
    ub::vector< complex<interval<T> > > c(2);
    a(0)=pow(q,(nu+0.5)*0.5);
    a(1)=-pow(q,(nu+0.5)*0.5);
    a(2)=0.;
    b(0)=-sqrt(q);
    b(1)=i*a(0)*z*0.5;
    c(0)=-sqrt(q);
    c(1)=-b(1);

    res=pow(z*0.5,nu)*infinite_qPochhammer(interval<T>(sqrt(q)),interval<T>(q))/Euler(interval<T>(q))*0.5
      *(infinite_qPochhammer(complex<interval<T> >(b(1)),interval<T>(sqrt(q)))
	*QHypergeom(ub::vector<complex<interval<T> > >(a),ub::vector<complex<interval<T> > >(b),interval<T>(sqrt(q)),complex<interval<T> >(sqrt(q),0))+
	infinite_qPochhammer(complex<interval<T> >(c(1)),interval<T>(sqrt(q)))
	*QHypergeom(ub::vector<complex<interval<T> > >(a),ub::vector<complex<interval<T> > >(c),interval<T>(sqrt(q)),complex<interval<T> >(sqrt(q),0)));
     }
    //std::cout<<qPochhammer(complex<interval<T> >(pow(q,(nu+0.5)*0.5)),interval<T>(q),int(1000))
    //*qPochhammer(complex<interval<T> >(-pow(q,(nu+0.5)*0.5)),interval<T>(q),int(1000))<<std::endl;

    return res;
}
  template <class TT> struct qBesselintegral_nu_int_real {
    TT x, q;
    int nu,n; // Setting parameters
    qBesselintegral_nu_int_real(TT x, TT q, int nu,int n) : x(x),q(q),nu(nu),n(n) {}

    template <class T> T operator() (const T& t) {
      complex<T>  pro;
      T proreal;
      complex<T> i;
      pro=1.;
      i=complex<T>::i();
      for(int k=0;k<=nu-1;k++){
	pro=pro*(1-pow(T(q),k)*exp(2*i*t))*(1-pow(T(q),k)*exp(-2*i*t));
      }
      for(int j=0;j<=n-1;j++){
	pro=pro*(1+i*T(x)*pow(T(q),T(nu)/2+0.5+j)*exp(i*t)/2)*(1+i*T(x)*pow(T(q),T(nu)/2+0.5+j)*exp(-i*t)/2);
      }

      proreal=pro.real();
      return proreal;
    }
  };
  template <class TT> struct qBesselintegral_nu_int_imag {
    TT x, q;
    int nu,n; // Setting parameters
    qBesselintegral_nu_int_imag(TT x, TT q, int nu,int n) : x(x),q(q),nu(nu),n(n) {}

    template <class T> T operator() (const T& t) {
      complex<T>  pro;
      T proimag;
      complex<T> i;
      pro=1.;
      i=complex<T>::i();
      for(int k=0;k<=nu-1;k++){
	pro=pro*(1-pow(T(q),k)*exp(2*i*t))*(1-pow(T(q),k)*exp(-2*i*t));
      }
      for(int j=0;j<=n-1;j++){
	pro=pro*(1+i*T(x)*pow(T(q),T(nu)/2+0.5+j)*exp(i*t)/2)*(1+i*T(x)*pow(T(q),T(nu)/2+0.5+j)*exp(-i*t)/2);
      }

      proimag=pro.imag();
      return proimag;
    }
  };
  template <class T> complex<interval<T> >Jackson2_integral(const interval<T>& z,const int & nu,const interval<T>& q){
      // verification program for Jackson`s 2nd q-Bessel function
      // Integral representation is used

      // References
      // Rahman(1987), An Integral Representation and Some Transformation Properties of q-Bessel Functions, Journal of Mathematical Analysis and Applications 125
      // Zhang(2008), Plancherel-Rotach asymptotics for certain basic hypergeometric series, Advances in Mathematics 217, Lemma 1.1
    int n;
    n=100;
    if(nu<=0){
      throw std::domain_error("nu must be positive");
    }
    if(abs(z)*pow(q,nu/2+n+0.5)/2/(1-q)>=0.5){
      n=n+10;
    }
    complex<interval<T> >res,integral;
    interval<T>num,realint,imagint,pi;
    T numrad;
    numrad=(abs(z)*pow(q,nu/2+n+0.5)*2/(1-q)).upper();
    num=pow((1+numrad*interval<T>(-1.,1.)),2);
    pi=constants<interval<T> >::pi();
    realint=defint(qBesselintegral_nu_int_real<interval<T> >(z,q,nu,n),interval<T>(0.),interval<T>(pi),10,10);
    imagint=defint(qBesselintegral_nu_int_imag<interval<T> >(z,q,nu,n),interval<T>(0.),interval<T>(pi),10,10);
    integral=complex<interval<T> >(realint,imagint);

    res=num*integral*pow(z/2,nu)*infinite_qPochhammer(interval<T>(pow(q,2*nu)),interval<T>(q))/infinite_qPochhammer(interval<T>(pow(q,nu)),interval<T>(q))/(2*pi);

    return res;
  }
    template <class TT> struct qBesselintegral_nu_double_real {
      TT x,q,nu;
      int n; // Setting parameters
      qBesselintegral_nu_double_real(TT x, TT q, TT nu,int n) : x(x),q(q),nu(nu),n(n) {}

      template <class T> T operator() (const T& t) {
      complex<T>  pro;
      T proreal;
      complex<T> i;
      pro=1.;
      i=complex<T>::i();
      for(int k=0;k<=n-1;k++){
	pro=pro*(1-pow(T(q),k)*exp(2*i*t))*(1-pow(T(q),k)*exp(-2*i*t));
      }
      for(int j=0;j<=n-1;j++){
	pro=pro*(1+i*T(x)*pow(T(q),T(nu)/2+0.5+j)*exp(i*t)/2)*(1+i*T(x)*pow(T(q),T(nu)/2+0.5+j)*exp(-i*t)/2);
      }
      for(int l=0;l<=n-1;l++){
	pro=pro/(1-pow(T(q),l+nu)*exp(2*i*t))/(1-pow(T(q),l+nu)*exp(-2*i*t));
      }
      proreal=pro.real();
      return proreal;
      }
  };
  template <class TT> struct qBesselintegral_nu_double_imag {
    TT x,q,nu;
    int n; // Setting parameters
    qBesselintegral_nu_double_imag(TT x, TT q, TT nu,int n) : x(x),q(q),nu(nu),n(n) {}

    template <class T> T operator() (const T& t) {
      complex<T>  pro;
      T proimag;
      complex<T> i;
      pro=1.;
      i=complex<T>::i();
      for(int k=0;k<=n-1;k++){
	pro=pro*(1-pow(T(q),k)*exp(2*i*t))*(1-pow(T(q),k)*exp(-2*i*t));
      }
      for(int j=0;j<=n-1;j++){
	pro=pro*(1+i*T(x)*pow(T(q),T(nu)/2+0.5+j)*exp(i*t)/2)*(1+i*T(x)*pow(T(q),T(nu)/2+0.5+j)*exp(-i*t)/2);
      }
      for(int l=0;l<=n-1;l++){
	pro=pro/(1-pow(T(q),l+nu)*exp(2*i*t))/(1-pow(T(q),l+nu)*exp(-2*i*t));
      }

      proimag=pro.imag();
      return proimag;
    }
  };
  template <class T> complex<interval<T> >Jackson2_integral(const interval<T>& z,const interval<T> & nu,const interval<T>& q){
      // verification program for Jackson`s 2nd q-Bessel function
      // Integral representation is used

      // References
      // Rahman(1987), An Integral Representation and Some Transformation Properties of q-Bessel Functions, Journal of Mathematical Analysis and Applications 125
      // Zhang(2008), Plancherel-Rotach asymptotics for certain basic hypergeometric series, Advances in Mathematics 217, Lemma 1.1
    int n;
    n=100;
    if(nu<=0){
      throw std::domain_error("nu must be positive");
    }
    if(abs(z)*pow(q,nu/2+n+0.5)/2/(1-q)>=0.5){
      n=n+10;
    }
    if(pow(q,nu+n)/2/(1-q)>=0.5){
      n=n+10;
    }

    complex<interval<T> >res,integral;
    interval<T>num1,num2,denom,realint,imagint,pi;
    T numrad1,numrad2,denomrad;
    numrad1=(abs(z)*pow(q,nu/2+n+0.5)*2/(1-q)).upper();
    num1=pow((1+numrad1*interval<T>(-1.,1.)),2);
    numrad2=(pow(q,n)*2/(1-q)).upper();
    num2=pow((1+numrad2*interval<T>(-1.,1.)),2);
    denomrad=(pow(q,nu+n)*2/(1-q)).upper();
    denom=pow((1+denomrad*interval<T>(-1.,1.)),2);
    pi=constants<interval<T> >::pi();
    realint=defint(qBesselintegral_nu_double_real<interval<T> >(z,q,nu,n),interval<T>(0.),interval<T>(pi),10,10);
    imagint=defint(qBesselintegral_nu_double_imag<interval<T> >(z,q,nu,n),interval<T>(0.),interval<T>(pi),10,10);
    integral=complex<interval<T> >(realint,imagint);

    res=num1*num2*denom*integral*pow(z/2,nu)*infinite_qPochhammer(interval<T>(pow(q,2*nu)),interval<T>(q))/infinite_qPochhammer(interval<T>(pow(q,nu)),interval<T>(q))/(2*pi);

    return res;
  }
    template <class TT> struct qBesselintegral_z_complex_real {
      TT q,nu;
      complex<TT> x;
      int n; // Setting parameters
      qBesselintegral_z_complex_real(complex<TT> x, TT q, TT nu,int n) : x(x),q(q),nu(nu),n(n) {}

      template <class T> T operator() (const T& t) {
      complex<T>  pro;
      T proreal;
      complex<T> i;
      pro=1.;
      i=complex<T>::i();
      for(int k=0;k<=n-1;k++){
	pro=pro*(1-pow(T(q),k)*exp(2*i*t))*(1-pow(T(q),k)*exp(-2*i*t));
      }
      for(int j=0;j<=n-1;j++){
	pro=pro*(1+i*complex<T>(x)*pow(T(q),T(nu)/2+0.5+j)*exp(i*t)/2)*(1+i*complex<T>(x)*pow(T(q),T(nu)/2+0.5+j)*exp(-i*t)/2);
      }
      for(int l=0;l<=n-1;l++){
	pro=pro/(1-pow(T(q),l+T(nu))*exp(2*i*t))/(1-pow(T(q),l+T(nu))*exp(-2*i*t));
      }
      proreal=pro.real();
      return proreal;
      }
  };
  template <class TT> struct qBesselintegral_z_complex_imag {
    TT q,nu;
    complex<TT> x;
    int n; // Setting parameters
    qBesselintegral_z_complex_imag(complex<TT> x, TT q, TT nu,int n) : x(x),q(q),nu(nu),n(n) {}

    template <class T> T operator() (const T& t) {
      complex<T>  pro;
      T proimag;
      complex<T> i;
      pro=1.;
      i=complex<T>::i();
      for(int k=0;k<=n-1;k++){
	pro=pro*(1-pow(T(q),k)*exp(2*i*t))*(1-pow(T(q),k)*exp(-2*i*t));
      }
      for(int j=0;j<=n-1;j++){
	pro=pro*(1+i*complex<T>(x)*pow(T(q),T(nu)/2+0.5+j)*exp(i*t)/2)*(1+i*complex<T>(x)*pow(T(q),T(nu)/2+0.5+j)*exp(-i*t)/2);
      }
      for(int l=0;l<=n-1;l++){
	pro=pro/(1-pow(T(q),l+T(nu))*exp(2*i*t))/(1-pow(T(q),l+T(nu))*exp(-2*i*t));
      }

      proimag=pro.imag();
      return proimag;
    }
  };
  template <class T> complex<interval<T> >Jackson2_integral(const complex<interval<T> >& z,const interval<T> & nu,const interval<T>& q){
      // verification program for Jackson`s 2nd q-Bessel function
      // Integral representation is used

      // References
      // Rahman(1987), An Integral Representation and Some Transformation Properties of q-Bessel Functions, Journal of Mathematical Analysis and Applications 125
      // Zhang(2008), Plancherel-Rotach asymptotics for certain basic hypergeometric series, Advances in Mathematics 217, Lemma 1.1
    int n;
    n=100;
    if(nu<=0){
      throw std::domain_error("nu must be positive");
    }
    if(abs(z*pow(q,nu/2+n+0.5))/2/(1-q)>=0.5){
      n=n+10;
    }
    if(abs(pow(q,nu+n))/2/(1-q)>=0.5){
      n=n+10;
    }

    complex<interval<T> >res,integral,num1,denom;

    interval<T> num2;
    interval<T> pi,realint,imagint;
    T numrad1,numrad2,denomrad;
    numrad1=(abs(z*pow(q,nu/2+n+0.5))*2/(1-q)).upper();
    num1=pow(complex_nbd(complex<interval<T> >(1,0),numrad1),2);
    numrad2=(pow(q,n)*2/(1-q)).upper();
    num2=pow((1+numrad2*interval<T>(-1.,1.)),2);
    denomrad=(abs(pow(q,nu+n))*2/(1-q)).upper();
    denom=pow(complex_nbd(complex<interval<T> >(1,0),denomrad),2);
    pi=constants<interval<T> >::pi();
    realint=defint(qBesselintegral_z_complex_real<interval<T> >(z,q,nu,n),interval<T>(0.),interval<T>(pi),10,10);
    imagint=defint(qBesselintegral_z_complex_imag<interval<T> >(z,q,nu,n),interval<T>(0.),interval<T>(pi),10,10);
    integral=complex<interval<T> >(realint,imagint);

    res=num1*num2*denom*integral*pow(z/2,nu)*infinite_qPochhammer(complex<interval<T> >(pow(q,2*nu)),interval<T>(q))/infinite_qPochhammer(complex<interval<T> >(pow(q,nu)),interval<T>(q))/(2*pi);

    return res;
  }
  template <class T> interval<T> J2ratio(const interval<T> & z,const interval<T> & nu,const interval<T>& q){
    interval<T>res;
    res=Jackson2(interval<T>(z),interval<T>(nu),interval<T>(q))/Jackson2(interval<T>(z),interval<T>(nu-1),interval<T>(q));
    return res;
  }
  template <class T> complex<interval<T> >modified_qBesselI2(const complex<interval<T> >& z,const complex<interval<T> >& nu,const interval<T>& q){
    //verification program for 2nd modified q-Bessel function I2
    complex<interval<T> >res,i;
    if(abs(q)>=1){
      throw std::domain_error("absolute value of q must be under 1");
    }
    /*implementation by original definition
      interval<T>pi;
      i=complex<interval<T> >::i();
      pi=constants<interval<T> >::pi();
      res=exp(-i*nu*pi/2)*Jackson2(complex<interval<T> >(i*z),complex<interval<T> >(nu),interval<T> (q));
    */
    // alternative implementation
    // reference
    // Ismail, M. E., & Zhang, R. (2015). $ q $-Bessel Functions and Rogers-Ramanujan Type Identities.
    // arXiv preprint arXiv:1508.06861.
    res=pow(z/2,nu)/Euler(interval<T>(q))
      *_1phi_1(complex<interval<T> >(z*z/4),complex<interval<T> >(0.),interval<T>(q),complex<interval<T> >(pow(q,nu+1)));
    return res;
  }
 template <class T> interval<T> modified_qBesselI2(const interval<T> & z,const interval<T> & nu,const interval<T>& q){
    //verification program for 2nd modified q-Bessel function I2
    interval<T> res;
    if(abs(q)>=1){
      throw std::domain_error("absolute value of q must be under 1");
    }
    /*implementation by original definition
      interval<T>pi;
      i=complex<interval<T> >::i();
      pi=constants<interval<T> >::pi();
      res=exp(-i*nu*pi/2)*Jackson2(complex<interval<T> >(i*z),complex<interval<T> >(nu),interval<T> (q));
    */
    // alternative implementation
    // reference
    // Ismail, M. E., & Zhang, R. (2015). $ q $-Bessel Functions and Rogers-Ramanujan Type Identities.
    // arXiv preprint arXiv:1508.06861.
    res=pow(z/2,nu)/Euler(interval<T>(q))
      *_1phi_1(interval<T> (z*z/4),interval<T> (0.),interval<T>(q),interval<T> (pow(q,nu+1)));
    return res;
  }
  /*template <class T> interval<T> modified_qBesselI2_ae(const interval<T> & z,const interval<T> & nu,const interval<T>& q){
    //verification program for 2nd modified q-Bessel function I2, z>0
    // reference
    // Ismail, M. E., & Zhang, R. (2015). $ q $-Bessel Functions and Rogers-Ramanujan Type Identities.
    // arXiv preprint arXiv:1508.06861.
    interval<T> res;
    if(abs(q)>=1){
      throw std::domain_error("absolute value of q must be under 1");
    }
    res=pow(z/2.,nu)*infinite_qPochhammer(interval<T>(sqrt(q)),interval<T>(q))*0.5/Euler(interval<T>(q))
      *(infinite_qPochhammer(interval<T>(z*0.5*pow(q,(nu+0.5)*0.5)),interval<T>(sqrt(q)))+infinite_qPochhammer(interval<T>(-z*0.5*pow(q,(nu+0.5)*0.5)),interval<T>(sqrt(q))));
    return res;
    }*/
  template <class T> interval<T> Hahn_Exton(const interval<T> & z,const interval<T> & nu,const interval<T>& q){
    // verification program for Hahn-Exton q-Bessel function
    interval<T> res,pq;
    if(abs(q)>=1){
      throw std::domain_error("absolute value of q must be under 1");
    }
    pq=pow(q,nu+1);
    /* implementation by original definition*/
    /*    res=pow(z,nu)*infinite_qPochhammer(interval<T> (pq),interval<T>(q))*
	  _1phi_1(interval<T> (0),interval<T> (pq),interval<T>(q),interval<T> (z*z*q))/Euler(interval<T>(q));*/
       // alternative implementation
       // A. B. Olde Daalhuis, Asymptotic Expansions for q-Gamma, q-Exponential and q-Bessel Functions, formula 4.6
       // Journal of Mathematical Analysis and Applications 186, 896-913 (1994)

    	 res=pow(z,nu)*infinite_qPochhammer(interval<T> (z*z*q),interval<T>(q))*_1phi_1(interval<T> (0),interval<T> (z*z*q),interval<T>(q),interval<T> (pq))/Euler(interval<T> (q));
       return res;
  }
  template <class T> complex<interval<T> >Hahn_Exton(const complex<interval<T> >& z,const complex<interval<T> >& nu,const interval<T>& q){
    // verification program for Hahn-Exton q-Bessel function
    complex<interval<T> >res,pq;
    if(abs(q)>=1){
      throw std::domain_error("absolute value of q must be under 1");
    }
    pq=pow(q,nu+1);
    /* implementation by original definition*/
    /*  res=pow(z,nu)*infinite_qPochhammer(complex<interval<T> >(pq),interval<T>(q))*
       _1phi_1(complex<interval<T> >(0),complex<interval<T> >(pq),interval<T>(q),complex<interval<T> >(z*z*q))/Euler(interval<T>(q));
    */
    // alternative implementation
    // A. B. Olde Daalhuis, Asymptotic Expansions for q-Gamma, q-Exponential and q-Bessel Functions, formula 4.6
    // Journal of Mathematical Analysis and Applications 186, 896-913 (1994)
    res=pow(z,nu)*infinite_qPochhammer(complex<interval<T> >(z*z*q),interval<T>(q))*_1phi_1(complex<interval<T> >(0),complex<interval<T> >(z*z*q),interval<T>(q), complex<interval<T> >(pq))/Euler(interval<T> (q));
     return res;
  }
   template <class T> interval<T> HEratio(const interval<T> & z,const interval<T> & nu,const interval<T>& q){
    interval<T>res;
    res=Hahn_Exton(interval<T>(z),interval<T>(nu),interval<T>(q))/Hahn_Exton(interval<T>(z),interval<T>(nu-1),interval<T>(q));
    return res;
  }

  template <class T> complex<interval<T> >little(const complex<interval<T> >& z,const complex<interval<T> >& nu,const interval<T>& q){
    // verification program for little q-Bessel function
    // reference
    // Koornwinder and Swarttouw, On q-analogues of the Fourier and Hankel transforms, 1992
    // Bouzeffour, New Addition Formula for the Little q-Bessel Functions, arXiv, 2013
    complex<interval<T> >res,pq;
    if(abs(q)>=1){
      throw std::domain_error("absolute value of q must be under 1");
    }
    pq=pow(q,nu+1);
    res=pow(z,nu)*infinite_qPochhammer(complex<interval<T> >(pq),interval<T>(q))*
      _1phi_1(complex<interval<T> >(0),complex<interval<T> >(pq),interval<T>(q),complex<interval<T> >(z))/Euler(interval<T>(q));
    return res;
  }

}

#endif


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

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

using namespace frovedis;
using namespace std;

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

    // norm of a std::vector
    std::vector<float> x = {0,0,3,4};
    auto d = nrm2<float> (x);
    BOOST_CHECK (d == 5);

    // creating a colmajor matrix local from file
    colmajor_matrix_local<float> cm (
           make_rowmajor_matrix_local_load<float>("./sample_4x4"));

    // checking norm() operation
    auto row1 = make_row_vector<float> (cm,1);

    // checking whether the norm operation successfully taken place
    float expected = 6;
    float res = nrm2<float>(row1);
    BOOST_CHECK (res == expected);
}


#include <iostream>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/matrix_proxy.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/algorithm/minmax.hpp>
#include <vector>

#include "dbscan.h"

namespace clustering
{
	DBSCAN::ClusterData DBSCAN::gen_cluster_data( size_t features_num, size_t elements_num )
	{
		DBSCAN::ClusterData cl_d( elements_num, features_num );

		for (size_t i = 0; i < elements_num; ++i)
		{
			for (size_t j = 0; j < features_num; ++j)
			{
				cl_d(i, j) = (-1.0 + rand() * (2.0) / RAND_MAX);
			}
		}

		return cl_d;
	}

	DBSCAN::FeaturesWeights DBSCAN::std_weights( size_t s )
	{
		// num cols
		DBSCAN::FeaturesWeights ws( s );

		for (size_t i = 0; i < s; ++i)
		{
			ws(i) = 1.0;
		}

		return ws;
	}

	DBSCAN::DBSCAN()
	{

	}

	void DBSCAN::init(double eps, size_t min_elems/*, int num_threads*/)
	{
		m_eps = eps;
		m_min_elems = min_elems;
	}

	DBSCAN::DBSCAN(double eps, size_t min_elems/*, int num_threads*/)
	: m_eps( eps )
	, m_min_elems( min_elems )
	, m_dmin(0.0)
	, m_dmax(0.0)
	{
		reset();
	}

	DBSCAN::~DBSCAN()
	{

	}

	void DBSCAN::reset()
	{
		m_labels.clear();
	}

	void DBSCAN::prepare_labels( size_t s )
	{
		m_labels.resize(s);

		for( auto & l : m_labels)
		{
			l = -1;
		}
	}

	const DBSCAN::DistanceMatrix DBSCAN::calc_dist_matrix( const DBSCAN::ClusterData & C, const DBSCAN::FeaturesWeights & W )
	{
		DBSCAN::ClusterData cl_d = C;

		for (size_t i = 0; i < cl_d.size2(); ++i)
		{
			ublas::matrix_column<DBSCAN::ClusterData>col(cl_d, i);

			const auto r = minmax_element( col.begin(), col.end() );

			double data_min = *r.first;
			double data_range = *r.second - *r.first;

			if (data_range == 0.0) { data_range = 1.0; }

			const double scale = 1/data_range;
			const double min = -1.0*data_min*scale;

			col *= scale;
			col.plus_assign( ublas::scalar_vector< typename ublas::matrix_column<DBSCAN::ClusterData>::value_type >(col.size(), min) );
		}

		// rows x rows
		DBSCAN::DistanceMatrix d_m( cl_d.size1(), cl_d.size1() );
		ublas::vector<double> d_max( cl_d.size1() );
		ublas::vector<double> d_min( cl_d.size1() );

		for (size_t i = 0; i < cl_d.size1(); ++i)
		{
			for (size_t j = i; j < cl_d.size1(); ++j)
			{
				d_m(i, j) = 0.0;

				if (i != j)
				{
					ublas::matrix_row<DBSCAN::ClusterData> U (cl_d, i);
					ublas::matrix_row<DBSCAN::ClusterData> V (cl_d, j);

					int k = 0;
					for (const auto e : ( U-V ) )
					{
						d_m(i, j) += fabs(e)*W[k++];
					}

					d_m(j, i) = d_m(i, j);
				}
			}

			const auto cur_row = ublas::matrix_row<DBSCAN::DistanceMatrix>(d_m, i);
			const auto mm = minmax_element( cur_row.begin(), cur_row.end() );

			d_max(i) = *mm.second;
			d_min(i) = *mm.first;
		}

		m_dmin = *(min_element( d_min.begin(), d_min.end() ));
		m_dmax = *(max_element( d_max.begin(), d_max.end() ));

		m_eps = (m_dmax - m_dmin) * m_eps + m_dmin;

		return d_m;
	}

	DBSCAN::Neighbors DBSCAN::find_neighbors(const DBSCAN::DistanceMatrix & D, uint32_t pid)
	{
		Neighbors ne;

		for (uint32_t j = 0; j < D.size1(); ++j)
		{
			if 	( D(pid, j) <= m_eps )
			{
				ne.push_back(j);
			}
		}
		return ne;
	}

	void DBSCAN::dbscan( const DBSCAN::DistanceMatrix & dm )
	{
		std::vector<uint8_t> visited( dm.size1() );

		uint32_t cluster_id = 0;

		for (uint32_t pid = 0; pid < dm.size1(); ++pid)
		{
			if ( !visited[pid] )
			{
				visited[pid] = 1;

				Neighbors ne = find_neighbors(dm, pid );

				if (ne.size() >= m_min_elems)
				{
					m_labels[pid] = cluster_id;

					for (uint32_t i = 0; i < ne.size(); ++i)
					{
						uint32_t nPid = ne[i];

						if ( !visited[nPid] )
						{
							visited[nPid] = 1;

							Neighbors ne1 = find_neighbors(dm, nPid);

							if ( ne1.size() >= m_min_elems )
							{
								for (const auto & n1 : ne1)
								{
									ne.push_back(n1);
								}
							}
						}

						if ( m_labels[nPid] == -1 )
						{
							m_labels[nPid] = cluster_id;
						}
					}

					++cluster_id;
				}
			}
		}
	}

	void DBSCAN::fit( const DBSCAN::ClusterData & C )
	{
		const DBSCAN::FeaturesWeights W = DBSCAN::std_weights( C.size2() );
		wfit( C, W );
	}
	void DBSCAN::fit_precomputed( const DBSCAN::DistanceMatrix & D )
	{
		prepare_labels( D.size1() );
		dbscan( D );
	}

	void DBSCAN::wfit( const DBSCAN::ClusterData & C, const DBSCAN::FeaturesWeights & W )
	{
		prepare_labels( C.size1() );
		const DBSCAN::DistanceMatrix D = calc_dist_matrix( C, W );
		dbscan( D );
	}

	const DBSCAN::Labels & DBSCAN::get_labels() const
	{
		return m_labels;
	}

	std::ostream& operator<<(std::ostream& o, DBSCAN & d)
	{
		o << "[ ";
		for ( const auto & l : d.get_labels() )
		{
			o << " " << l;
		}
		o << " ] " << std::endl;

		return o;
	}
}


// souece: http://eigen.tuxfamily.org/dox/group__TutorialMatrixClass.html
// Compile: g++ -I /usr/include/eigen3/ eigen_ex1.cpp -o eigen_ex1
#include <iostream>
#include <Eigen/Dense>

using Eigen::MatrixXd;

int main(void)
{
    Eigen::Matrix3f m3;
    // Comma-initialization
    m3 << 1, 2, 3,
          4, 5, 6,
          7, 8, 9;
    std::cout << m3 << std::endl;

    // Resize
    MatrixXd m(2,5);
    m.resize(4,3);
    std::cout << "The matrix m is of size "
              << m.rows() << "X" << m.cols() << std::endl;
    std::cout << "It has " << m.size() << " coefficients"
        << std::endl;
    Eigen::VectorXd v(2);
    v.resize(5);
    std::cout << "The vector v is of size " << v.size()
        << std::endl;
    std::cout << "As a matrix, v is of size "
              << v.rows() << "x" << v.cols() << std::endl;

    // Assignment (operator=) and resizing
    Eigen::MatrixXf a(2,2);
    std::cout << "a is of size " << a.rows() << "x" << a.cols()
        << std::endl;
    Eigen::MatrixXf b(3,3);
    a = b;  // using operator=, will resize the matrix on the left-hand.
    std::cout << "a is now of size " << a.rows() << "x" << a.cols()
        << std::endl;
    return 0;
}


#include <algorithm>
#include <cassert>
#include <vector>

#include <gtest/gtest.h>
#include <boost/math/distributions/binomial.hpp>

#include <hypergirgs/Hyperbolic.h>
#include <hypergirgs/HyperbolicTree.h>

class EdgeProbabilities_test: public ::testing::TestWithParam< std::tuple<unsigned, unsigned, double, double> > {
protected:
    const int sampleSeed = 12456;
    const int edgesSeed = 1400;

    static size_t edge2idx(unsigned u, unsigned v, size_t n) {
        const auto mm = std::minmax(u, v);
        return mm.first * n + mm.second;
    }

    static std::vector<uint64_t>
    count_edges(const std::vector<double> &radii, const std::vector<double> &angles, double T, double R, int seed,
                const unsigned num_graphs) {
        const auto n = radii.size();
        assert(radii.size() == angles.size());
        assert(n > 0);

        std::vector<uint64_t> counts(n * n, 0);
        if (!num_graphs)
            return counts;

        auto count_callback = [&counts, n](int u, int v, int /*tid*/) { counts[edge2idx(u, v, n)]++; };
        auto generator = hypergirgs::makeHyperbolicTree(radii, angles, T, R, count_callback, false);

        for (unsigned i = 0; i < num_graphs; ++i)
            generator.generate(i * 1234567 + 123);

        return counts;
    }

    static uint64_t count_violations(const std::vector<double> &radii, const std::vector<double> &angles, double T, double R,
                                     const std::vector<uint64_t> &counts, const unsigned num_graphs, const double confidence) {
        using namespace boost::math;

        const auto n = radii.size();
        assert(radii.size() == angles.size());
        assert(n > 0);

        uint64_t violations = 0;
        #pragma omp parallel for schedule(dynamic) reduction(+:violations)
        for (int u = 0; u < n; ++u) {
            for (int v = u + 1; v < n; ++v) {
                const auto idx = edge2idx(u, v, n);
                const auto dist = hypergirgs::hyperbolicDistance(radii[u], angles[u], radii[v], angles[v]);
                const auto prob = 1.0 / (1.0 + std::exp(0.5 / T * (dist - R)));
                const auto count = counts[idx];
                const auto mean = prob * num_graphs;

                const double pvalue = 2.0 * ((count < mean) ? cdf(binomial(num_graphs, prob), count) : cdf(
                    complement(binomial(num_graphs, prob), count - 1)));

                EXPECT_GT(pvalue, confidence)
                                << "Edge (" << u << ", " << v << ") with r1=" << radii[u] << ", phi1" << angles[u] << ", r2=" << radii[v]
                                << ", phi2" << angles[v] << ", dist=" << dist << ", prob=" << prob << ", count=" << count << ", p-value="
                                << pvalue;

                violations += pvalue < confidence;
            }
        }

        return violations;
    }
};

/*
 * For positive temperatures we carry out statistical tests for each edge. Our null hypothesis is that our generator works
 * correct. Then we compute numGraphs many graph instances for the same set of points and count for each possible edge (u,v) the
 * number N(u,v) of times it is generated. Given that (u,v) has a probability of p(u,v) we expect N(u,v) to be distributed as
 * Bernoulli(numGraphs, p(u,v)). We compute its p-value and reject with a significance level of sig (corrected for the fact,
 * the we carry out (n over 2) trials -- one for each possible edge).
 */
TEST_P(EdgeProbabilities_test, StatTestCorrectProbs) {
    unsigned n, deg;
    double alpha, T;
    std::tie(n, deg, alpha, T) = GetParam();
    ASSERT_GT(T, 0.0);

    const auto sig = 0.05; // Reject H0 (generator is correct) with a significance level of 5%
    const auto numGraphs = 100; // Compute 100 graphs
    const auto corrected_sig = sig / n / (n - 1) * 2.0; // Bonferroni correction as we carry out (n over 2) independent trails

    auto R = hypergirgs::calculateRadius(n, alpha, T, deg);
    std::vector<double> radii, angles;
    std::tie(radii, angles) = hypergirgs::sampleRadiiAndAngles(n, alpha, R, sampleSeed + n);

    const auto counts = count_edges(radii, angles, T, R, edgesSeed + n, numGraphs);
    const auto violations = count_violations(radii, angles, T, R, counts, numGraphs, corrected_sig);
    ASSERT_EQ(violations, 0);
}

static std::vector< std::tuple<unsigned, unsigned, double, double> > params({
    {1000, 10, 0.75, 0.5}, {900, 100, 0.75, 0.5}, {800, 10, 0.6, 0.5}, {700, 10, 0.75, 0.9}
});
INSTANTIATE_TEST_SUITE_P(Params, EdgeProbabilities_test,
                         ::testing::ValuesIn(params.begin(), params.end()));


/*    This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT.
 *    See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details.
 *    Author(s):       Siddharth Pritam, Vincent Rouvreau
 *
 *    Copyright (C) 2020 Inria
 *
 *    Modification(s):
 *      - YYYY/MM Author: Description of the modification
 */

#include <gudhi/Flag_complex_edge_collapser.h>
#include <gudhi/Simplex_tree.h>
#include <gudhi/Persistent_cohomology.h>
#include <gudhi/reader_utils.h>
#include <gudhi/graph_simplicial_complex.h>

#include <boost/program_options.hpp>
#include <boost/range/adaptor/transformed.hpp>

using Simplex_tree = Gudhi::Simplex_tree<Gudhi::Simplex_tree_options_fast_persistence>;
using Filtration_value = Simplex_tree::Filtration_value;
using Vertex_handle = Simplex_tree::Vertex_handle;

using Filtered_edge = std::tuple<Vertex_handle, Vertex_handle, Filtration_value>;
using Proximity_graph = Gudhi::Proximity_graph<Simplex_tree>;

using Field_Zp = Gudhi::persistent_cohomology::Field_Zp;
using Persistent_cohomology = Gudhi::persistent_cohomology::Persistent_cohomology<Simplex_tree, Field_Zp>;
using Distance_matrix = std::vector<std::vector<Filtration_value>>;

void program_options(int argc, char* argv[], std::string& csv_matrix_file, std::string& filediag,
                     Filtration_value& threshold, int& dim_max, int& p, int& edge_collapse_iter_nb,
                     Filtration_value& min_persistence);

int main(int argc, char* argv[]) {
  std::string csv_matrix_file;
  std::string filediag;
  Filtration_value threshold;
  int dim_max = 2;
  int p;
  int edge_collapse_iter_nb;
  Filtration_value min_persistence;

  program_options(argc, argv, csv_matrix_file, filediag, threshold, dim_max, p, edge_collapse_iter_nb,
                  min_persistence);

  Distance_matrix distances = Gudhi::read_lower_triangular_matrix_from_csv_file<Filtration_value>(csv_matrix_file);
  std::cout << "Read the distance matrix succesfully, of size: " << distances.size() << std::endl;

  Proximity_graph proximity_graph = Gudhi::compute_proximity_graph<Simplex_tree>(boost::irange((size_t)0,
                                                                                distances.size()),
                                                                                threshold,
                                                                                [&distances](size_t i, size_t j) {
                                                                                return distances[j][i];
                                                                                });

  auto edges_from_graph = boost::adaptors::transform(edges(proximity_graph), [&](auto&&edge){
        return std::make_tuple(source(edge, proximity_graph),
                               target(edge, proximity_graph),
                               get(Gudhi::edge_filtration_t(), proximity_graph, edge));
      });
  std::vector<Filtered_edge> edges_list(edges_from_graph.begin(), edges_from_graph.end());
  std::vector<Filtered_edge> remaining_edges;
  for (int iter = 0; iter < edge_collapse_iter_nb; iter++) {
    auto remaining_edges = Gudhi::collapse::flag_complex_collapse_edges(edges_list);
    edges_list = std::move(remaining_edges);
    remaining_edges.clear();
  }

  Simplex_tree stree;
  for (Vertex_handle vertex = 0; static_cast<std::size_t>(vertex) < distances.size(); vertex++) {
    // insert the vertex with a 0. filtration value just like a Rips
    stree.insert_simplex({vertex}, 0.);
  }
  for (auto filtered_edge : edges_list) {
    stree.insert_simplex({std::get<0>(filtered_edge), std::get<1>(filtered_edge)}, std::get<2>(filtered_edge));
  }

  stree.expansion(dim_max);

  std::cout << "The complex contains " << stree.num_simplices() << " simplices  after collapse. \n";
  std::cout << "   and has dimension " << stree.dimension() << " \n";

  // Sort the simplices in the order of the filtration
  stree.initialize_filtration();
  // Compute the persistence diagram of the complex
  Persistent_cohomology pcoh(stree);
  // initializes the coefficient field for homology
  pcoh.init_coefficients(3);

  pcoh.compute_persistent_cohomology(min_persistence);
  if (filediag.empty()) {
    pcoh.output_diagram();
  } else {
    std::ofstream out(filediag);
    pcoh.output_diagram(out);
    out.close();
  }
  return 0;
}

void program_options(int argc, char* argv[], std::string& csv_matrix_file, std::string& filediag,
                     Filtration_value& threshold, int& dim_max, int& p, int& edge_collapse_iter_nb,
                     Filtration_value& min_persistence) {
  namespace po = boost::program_options;
  po::options_description hidden("Hidden options");
  hidden.add_options()(
      "input-file", po::value<std::string>(&csv_matrix_file),
      "Name of file containing a distance matrix. Can be square or lower triangular matrix. Separator is ';'.");

  po::options_description visible("Allowed options", 100);
  visible.add_options()("help,h", "produce help message")(
      "output-file,o", po::value<std::string>(&filediag)->default_value(std::string()),
      "Name of file in which the persistence diagram is written. Default print in std::cout")(
      "max-edge-length,r",
      po::value<Filtration_value>(&threshold)->default_value(std::numeric_limits<Filtration_value>::infinity()),
      "Maximal length of an edge for the Rips complex construction.")(
      "cpx-dimension,d", po::value<int>(&dim_max)->default_value(1),
      "Maximal dimension of the Rips complex we want to compute.")(
      "field-charac,p", po::value<int>(&p)->default_value(11),
      "Characteristic p of the coefficient field Z/pZ for computing homology.")(
      "edge-collapse-iterations,i", po::value<int>(&edge_collapse_iter_nb)->default_value(1),
      "Number of iterations edge collapse is performed.")(
      "min-persistence,m", po::value<Filtration_value>(&min_persistence),
      "Minimal lifetime of homology feature to be recorded. Default is 0. Enter a negative value to see zero length "
      "intervals");

  po::positional_options_description pos;
  pos.add("input-file", 1);

  po::options_description all;
  all.add(visible).add(hidden);

  po::variables_map vm;
  po::store(po::command_line_parser(argc, argv).options(all).positional(pos).run(), vm);
  po::notify(vm);

  if (vm.count("help") || !vm.count("input-file")) {
    std::cout << std::endl;
    std::cout << "Compute the persistent homology with coefficient field Z/pZ \n";
    std::cout << "of a Rips complex after edge collapse defined on a set of distance matrix.\n \n";
    std::cout << "The output diagram contains one bar per line, written with the convention: \n";
    std::cout << "   p   dim b d \n";
    std::cout << "where dim is the dimension of the homological feature,\n";
    std::cout << "b and d are respectively the birth and death of the feature and \n";
    std::cout << "p is the characteristic of the field Z/pZ used for homology coefficients." << std::endl << std::endl;

    std::cout << "Usage: " << argv[0] << " [options] input-file" << std::endl << std::endl;
    std::cout << visible << std::endl;
    exit(-1);
  }
}


#ifndef MATHTOOLBOX_GAUSSIAN_PROCESS_REGRESSION_HPP
#define MATHTOOLBOX_GAUSSIAN_PROCESS_REGRESSION_HPP

#include <Eigen/Cholesky>
#include <Eigen/Core>
#include <mathtoolbox/kernel-functions.hpp>

namespace mathtoolbox
{
    class GaussianProcessRegressor
    {
    public:
        enum class KernelType
        {
            ArdSquaredExp,
            ArdMatern52
        };

        /// \brief Construct an instance with input data
        GaussianProcessRegressor(const Eigen::MatrixXd& X,
                                 const Eigen::VectorXd& y,
                                 const KernelType       kernel_type            = KernelType::ArdMatern52,
                                 const bool             use_data_normalization = true);

        /// \brief Calculate the mean of the predicted distribution
        double PredictMean(const Eigen::VectorXd& x) const;

        /// \brief Calculate the standard deviation of the predicted distribution
        double PredictStdev(const Eigen::VectorXd& x) const;

        /// \brief Calculate the derivative of the mean of the predicted distribution
        Eigen::VectorXd PredictMeanDeriv(const Eigen::VectorXd& x) const;

        /// \brief Calculate the derivative of the standard deviation of the predicted distribution
        Eigen::VectorXd PredictStdevDeriv(const Eigen::VectorXd& x) const;

        /// \brief Set hyperparameters directly
        ///
        /// \details Covariance matrix calculation will run within this method.
        void SetHyperparams(const Eigen::VectorXd& kernel_hyperparams, const double noise_hyperparam);

        /// \brief Perform maximum likelihood estimation of the hyperparameters
        void PerformMaximumLikelihood(const Eigen::VectorXd& kernel_hyperparams_initial,
                                      const double           noise_hyperparam_initial);

        /// \brief Get the input data points
        const Eigen::MatrixXd& GetDataPoints() const { return m_X; }

        /// \brief Get the input data values
        const Eigen::VectorXd& GetDataValues() const { return m_y; }

    private:
        // Data points
        Eigen::MatrixXd m_X;
        Eigen::VectorXd m_y;

        // Derivative data
        Eigen::MatrixXd             m_K_y;
        Eigen::LLT<Eigen::MatrixXd> m_K_y_llt;
        Eigen::VectorXd             m_K_y_inv_y;

        // Normalization parameters
        double m_data_mu;
        double m_data_sigma;
        double m_data_scale;

        // Hyperparameters
        Eigen::VectorXd m_kernel_hyperparams;
        double          m_noise_hyperparam;

        // Kernel functions
        Kernel                   m_kernel;
        KernelThetaIDerivative   m_kernel_deriv_theta_i;
        KernelFirstArgDerivative m_kernel_deriv_first_arg;
    };
} // namespace mathtoolbox

#endif // MATHTOOLBOX_GAUSSIAN_PROCESS_REGRESSION_HPP


#include <cstdlib>
#include <cstdio>

//#include <boost/random.hpp>
#include <boost/random/random_device.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real_distribution.hpp>

//Test::Test(boost::random::mt19937_64, boost::random::uniform_real_distribution<>);
//Test::~Test();
//Test::double GetRNG01();

struct Test
{
    boost::random::mt19937_64 rng;
    boost::random::uniform_real_distribution<double> dist01;

    Test(boost::random::mt19937_64 engine, boost::random::uniform_real_distribution<double> dist) : rng(engine), dist01(dist) {};
    ~Test() {};

    double GetRNG01()
    {
        return dist01(rng);
    }
};

int main()
{
    boost::random::random_device rd;
    boost::random::mt19937_64 engine(rd);
    boost::random::uniform_real_distribution<double> dist(0,1);

    Test testStruct(engine, dist);
    printf("Result: %f\n", testStruct.GetRNG01());

    return EXIT_SUCCESS;
}


#include "QuadraticMinimization.hpp"
#include "gtest/gtest.h"
#include <vector>
#include <Eigen/Core>

//--------------------------------------------------------------------------------
class Wrapper_QuadraticMinimization : public QuadraticMinimization {
  public:
    Wrapper_QuadraticMinimization ( int dim_input ) :
       QuadraticMinimization ( dim_input ) { };

    int quadratic_minimization_test1 ( ) {
      std::vector<double> y(2);
      std::vector<double> g(2);
      std::vector< std::vector<double> > H(2);
      H[0].resize(2);
      H[1].resize(2);
      g[0] = 1.0; g[1] = 2.0;
      H[0][0] = -4.0; H[0][1] = 1.0;   //XX check second
      H[1][0] =  1.0; H[1][1] = 5.0;   //XX check second
      QuadraticMinimization::minimize( y, g, H );
      if ( fabs(y[0] + 0.994835624149) > 1e-6 ) return 0;
      if ( fabs(y[1] + 0.101499306351) > 1e-6 ) return 0;
      return 1;
    }
    int quadratic_minimization_test2 ( ) {
      std::vector<double> y(2);
      std::vector<double> g(2);
      std::vector< std::vector<double> > H(2);
      H[0].resize(2);
      H[1].resize(2);
      g[0] = 1.0; g[1] = 0.0;
      H[0][0] = -1.0; H[0][1] = 0.0;
      H[1][0] =  0.0; H[1][1] = 1.0;
      QuadraticMinimization::minimize( y, g, H );
      if ( fabs(y[0] + 1.0 ) > 1e-6 ) return 0;
      if ( fabs(y[1] + 0.0 ) > 1e-6 ) return 0;
      return 1;
    }
    int quadratic_minimization_test3 ( ) {
      std::vector<double> y(2);
      std::vector<double> g(2);
      std::vector< std::vector<double> > H(2);
      H[0].resize(2);
      H[1].resize(2);
      g[0] = 0.0; g[1] = 2.0;
      H[0][0] = -4.0; H[0][1] = 0.0;
      H[1][0] =  0.0; H[1][1] = 4.0;
      QuadraticMinimization::minimize( y, g, H );
      if ( fabs(y[0] - 0.968252226024 ) > 1e-6 &&
           fabs(y[0] - 0.968252226024 ) > 1e-6 ) return 0;
      if ( fabs(y[1] + 0.249975252374 ) > 1e-6 ) return 0;
      return 1;
    }

};
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
TEST ( QuadraticMinimizationTest, minimize_test1 )
{
  Wrapper_QuadraticMinimization W ( 2 );
  EXPECT_EQ( 1, W.quadratic_minimization_test1() );
}
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
TEST ( QuadraticMinimizationTest, minimize_test2 )
{
  Wrapper_QuadraticMinimization W ( 2 );
  EXPECT_EQ( 1, W.quadratic_minimization_test2() );
}
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
TEST ( QuadraticMinimizationTest, minimize_test3 )
{
  Wrapper_QuadraticMinimization W ( 2 );
  EXPECT_EQ( 1, W.quadratic_minimization_test3() );
}
//--------------------------------------------------------------------------------


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

  @copyright 2016 NumScale SAS

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

/*!
  @ingroup group-constant
  @defgroup constant-Nbmantissabits Nbmantissabits (function template)

  Generates a constant representing the number of mantissa bits of a floating point type.

  @headerref{<boost/simd/constant/nbexponentbits.hpp>}

  @par Description

  1.  @code
      template<typename T> as_integer_t<T> Nbmantissabits();
      @endcode

  2.  @code
      template<typename T> as_integer_t<T> Nbmantissabits( boost::simd::as_<T> const& target );
      @endcode

    Generates a value of type `as_integer_t<T>` that evaluates to the number of bits used to
    represents the mantissa of an IEEE value.

  @par Parameters

  | Name                | Description                                                         |
  |--------------------:|:--------------------------------------------------------------------|
  | **target**          | a [placeholder](@ref type-as) value encapsulating the constant type |

  @par Return Value
  A value of type @c as_integer_t<T> that evaluates to:

  | Type         | double      | float         |
  |:-------------|:------------|---------------|
  | **Values**   |   52        |     23        |

  @par Requirements
  - **T** models IEEEValue
**/

#include <boost/simd/constant/scalar/nbmantissabits.hpp>
#include <boost/simd/constant/simd/nbmantissabits.hpp>

#endif


#include <boost/units/cmath.hpp>
#include <boost/units/io.hpp>
#include <boost/units/quantity.hpp>
#include <boost/units/systems/si.hpp>
#include <iostream>
#include <tuple>

using Length_unit = boost::units::si::length;
using Length = boost::units::quantity<Length_unit>;
using Time_unit = boost::units::si::time;
using Time = boost::units::quantity<Time_unit>;
using Velocity_unit = boost::units::si::velocity;
using Velocity = boost::units::quantity<Velocity_unit>;

Velocity compute_velocity(const Length dx, const Time dt) {
    return dx/dt;
}

using Point = std::tuple<Length, Length>;

Length distance(const Point& p1, const Point& p2) {
    Length x1 {std::get<0>(p1)};
    Length x2 {std::get<0>(p2)};
    Length y1 {std::get<1>(p1)};
    Length y2 {std::get<1>(p2)};
    return sqrt((x1 - x2)*(x1 - x2) + (y1 - y2)*(y1 - y2));
}

int main() {
    // define units to make formulas easier to read
    Length_unit m {boost::units::si::meter};
    Time_unit s {boost::units::si::seconds};

    Length dx {3.5*m};
    Time dt {2.0*s};
    Velocity velocity = compute_velocity(dx, dt);
    std::cout << velocity << std::endl;
    std::cout << velocity.value() << std::endl;

    Point p1 {std::make_tuple(3.0*m, 4.0*m)};
    Point p2 {std::make_tuple(5.0*m, 2.0*m)};
    std::cout << distance(p1, p2) << std::endl;

    return 0;
}


// STL includes
#include <iostream>
#include <vector>

// BGL includes
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/kruskal_min_spanning_tree.hpp>
#include <boost/graph/dijkstra_shortest_paths.hpp>

typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS,
  boost::no_property, boost::property<boost::edge_weight_t, int> >      weighted_graph;
typedef boost::property_map<weighted_graph, boost::edge_weight_t>::type weight_map;
typedef boost::graph_traits<weighted_graph>::edge_descriptor            edge_desc;
typedef boost::graph_traits<weighted_graph>::vertex_descriptor          vertex_desc;

using namespace std;

int dijkstra(const weighted_graph &G, int s, int t) {
  int n = boost::num_vertices(G);
  std::vector<int> dist_map(n);

  boost::dijkstra_shortest_paths(G, s,
    boost::distance_map(boost::make_iterator_property_map(
      dist_map.begin(), boost::get(boost::vertex_index, G))));

  return dist_map[t];
}

void kruskal(const weighted_graph &G, vector<edge_desc> &mst) {
  boost::kruskal_minimum_spanning_tree(G, std::back_inserter(mst));
}

void testcase() {
  int n; cin >> n;
  int m; cin >> m;
  int s; cin >> s;
  int a, b; cin >> a; cin >> b;
  vector<weighted_graph> Gs(s, weighted_graph(n));
  weighted_graph G(n);
  vector<weight_map> weights(s);

  for(int i = 0; i < s; i++)
    weights[i] = boost::get(boost::edge_weight, Gs[i]);

  weight_map weights_g = boost::get(boost::edge_weight, G);
  edge_desc e;
  for(int i = 0; i < m; i++) {
    int u,v; cin >> u; cin >> v;
    for(int j = 0; j < s; j++) {
      int w; cin >> w;
      e = boost::add_edge(u, v, Gs[j]).first; weights[j][e] = w;
    }
  }

  int hive; // disregard hives
  for(int i = 0; i < s; i++) {
    cin >> hive;
  }
  for(int i = 0; i < s; i++) {
    vector<edge_desc> mst;
    kruskal(Gs[i], mst);
    for (std::vector<edge_desc>::iterator it = mst.begin(); it != mst.end(); ++it) {
        int u = boost::source(*it, Gs[i]);
        int v = boost::target(*it, Gs[i]);
        if(!edge(u,v,G).second) { // edge not yet in G
          e = boost::add_edge(u, v, G).first;
          weights_g[e] = weights[i][*it];
        } else {
          e = edge(u,v,G).first;
          weights_g[e] = min(weights_g[e], weights[i][*it]);
        }
    }
  }

  cout << dijkstra(G, a, b) << endl;

}
int main()
{
  std::ios_base::sync_with_stdio(false); // Always!
  int t; cin >> t;
  for(int i = 0; i < t; i++)
    testcase();
  return 0;
}


//  (C) Copyright Nick Thompson 2020.
//  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)
//

#include <iostream>
#include <string>
#include <fstream>
#include <map>
#include <cmath>
#include <vector>
#include <iomanip>
#include <boost/algorithm/string.hpp>
#include <boost/math/statistics/linear_regression.hpp>
#include <boost/assert.hpp>

int main(int argc, char** argv)
{
    if (argc != 2)
    {
        std::cout << "Usage: ./regress_accuracy.x foo.csv\n";
        return 1;
    }
    std::string filename = std::string(argv[1]);
    std::ifstream ifs(filename.c_str());
    if (!ifs.good())
    {
        std::cerr << "Couldn't find file " << filename << "\n";
        return 1;
    }
    std::map<std::string, std::vector<double>> m;

    std::string header_line;
    std::getline(ifs, header_line);
    std::cout << "Header line = " << header_line << "\n";
    std::vector<std::string> header_strs;
    boost::split(header_strs, header_line, boost::is_any_of(","));
    for (auto & s : header_strs) {
        boost::algorithm::trim(s);
    }

    std::string line;
    std::vector<double> r;
    std::vector<double> matched_holder;
    std::vector<double> linear;
    std::vector<double> quadratic_b_spline;
    std::vector<double> cubic_b_spline;
    std::vector<double> quintic_b_spline;
    std::vector<double> cubic_hermite;
    std::vector<double> pchip;
    std::vector<double> makima;
    std::vector<double> fotaylor;
    std::vector<double> quintic_hermite;
    std::vector<double> sotaylor;
    std::vector<double> totaylor;
    std::vector<double> septic_hermite;
    while(std::getline(ifs, line))
    {
        std::vector<std::string> strs;
        boost::split(strs, line, boost::is_any_of(","));
        for (auto & s : strs)
        {
            boost::algorithm::trim(s);
        }
        std::vector<double> v(strs.size(), std::numeric_limits<double>::quiet_NaN());
        for (size_t i = 0; i < v.size(); ++i)
        {
            v[i] = std::stod(strs[i]);
        }
        r.push_back(v[0]);
        matched_holder.push_back(std::log2(v[1]));
        linear.push_back(std::log2(v[2]));
        quadratic_b_spline.push_back(std::log2(v[3]));
        cubic_b_spline.push_back(std::log2(v[4]));
        quintic_b_spline.push_back(std::log2(v[5]));
        cubic_hermite.push_back(std::log2(v[6]));
        pchip.push_back(std::log2(v[7]));
        makima.push_back(std::log2(v[8]));
        fotaylor.push_back(std::log2(v[9]));
        if (v.size() > 10) {
            quintic_hermite.push_back(std::log2(v[10]));
            sotaylor.push_back(std::log2(v[11]));
        }
        if (v.size() > 12) {
            totaylor.push_back(std::log2(v[12]));
            septic_hermite.push_back(std::log2(v[13]));
        }
    }

    std::cout << std::fixed << std::setprecision(16);
    auto q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, matched_holder);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Matched Holder    : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, linear);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Linear            : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, quadratic_b_spline);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Quadratic B-spline: " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, cubic_b_spline);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Cubic B-spline    : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, quintic_b_spline);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Quintic B-spline  : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, cubic_hermite);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Cubic Hermite     : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, pchip);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "PCHIP             : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, makima);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Makima            : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, fotaylor);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "First-order Taylor: " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    if (sotaylor.size() > 0)
    {
    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, quintic_hermite);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Quintic Hermite   : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, sotaylor);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "2nd order Taylor  : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    }

    if (totaylor.size() > 0)
    {
    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, totaylor);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "3rd order Taylor  : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    q  = boost::math::statistics::simple_ordinary_least_squares_with_R_squared(r, septic_hermite);
    BOOST_ASSERT(std::get<1>(q) < 0);
    std::cout << "Septic Hermite    : " << std::get<0>(q) << " - " << std::abs(std::get<1>(q)) << "r, R^2 = " << std::get<2>(q) << "\n";

    }

}


/**
cnt.cpp
Stores all relevant information for a carbon nanotube
*/

#include <iostream>
#include <numeric>
#include <armadillo>
#include <complex>
#include <stdexcept>

#include "constants.h"
#include "cnt.h"
#include "../helper/progress.hpp"

void cnt::get_parameters()
{
  // graphen unit vectors and reciprocal lattice vectors
  _a1 = arma::vec({_a_l*std::sqrt(3.0)/2.0, +_a_l/2.0});
  _a2 = arma::vec({_a_l*std::sqrt(3.0)/2.0, -_a_l/2.0});
  _b1 = arma::vec({1.0/sqrt(3.0)*2.0*constants::pi/_a_l, +2.0*constants::pi/_a_l});
  _b2 = arma::vec({1.0/sqrt(3.0)*2.0*constants::pi/_a_l, -2.0*constants::pi/_a_l});

  // carbon-carbon translation vector
  _aCC_vec = 1.0/3.0*(_a1+_a2);

  // cnt chirality vector and its length
	_ch_vec = double(_n) * _a1 + double(_m) * _a2;
	_ch_len = arma::norm(_ch_vec,2);

  // cnt radius
  _radius = _ch_len/2.0/constants::pi;

  // calculate cnt t_vector
  int dR = std::gcd(2*_n+_m,_n+2*_m);
	_t1 = +(2*_m+_n)/dR;
	_t2 = -(2*_n+_m)/dR;
  _t_vec = double(_t1)*_a1 + double(_t2)*_a2;

  // number of hexagons in cnt unit cells which is equal to number of carbon atoms divided by half
  _Nu = 2*(std::pow(_n,2)+std::pow(_m,2)+_n*_m)/dR;

  // rotate basis vectors so that ch_vec is along the x_axis and t_vec is along y_axis
	double cos_theta = _ch_vec(0)/arma::norm(_ch_vec);
	double sin_theta = _ch_vec(1)/arma::norm(_ch_vec);
	arma::mat rot = {{+cos_theta, +sin_theta},
                   {-sin_theta, +cos_theta}}; // rotation matrix

	_ch_vec = rot*_ch_vec;
	_t_vec = rot*_t_vec;
	_a1 = rot*_a1;
	_a2 = rot*_a2;
	_b1 = rot*_b1;
	_b2 = rot*_b2;
	_aCC_vec = rot*_aCC_vec;

  	//make 3d t_vec where the cnt axis is parallel to y-axis
	_t_vec_3d = arma::vec(3,arma::fill::zeros);
	_t_vec_3d(1) = _t_vec(1);

  std::cout << "\n...graphene unit cell vectors:\n";
  _a1.print("a1:");
  _a2.print("a2:");

  std::cout << "\n...graphene reciprocal lattice vectors:\n";
  _b1.print("b1:");
  _b2.print("b2:");

  std::cout << "\n...vector connecting basis carbon atoms:\n";
  _aCC_vec.print("aCC vector:");

  _ch_vec.print("chirality vector:");
  std::cout << "ch_vec length:\n   " << _ch_len << std::endl;

  _t_vec.print("t_vec:");
  _t_vec_3d.print("3d t_vec:");

	// calculate reciprocal lattice of CNT
	_K1 = (-double(_t2)*_b1 + double(_t1)*_b2)/(double(_Nu));
	_K2 = (double(_m)*_b1-double(_n)*_b2)/(double(_Nu));
  _K2_normed = arma::normalise(_K2);
  _nk_K1 = _number_of_cnt_unit_cells;
	_dk_l = _K2/(double(_nk_K1));

  std::cout << "\n...cnt reciprocal lattice vectors:\n";
  _K1.print("K1:");
  _K2.print("K2:");

	// calculate K2-extended representation parameters
  {
    double p_min = (1./double(_t1)+1./double(_n))/(double(_m)/double(_n)-double(_t2)/double(_t1));
    double p_max = (1./double(_t1)+double(_Nu)/double(_n))/(double(_m)/double(_n)-double(_t2)/double(_t1));

    bool found = false;

    for (int p=std::ceil(p_min); p<std::ceil(p_max); p++)
    {
      if (((1+_t2*p) % _t1) == 0)
      {
        int q = (1+_t2*p)/_t1;
        _M = _m*p - _n*q;
        _Q = std::gcd(_Nu,_M);
        std::cout << "\n...K2-extended representation parameters:\n M: " << _M << " ,Q: " << _Q << "\n";
        found = true;
        break;
      }
    }
    if (not found)
    {
      std::cout << "Failed to calculate p and q for K2-extended representation .... investigate! .... aborting the simulation!!!\n";
    }
  }

}

// calculates position of atoms and reciprocal lattice vectors
void cnt::get_atom_coordinates()
{

	// calculate positions of atoms in the cnt unit cell
	_pos_a = arma::mat(_Nu,2,arma::fill::zeros);
	_pos_b = arma::mat(_Nu,2,arma::fill::zeros);

	int k = 0;

	for (int i=0; i<=_t1+_n; i++)
	{
		for (int j=_t2; j<=_m; j++)
		{
			bool flag1 = double(_t2*i)/(double)_t1 <= double(j);
			bool flag2 = double(_m*i)/(double)_n >= double(j);
			bool flag3 = double(_t2*(i-_n))/double(_t1) > double(j-_m);
			bool flag4 = double(_m*(i-_t1))/double(_n) < double(j-_t2);

			if(flag1 && flag2 && flag3 && flag4)
			{
        _pos_a.row(k) = double(i)*_a1.t() + double(j)*_a2.t();
        _pos_b.row(k) = _pos_a.row(k) + _aCC_vec.t();

				if(_pos_a(k,0) > _ch_vec(0))
          _pos_a(k,0) -= _ch_vec(0);
				if(_pos_a(k,0) < 0.0)
          _pos_a(k,0) += _ch_vec(0);
				if(_pos_a(k,1) > _ch_vec(1))
          _pos_a(k,1) -= _ch_vec(1);
				if(_pos_a(k,1) < 0.0)
          _pos_a(k,1) += _ch_vec(1);

				if(_pos_b(k,0) > _ch_vec(0))
          _pos_b(k,0) -= _ch_vec(0);
				if(_pos_b(k,0) < 0.0)
          _pos_b(k,0) += _ch_vec(0);
				if(_pos_b(k,1) > _ch_vec(1))
          _pos_b(k,1) -= _ch_vec(1);
				if(_pos_b(k,1) < 0.0)
          _pos_b(k,1) += _ch_vec(1);

				k++;
			}
		}
	}

  std::cout << "\n...atom coordinates:\n";
  _pos_a.print("pos_a:");
  _pos_b.print("pos_b:");

	if (k != _Nu)
	{
		std::cout << "error in finding position of atoms in cnt unit cell!!!" << std::endl;
		std::cout << "Nu = " << _Nu << "  ,  k = " << k << std::endl;
		exit(1);
	}

	// put position of all atoms in a single variable in 2d space(unrolled graphene sheet)
	_pos_2d = arma::mat(2*_Nu,2,arma::fill::zeros);
  _pos_2d.rows(0,_Nu-1) = _pos_a;
  _pos_2d.rows(_Nu,2*_Nu-1) = _pos_b;

	// calculate position of all atoms in the 3d space (rolled graphene sheet)
	_pos_3d = arma::mat(2*_Nu,3,arma::fill::zeros);
	for (unsigned int i=0; i<_pos_3d.n_rows; i++)
	{
		_pos_3d(i,0) = _radius*cos(_pos_2d(i,0)/_radius);
		_pos_3d(i,1) = _pos_2d(i,1);
		_pos_3d(i,2) = _radius*sin(_pos_2d(i,0)/_radius);
	}

	// save coordinates of atoms in 2d space
  std::string filename = _directory.path() / "pos_2d.dat";
  _pos_2d.save(filename, arma::arma_ascii);

  // save coordinates of atoms in 3d space
  filename = _directory.path() / "pos_3d.dat";
  _pos_3d.save(filename, arma::arma_ascii);

  // put position of all graphene unit cells in 2d (unrolled graphene sheet) and 3d space (rolled graphene sheet)
	_pos_u_2d = _pos_a;
	_pos_u_3d = arma::mat(_Nu,3,arma::fill::zeros);
	for (unsigned int i=0; i<_pos_u_3d.n_rows; i++)
	{
		_pos_u_3d(i,0) = _radius*cos(_pos_u_2d(i,0)/_radius);
		_pos_u_3d(i,1) = _pos_u_2d(i,1);
		_pos_u_3d(i,2) = _radius*sin(_pos_u_2d(i,0)/_radius);
	}

}

// calculate electron energy dispersions in the K1-extended representation using full unit cell (2*Nu atoms)
void cnt::electron_full_unit_cell()
{

	// make the list of 1st nearest neighbor atoms
	arma::umat nn_list(2*_Nu,3,arma::fill::zeros); // contains index of the nearest neighbor atom
	arma::imat nn_tvec_index(2*_Nu,3,arma::fill::zeros); // contains the index of the cnt unit cell that the nearest neigbor atom is in.
	for (unsigned int i=0; i<_pos_3d.n_rows; i++)
	{
		int k=0;
		for (unsigned int j=0; j<_pos_3d.n_rows; j++)
		{
			for (int l=-1; l<=1; l++)
			{
        double dR = arma::norm(_pos_3d.row(i)-_pos_3d.row(j)+double(l)*_t_vec_3d.t());
				if ( (i!=j) && (dR<(1.4*_a_cc)) )
				{
					nn_list(i,k) = j;
					nn_tvec_index(i,k) = l;
					k++;
				}
			}
		}
    if (k != 3)
    {
      std::cout << "error: nearest neighbors partially found!!!\n";
      std::exit(1);
    }
	}

	arma::cx_mat H(2*_Nu, 2*_Nu, arma::fill::zeros);
	arma::cx_mat S(2*_Nu, 2*_Nu, arma::fill::zeros);
  arma::vec E;
  arma::cx_mat C;

	int NK = _nk_K1;

	arma::mat el_energy_full(2*_Nu, NK, arma::fill::zeros);
	arma::cx_cube el_psi_full(2*_Nu, 2*_Nu, NK, arma::fill::zeros);

	double t_len = arma::norm(_t_vec_3d);

	for (int n=0; n<NK; n++)
	{
		double wave_vec = double(n-_nk_K1/2)*arma::norm(_dk_l);

		H.zeros();
		S.zeros();

		for (int i=0; i<2*_Nu; i++)
		{
			H(i,i) = std::complex<double>(_e2p,0.e0);
			for (int k=0; k<3; k++)
			{
				int j = nn_list(i,k);
				int l = nn_tvec_index(i,k);

				H(i,j) += arma::cx_double(_t0,0.e0)*exp(arma::cx_double(0.0,wave_vec*double(l)*t_len));
				S(i,j) += arma::cx_double(_s0,0.e0)*exp(arma::cx_double(0.0,wave_vec*double(l)*t_len));
			}
		}

		arma::eig_sym(E, C, H);

		// fix the phase of the eigen vectors
		for (unsigned int i=0; i<C.n_cols; i++)
		{
			arma::cx_double phi = std::conj(C(0,i))/std::abs(C(0,i));
			for (unsigned int j=0; j<C.n_rows; j++)
			{
				C(j,i) *= phi;
			}
		}

    el_energy_full.col(n) = E;
    el_psi_full.slice(n) = C;

	}

  // save electron energy bands using full Brillouine zone
  std::cout << "saved electron energy dispersion in K1-extended representation\n";
  std::string filename = _directory.path() / "el_energy_full.dat";
  el_energy_full.save(filename, arma::arma_ascii);

  // // save electron wavefunctions using full Brillouine zone
  // filename = _directory.path()/"el_psi_full.dat";
  // el_psi_full.save(filename, arma::arma_ascii);

}

// calculate electron dispersion energies for an input range of ik and mu
cnt::el_energy_struct cnt::electron_energy(const std::array<int,2>& ik_range, const std::array<int,2>& mu_range, const std::string& name)
{
  int number_of_bands = 2;
  int number_of_atoms_in_graphene_unit_cell = number_of_bands;

  int nk = ik_range[1] - ik_range[0];
  int n_mu = mu_range[1] - mu_range[0];

  arma::cube energy(number_of_bands, nk, n_mu, arma::fill::zeros);
  arma::field<arma::cx_cube> wavefunc(n_mu); // this pretty weird order is chosen so than we can select each cutting line easier and more efficiently
  wavefunc.for_each([&](arma::cx_cube& c){c.zeros(number_of_atoms_in_graphene_unit_cell, number_of_bands, nk);}); // this pretty weird order is chosen so than we can select each cutting line easier and more efficiently

  const std::complex<double> i1(0,1);

  const int ic = 1;
  const int iv = 0;

  const int iA = 0;
  const int iB = 1;

  for (int mu=mu_range[0]; mu<mu_range[1]; mu++)
  {
    for (int ik=ik_range[0]; ik<ik_range[1]; ik++)
    {
      arma::vec k_vec = double(mu)*_K1 + double(ik)*_dk_l;
      std::complex<double> fk = std::exp(std::complex<double>(0,arma::dot(k_vec,(_a1+_a2)/3.))) + \
                                std::exp(std::complex<double>(0,arma::dot(k_vec,(_a1-2.*_a2)/3.))) + \
                                std::exp(std::complex<double>(0,arma::dot(k_vec,(_a2-2.*_a1)/3.)));

      energy(ic,ik-ik_range[0],mu-mu_range[0]) = +_t0*std::abs(fk);
      energy(iv,ik-ik_range[0],mu-mu_range[0]) = -_t0*std::abs(fk);

      (wavefunc(mu-mu_range[0]))(iA,ic,ik-ik_range[0]) = +1./std::sqrt(2.); // this pretty weird order is chosen so than we can select each cutting line easier and more efficiently
      (wavefunc(mu-mu_range[0]))(iA,iv,ik-ik_range[0]) = +1./std::sqrt(2.); // this pretty weird order is chosen so than we can select each cutting line easier and more efficiently
      (wavefunc(mu-mu_range[0]))(iB,ic,ik-ik_range[0]) = -1./std::sqrt(2.)*std::conj(fk)/std::abs(fk); // this pretty weird order is chosen so than we can select each cutting line easier and more efficiently
      (wavefunc(mu-mu_range[0]))(iB,iv,ik-ik_range[0]) = +1./std::sqrt(2.)*std::conj(fk)/std::abs(fk); // this pretty weird order is chosen so than we can select each cutting line easier and more efficiently
    }
  }

  // save electron energy bands using full Brillouine zone
  std::string filename = _directory.path() / (name +".el_energy.dat");
  energy.save(filename,arma::arma_ascii);

  std::cout << "\n...calculated " + name + " electron dispersion\n";

  el_energy_struct energy_s;
  energy_s.name = name;
  energy_s.energy = energy;
  energy_s.wavefunc = wavefunc;
  energy_s.ik_range = ik_range;
  energy_s.mu_range = mu_range;
  energy_s.nk = nk;
  energy_s.n_mu = n_mu;
  energy_s.no_of_atoms = number_of_atoms_in_graphene_unit_cell;
  energy_s.no_of_bands = number_of_bands;

  return energy_s;
}

void cnt::find_valleys(const cnt::el_energy_struct& elec_struct)
{

  // get the indicies of valleys
  std::vector<std::array<unsigned int,2>> ik_valley_idx;

  int iC = 1;
  for (int ik_idx=0; ik_idx<elec_struct.nk; ik_idx++)
  {
    for (int i_mu_idx=0; i_mu_idx<elec_struct.n_mu; i_mu_idx++)
    {
      int ik_idx_p1 = ik_idx + 1;
      int ik_idx_m1 = ik_idx - 1;
      while(ik_idx_p1 >= elec_struct.nk)
      {
        ik_idx_p1 -= elec_struct.nk;
      }
      while(ik_idx_m1 < 0)
      {
        ik_idx_m1 += elec_struct.nk;
      }

      if ((elec_struct.energy(iC,ik_idx,i_mu_idx) < elec_struct.energy(iC,ik_idx_m1,i_mu_idx)) \
            and (elec_struct.energy(iC,ik_idx,i_mu_idx) < elec_struct.energy(iC,ik_idx_p1,i_mu_idx)))
      {
        ik_valley_idx.push_back({(unsigned int)ik_idx, (unsigned int)i_mu_idx});
      }

    }
  }

  // sort valleys in order of their
  std::sort(ik_valley_idx.begin(),ik_valley_idx.end(), [&](const auto& s1, const auto& s2) {
                                    return elec_struct.energy(1,s1[0],s1[1]) < elec_struct.energy(1,s2[0],s2[1]);
                                  });

  // put them in a vector with each element containing the two equivalent valleys
  for (unsigned int i=0; i<ik_valley_idx.size()/2; i++)
  {
    std::array<std::array<unsigned int, 2>, 2> valley = {ik_valley_idx.at(2*i), ik_valley_idx.at(2*i+1)};
    _valleys_K2.push_back(valley);
  }

  std::cout << "\n...found and sorted indices of valleys:\n";
  for (const auto& valleys: _valleys_K2)
  {
    auto v1 = valleys.at(0);
    auto v2 = valleys.at(1);
    std::cout << "[" << v1[0] << "," << v1[1] << "] , [" << v2[0] << "," << v2[1] << "]\n";
  }

  std::cout << "number of valleys: " << _valleys_K2.size() << std::endl;

}

// find ik values that are energetically relevant around the bottom of the valley
void cnt::find_relev_ik_range(double delta_energy, const cnt::el_energy_struct& elec_struct)
{
  std::vector<std::vector<std::array<int,2>>> relev_ik_range(2);

  // first get ik for relevant states in the first valley
  int i_valley = 0;
  int ik_bottom = _valleys_K2[_i_sub][i_valley][0]+elec_struct.ik_range[0];
  int mu_bottom = _valleys_K2[_i_sub][i_valley][1]+elec_struct.mu_range[0];
  int iC = 1;
  double max_energy = elec_struct.energy(iC, ik_bottom-elec_struct.ik_range[0], mu_bottom-elec_struct.mu_range[0]) + delta_energy;

  relev_ik_range.at(i_valley).push_back({ik_bottom,mu_bottom});
  bool in_range = true;
  int count = 0;
  while (in_range)
  {
    in_range = false;
    count ++;
    int ik = ik_bottom + count;
    while(ik >= elec_struct.ik_range[1])
    {
      ik -= elec_struct.nk;
    }
    if (elec_struct.energy(iC, ik-elec_struct.ik_range[0], mu_bottom-elec_struct.mu_range[0]) < max_energy)
    {
      relev_ik_range.at(i_valley).push_back({ik,mu_bottom});
      in_range = true;
    }

    ik = ik_bottom - count;
    while(ik < elec_struct.ik_range[0])
    {
      ik += elec_struct.nk;
    }
    if (elec_struct.energy(iC, ik-elec_struct.ik_range[0], mu_bottom-elec_struct.mu_range[0]) < max_energy)
    {
      // std::array<int,2> relev_state = {ik,mu_bottom};
      relev_ik_range.at(i_valley).insert(relev_ik_range.at(i_valley).begin(), {ik,mu_bottom});
      in_range = true;
    }
  }

  // do the same thing for the second valley
  i_valley = 1;
  ik_bottom = _valleys_K2[_i_sub][i_valley][0]+elec_struct.ik_range[0];
  mu_bottom = _valleys_K2[_i_sub][i_valley][1]+elec_struct.mu_range[0];
  max_energy = elec_struct.energy(iC, ik_bottom-elec_struct.ik_range[0], mu_bottom-elec_struct.mu_range[0]) + delta_energy;

  relev_ik_range.at(i_valley).push_back({ik_bottom,mu_bottom});
  in_range = true;
  count = 0;
  while (in_range)
  {
    in_range = false;
    count ++;
    int ik = ik_bottom + count;
    while(ik >= elec_struct.ik_range[1])
    {
      ik -= elec_struct.nk;
    }
    if (elec_struct.energy(iC, ik-elec_struct.ik_range[0], mu_bottom-elec_struct.mu_range[0]) < max_energy)
    {
      relev_ik_range.at(i_valley).push_back({ik,mu_bottom});
      in_range = true;
    }

    ik = ik_bottom - count;
    while(ik < elec_struct.ik_range[0])
    {
      ik += elec_struct.nk;
    }
    if (elec_struct.energy(iC, ik-elec_struct.ik_range[0], mu_bottom-elec_struct.mu_range[0]) < max_energy)
    {
      relev_ik_range.at(i_valley).insert(relev_ik_range.at(i_valley).begin(), {ik,mu_bottom});
      in_range = true;
    }
  }

  std::cout << "\n...ik for relevant states calculated:\n";
  std::cout << "relev_ik_range has length of " << relev_ik_range[0].size() << std::endl;

  // i_valley = 0;
  // std::cout << "valley: " << i_valley << "\n";
  // for (const auto& state: relev_ik_range.at(i_valley))
  // {
  //   std::cout << "   [" << state.at(0) << "," << state.at(1) << "]\n";
  // }

  // i_valley = 1;
  // std::cout << "\nvalley: " << i_valley << "\n";
  // for (const auto& state: relev_ik_range.at(i_valley))
  // {
  //   std::cout << "   [" << state.at(0) << "," << state.at(1) << "]\n";
  // }

  _relev_ik_range = relev_ik_range;

}

// fourier transformation of the coulomb interaction a.k.a v(q)
cnt::vq_struct cnt::calculate_vq(const std::array<int,2> iq_range, const std::array<int,2> mu_range, unsigned int no_of_cnt_unit_cells)
{
  // primary checks for function input
  int nq = iq_range.at(1) - iq_range.at(0);
  if (nq <= 0) {
    throw "Incorrect range for iq!";
  }
  int n_mu = mu_range.at(1) - mu_range.at(0);
  if (n_mu <= 0) {
    throw "Incorrect range for mu_q!";
  }
  if (no_of_cnt_unit_cells % 2 == 0)  no_of_cnt_unit_cells ++;

  // calculate distances between atoms in a warped cnt unit cell.
	arma::mat pos_aa = arma::mat(_Nu,2,arma::fill::zeros);
	arma::mat pos_ab = arma::mat(_Nu,2,arma::fill::zeros);
	arma::mat pos_ba = arma::mat(_Nu,2,arma::fill::zeros);
  arma::mat pos_bb = arma::mat(_Nu,2,arma::fill::zeros);

	for (int i=0; i<_Nu; i++)
	{
    pos_aa.row(i) = _pos_a.row(i)-_pos_a.row(0);
    pos_ab.row(i) = _pos_a.row(i)-_pos_b.row(0);
    pos_ba.row(i) = _pos_b.row(i)-_pos_a.row(0);
    pos_bb.row(i) = _pos_b.row(i)-_pos_b.row(0);

		if(pos_aa(i,0) > _ch_vec(0)/2)
      pos_aa(i,0) -= _ch_vec(0);
		if(pos_ab(i,0) > _ch_vec(0)/2)
      pos_ab(i,0) -= _ch_vec(0);
		if(pos_ba(i,0) > _ch_vec(0)/2)
      pos_ba(i,0) -= _ch_vec(0);
		if(pos_bb(i,0) > _ch_vec(0)/2)
      pos_bb(i,0) -= _ch_vec(0);
	}

  arma::cube rel_pos(_Nu*no_of_cnt_unit_cells,2,4,arma::fill::zeros);
  for (int i=-std::floor(double(no_of_cnt_unit_cells)/2.); i<=std::floor(double(no_of_cnt_unit_cells)/2.); i++)
  {
    int idx = (i+std::floor(double(no_of_cnt_unit_cells)/2.))*_Nu;
    for (int j=0; j<_Nu; j++)
    {
      rel_pos.slice(0).row(idx+j) = pos_aa.row(j)+(i*_t_vec.t());
      rel_pos.slice(1).row(idx+j) = pos_ab.row(j)+(i*_t_vec.t());
      rel_pos.slice(2).row(idx+j) = pos_ba.row(j)+(i*_t_vec.t());
      rel_pos.slice(3).row(idx+j) = pos_bb.row(j)+(i*_t_vec.t());
    }
  }

  // calculate vq
  arma::cx_cube vq(nq,n_mu,4,arma::fill::zeros);
  arma::vec q_vec(nq,arma::fill::zeros);

  arma::vec q(2,arma::fill::zeros);
  const double coeff = std::pow(4.*constants::pi*constants::eps0*_Upp/constants::q0/constants::q0,2);
  const std::complex<double> i1(0.,1.);
  auto Uhno = [&](const arma::mat& R){
    return std::exp(i1*arma::dot(q,R))*_Upp/std::sqrt(coeff*(std::pow(R(0),2)+std::pow(R(1),2))+1);
  };

  progress_bar prog(nq, "vq");

  for (int iq=iq_range[0]; iq<iq_range[1]; iq++)
  {
    int iq_idx = iq-iq_range[0];

    prog.step();

    q_vec(iq_idx) = iq*arma::norm(_dk_l,2);
    for (int mu=mu_range[0]; mu<mu_range[1]; mu++)
    {
      int mu_idx = mu - mu_range[0];
      q = iq*_dk_l + mu*_K1;
      // std::cout << "after addition!\n";
      for (int i=0; i<4; i++)
      {
        for (unsigned int k=0; k<_Nu*no_of_cnt_unit_cells; k++)
        {
          vq(iq_idx,mu_idx,i) += Uhno(rel_pos.slice(i).row(k));
        }
      }
    }
  }

  vq = vq/(2*_Nu*no_of_cnt_unit_cells);

  std::cout << "\n...calculated vq\n";

  std::cout << "saved real part of vq\n";
  arma::cube vq_real = arma::real(vq);
  std::string filename = _directory.path()/"vq_real.dat";
  vq_real.save(filename, arma::arma_ascii);

  std::cout << "saved imaginary part of vq\n";
  arma::cube vq_imag = arma::imag(vq);
  filename = _directory.path()/"vq_imag.dat";
  vq_imag.save(filename, arma::arma_ascii);

  std::cout << "saved q_vector for vq\n";
  filename = _directory.path()/"vq_q_vec.dat";
  q_vec.save(filename, arma::arma_ascii);

  // make the vq_struct that is to be returned
  vq_struct vq_s;
  vq_s.data = vq;
  vq_s.iq_range = iq_range;
  vq_s.mu_range = mu_range;
  vq_s.nq = nq;
  vq_s.n_mu = n_mu;

  return vq_s;
}

// polarization of electronic states a.k.a PI(q)
cnt::PI_struct cnt::calculate_polarization(const std::array<int,2> iq_range, const std::array<int,2> mu_range, const cnt::el_energy_struct& elec_struct)
{
  // primary checks for function input
  int nq = iq_range.at(1) - iq_range.at(0);
  if (nq <= 0) {
    throw "Incorrect range for iq in calculate_polarization!";
  }
  int n_mu = mu_range.at(1) - mu_range.at(0);
  if (n_mu <= 0) {
    throw "Incorrect range for mu_q in calculate_polarization!";
  }

  int ikq, mu_kq;
  int ik, mu_k;
  int iq, mu_q;
  // lambda function to wrap iq+ik and mu_k+mu_q inside the K2-extended brillouine zone
  auto get_kq = [&](){
    mu_kq = mu_k+mu_q;
    ikq = ik+iq;
    while (mu_kq >= elec_struct.mu_range[1]) {
      mu_kq -= elec_struct.n_mu;
      ikq += _nk_K1*_M;
    }
    while (mu_kq < elec_struct.mu_range[0]) {
      mu_kq += elec_struct.n_mu;
      ikq -= _nk_K1*_M;
    }
    while (ikq >= elec_struct.ik_range[1]){
      ikq -= elec_struct.nk;
    }
    while (ikq < elec_struct.ik_range[0]){
      ikq += elec_struct.nk;
    }
  };

  arma::mat PI(nq,n_mu,arma::fill::zeros);
  arma::vec q_vec(nq,arma::fill::zeros);

  const int iv = 0;
  const int ic = 1;

  progress_bar prog(nq, "calculate polarization");

  int iq_idx, mu_q_idx;
  int ik_idx, mu_k_idx;
  int i_kq_idx, mu_kq_idx;
  for (iq=iq_range[0]; iq<iq_range[1]; iq++)
  {
    iq_idx = iq - iq_range[0];
    q_vec(iq_idx) = iq*arma::norm(_dk_l);

    prog.step(iq_idx);

    for (mu_q=mu_range[0]; mu_q<mu_range[1]; mu_q++)
    {
      mu_q_idx = mu_q - mu_range[0];
      for (ik=elec_struct.ik_range[0]; ik<elec_struct.ik_range[1]; ik++)
      {
        ik_idx = ik - elec_struct.ik_range[0];
        for (mu_k=elec_struct.mu_range[0]; mu_k<elec_struct.mu_range[1]; mu_k++)
        {
          mu_k_idx = mu_k - elec_struct.mu_range[0];
          get_kq();
          mu_kq_idx = mu_kq - elec_struct.mu_range[0];
          i_kq_idx = ikq - elec_struct.ik_range[0];

          PI(iq_idx,mu_q_idx) += std::pow(std::abs(arma::dot(arma::conj(elec_struct.wavefunc(mu_k_idx).slice(ik_idx).col(iv)),\
                                                             elec_struct.wavefunc(mu_kq_idx).slice(i_kq_idx).col(ic))),2)/ \
                                          (elec_struct.energy(ic,i_kq_idx,mu_kq_idx)-elec_struct.energy(iv,ik_idx,mu_k_idx)) + \
                                 std::pow(std::abs(arma::dot(arma::conj(elec_struct.wavefunc(mu_k_idx).slice(ik_idx).col(ic)), \
                                                             elec_struct.wavefunc(mu_kq_idx).slice(i_kq_idx).col(iv))),2)/ \
                                          (elec_struct.energy(ic,ik_idx,mu_k_idx)-elec_struct.energy(iv,i_kq_idx,mu_kq_idx));
        }
      }
    }
  }

  PI = 2*PI;

  std::cout << "\n...calculated polarization: PI(q)\n";

  std::cout << "saved PI\n";
  std::string filename = _directory.path()/"PI.dat";
  PI.save(filename, arma::arma_ascii);

  std::cout << "saved q_vector for PI\n";
  filename = _directory.path()/"PI_q_vec.dat";
  q_vec.save(filename, arma::arma_ascii);

  // make the vq_struct that is to be returned
  PI_struct PI_s;
  PI_s.data = PI;
  PI_s.iq_range = iq_range;
  PI_s.mu_range = mu_range;
  PI_s.nq = nq;
  PI_s.n_mu = n_mu;

  return PI_s;
}

// dielectric function a.k.a eps(q)
cnt::epsilon_struct cnt::calculate_dielectric(const std::array<int,2> iq_range, const std::array<int,2> mu_range)
{
  // check if vq has been calculated properly before
  if (not (in_range(iq_range,_vq.iq_range) and in_range(mu_range,_vq.mu_range))){
    throw std::logic_error("You need to calculate vq with correct range before \
                            trying to calculate dielectric function");
  }
  // check if PI has been calculated properly before
  if (not (in_range(iq_range,_PI.iq_range) and in_range(mu_range,_PI.mu_range))){
    throw std::logic_error("You need to calculate PI with correct range before \
                            trying to calculate dielectric function");
  }

  int nq = iq_range[1] - iq_range[0];
  int n_mu = mu_range[1] - mu_range[0];
  arma::mat eps = arma::real(arma::mean(_vq.data,2));
  eps = eps.submat(iq_range[0]-_vq.iq_range[0],mu_range[0]-_vq.mu_range[0],arma::size(nq,n_mu));
  eps %= _PI.data.submat(iq_range[0]-_PI.iq_range[0],mu_range[0]-_PI.mu_range[0],arma::size(nq,n_mu));
  std::cout << "size of dielectric function matrix: " << arma::size(eps) << std::endl;
  eps += 1.;

  arma::vec q_vec(nq);
  for (int iq=iq_range[0]; iq<iq_range[1]; iq++)
  {
    int iq_idx = iq - iq_range[0];
    q_vec(iq_idx) = iq*arma::norm(_dk_l);
  }

  std::cout << "\n...calculated dielectric function: epsilon(q)\n";

  std::cout << "saved epsilon\n";
  std::string filename = _directory.path()/"eps.dat";
  eps.save(filename, arma::arma_ascii);

  std::cout << "saved q_vector for epsilon\n";
  filename = _directory.path()/"eps_q_vec.dat";
  q_vec.save(filename, arma::arma_ascii);

  epsilon_struct eps_s;
  eps_s.data = eps;
  eps_s.iq_range = iq_range;
  eps_s.mu_range = mu_range;
  eps_s.nq = nq;
  eps_s.n_mu = n_mu;
  return eps_s;
}

// calculate exciton dispersion
std::vector<cnt::exciton_struct> cnt::calculate_A_excitons(const std::array<int,2> ik_cm_range, const cnt::el_energy_struct& elec_struct) {
  // some utility variables that are going to be used over and over again
  int ik_c, mu_c;
  int ik_v, mu_v;
  int ik_cp, mu_cp;
  int ik_vp, mu_vp;
  int ik_c_diff, mu_c_diff;
  int ik_cm, mu_cm;

  std::complex<double> dir_interaction;
  std::complex<double> xch_interaction;

  const int iv = 0;
  const int ic = 1;

  const int i_valley_1 = 0;
  const int i_valley_2 = 1;

  // lambda function to calculate direct interaction
  auto get_direct_interaction = [&](){
    ik_c_diff = ik_c-ik_cp;
    mu_c_diff = mu_c-mu_cp;
    while(ik_c_diff < elec_struct.ik_range[0]){
      ik_c_diff += elec_struct.nk;
    }
    while(ik_c_diff >= elec_struct.ik_range[1]){
      ik_c_diff -= elec_struct.nk;
    }

    dir_interaction = 0;
    for (int i=0; i<2; i++)
    {
      for (int j=0; j<2; j++)
      {
        dir_interaction += std::conj(elec_struct.wavefunc(mu_c -elec_struct.mu_range[0])(i,ic,ik_c -elec_struct.ik_range[0]))* \
                                     elec_struct.wavefunc(mu_v -elec_struct.mu_range[0])(j,iv,ik_v -elec_struct.ik_range[0]) * \
                                     elec_struct.wavefunc(mu_cp-elec_struct.mu_range[0])(i,ic,ik_cp-elec_struct.ik_range[0]) * \
                           std::conj(elec_struct.wavefunc(mu_vp-elec_struct.mu_range[0])(j,iv,ik_vp-elec_struct.ik_range[0]))* \
                                                          _vq.data(ik_c_diff-_vq.iq_range[0],mu_c_diff-_vq.mu_range[0],2*i+j)/ \
                                                             _eps.data(ik_c_diff-_eps.iq_range[0],mu_c_diff-_eps.mu_range[0]);
      }
    }
    return dir_interaction;
  };

  // lambda function to calculate exchange interaction
  auto get_exchange_interaction = [&](){
    xch_interaction = 0;
    for (int i=0; i<2; i++)
    {
      for (int j=0; j<2; j++)
      {
        xch_interaction += std::conj(elec_struct.wavefunc(mu_c -elec_struct.mu_range[0])(i,ic,ik_c -elec_struct.ik_range[0]))* \
                                     elec_struct.wavefunc(mu_v -elec_struct.mu_range[0])(i,iv,ik_v -elec_struct.ik_range[0]) * \
                                     elec_struct.wavefunc(mu_cp-elec_struct.mu_range[0])(j,ic,ik_cp-elec_struct.ik_range[0]) * \
                           std::conj(elec_struct.wavefunc(mu_vp-elec_struct.mu_range[0])(j,iv,ik_vp-elec_struct.ik_range[0]))* \
                                                                  _vq.data(ik_cm-_vq.iq_range[0],mu_cm-_vq.mu_range[0],2*i+j);
      }
    }
    return xch_interaction;
  };

  // get ik of valence band state by taking care of wrapping around K2-extended zone
  auto get_ikv = [&elec_struct](const int& ik_c, const int& ik_cm){
    int ik_v = ik_c - ik_cm;
    while (ik_v >= elec_struct.ik_range[1]){
      ik_v -= elec_struct.nk;
    }
    while (ik_v < elec_struct.ik_range[0]){
      ik_v += elec_struct.nk;
    }
    return ik_v;
  };

  // get ik of conduction band state by taking care of wrapping around K2-extended zone
  auto get_ikc = [&elec_struct](const int& ik_v, const int& ik_cm){
    int ik_c = ik_v + ik_cm;
    while (ik_c >= elec_struct.ik_range[1]){
      ik_c -= elec_struct.nk;
    }
    while (ik_c < elec_struct.ik_range[0]){
      ik_c += elec_struct.nk;
    }
    return ik_c;
  };

  int nk_cm = ik_cm_range[1] - ik_cm_range[0];
  int nk_relev = int(_relev_ik_range[0].size());
  int nk_c = 2*nk_relev;

  arma::mat ex_energy_A1(nk_cm,nk_relev,arma::fill::zeros);
  arma::mat ex_energy_A2_singlet(nk_cm,nk_relev,arma::fill::zeros);
  arma::mat ex_energy_A2_triplet(nk_cm,nk_relev,arma::fill::zeros);

  arma::cx_cube ex_psi_A1(nk_c,nk_relev,nk_cm, arma::fill::zeros);
  arma::cx_cube ex_psi_A2_singlet(nk_c,nk_relev,nk_cm, arma::fill::zeros);
  arma::cx_cube ex_psi_A2_triplet(nk_c,nk_relev,nk_cm, arma::fill::zeros);

  arma::ucube ik_idx(4, nk_c, nk_cm);

  arma::cx_mat kernel_11(nk_relev,nk_relev,arma::fill::zeros);
  arma::cx_mat kernel_12(nk_relev,nk_relev,arma::fill::zeros);
  arma::cx_mat kernel_exchange(nk_relev,nk_relev,arma::fill::zeros);
  arma::vec energy;
  arma::cx_mat psi;
  arma::vec k_cm_vec(nk_cm,arma::fill::zeros);

  progress_bar prog(nk_cm, "calculate ex_energy");

  // loop to calculate exciton dispersion
  for (ik_cm=ik_cm_range[0]; ik_cm<ik_cm_range[1]; ik_cm++)
  {
    kernel_11.zeros();
    kernel_12.zeros();
    kernel_exchange.zeros();
    mu_cm = 0;
    int ik_cm_idx = ik_cm - ik_cm_range[0];
    k_cm_vec(ik_cm_idx) = ik_cm*arma::norm(_dk_l);

    prog.step(ik_cm_idx);

    for (int ik_c_idx=0; ik_c_idx<nk_relev; ik_c_idx++)
    {
      ik_c = _relev_ik_range[i_valley_1][ik_c_idx][0];
      mu_c = _relev_ik_range[i_valley_1][ik_c_idx][1];
      ik_v = get_ikv(ik_c,ik_cm);
      mu_v = mu_c;

      kernel_11(ik_c_idx, ik_c_idx) += elec_struct.energy(ic,ik_c-elec_struct.ik_range[0],mu_c-elec_struct.mu_range[0]) - \
                                       elec_struct.energy(iv,ik_v-elec_struct.ik_range[0],mu_c-elec_struct.mu_range[0]);

      // interaction  between valley_1 and valley_1
      for (int ik_cp_idx=0; ik_cp_idx<=ik_c_idx; ik_cp_idx++)
      {
        ik_cp = _relev_ik_range[i_valley_1][ik_cp_idx][0];
        mu_cp = _relev_ik_range[i_valley_1][ik_cp_idx][1];
        ik_vp = get_ikv(ik_cp,ik_cm);
        mu_vp = mu_cp;

        kernel_11(ik_c_idx,ik_cp_idx) -= get_direct_interaction();
        kernel_exchange(ik_c_idx,ik_cp_idx) += std::complex<double>(2,0)*get_exchange_interaction();
      }

      // interaction  between valley_1 and valley_2
      for (int ik_vp_idx=ik_c_idx; ik_vp_idx<nk_relev; ik_vp_idx++)
      {
        ik_vp = _relev_ik_range[i_valley_2][ik_vp_idx][0];
        mu_vp = _relev_ik_range[i_valley_2][ik_vp_idx][1];
        ik_cp = get_ikc(ik_vp,ik_cm);
        mu_cp = mu_vp;

        kernel_12(ik_c_idx,nk_relev-1-ik_vp_idx) -= get_direct_interaction();
      }
    }

    kernel_11 += kernel_11.t();
    kernel_12 += kernel_12.t();
    kernel_exchange += kernel_exchange.t();
    for (int ik_c_idx=0; ik_c_idx<nk_relev; ik_c_idx++)
    {
      kernel_11(ik_c_idx,ik_c_idx) /= std::complex<double>(2,0);
      kernel_12(ik_c_idx,ik_c_idx) /= std::complex<double>(2,0);
      kernel_exchange(ik_c_idx,ik_c_idx) /= std::complex<double>(2,0);
    }

    arma::eig_sym(energy,psi,kernel_11-kernel_12);
    ex_energy_A1.row(ik_cm_idx) = energy.t();
    ex_psi_A1.slice(ik_cm_idx).head_rows(nk_relev) = (+1/std::sqrt(2.))*psi;
    ex_psi_A1.slice(ik_cm_idx).tail_rows(nk_relev) = (-1/std::sqrt(2.))*psi;

    // energy = arma::eig_sym(kernel_11+kernel_12);
    arma::eig_sym(energy,psi,kernel_11+kernel_12);
    ex_energy_A2_triplet.row(ik_cm_idx) = energy.t();
    ex_psi_A2_triplet.slice(ik_cm_idx).head_rows(nk_relev) = (+1/std::sqrt(2.))*psi;
    ex_psi_A2_triplet.slice(ik_cm_idx).tail_rows(nk_relev) = (+1/std::sqrt(2.))*psi;

    // energy = arma::eig_sym(kernel_11+kernel_12+std::complex<double>(2,0)*kernel_exchange);
    arma::eig_sym(energy,psi,kernel_11+kernel_12+std::complex<double>(2,0)*kernel_exchange);
    ex_energy_A2_singlet.row(ik_cm_idx) = energy.t();
    ex_psi_A2_singlet.slice(ik_cm_idx).head_rows(nk_relev) = (+1/std::sqrt(2.))*psi;
    ex_psi_A2_singlet.slice(ik_cm_idx).tail_rows(nk_relev) = (+1/std::sqrt(2.))*psi;

    // save the index of kc and kv states from i_valley_1
    for (int ik_c_idx=0; ik_c_idx<nk_relev; ik_c_idx++)
    {
      ik_c = _relev_ik_range[i_valley_1][ik_c_idx][0];
      mu_c = _relev_ik_range[i_valley_1][ik_c_idx][1];
      ik_v = get_ikv(ik_c,ik_cm);
      mu_v = mu_c;

      ik_idx(0,ik_c_idx,ik_cm_idx) = ik_c-elec_struct.ik_range[0];
      ik_idx(1,ik_c_idx,ik_cm_idx) = mu_c-elec_struct.mu_range[0];
      ik_idx(2,ik_c_idx,ik_cm_idx) = ik_v-elec_struct.ik_range[0];
      ik_idx(3,ik_c_idx,ik_cm_idx) = mu_v-elec_struct.mu_range[0];
    }

    // save the index of kc and kv states from i_valley_2
    for (int ik_v_idx=0; ik_v_idx<nk_relev; ik_v_idx++)
    {
      ik_v = _relev_ik_range[i_valley_2][ik_v_idx][0];
      mu_v = _relev_ik_range[i_valley_2][ik_v_idx][1];
      ik_c = get_ikc(ik_v,ik_cm);
      mu_c = mu_v;

      ik_idx(0,nk_c-1-ik_v_idx,ik_cm_idx) = ik_c-elec_struct.ik_range[0];
      ik_idx(1,nk_c-1-ik_v_idx,ik_cm_idx) = mu_c-elec_struct.mu_range[0];
      ik_idx(2,nk_c-1-ik_v_idx,ik_cm_idx) = ik_v-elec_struct.ik_range[0];
      ik_idx(3,nk_c-1-ik_v_idx,ik_cm_idx) = mu_v-elec_struct.mu_range[0];
    }

  }

  std::cout << "\n...calculated exciton dispersion\n";

  std::cout << "saved exciton dispersion: A2 singlet\n";
  std::string filename = _directory.path()/"ex_energy_A2_singlet.dat";
  ex_energy_A2_singlet.save(filename, arma::arma_ascii);

  std::cout << "saved exciton dispersion: A2 triplet\n";
  filename = _directory.path()/"ex_energy_A2_triplet.dat";
  ex_energy_A2_triplet.save(filename, arma::arma_ascii);

  std::cout << "saved exciton dispersion: A1\n";
  filename = _directory.path()/"ex_energy_A1.dat";
  ex_energy_A1.save(filename, arma::arma_ascii);

  std::cout << "saved k_vector for center of mass\n";
  filename = _directory.path()/"exciton_k_cm_vec.dat";
  k_cm_vec.save(filename, arma::arma_ascii);

  // prepare the values that are to be returned
  std::vector<exciton_struct> excitons(3,exciton_struct(this));

  excitons[0].name = "A1 exciton";
  excitons[0].energy = ex_energy_A1;
  excitons[0].spin = 0;
  excitons[0].mu_cm = 0;
  excitons[0].n_principal = nk_relev;
  excitons[0].nk_c = nk_c;
  excitons[0].nk_cm = nk_cm;
  excitons[0].psi = ex_psi_A1;
  excitons[0].ik_idx = ik_idx;
  excitons[0].ik_cm_range = ik_cm_range;

  excitons[1].name = "A2 triplet exciton";
  excitons[1].energy = ex_energy_A2_triplet;
  excitons[1].spin = 1;
  excitons[1].mu_cm = 0;
  excitons[1].n_principal = nk_relev;
  excitons[1].nk_c = nk_c;
  excitons[1].nk_cm = nk_cm;
  excitons[1].psi = ex_psi_A2_triplet;
  excitons[1].ik_idx = ik_idx;
  excitons[1].ik_cm_range = ik_cm_range;

  excitons[2].name = "A2 singlet exciton";
  excitons[2].energy = ex_energy_A2_singlet;
  excitons[2].spin = 0;
  excitons[2].mu_cm = 0;
  excitons[2].n_principal = nk_relev;
  excitons[2].nk_c = nk_c;
  excitons[2].nk_cm = nk_cm;
  excitons[2].psi = ex_psi_A2_singlet;
  excitons[2].ik_idx = ik_idx;
  excitons[2].ik_cm_range = ik_cm_range;

  return excitons;
}

// call this to do all the calculations at once
void cnt::calculate_exciton_dispersion()
{
  get_parameters();
  get_atom_coordinates();

  // calculate K2-extended representation of electron energy
  std::array<int,2> ik_range_K2 = {0,_Nu/_Q*_nk_K1};
  std::array<int,2> mu_range_K2 = {0,_Q};
  _elec_K2 = electron_energy(ik_range_K2, mu_range_K2, "K2_extended");

  // find valleys and select a range of relevant iks in the focus valleys
  find_valleys(_elec_K2);
  find_relev_ik_range(1.*constants::eV, _elec_K2);

  // calculate vq, and dielectric function for a sufficiently large range of mu and ik.
  std::array<int,2> iq_range = {-(_elec_K2.ik_range[1]-1),_elec_K2.ik_range[1]};
  std::array<int,2> mu_range = {-(_elec_K2.mu_range[1]-1),_elec_K2.mu_range[1]};
  _vq = calculate_vq(iq_range, mu_range, _number_of_cnt_unit_cells);
  _PI = calculate_polarization(iq_range, mu_range, _elec_K2);
  _eps = calculate_dielectric(iq_range, mu_range);

  // calculate exciton dispersions using the information calculated above
  std::array<int,2> ik_cm_range = {-int(_relev_ik_range[0].size()), int(_relev_ik_range[0].size())};
  _excitons = calculate_A_excitons(ik_cm_range, _elec_K2);

}