//rego: Automatic time series forecasting and missing value imputation.
//
//Copyright (C) Davide Altomare and David Loris <https://channelattribution.io>
//
//This source code is licensed under the MIT license found in the
//LICENSE file in the root directory of this source tree.

#define language_cpp
//#define language_python
//#define language_R

#include <iostream>
#include <vector>
#include <set>
#include <math.h>
#include <time.h>
#include <stdio.h>
#include <sstream>
#include <list>
#include <string>
#include <random>
#include <numeric>
#include <time.h>
#include <thread>
#include <map>
#include <algorithm>
#include <list>
#include <limits>
#include <functional>
#include <ctime>

#define OPTIM_ENABLE_ARMA_WRAPPERS
#ifdef language_py
  #ifdef _WIN32
    #define ARMA_DONT_USE_LAPACK
    #define ARMA_DONT_USE_BLAS
  #endif
#endif
#define ARMA_USE_CXX11
#define ARMA_64BIT_WORD
#define ARMA_DONT_PRINT_ERRORS

#include <armadillo>
#include <optim.hpp>

#ifdef language_R
 #define __GXX_EXPERIMENTAL_CXX0X__ 1

 #include <Rcpp.h>
 //#include <RcppArmadillo.h>
 //#define USE_RCPP_ARMADILLO

 #ifndef BEGIN_RCPP
 #define BEGIN_RCPP
 #endif

 #ifndef END_RCPP
 #define END_RCPP
 #endif

 using namespace Rcpp;
#endif

#ifdef language_py
 #include <Python.h>
#endif

using namespace std;
using namespace arma;

#define uli unsigned long int
using svec1=vector<double>;
using svec2=vector< vector <double> >;
using svec3=vector < vector< vector <double> > >;
using svec4=vector < vector < vector< vector <double> > > >;

//------------------------------------------------------------------------------------------------------------------------
//GENERAL FUNCTIONS
//------------------------------------------------------------------------------------------------------------------------

double lfactorial(uli n)
{

 double x;

 x=lgamma(n+1);

 return(x);

} //end function

double lchoose(uli n, uli k)
{

  double x;

  x=lgamma(n+1)-lgamma(k+1)-lgamma(n-k+1);

  return(x);

} //end function

mat sub_mat(mat* M, vec vr, vec vc)
{
  uli r, c, lvr, lvc;
  lvr=vr.n_elem;
  lvc=vc.n_elem;
  mat Q(lvr,lvc);

  for(c=0; c<lvc; c++){
   for(r=0; r<lvr; r++){
    Q(r,c)=(*M)(vr(r),vc(c));
   }
  }

  return Q;

} //end function

mat pow_vec(vec* v, vec* w)
{

   uli k, lv;
   vec vv;

   lv=(*v).n_elem;
   vv=zeros<vec>(lv);

   for(k=0; k<lv; k++){
    vv(k)=pow((*v)(k),(*w)(k));
   }

 return vv;

}

mat Cholesky(mat* M)
{
    double n=(*M).n_rows;
    mat lower(n,n);

    // Decomposing a matrix into Lower Triangular
    for (uli i = 0; i < n; i++) {
        for (uli j = 0; j <= i; j++) {
            uli sum = 0;

            if (j == i) // summation for diagonals
            {
                for (uli k = 0; k < j; k++){
                    sum += pow(lower(j,k), 2);
                    lower(j,j) = sqrt((*M)(j,j) - sum);
                }
            } else {

                // Evaluating L(i, j) using L(j, j)
                for (uli k = 0; k < j; k++){
                    sum += (lower(i,k) * lower(j,k));
                    lower(i,j) = ((*M)(i,j) - sum) / lower(j,j);
                }
            }
        }
    }

    return(lower);

}

vec forward_sub(mat* L, vec* b)
{
    /*x = forward_sub(L, b) is the solution to L x = b
       L must be a lower-triangular matrix
       b must be a vector of the same leading dimension as L
    */

    double n = (*L).n_cols;
    vec x = zeros<vec>(n);
    double tmp;
    for(double i=0; i<n; ++i){
        tmp = (*b)(i);
         for(double j=0; j<i; ++j){
          tmp -= (*L)(i,j) * x(j);
         }

        x(i) = tmp / (*L)(i,i);
    }
    return(x);
}

vec back_sub(mat* U, vec* b)
{
    /*x = back_sub(U, b) is the solution to U x = b
       U must be an upper-triangular matrix
       b must be a vector of the same leading dimension as U
    */

    double n = (*U).n_cols; //it must be double
    vec x = zeros<vec>(n);
    double tmp;
    for(double i=n-1; i>=0; i--){
      tmp = (*b)(i);
       for(uli j=i; j<n; ++j){
        tmp -= (*U)(i,j) * x(j);
        x(i) = tmp / (*U)(i,i);
       }
    }

    return(x);

}

vec solve0(mat* A, vec* b){

  mat L=Cholesky(A);
  mat Lt=trans(L);

  vec vy=forward_sub(&L,b);
  vec vx=back_sub(&Lt, &vy);

  return(vx);

}

vec solve_linear(mat* A, vec* b){

 #ifdef ARMA_DONT_USE_LAPACK
  return(solve0(A,b));
 #else
  return(solve(*A,*b));
 #endif

}

//------------------------------------------------------------------------------------------------------------------------
//FUNCTIONS FOR STORING MODELS IN A BINARY TREE
//------------------------------------------------------------------------------------------------------------------------

//this function add a model "M" to "tree"

field<mat> add_to_tree(vec* M, double lM, uli nM, mat* tree, double ltree)
{

 uli j,k;

 field<mat> Res(2,1);

 if(nM==0){

  for(j=0; j<=lM; j++){

   if((*M)(j)==1){(*tree)(j,0)=j+1;}
   else{(*tree)(j,1)=j+1;}

  }

  ltree=lM;
  (*tree).row(ltree)=(*tree).row(ltree)*0+nM;

 }

 uli z,h,iM=0;

 if(nM>0){ //if1

  z=0;
  h=ltree+1;

  for(j=0; j<=lM; j++){ //for1

   iM=1-(*M)(j);

   if(!is_finite((*tree)(z,iM)) && (j<=lM) ){ //if2

    (*tree)(z,iM)=h;

    for(k=(j+1); k<=lM; k++){ //for2

      if((*M)(k)==1){(*tree)(h,0)=h+1;} else{(*tree)(h,1)=h+1;}

      h=h+1;

    } //end for2

    iM=1-(*M)(lM);
    ltree=h-1;
    break;

   } //end if2

   if(j==lM){(*tree)(z,iM)=nM; ltree=ltree+1; break;}

   if((*tree)(z,iM)>=0){z=(*tree)(z,iM);}

  } //end for1

  (*tree)(ltree,iM)=(*tree)(ltree,iM)*0+nM;

 } //end if1

 Res(0,0)=(*tree);
 Res(1,0)=ltree;

 return Res;

} //end function

//this function returns the possible movements from a model "M", given all the previous models visited and stored in "tree"

vec mov_tree(mat* tree, vec* M, uli lM, vec* vlM, uli max_lM)
{

 uli q, k, z, h, iM2;
 double sumM;
 vec mov(lM+1);
 uvec imov, umov;
 vec mov2, M2;

 mov.fill(-1);
 sumM=sum(*M);
 q=0;

 for(k=0; k<=lM; k++){ //for1

  M2=(*M);
  M2(k)=1-(*M)(k);
  z=0;

  for(h=0; h<=lM; h++){ //for2

   iM2=1-M2(h);
   if(!is_finite((*tree)(z,iM2))){mov(q)=k; q=q+1; break;} else{z=(*tree)(z,iM2);}

    } //end for2

 } //end for1

 imov=find(mov>-1);

 if(!imov.is_empty()){

  mov=mov.elem(imov);
  umov=conv_to<uvec>::from(mov);

  if(sumM>=max_lM){

   mov2=zeros<vec>(lM+1);
   mov2.elem(umov)=ones<vec>(mov.n_elem);
   mov=(mov2%(*M))%(*vlM);
   imov=find(mov>0);
   if(!imov.is_empty()){mov=mov.elem(imov); mov=mov-1;} else{mov=datum::nan;}
  }

 } else {mov=datum::nan;}

 return mov;

} //end function

//------------------------------------------------------------------------------------------------------------------------
//FUNCTIONS FOR BAYESIAN STOCHASTIC SEARCH
//------------------------------------------------------------------------------------------------------------------------

double log_H_h_i(double mu, double sigma, double h, double i)
{

 double x;

 x=lfactorial(2*h)+i*log(sigma)-lfactorial(i)+(2*h-2*i)*log(abs(mu))-lfactorial(2*h-2*i);

 return x;

} //end function

double log_FBF_Ga_Gb(vec* G_a, vec* G_b, uli edge, mat* edges, mat* YtY, uli add, double n, double h)
{

  uli e1, e2, iwi;
  double i, p, b, S2, mu, sigma, logS2, ilogS2, logHhi, ilog4, log_num1, log_den1, log_w_1, log_num0i0, log_den0i0, log_w_0, log_FBF_unpasso;
  vec V1, V2, G1, V11, pa1, pa0, betah, vv(1), z1;
  uvec iw, ipa1;
  mat e, yty, XtX, invXtX;
  vec Xty;

  e=(*edges).row(edge);
  e1=e(0);
  e2=e(1);

  V1=(*edges).col(0);
  V2=(*edges).col(1);

  if(add==1){G1=(*G_a);}else{G1=(*G_b);}

  V11=(V1+1)%G1;
  iw=find(V2==e2); pa1=V11.elem(iw);
  iw=find(pa1>0); pa1=pa1.elem(iw); pa1=pa1-1;

  iw=find(pa1!=e1); if(!iw.is_empty()){pa0=pa1.elem(iw);}else{pa0=datum::nan;}

  p=pa1.n_elem;
  b=(p+2*h+1)/n;

  yty=(*YtY)(e2,e2);

  // //calcolo w1

  vv(0)=e2; Xty=conv_to<vec>::from(sub_mat(YtY,pa1,vv));
  XtX=sub_mat(YtY,pa1,pa1);
  betah=solve_linear(&XtX,&Xty);

  S2=conv_to<double>::from(yty-(trans(Xty)*betah));

  iw=find(pa1==e1); mu=conv_to<double>::from(betah.elem(iw));
  iwi=conv_to<uli>::from(iw);
  z1=zeros<vec>(pa1.n_elem);
  z1(iwi)=1;
  z1=solve_linear(&XtX,&z1);

  sigma=conv_to<double>::from(z1.elem(iw));

  if(S2>0){
   log_w_1=(-n*(1-b)/2)*log(datum::pi*b*S2);
   logS2=log(S2);
   log_num1=-datum::inf;
   log_den1=-datum::inf;

   for(i=0; i<=h; i++){

    ilogS2=i*logS2;
    logHhi=log_H_h_i(mu,sigma,h,i);
    ilog4=-i*log(4);

    log_num1=log_add(log_num1, (ilog4+logHhi+lgamma((n-p-2*i)/2)+ilogS2));
    log_den1=log_add(log_den1, (ilog4+logHhi+lgamma((n*b-p-2*i)/2)+ilogS2));

   }

   log_w_1=log_w_1+log_num1-log_den1;
  }else{
   log_w_1=datum::inf;
  }

  //calcolo w0

  if(!pa0.is_finite()){p=0;}else{p=pa0.n_elem;}

  log_num0i0=lgamma((n-p)/2);
  log_den0i0=lgamma((n*b-p)/2);

  if(p==0){S2=conv_to<double>::from(yty);}
  else{
   vv(0)=e2; Xty=conv_to<vec>::from(sub_mat(YtY,pa0,vv));
   XtX=sub_mat(YtY,pa0,pa0);
   betah=solve_linear(&XtX,&Xty);

   S2=conv_to<double>::from(yty-(trans(Xty)*betah));
  }

  if(S2>0){
   log_w_0=(-(n*(1-b)/2))*log(datum::pi*b*S2)+log_num0i0-log_den0i0;
  }else{
   log_w_0=datum::inf;
  }

  //calcolo FBF

  if(add==1){log_FBF_unpasso=log_w_1-log_w_0;}
  else{log_FBF_unpasso=log_w_0-log_w_1;}

  if(!is_finite(log_FBF_unpasso)){
   log_FBF_unpasso=0;
  }

  return log_FBF_unpasso;

} // end function

field<mat> FBF_heart(double nt, mat* YtY, vec* vG_base, double lcv, vec* vlcv, mat* edges, double n_tot_mod, double C, double maxne, double h, bool univariate)
{

   uli t, add, edge, imq, limodR, s;
   double ltree, lM, sum_log_FBF, log_FBF_G, log_pi_G, log_num_MP_G, sum_log_RSMP, n_mod_r, log_FBF_t, log_FBF1;
   vec M_log_FBF, log_num_MP, log_sume, G, imod_R, M_log_RSMP, pRSMP, mov, vlM, qh, G_t, M_q, M_P;
   uvec iw;
   mat tree, SM, M_G;
   field<mat> treeRes, Res(4,1);
   uword i_n_mod_r, imaxe;

   M_G=zeros<mat>(lcv,n_tot_mod);
   M_P=zeros<vec>(n_tot_mod);
   M_log_FBF=zeros<vec>(n_tot_mod);
   log_num_MP=zeros<vec>(n_tot_mod);
   M_q=zeros<vec>(lcv);
   tree=zeros<mat>(n_tot_mod*lcv,2); tree.fill(datum::nan);
   ltree=datum::nan;
   lM=lcv-1;

   sum_log_FBF=-datum::inf;
   log_sume=zeros<vec>(lcv); log_sume.fill(-datum::inf);

   M_log_RSMP=zeros<vec>(n_tot_mod);
   sum_log_RSMP=-datum::inf;
   imod_R=zeros<vec>(n_tot_mod);

   lM=lcv-1;

   Col<uli> vexit(1);

   for(t=0; t<lcv; t++){ //for1

    G=(*vG_base);
    G(t)=1-(*vG_base)(t);
    add=G(t);
    edge=t;

    log_FBF_G=log_FBF_Ga_Gb(&G,vG_base,edge,edges,YtY,add,nt,h);

    M_G.col(t)=G;

    treeRes=add_to_tree(&G,lM,t,&tree,ltree);
    tree=treeRes(0,0);
    ltree=conv_to<double>::from(treeRes(1,0));

    M_log_FBF(t)=log_FBF_G;
    log_pi_G=-log(lcv+1)-lchoose(lcv,sum(G));
    log_num_MP_G=log_FBF_G+log_pi_G;
    log_num_MP(t)=log_num_MP_G;

    sum_log_FBF=log_add(sum_log_FBF, log_num_MP_G);

    for(imq=0; imq<lcv; imq++){
     if(G(imq)==1){log_sume(imq)=log_add(log_sume(imq), log_num_MP_G);}
    }

    M_q=exp(log_sume-sum_log_FBF);

    M_log_RSMP(t)=log_num_MP_G;
    sum_log_RSMP=log_add(sum_log_RSMP, log_num_MP_G);

    imod_R(t)=t;

   } //end for1

   vec vtmp1,vtmp2;

   if(univariate==0){

    limodR=t-1;
    s=lcv;

    while(t<n_tot_mod){ //while1

     pRSMP=exp(M_log_RSMP.subvec(0,limodR)-sum_log_RSMP);
	   pRSMP.max(i_n_mod_r);

     n_mod_r=imod_R(i_n_mod_r);

     G=M_G.col(n_mod_r);
     G_t=G;
     log_FBF_t=M_log_FBF(n_mod_r);

     vlM=(*vlcv)+1;
     mov=mov_tree(&tree,&G,lM,&vlM,maxne);

     if(!is_finite(mov)){ //if1

      imod_R(i_n_mod_r)=-1;
      iw=find(imod_R>-1); imod_R=imod_R.elem(iw);
      M_log_RSMP=M_log_RSMP.elem(iw);

      limodR=limodR-1;
      t=t-1;

     } else{

       vtmp1=(M_q+C)/(1-M_q+C);
       vtmp2=(2*(1-G))-1;
       qh=pow_vec(&vtmp1, &vtmp2);
       qh=qh.elem(conv_to<uvec>::from(mov));

       if(mov.n_elem==1){ //if2

        imod_R(i_n_mod_r)=-1;
        iw=find(imod_R>-1); imod_R=imod_R.elem(iw);
        M_log_RSMP=M_log_RSMP.elem(iw);

         limodR=limodR-1;
         edge=mov(0);

        } else{

           qh.max(imaxe);
           edge=mov(imaxe);

        } // end if2

        G(edge)=1-G(edge);
        add=G(edge);

        log_FBF1=log_FBF_Ga_Gb(&G,&G_t,edge,edges,YtY,add,nt,h);
        log_FBF_G=log_FBF1+log_FBF_t;

        M_G.col(t)=G;

        treeRes=add_to_tree(&G,lM,t,&tree,ltree);
        tree=treeRes(0,0);
        ltree=conv_to<double>::from(treeRes(1,0));

        M_log_FBF(t)=log_FBF_G;
        log_pi_G=-log(lcv+1)-lchoose(lcv,sum(G));
        log_num_MP_G=log_FBF_G+log_pi_G;
        log_num_MP(t)=log_num_MP_G;

        sum_log_FBF=log_add(sum_log_FBF, log_num_MP_G);

        for(imq=0; imq<lcv; imq++){
         if(G(imq)==1){log_sume(imq)=log_add(log_sume(imq), log_num_MP_G);}
        }
        M_q=exp(log_sume-sum_log_FBF);

        limodR=limodR+1;
        imod_R(limodR)=t;
        M_log_RSMP(limodR)=log_num_MP_G;

     } //end if1

     t=t+1;
     s=s+1;

    } //end while1

  }// end if univariate

  t=t-1;
  s=s-1;

  M_P.subvec(0,t)=exp(log_num_MP.subvec(0,t)-sum_log_FBF);
  if(max(M_P.subvec(0,t))>0){
   M_P.subvec(0,t)=M_P.subvec(0,t)/sum(M_P.subvec(0,t));
  }else{
   M_P.subvec(0,t)=zeros<vec>(t+1);
  }

  M_G=M_G.submat(0,0,lcv-1,t);

  Res(0,0)=M_q;
  Res(1,0)=M_G;
  Res(2,0)=M_P;
  Res(3,0)=M_log_FBF.subvec(0,t);

  return Res;

} // end function

field<mat> FBF_RS(Mat<double>* Corr_c, double nobs_c, Col<double>* G_base_c, double h_c, double C_c, double n_tot_mod_c, double n_hpp_c, bool univariate)
{

 uli neq, rr;
 double maxne, Mlogbin_sum, lcv, rrmax, q;
 vec V1, V2, vlcv, vG_base, M_q, M_P, iM_P, M_P2;
 mat edges, G_fin, M_G, M_G2;
 mat YtY;
 field<mat> heartRes;

 q=(*Corr_c).n_cols;

 maxne=nobs_c-2*h_c-2;

 neq=1;

 V1=linspace<vec>(1,q-1,q-1);
 V2=zeros<vec>(q-neq);

 edges=join_rows(V1,V2);

 lcv=V1.n_elem;
 vlcv=linspace<vec>(0,lcv-1,lcv);

 vG_base=flipud(*G_base_c);

 rrmax=std::min(maxne,lcv);
 Mlogbin_sum=0;

 for(rr=1; rr<=rrmax; rr++){
  Mlogbin_sum=log_add(Mlogbin_sum,lchoose(lcv,rr));
 }

 n_tot_mod_c=std::min(Mlogbin_sum,log(n_tot_mod_c));
 n_tot_mod_c=round(exp(n_tot_mod_c));

 YtY=(*Corr_c)*nobs_c;
 heartRes=FBF_heart(nobs_c, &YtY, &vG_base, lcv, &vlcv, &edges, n_tot_mod_c, C_c, maxne, h_c, univariate);

 return(heartRes);

} // end function

void printA(string msg)
{

 #ifdef language_cpp
  cout << msg << endl;
 #endif

 #ifdef language_py
  msg="print('" + msg + "')";
  PyRun_SimpleString(msg.c_str());
 #endif

 #ifdef language_R
  Rcout << msg << endl;
 #endif

}

void xit(){
  vector<int> v;
  printA("execution intentionally interrupted");
  printA(to_string(v[0]));
}

template <typename T>
void printV(vector<T> vec,string name){
  printA(name+": ");
  for (auto i: vec){
    printA(to_string(i));
  }
  printA("");
}

template<typename T>
string vec_to_string (T v, uli len){

    // string type0=typeid(v(0)).name();
    // bool flg_string=0;
    // if(type0.find("string")!=string::npos){
    //   flg_string=1;
    // }

    string res;
    if(len>0){
     res=to_string(v(0));
     for(uli t=1; t<len; ++t){
       res=res+","+to_string(v(t));
     }
    }else{
     res="empty";
    }
    return(res);
}

template <typename T>
string NumberToString ( T Number )
{
   ostringstream ss;
   ss << Number;
   return ss.str();
}

vector<long int> split_string(const string &s, uli order) {

	char delim=' ';
	vector<long int> result(order,-1);
    stringstream ss (s);
    string item;

	uli h=0;
    while (getline (ss, item, delim)) {
		result[h]=stoi(item);
		h=h+1;
    }

    return result;
}

template<typename T>
uli find_consecutive_finite(T* x, uli col){

   uli max_num=0;
   uli num=0;
   for(uli j=0; j<(*x).n_rows; ++j){
    if(isfinite((*x)(j,col))==1){
     num=num+1;
     if(num>max_num){
       max_num=num;
     }
    }else{
     num=0;
    }
   }

  return(max_num);

}

template<typename T>
uli find_consecutive_nan(T* x, uli col){

   uli max_num=0;
   uli num=0;
   for(uli j=0; j<(*x).n_rows; ++j){
    if(isfinite((*x)(j,col))==0){
     num=num+1;
     if(num>max_num){
       max_num=num;
     }
    }else{
     num=0;
    }
   }

  return(max_num);

}

// template<typename T>
// void save_mat(T* obj,string dir, string name, string type){
//   struct timeval time_now{};
//   gettimeofday(&time_now, nullptr);
//   time_t msecs_time = (time_now.tv_sec * 1000) + (time_now.tv_usec / 1000);
//   (*obj).save(dir + name + "_" + to_string(msecs_time) + "." + type, csv_ascii);
// }

string f_print_perc(double num){

 string res;
 if(num>=1){
  res=to_string((double)(floor(num*10000)/100)).substr(0,6);
 }else if(num>=0.1){
  res=to_string((double)(floor(num*10000)/100)).substr(0,5);
 }else{
  res=to_string((double)(floor(num*10000)/100)).substr(0,4);
 }
 return(res);
}

vector<string> subvector(vector<string> v, Col<uli> idx){
 vector<string> sub_v;
 for(uli j=0; j<idx.n_rows; ++j){
  sub_v.push_back(v[idx(j)]);
 }
 return(sub_v);
}

int rand11(){
 std::random_device rd;
 std::mt19937 gen(rd());
 std::uniform_int_distribution<> distrib(0,  RAND_MAX);
 return(distrib(gen));
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//FUNCTIONS FOR VARIABLE SELECTION AND PREDICTIONS
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

struct str_input
{

  mat MY0;
  mat MY;
  mat corY;
  uli corY_nr;
  uli corY_nc;

  // mat* prt_MY0(){
  //  return(&MY0);
  // }

  // mat* prt_MY(){
  //  return(&MY);
  // }

};

str_input data_preparation(mat* Y, Col<uli> vretard)
{

  //varibile target in differenze ritardata

  mat MY;
  if(vretard.n_rows>0){
    MY.resize((*Y).n_rows,vretard.n_rows);

    for(uli k=0; k<vretard.n_rows; ++k){
     MY.col(k)=shift((*Y).col(0),vretard(k));
     if(vretard(k)>0){
       MY.submat(0,k,vretard(k)-1,k)+=datum::nan;
     }
    }
    //join
    MY=join_rows((*Y),MY);
  }else{
   MY=(*Y);
  }

  //output

  str_input tab_input;

  tab_input.MY0=MY;

  MY=MY.rows(find_finite(sum(MY,1)));

  tab_input.MY=MY;

  mat corY=cor(MY);

  tab_input.corY=corY;
  tab_input.corY_nr=MY.n_rows;
  tab_input.corY_nc=MY.n_cols;

  return(tab_input);

}

struct str_output_reg
{
    Col<double> vactual;
    Col<double> vfitted0;
    Col<double> vresid0;
    Col<double> vfitted;
    Col<double> vresid;
    Col<double> vbeta;
    double TSS;
    double RSS;
    double L;
    double L_adj;
};

double f_loss_function(const vec& vals_inp, vec* grad_out, void* opt_data)
{

  str_input* data = reinterpret_cast<str_input*>(opt_data);
  mat MY=data->MY;

  double err=0;
  double fit=0;

  uli n=MY.n_rows;

  for(uli t=0; t<n; ++t){
    fit=0;
    for(uli k=0; k<vals_inp.n_rows; ++k){
      fit=fit+vals_inp(k)*MY(t,k+1);
    }
    err=err+(abs(MY(t,0)-fit)/n);
  }

  return err;

}

str_output_reg reg(str_input* tab_input, string loss_function)
{

    vec vbeta;

    mat X=(*tab_input).MY.cols(1,(*tab_input).MY.n_cols-1);
    vec Y=(*tab_input).MY.col(0);
    mat XtX=trans(X)*X;
    vec Xty=trans(X)*Y;
    vbeta=solve_linear(&XtX,&Xty);

    if(loss_function=="MAE"){
     optim::de(vbeta,f_loss_function,tab_input);
    }
    //vec vbeta=inv_sympd(XtX)*Xty;

    vec vactual=Y;
    vec vfitted=X*vbeta;
    vec vresid=vactual-vfitted;

    vec vactual0=(*tab_input).MY0.col(0);
    vec vfitted0=(*tab_input).MY0.cols(1,(*tab_input).MY0.n_cols-1)*vbeta;
    vec vresid0=vactual0-vfitted0;

    str_output_reg Res;

    Res.vbeta=vbeta;
    Res.vactual=Y;
    Res.vfitted0=vfitted0;
    Res.vresid0=vresid0;
    Res.vfitted=vfitted;
    Res.vresid=vresid;

    return(Res);
}

struct str_out_uni_select
{
 Col<uli> vars_x_idx;
 Col<uli> vars_ar_idx;
 vec logs_FBF_x;
 vec logs_FBF_ar;
 uli odiff;

};

str_out_uni_select model_univariate_selection(mat* Y, double from_lag, double max_lag, bool flg_diff){

  str_out_uni_select str_out;
  Col<uli> vretard;
  uli odiff=0;

  if((max_lag!=0) || (((*Y).n_cols-1)>0)){

    double cons_rows;
    uli max_lag0;

    cons_rows=(double) find_consecutive_finite(Y,0);

    // if(log(cons_rows/10)>0){
    //  max_lag0=(uli) min(((cons_rows/2)+1),cons_rows/log(cons_rows/10));
    // }else{
    //  max_lag0=1;
    // }
    if((cons_rows-8)>0){
     max_lag0=cons_rows-8;
    }else{
     max_lag0=1;
    }

    if(max_lag==-1){
     max_lag=(double) max_lag0;
    }else if(max_lag>0){
     max_lag=(double) min(max_lag,(double)max_lag0);
    }

    Col<uli> ids_vars_x;
    vec log_FBF_x;
    Col<uli> ids_vars_ar;
    vec log_FBF_ar;

    if(from_lag<=max_lag){

      if(max_lag>0){
       vretard=linspace<Col<uli>>((uli)from_lag,(uli)max_lag,(uli)(max_lag-from_lag+1));
      }

      double nx=(*Y).n_cols-1;
      double nretard=vretard.n_rows;
      double nvars=nx+nretard;

      double h_c=1;
      if(nvars>50){
       h_c=2;
      }
      double n_tot_mod_c=nvars*100;
      double C_c=0.01;
      double threshold;

      str_input tab_input;
      field<mat> res;

      uli nr,nc,nvar_max;
      Col<double> G_base_c;
      bool univariate=1;
      Col<double> M_q;

      vec v_log_FBF;
      mat M_log_FBF(1,3),M_log_FBF_x,M_log_FBF_ar;

      uvec idfinite=find_finite((*Y).col(0));
      nr=idfinite.n_rows;

      double qt;
      if(nvars<10){
        qt=0.90;
      }else{
        qt=0.99;
      }

      vec x;
      vec G_a(1);G_a(0)=1;
      vec G_b(1);G_b(0)=0;
      uli edge=0;
      mat edges(1,2);edges(0,0)=0;edges(0,1)=1;
      uli add=1;
      double log_FBF;

      //estimate threshold

      uli nv1=(uli)((double)nx);
      uli nv=std::max((uli)200,nv1);

      vec y=(*Y).col(0);
      y=y.elem(idfinite);

      mat YtY(2,2);YtY(0,0)=1;YtY(1,1)=1;
      v_log_FBF.set_size(nv);

      //arma_rng::set_seed(1234567);
      mt19937 gen(1234567);
      normal_distribution<double> distribution(0.0,1.0);
      x.resize(nr);

      for(uli j=0; j<nv; ++j){

        //x = randu<vec>(nr,1);
        for(uli k=0; k<nr; ++k){
          x(k)=distribution(gen);
        }

        YtY(1,0)=as_scalar(cor(y,x));
        YtY(0,1)=YtY(1,0);

        v_log_FBF(j)=log_FBF_Ga_Gb(&G_a, &G_b, edge, &edges, &YtY, add, nr, h_c);

      }

      uli nth=(uli) (qt*v_log_FBF.size());
      v_log_FBF=sort(v_log_FBF);
      threshold=v_log_FBF(nth);

      mat MY((*Y).col(0).n_rows,2);
      MY.col(0)=(*Y).col(0);
      mat MY1;

      //select X variables

      if(nx>0){

        M_log_FBF_x.set_size(nx,3);

        for(uli j=0; j<nx; ++j){

          MY.col(1)=(*Y).col(j+1);
          MY1=MY.rows(find_finite(sum(MY,1)));
          nr=MY1.n_rows;
          YtY=cor(MY1);

          M_log_FBF_x(j,0)=0;
          M_log_FBF_x(j,1)=j+1;
          M_log_FBF_x(j,2)=log_FBF_Ga_Gb(&G_a, &G_b, edge, &edges, &YtY, add, nr, h_c);

        }

        M_log_FBF=join_cols(M_log_FBF,M_log_FBF_x);

      }else{

        //differentiation

        if(flg_diff==1){

          double fbf=datum::inf;
          mat Y0;

          while(((fbf-threshold)>0) & (odiff<2)){
            MY.col(0)=(*Y).col(0);
            MY.col(1)=linspace<vec>(1,(*Y).n_rows,(*Y).n_rows);
            MY1=MY.rows(find_finite(sum(MY,1)));
            nr=MY1.n_rows;
            YtY=cor(MY1);
            fbf=log_FBF_Ga_Gb(&G_a, &G_b, edge, &edges, &YtY, add, nr, h_c);

            if((fbf-threshold)>0){
              Y0=(*Y);
              (*Y).col(0)=shift((*Y).col(0),1);
              (*Y).submat(0,0,0,0)+=datum::nan;
              for(uli j=0; j<(*Y).n_rows; ++j){
               (*Y)(j,0)=Y0(j,0)-(*Y)(j,0);
              }
              odiff=odiff+1;
            }else{
              break;
            }

          }

        }

      }

      //select retard

      if(nretard>0){

        M_log_FBF_ar.set_size(nretard,3);

        for(uli j=0; j<nretard; ++j){

          MY.col(1)=shift((*Y).col(0),vretard(j));
          if(vretard(j)>0){
            MY.submat(0,1,vretard(j)-1,1)+=datum::nan;
          }

          MY1=MY.rows(find_finite(sum(MY,1)));
          nr=MY1.n_rows;
          YtY=cor(MY1);

          M_log_FBF_ar(j,0)=1;
          M_log_FBF_ar(j,1)=vretard(j);
          M_log_FBF_ar(j,2)=log_FBF_Ga_Gb(&G_a, &G_b, edge, &edges, &YtY, add, nr, h_c);

        }

        M_log_FBF=join_cols(M_log_FBF,M_log_FBF_ar);

      }

      M_log_FBF.shed_row(0);

      uvec ids=find(M_log_FBF.col(2)<=threshold);
      M_log_FBF.shed_rows(ids);

      if(M_log_FBF.n_rows>0){

        nvar_max=(uli)2*std::pow(idfinite.n_rows,0.25); //nb: only non missing target is considered

        if(M_log_FBF.n_rows>nvar_max){
          ids = sort_index(M_log_FBF.col(2),"descend");
          ids=ids.rows(0,nvar_max-1);
          ids=sort(ids);
          M_log_FBF=M_log_FBF.rows(ids);
        }

        uvec ids_x=find(M_log_FBF.col(0)==0);

        if(ids_x.size()>0){
          ids_vars_x=conv_to< Col<uli> >::from(M_log_FBF.col(1));
          ids_vars_x=ids_vars_x.elem(ids_x);
          log_FBF_x=M_log_FBF.col(2);
          log_FBF_x=log_FBF_x.elem(ids_x);
        }

        uvec ids_ar=find(M_log_FBF.col(0)==1);

        if(ids_ar.size()>0){
          ids_vars_ar=conv_to< Col<uli> >::from(M_log_FBF.col(1));
          ids_vars_ar=ids_vars_ar.elem(ids_ar);
          log_FBF_ar=M_log_FBF.col(2);
          log_FBF_ar=log_FBF_ar.elem(ids_ar);
        }

      }

    }

    str_out.vars_x_idx=ids_vars_x; //nb. first regressor has id=1
    str_out.vars_ar_idx=ids_vars_ar;
    str_out.logs_FBF_x=log_FBF_x;
    str_out.logs_FBF_ar=log_FBF_ar;
    str_out.odiff=odiff;

  }

  return(str_out);

}

field<vec> model_multivariate_selection(mat* Y, Col<uli>* ids_vars_x_uni, Col<uli>* ids_vars_ar_uni, string loss_function, bool flg_const, bool flg_arx){

    Col<uli> ids_vars_x,v_ar;
    vec vbeta_arx;
    vec vresid((*Y).n_rows,1);

    double nvars=(*Y).n_cols-1+(*ids_vars_ar_uni).n_rows;

    if(nvars>0){

      double threshold=0.5;
      double h_c=1;
      if(nvars>50){
       h_c=2;
      }
      double n_tot_mod_c=nvars*100;
      double C_c=0.01;
      double n_hpp_c=1;

      mat Y0;
      uvec u_ids_vars_x;
      if((*ids_vars_x_uni).n_rows>0){
       u_ids_vars_x=join_cols(zeros<uvec>(1),conv_to< uvec >::from(*ids_vars_x_uni));
       Y0=(*Y).cols(u_ids_vars_x);
      }else{
       Y0=(*Y).cols(0,0);
      }

      str_input tab_input=data_preparation(&Y0,(*ids_vars_ar_uni));

      uli nvar_max=(uli)std::pow(tab_input.MY.n_rows,0.25);

      uli nr,nc;
      Col<double> G_base_c;
      bool univariate=0;
      vec M_q;
      field<mat> res;
      uvec ids;
      vec rvals;

      uli len_ar=(*ids_vars_ar_uni).n_rows;
      vec ids_vars;

      mat M_G;
      vec M_P;

      if((len_ar>0) || (Y0.n_cols>1)){

	      nr=tab_input.corY_nr;
        nc=tab_input.corY_nc;

        h_c=1;
        if((nc-1)>50){
         h_c=2;
        }

        G_base_c=zeros<vec>(nc-1);

        res=FBF_RS(&tab_input.corY,nr,&G_base_c,h_c,C_c,n_tot_mod_c,n_hpp_c,univariate);

        M_q=res(0,0);

	      ids=find(M_q>=threshold);
	      if(ids.n_rows>nvar_max){
          ids = sort_index(M_q,"descend");
          ids=ids.rows(0,nvar_max-1);
          ids=sort(ids);
        }

        rvals=linspace<vec>(0,M_q.n_rows-1, M_q.n_rows).elem(ids);

        ids_vars=rvals-(Y0.n_cols-1);
        vec ids_vars_ar=ids_vars(find(ids_vars>=0));
        uvec fd_ids_vars=find(ids_vars<0);
        if(fd_ids_vars.n_cols>0){
         ids_vars_x=conv_to< Col<uli> >::from(rvals(fd_ids_vars));
        }

	      if(ids_vars.n_rows>0){
          if(len_ar>0){
            uvec u_ids_vars_ar=conv_to< uvec >::from(ids_vars_ar);
            v_ar=(*ids_vars_ar_uni).rows(u_ids_vars_ar);
          }

          uvec urvals=join_cols(zeros<uvec>(1),conv_to< uvec >::from(rvals+1));
          tab_input.MY0=tab_input.MY0.cols(urvals);
          tab_input.MY=tab_input.MY.cols(urvals);

          if((flg_const==1) & (flg_arx==1)){
           tab_input.MY0.resize(tab_input.MY0.n_rows,tab_input.MY0.n_cols+1);
           tab_input.MY0.col(tab_input.MY0.n_cols-1)=zeros<vec>(tab_input.MY0.n_rows)+1;

           tab_input.MY.resize(tab_input.MY.n_rows,tab_input.MY.n_cols+1);
           tab_input.MY.col(tab_input.MY.n_cols-1)=zeros<vec>(tab_input.MY.n_rows)+1;
          }

          str_output_reg tab_out_reg=reg(&tab_input, loss_function);
          vbeta_arx=tab_out_reg.vbeta;
          vresid=tab_out_reg.vresid0;
        }else{
	    	  vresid=Y0.col(0);
          double m0=as_scalar(mean(vresid.rows(find_finite(vresid))));
          for(uli t=0; t<vresid.n_rows; ++t){
            vresid(t)=vresid(t)-m0;
	        }
        }
      }else{

        vresid=Y0.col(0);
        double m0=as_scalar(mean(vresid.rows(find_finite(vresid))));
        for(uli t=0; t<vresid.n_rows; ++t){
         vresid(t)=vresid(t)-m0;
        }

      }

      if(ids_vars_x.size()>0){
       u_ids_vars_x=conv_to< uvec >::from(ids_vars_x);
       ids_vars_x=(*ids_vars_x_uni).elem(u_ids_vars_x);
      }

    }else{

     vresid=(*Y).col(0);

    }

    uli nmodels=1;

    field<vec> str_out;
    str_out.set_size(nmodels,4);
    str_out(0,0)=conv_to< vec >::from(ids_vars_x);
    str_out(0,1)=conv_to< vec >::from(v_ar);
    str_out(0,2)=vbeta_arx;
    str_out(0,3)=vresid;

    return(str_out);

}

map<string,double> performances(vec vactual, vec vfitted, uli nvars){

  vec vresid=vactual-vfitted;
  uvec non_missing=find_finite(vresid);
  vresid=vresid.elem(non_missing);
  //double RSS_=as_scalar(sum(pow(vresid,2)));

  vactual=vactual.elem(non_missing);
  double m0=mean(vactual);
  // Col<double> vTSS=vactual;
  // for(uli i=0; i<vTSS.n_rows; ++i){
  //  vTSS(i)=vTSS(i)-m0;
  // }
  // vTSS=pow(vTSS,2);
  // double TSS=sum(vTSS);

  // double R2 = 1 - (RSS/TSS);
  // double R2_adj = 1 - (((double)vactual.n_rows-1)/((double)vactual.n_rows-(double)nvars-1))*(RSS/TSS);

  //abs dist

  double L1=as_scalar(sum(abs(vresid)));

  double L0=0;
  for(uli i=0; i<vactual.n_rows; ++i){
   L0=L0+abs(vactual(i)-m0);
  }

  double L=1-(L1/L0);

  double L_adj=1 - (((double)vactual.n_rows-1)/((double)vactual.n_rows-(double)nvars-1))*(L1/L0);

  map<string,double> res;
  res["L"]=L;
  res["L_adj"]=L_adj;

  return(res);

}

struct str_pred_out
{

 mat predictions;
 mat fitted;
 double L;
 double L_adj;

};

str_pred_out sarimax_pred(mat* Y, bool flg_sim, mat Mfitted, vec probs, uli nsim, string loss_function, bool pred_only, bool flg_const, field<vec> models)
{

  Col<uli> ids_vars_x=conv_to< Col<uli> >::from(models(0,0));
  Col<uli> ids_vars_ar=conv_to< Col<uli> >::from(models(0,1));
  vec vbeta_arx=models(0,2);
  ///
  double const0=0;
  if(flg_const==1){
   if(vbeta_arx.n_rows>0){
    const0=vbeta_arx(vbeta_arx.n_rows-1);
    vbeta_arx.shed_row(vbeta_arx.n_rows-1);
   }
  }
  ///

  Col<uli> ids_vars_ma=conv_to< Col<uli> >::from(models(0,3));
  vec vbeta_ma=models(0,4);

  str_pred_out str_out;
  vec vresid;

  uli p=ids_vars_ar.n_rows;
  uli q=ids_vars_ma.n_rows;
  uli k=vbeta_arx.n_rows-p; //number of regressors

  uli maxpq=0;
  if((p==0) && (q!=0)){
    maxpq=ids_vars_ma.max();
  }else if((p!=0) && (q==0)){
    maxpq=ids_vars_ar.max();
  }else if((p!=0) && (q!=0)){
    maxpq=max(ids_vars_ar.max(),ids_vars_ma.max());
  }

  uli npred;
  if(flg_sim==0){
    uli npred1=find_consecutive_nan(Y,0);
    uli npred2=find_consecutive_finite(Y,0)-maxpq-k;

    npred=min(npred1,npred2);
    if(npred==0){
      npred=1;
    }
  }else{
    npred=Mfitted.n_cols;
  }

  uli ri;
  double rd;
  // //random_device rdv;
  mt19937 gen(1234567);
  // default_random_engine gen;
  uniform_real_distribution<double> distrib(0.0,1.0);

  // mt19937 engine(1234567);
  // uniform_real_distribution<double> distrib(0.0,1.0);
  // auto gen = bind(ref(distrib), ref(engine));

  field<vec> Fresid(npred,1);
  vec resid_size(npred);

  if(flg_sim==1){
     for(uli kpred=0; kpred<npred; ++kpred){
       vresid=(*Y).col(0)-Mfitted.col(kpred);
       vresid=vresid(find_finite(vresid));
       Fresid(kpred,0)=vresid;
       resid_size(kpred)=(double) vresid.size();
     }
  }else{
    nsim=1;
  }

  vec vbeta_x;
  vec vbeta_ar;

  if(k>0){
   vbeta_x=vbeta_arx.rows(0,k-1);
  }
  if(p>0){
   vbeta_ar=vbeta_arx.rows(k,vbeta_arx.n_rows-1);
  }
  double tar;
  double tma;

  mat My_pred((*Y).n_rows, npred);
  vec veps((*Y).n_rows);
  field<mat> Fout(npred,1);
  mat Mout;

  if(flg_sim==1){
    for(uli kpred=0; kpred<npred; ++kpred){
     Fout(kpred,0).resize(nsim,(*Y).n_rows);
     Fout(kpred,0).fill(datum::nan);
    }
    }else{
     for(uli kpred=0; kpred<npred; ++kpred){
      Fout(kpred,0).resize((*Y).n_rows,2);
    }
  }

  double pred_x=0;
  double pred_ar=0;
  double pred_ma=0;
  double pred_err;

  uvec ut(1);
  uvec uids_vars_x=conv_to<uvec>::from(ids_vars_x);

  double eps_tma;

  bool is_na_pred_ar=0;

  uli t0, tk, last_kpred;

  for(uli s=0; s<nsim; ++s){

    My_pred.fill(datum::nan);
    veps.fill(datum::nan);

    if(maxpq>0){
     t0=(maxpq+1);
    }else{
     t0=0;
    }

    for(uli t=t0; t<(*Y).n_rows; ++t){

      last_kpred=std::min(npred,(uli)(*Y).n_rows-t);

      for(uli kpred=0; kpred<last_kpred; ++kpred){

        tk=t+kpred;

        ut(0)=tk;

        //X

        if(k>0){
         pred_x=as_scalar((*Y).submat(ut,uids_vars_x)*vbeta_x);
        }else{
         pred_x=0;
        }

        //AR

        pred_ar=const0;

        if(p>0){

          for(uli p0=0; p0<p; ++p0){

           tar=(double)tk-(double)ids_vars_ar(p0);

           if(tar>0){
            if((tar<t) && (isfinite((*Y)(tar,0)))){
             pred_ar=pred_ar+(*Y)(tar,0)*vbeta_ar(p0);
            }else{
             is_na_pred_ar=1;
             for(uli z=0; z<=kpred; ++z){
              if(isfinite(My_pred(tar,z))){
               pred_ar=pred_ar+My_pred(tar,z)*vbeta_ar(p0);
               is_na_pred_ar=0;
               break;
              }
             }
             if(is_na_pred_ar==1){
               pred_ar=datum::nan;
             }
            }
           }else{
            pred_ar=datum::nan;
            break;
           }

          }

        }//end if p

        pred_ma=0;

        if(isfinite(pred_ar)){

          //MA

          if(q>0){

            for(uli q0=0; q0<q; ++q0){

             tma=(double)tk-(double)ids_vars_ma(q0);

             if(tma>0){
              eps_tma=veps(tma);
              if((tma<t) && (isfinite(eps_tma))){
                pred_ma=pred_ma+eps_tma*vbeta_ma(q0);
              }else{
                pred_ma=pred_ma+0;
              }
             }else{
              pred_ma=pred_ma+0;
             }

            }

          }//end if q

        }

        if(isfinite(pred_ar)){
         My_pred(tk,kpred)=pred_x+pred_ar+pred_ma;
        }else{
         My_pred(tk,kpred)=datum::nan;
        }

        if(flg_sim==1){

          rd=distrib(gen);
          ri=(uli)floor(resid_size(kpred)*rd);

          pred_err=Fresid(kpred,0)(ri);

          if(kpred==0){
            if(isfinite((*Y)(t,0))){
              veps(t)=(*Y)(t,0)-pred_x-pred_ar;
            }else{
              veps(t)=0+pred_err;
            }
          }

          if(isfinite(pred_ar)){
           My_pred(tk,kpred)=pred_x+pred_ar+pred_ma+pred_err;
          }else{
           My_pred(tk,kpred)=datum::nan;
          }

          Fout(kpred,0)(s,tk)=My_pred(tk,kpred);

        }else{

          if(kpred==0){
           if(isfinite((*Y)(t,0))){
            veps(t)=(*Y)(t,0)-pred_x-pred_ar;
           }else{
            veps(t)=0;
           }
          }

        }

        // if(kpred==0){
        //  cout << "--------" << endl;
        //  cout << "tk:" << tk << endl;
        //  cout << "pred_ma:" << pred_ma << endl;
        //  cout << "pred_ar:" << pred_ar << endl;
        //  cout << "eps-1:" << veps(tk-1) << endl;
        //  cout << "eps:" << veps(tk) << endl;
        //  cout << "target-1:" << (*Y)(tk-1,0) << endl;
        //  cout << "target:" << (*Y)(tk,0) << endl;
        //  cout << "pred:" << My_pred(tk,0) << endl;
        // }

      }//end kpred

    }//end t

  }//end s

  if(flg_sim==1){

   for(uli kpred=0; kpred<npred; ++kpred){
     Fout(kpred,0)=quantile(Fout(kpred,0),probs);
     Fout(kpred,0)=join_rows(Mfitted.col(kpred),Fout(kpred,0).t());
     Fout(kpred,0)=join_rows((*Y).col(0),Fout(kpred,0));
   }

   if(npred>1){

     //prediction interval correction

     uli kpred=0;
     uli h_low=0, h_up=0;

     mat M_low((*Y).n_rows,3); M_low.fill(datum::nan);
     mat M_up((*Y).n_rows,3); M_up.fill(datum::nan);

     mat M0_low((*Y).n_rows,3); M0_low.fill(datum::nan);
     M0_low.col(1)=Fout(0,0).col(2);
     M0_low.col(2).fill(1);
     mat M0_up((*Y).n_rows,3); M0_up.fill(datum::nan);
     M0_up.col(1)=Fout(0,0).col(4);
     M0_up.col(2).fill(1);

     for(uli j=0; j<(*Y).n_rows; ++j){
       if(isfinite((*Y)(j,0))==0){
         if((kpred>0) && (kpred<npred)){
           M_low(h_low,0)=Fout(kpred,0)(j,2);
           M_low(h_low,1)=Fout(0,0)(j,2);
           M_low(h_low,2)=1;
           M0_low(j,0)=Fout(kpred,0)(j,2);
           h_low=h_low+1;

           M_up(h_up,0)=Fout(kpred,0)(j,4);
           M_up(h_up,1)=Fout(0,0)(j,4);
           M_up(h_up,2)=1;
           M0_up(j,0)=Fout(kpred,0)(j,4);
           h_up=h_up+1;
         }
         kpred=kpred+1;
       }else{
        kpred=0;
       }
     }

     str_input tab_input_ci;
     str_output_reg reg_ci;
     vec vlower_bound, vupper_bound;
     vec vquantiles_ci;

     tab_input_ci.MY0=M0_low;
     tab_input_ci.MY=M_low.rows(0,h_low-1);
     reg_ci=reg(&tab_input_ci, loss_function);
     vquantiles_ci=quantile(reg_ci.vresid,probs);
     vlower_bound=reg_ci.vfitted0;
     for(uli j=0; j<vlower_bound.n_rows; ++j){
      vlower_bound(j)=vlower_bound(j)+vquantiles_ci(0);
     }

     tab_input_ci.MY0=M0_up;
     tab_input_ci.MY=M_up.rows(0,h_low-1);
     reg_ci=reg(&tab_input_ci, loss_function);
     vquantiles_ci=quantile(reg_ci.vresid,probs);
     vupper_bound=reg_ci.vfitted0;
     for(uli j=0; j<vupper_bound.n_rows; ++j){
      vupper_bound(j)=vupper_bound(j)+vquantiles_ci(2);
     }

     Mout=Fout(0,0);

     kpred=0;
     for(uli j=0; j<(*Y).n_rows; ++j){
       if(isfinite((*Y)(j,0))==0){
         if((kpred>0) && (kpred<npred)){
           if((isfinite(vlower_bound(j))) && isfinite(vupper_bound(j))){
            Mout(j,2)=vlower_bound(j);
            Mout(j,4)=vupper_bound(j);
           }
         }
         kpred=kpred+1;
       }else{
        kpred=0;
       }
     }

   }else{

    Mout=Fout(0,0);

   }//end if(npred>1)

  }else{

   uvec nonmiss;

   for(uli kpred=0; kpred<npred; ++kpred){
     nonmiss=find_finite((*Y).col(0)-My_pred.col(kpred));
     npred=kpred+1;
     if(nonmiss.n_rows<20){ //20 is the number of observations over that the forecasting horizon can be considered significative
       break;
     }
   }

  }

  map<string,double> mp_idx_perf;

  double L, L_adj;
  if(pred_only==0){
   mp_idx_perf=performances((*Y).col(0), My_pred.col(0), (uli)(ids_vars_x.n_rows+ids_vars_ar.n_rows+ids_vars_ma.n_rows));
   L=mp_idx_perf["L"];
   L_adj=mp_idx_perf["L_adj"];
  }

  str_out.predictions=Mout;
  if(flg_sim==0){
   str_out.fitted=My_pred.cols(0,npred-1);
  }else{
   str_out.fitted=My_pred;
  }
  str_out.L=L;
  str_out.L_adj=L_adj;

  return(str_out);

}

struct str_model_out
{
  Col<uli> ids_vars_x;
  Col<uli> ids_vars_ar;
  Col<double> vbeta_arx;
  Col<uli> ids_vars_ma;
  Col<double> vbeta_ma;
};

struct str_model_selection
{

 field<vec> models;
 mat predictions;

};

str_model_selection model_selection_prediction(mat* Y, double from_lag, double max_lag, vec probs, uli nsim, string loss_function, bool pred_only, bool flg_const, bool flg_diff, field<vec> models)
{

  str_pred_out out_pred_i;

  str_out_uni_select out_uni_select_arx;
  field<vec> out_multi_select_arx;

  str_out_uni_select out_uni_select_ma;
  field<vec> out_multi_select_ma;

  vec vresid;
  mat Mfitted, Mfitted_empty;
  Col<uli> vretard_empty;

  bool flg_x_only=0;
  if(max_lag==0){
   flg_x_only=1;
  }

  str_model_selection res_out;

  res_out.models.set_size(1,6);

  //SARIX

  uli ndiff=0;
  vec old_target=(*Y).col(0);

  if(pred_only==0){

    out_uni_select_arx=model_univariate_selection(Y, from_lag, max_lag, flg_diff);

    res_out.models(0,5)=out_uni_select_arx.odiff;
    ndiff=(uli)out_uni_select_arx.odiff;

  }else{

    ndiff=(uli)as_scalar(models(0,5));
    mat Y0;
    for(uli k=0; k<ndiff; ++k){
      Y0=(*Y);
      (*Y).col(0)=shift((*Y).col(0),1);
      (*Y).submat(0,0,0,0)+=datum::nan;
      for(uli j=0; j<(*Y).n_rows; ++j){
       (*Y)(j,0)=Y0(j,0)-(*Y)(j,0);
      }
    }

  }

  if(((flg_x_only==0) && (out_uni_select_arx.vars_ar_idx.n_rows>0) && (pred_only==0)) || ((flg_x_only==0) && (pred_only==1))){ //if it is a sarimax

    if(pred_only==0){

      out_multi_select_arx=model_multivariate_selection(Y, &out_uni_select_arx.vars_x_idx, &out_uni_select_arx.vars_ar_idx, loss_function, flg_const, 1);

      res_out.models(0,0)=out_multi_select_arx(0,0);
      res_out.models(0,1)=out_multi_select_arx(0,1);
      res_out.models(0,2)=out_multi_select_arx(0,2);

      //MA

      vresid=out_multi_select_arx(0,3);

      out_uni_select_ma=model_univariate_selection(&vresid, from_lag, max_lag, 0);

      out_multi_select_ma=model_multivariate_selection(&vresid, &out_uni_select_ma.vars_x_idx, &out_uni_select_ma.vars_ar_idx, loss_function, flg_const, 0);

      res_out.models(0,3)=out_multi_select_ma(0,1);
      res_out.models(0,4)=out_multi_select_ma(0,2);

      models=res_out.models;

    }

    out_pred_i=sarimax_pred(Y, 0, Mfitted_empty, probs, nsim, loss_function, pred_only, flg_const, models);
    Mfitted=out_pred_i.fitted;

    out_pred_i=sarimax_pred(Y, 1, Mfitted, probs, nsim, loss_function, pred_only, flg_const, models);

    res_out.predictions=out_pred_i.predictions;

  }else{

    flg_x_only=1;

  }

  if(flg_x_only==1){

    if(pred_only==0){

      out_multi_select_arx=model_multivariate_selection(Y, &out_uni_select_arx.vars_x_idx, &vretard_empty, loss_function, flg_const, 1);

      res_out.models(0,0)=out_multi_select_arx(0,0);
      res_out.models(0,1)=out_multi_select_arx(0,1);
      res_out.models(0,2)=out_multi_select_arx(0,2);

      models=res_out.models;

    }

    out_pred_i=sarimax_pred(Y, 0, Mfitted_empty, probs, nsim, loss_function, pred_only, flg_const, models);
    Mfitted=out_pred_i.fitted;

    out_pred_i=sarimax_pred(Y, 1, Mfitted, probs, nsim, loss_function, pred_only, flg_const, models);

    res_out.predictions=out_pred_i.predictions;

  }

  if(ndiff>0){

      double y_t1,y_t2;
      out_pred_i.predictions.col(0)=old_target;

      for(uli j=0; j<ndiff; ++j){
        for(uli k=1; k<5; ++k){
          out_pred_i.predictions(j,k)=datum::nan;
        }
      }

      for(uli j=ndiff; j<out_pred_i.predictions.n_rows; ++j){
        for(uli k=1; k<5; ++k){
          if(isfinite(out_pred_i.predictions(j,k))==1){
            y_t1=out_pred_i.predictions(j-1,0);
            if(isfinite(y_t1)==0){
             y_t1=out_pred_i.predictions(j-1,k);
            }
            if(ndiff==1){
             out_pred_i.predictions(j,k)=out_pred_i.predictions(j,k)+y_t1;
            }
            if(ndiff==2){
             y_t2=out_pred_i.predictions(j-2,0);
             if(isfinite(y_t2)==0){
              y_t2=out_pred_i.predictions(j-2,k);
             }
             out_pred_i.predictions(j,k)=out_pred_i.predictions(j,k)+2*y_t1-y_t2;
            }
          }
        }
      }

      res_out.predictions=out_pred_i.predictions;

  }

  (*Y).col(0)=old_target;

  return(res_out);

}

struct str_output
{

 mat predictions;
 vec performances;

 mat fw_predictions;
 field<vec> fw_models;
 vec fw_performances;

 mat bw_predictions;
 field<vec> bw_models;
 vec bw_performances;

};

str_output regpred_cpp(mat* Y, double from_lag, double max_lag, double alpha, uli nsim, bool flg_print, string direction, string loss_function, bool pred_only, bool flg_const, bool flg_diff, vector < field<vec> > vmodels)
{

  str_model_out tab_model;
  str_pred_out tab_pred;
  str_output str_out;

  double pinf=(alpha/2);
  double psup=1-(alpha/2);

  vec probs={pinf, 0.5, psup};

  vec vresid_empty;
  vec vresid;
  vec vfitted;

  Col<uli> vtmp_uli;
  map<string,double> mp_idx_perf;

  uli nrows=(*Y).n_rows;

  mat Yr;
  if((direction=="<->") || (direction=="<-")){ //do not move below
   Yr=reverse((*Y),0);
  }

  uli bw_k=0, fw_k=0;

  mat predictions, predictions_rev;

  str_model_selection out_sel_pred;

  field<vec> models0;
  field<vec> models;

  double L,L_adj;

  if((direction=="<->") || (direction=="->")){

    //model selection
    if(flg_print==1){
     printA("Forward prediction: model selection and prediction...");
    }

    if(pred_only==1){
     models0=vmodels[0];
    }

    out_sel_pred=model_selection_prediction(Y, from_lag, max_lag, probs, nsim, loss_function,pred_only,flg_const,flg_diff,models0);

    predictions=out_sel_pred.predictions;

    str_out.fw_predictions=predictions;

    models=out_sel_pred.models;
    str_out.fw_models=models;

    L=datum::nan;
    L_adj=datum::nan;
    if(pred_only==0){
      fw_k=(uli) (models(0,0).size() + models(0,1).size() + models(0,3).size());
      mp_idx_perf=performances(predictions.col(0), predictions.col(3), fw_k);
      L=mp_idx_perf["L"];
      L_adj=mp_idx_perf["L_adj"];
    }

    str_out.fw_performances.set_size(2);
    str_out.fw_performances(0)=L;
    str_out.fw_performances(1)=L_adj;

  }

  if((direction=="<->") || (direction=="<-")){

    //model selection
    if(flg_print==1){
     printA("Backward prediction: model selection and prediction...");
    }

    if(pred_only==1){
     models0=vmodels[1];
    }

    out_sel_pred=model_selection_prediction(&Yr, from_lag, max_lag, probs, nsim, loss_function,pred_only,flg_const,flg_diff,models0);

    predictions_rev=reverse(out_sel_pred.predictions);

    str_out.bw_predictions=predictions_rev;

    models=out_sel_pred.models;
    str_out.bw_models=models;

    L=datum::nan;
    L_adj=datum::nan;
    if(pred_only==0){
      bw_k=(uli) (models(0,0).size() + models(0,1).size() + models(0,3).size());
      mp_idx_perf=performances(predictions_rev.col(0), predictions_rev.col(3), bw_k);
      L=mp_idx_perf["L"];
      L_adj=mp_idx_perf["L_adj"];
    }

    str_out.bw_performances.set_size(2);
    str_out.bw_performances(0)=L;
    str_out.bw_performances(1)=L_adj;

  }

  //collapse

  if(direction=="<-"){
    predictions=predictions_rev;
  }

  if(direction=="<->"){

   for(uli t=0; t<nrows; ++t){
    for(uli k=1; k<4; ++k){
     if(isfinite(predictions(t,1)) && isfinite(predictions_rev(t,1))){
      predictions(t,1)=(predictions(t,1)+predictions_rev(t,1))/2;
     }else if(isfinite(predictions_rev(t,1))){
      predictions(t,1)=predictions_rev(t,1);
     }
    }
   }

  }

  str_out.predictions=predictions;

  L=datum::nan;
  L_adj=datum::nan;
  if(pred_only==0){
    mp_idx_perf=performances(predictions.col(0), predictions.col(3), max(bw_k,fw_k));
    L=mp_idx_perf["L"];
    L_adj=mp_idx_perf["L_adj"];
  }

  str_out.performances.set_size(2);
  str_out.performances(0)=L;
  str_out.performances(1)=L_adj;

  if(flg_print==1){
   printA("Process ended successfully!");
  }

  return(str_out);

}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//FUNCTION FOR PASSING RESULTS TO PYTHON AND R
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

mat  std_vec2_to_arma_mat(svec2* A) {

    uli nrows=(*A).size();
    uli ncols=(*A)[0].size();

    mat V(nrows,ncols);

    for (uli j = 0; j < nrows; ++j) {
     for (uli i = 0; i < ncols; ++i) {
       V(j,i)=(*A)[j][i];
     }
    }

    return V;

}

svec2  arma_mat_to_std_vec2(mat* A) {

    svec2  V((*A).n_rows);
    for (size_t i = 0; i < (*A).n_rows; ++i) {
        V[i] = conv_to< svec1 >::from((*A).row(i));
    };

    return V;
}

field<vec> std_vec3_to_arma_fie_vec(svec3* A){

    uli nrows=(*A).size(); //number of models
    uli ncols=(*A)[0].size();; //number of parameter vectors

    field<vec> V(nrows,ncols);

    for (uli j = 0; j < nrows; ++j) {
     for (uli i = 0; i < ncols; ++i) {
       V(j,i)=(*A)[j][i];
     }
    }

    return V;

}

svec3 arma_fie_vec_to_std_vec3(field<vec>* A){

    uli nrows=(*A).n_rows; //number of models
    uli ncols=(*A).n_cols; //number of parameter vectors

    svec3 V(nrows, vector< vector<double> >(ncols , vector<double>()));
    // svec3 V;
    // svec3.resize(nrows);

    for (uli j = 0; j < nrows; ++j) {
     //V[j].resize(ncols);
     for (uli i = 0; i < ncols; ++i) {
       //V[j][i].push_back(conv_to< svec1 >::from((*A)(j,i)));
       V[j][i]=conv_to< svec1 >::from((*A)(j,i));
     }
    }

    return V;

}

#ifdef language_py

pair < svec3, pair < svec2, svec4 > >
regpred_py(svec2& Y, double from_lag, double max_lag, double alpha, uli nsim, bool flg_print, string direction, string loss_function, bool pred_only, bool flg_const, bool flg_diff, svec4& vmodels){

  mat Y0=std_vec2_to_arma_mat(&Y);

  vector < field<vec> > vmodels0(2);
  if(pred_only==1){
   if((direction=="->") || (direction=="<->")){
    vmodels0[0]=std_vec3_to_arma_fie_vec(&vmodels[0]);
   }
   if((direction=="<-") || (direction=="<->")){
    vmodels0[1]=std_vec3_to_arma_fie_vec(&vmodels[1]);
   }
  }

  str_output str_out=regpred_cpp(&Y0, from_lag, max_lag, alpha, nsim, flg_print, direction, loss_function, pred_only, flg_const, flg_diff, vmodels0);

  //store predictions

  svec3 vpredictions(3);

  svec2 predictions=arma_mat_to_std_vec2(&str_out.predictions);
  svec2 fw_predictions=arma_mat_to_std_vec2(&str_out.fw_predictions);
  svec2 bw_predictions=arma_mat_to_std_vec2(&str_out.bw_predictions);

  vpredictions[0]=predictions;
  vpredictions[1]=fw_predictions;
  vpredictions[2]=bw_predictions;

  //store performances

  svec2 vperformances(3);

  svec1 performances=conv_to< svec1 >::from(str_out.performances);
  svec1 fw_performances=conv_to< svec1 >::from(str_out.fw_performances);
  svec1 bw_performances=conv_to< svec1 >::from(str_out.bw_performances);

  vperformances[0]=performances;
  vperformances[1]=fw_performances;
  vperformances[2]=bw_performances;

  //store models

  svec4 vmodels1(2);
  if(pred_only==0){
     vmodels1[0]=arma_fie_vec_to_std_vec3(&str_out.fw_models);
     vmodels1[1]=arma_fie_vec_to_std_vec3(&str_out.bw_models);
  }

  //final store

  pair < svec3, pair < svec2, svec4 > > res;
  res.first=vpredictions;
  res.second.first=vperformances;
  res.second.second=vmodels1;

  return(res);

}

#endif

#ifdef language_R

template <typename T>
NumericVector arma_vec_to_R_vec(const T* x) {
    return NumericVector((*x).begin(), (*x).end());
}

vec R_vec_to_arma_vec(NumericVector* V) {

    uli nr=(uli) (*V).size();

    vec O(nr);

    for (size_t j = 0; j < nr; ++j) {
      O(j)=(*V)(j);
    }

    return(O);

}

NumericMatrix  arma_mat_to_R_mat(mat* A) {

  NumericMatrix  V((*A).n_rows,(*A).n_cols);

	for (size_t j = 0; j < (*A).n_rows; ++j) {
     for (size_t i = 0; i < (*A).n_cols; ++i) {
        V(j,i) =(*A)(j,i);
     }
	}

  return V;
}

field<vec> R_List2_vec_to_arma_fie_vec(List L0){

  uli nrows=L0.size();
  List L1=L0[0];
  uli ncols=L1.size();

  NumericVector v;
  field<vec> O(nrows,ncols);

  for (uli i0 = 0; i0 < nrows; ++i0){
    L1=L0[i0];
    for (uli i1 = 0; i1 < ncols; ++i1){
      v=L1[i1];
      O(i0,i1)=R_vec_to_arma_vec(&v);
    }
  }

  return(O);

}

List arma_fie_vec_to_R_List2_vec(field<vec>* F){

  uli nrows=(*F).n_rows;
  uli ncols=(*F).n_cols;

  vec v;
  List R0(nrows);

  for (uli i0 = 0; i0 < nrows; ++i0){
    List R1(ncols);
    for (uli i1 = 0; i1 < ncols; ++i1){
      v=(*F)(i0,i1);
      R1[i1]=arma_vec_to_R_vec(&v);
    }
    R0[i0]=R1;
  }

  return(R0);

}

RcppExport SEXP regpred_R(SEXP Y_p, SEXP from_lag_p, SEXP max_lag_p, SEXP alpha_p, SEXP nsim_p, SEXP flg_print_p, SEXP direction_p, SEXP loss_function_p, SEXP pred_only_p, SEXP flg_const_p, SEXP flg_diff_p, SEXP vmodels_p)
{

  NumericMatrix Y_0(Y_p);
  mat Y(Y_0.begin(), Y_0.nrow(), Y_0.ncol(), false);

  NumericVector from_lag_0(from_lag_p);
  double from_lag = Rcpp::as<double>(from_lag_0);

  NumericVector max_lag_0(max_lag_p);
  double max_lag = Rcpp::as<double>(max_lag_0);

  NumericVector alpha_0(alpha_p);
  double alpha = Rcpp::as<double>(alpha_0);

  NumericVector nsim_0(nsim_p);
  uli nsim = Rcpp::as<uli>(nsim_0);

  NumericVector flg_print_0(flg_print_p);
  bool flg_print = Rcpp::as<bool>(flg_print_0);

  CharacterVector direction_0(direction_p);
  string direction = Rcpp::as<string>(direction_0);

  CharacterVector loss_function_0(loss_function_p);
  string loss_function = Rcpp::as<string>(loss_function_0);

  NumericVector pred_only_0(pred_only_p);
  bool pred_only = Rcpp::as<bool>(pred_only_0);

  NumericVector flg_const_0(flg_const_p);
  bool flg_const = Rcpp::as<bool>(flg_const_0);

  NumericVector flg_diff_0(flg_diff_p);
  bool flg_diff = Rcpp::as<bool>(flg_diff_0);

  List vmodels(vmodels_p);

  vector < field<vec> > vmodels0(2);
  List fw_models;
  List bw_models;
  if(pred_only==1){
   vmodels0[0]=R_List2_vec_to_arma_fie_vec(vmodels[0]);
   vmodels0[1]=R_List2_vec_to_arma_fie_vec(vmodels[1]);
   fw_models=vmodels[0];
   bw_models=vmodels[1];
  }

  str_output str_out=regpred_cpp(&Y, from_lag, max_lag, alpha, nsim, flg_print, direction, loss_function, pred_only, flg_const, flg_diff, vmodels0);

  //store predictions

  NumericMatrix predictions=arma_mat_to_R_mat(&str_out.predictions);
  NumericMatrix fw_predictions=arma_mat_to_R_mat(&str_out.fw_predictions);
  NumericMatrix bw_predictions=arma_mat_to_R_mat(&str_out.bw_predictions);

  //store performances

  NumericVector performances=arma_vec_to_R_vec(&str_out.performances);
  NumericVector fw_performances=arma_vec_to_R_vec(&str_out.fw_performances);
  NumericVector bw_performances=arma_vec_to_R_vec(&str_out.bw_performances);

  //store models

  if(pred_only==0){
     fw_models=arma_fie_vec_to_R_List2_vec(&str_out.fw_models);
     bw_models=arma_fie_vec_to_R_List2_vec(&str_out.bw_models);
  }

  /*maximum 20 elements admitted for each level*/
  List res=List::create(
    Named("prediction")=List::create(
      Named("final") = predictions,
      Named("forward") = fw_predictions,
      Named("backward") = bw_predictions
    ),
    Named("performance")=List::create(
      Named("final") = performances,
      Named("forward") = fw_performances,
      Named("backward") = bw_performances
    ),
    Named("model")=List::create(
      Named("forward") = fw_models,
      Named("backward") = bw_models
    )
  );

  return(res);

}

#endif


// Copyright (c) 2013, Manuel Blum
// All rights reserved.

#include <Eigen/Dense>
#include <iostream>
#include <cstdio>

#include "nn.h"

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

  // input dimensionality
  int n_input = 2;
  // output dimensionality
  int n_output = 1;
  // number of training samples
  int m = 4;
  // number of layers
  int k = 3;
  // number of optimization steps
  int max_steps = 50;
  // regularization parameter
  double lambda = 0.000001;

  // training inputs
  matrix_t X(m, n_input);
  matrix_t Y(m, n_output);

  // XOR problem
  X << 0, 0, 0, 1, 1, 0, 1, 1;
  Y << 0, 1, 1, 0;
  std::cout << "training input: " << std::endl << X << std::endl;
  std::cout << "training output: " << std::endl << Y << std::endl;

  // specify network topology
  Eigen::VectorXi topo(k);
  topo << n_input, 6, n_output;
  std::cout << "topology: " << std::endl << topo << std::endl;

  // initialize a neural network with given topology
  NeuralNet nn(topo);

  nn.autoscale(X,Y);

  // train the network
  std::cout << "starting training" << std::endl;
  double err;
  for (int i = 0; i < max_steps; ++i) {
    err = nn.loss(X, Y, lambda);
    nn.rprop();
    printf("%3i   %4.4f\n", i, err);
  }

  // write model to disk
  nn.write("example.nn");

  // read model from disk
  NeuralNet nn2("example.nn");

  // testing
  nn2.forward_pass(X);
  matrix_t Y_test = nn2.get_activation();

  std::cout << "test input:" << std::endl << X << std::endl;
  std::cout << "test output:" << std::endl << Y_test << std::endl;

  return 0;
}


#include <iostream>
#include <complex>
#include <cmath>
#include <string>
#include <fstream>
#include <Eigen/Eigen>
#include "Component.h"

using namespace Eigen;
using namespace std;

const double PI = 3.141592;
double w;
int VS_num = 0, CS_num = 0, R_num = 0, L_num = 0, C_num = 0, nodes_num = 0;
ifstream input;
ofstream output;
vector<Component> components;

// Read The input from external file
bool read()
{
    string s;
    cout << "Enter INPUT file name: ";
    cin >> s;
    input.open(s + ".txt");
    if (input.is_open())
    {
        input >> s;
        w = stoi(s);

        while (!input.eof())
        {
            input >> s;
            if (s == "Vs")
            {
                string n1, n2, mag, phase;
                Component VScource;
                VScource.name = s;
                input >> n1 >> n2 >> mag >> phase;

				VScource.node1 = stoi(n1);
                VScource.node2 = stoi(n2);

				double magn = stod(mag);
                double ph = stod(phase);

				VScource.voltage = complex<double>(magn * cos(ph * PI / 180),
                    magn * sin(ph * PI / 180));

                components.push_back(VScource);
                VS_num++;
            }
            else if (s == "Cs")
            {
                string n1, n2, mag, phase;
                Component SScource;
                SScource.name = s;

				input >> n1 >> n2 >> mag >> phase;

				SScource.node1 = stoi(n1);
                SScource.node2 = stoi(n2);

				double magn = stod(mag);
                double ph = stod(phase);

				SScource.current = complex<double>(magn * cos(ph * PI / 180),
                    magn * sin(ph * PI / 180));
                components.push_back(SScource);

                CS_num++;
            }

            else if (s[0] == 'R')
            {
                string n1, n2, mag;
                Component R;
                R.name = s;
                input >> n1 >> n2 >> mag;
                R.node1 = stoi(n1);
                R.node2 = stoi(n2);

                float magn = stof(mag);
                R.Z = complex<double>(magn, 0);

				R.Y = pow(R.Z, -1);
                R.factor = 0;
                components.push_back(R);
                R_num++;
            }
            else if (s[0] == 'C')
            {
                string n1, n2, factor;
                Component C;
                C.name = s;

				input >> n1 >> n2 >> factor;

				C.node1 = stoi(n1);
                C.node2 = stoi(n2);
                double magn = stod(factor);

				C.Z = complex<double>(0, -1 / (w * magn));
                C.Y = pow(C.Z, -1);

				C.factor = magn;
                components.push_back(C);
                C_num++;
            }
            else if (s[0] == 'L')
            {
                string n1, n2, factor;
                Component L;
                L.name = s;

				input >> n1 >> n2 >> factor;

				L.node1 = stoi(n1);
                L.node2 = stoi(n2);

				double magn = stod(factor);

				L.Z = complex<double>(0, w * magn);
                L.Y = pow(L.Z, -1); //(0, -1/(w*magn));
                L.factor = magn;

				components.push_back(L);
                L_num++;
            }
        }
    }
    else
    {
        cout << "Wrong name, Enter a valid name\n";
        return false;
    }
    return true;
}

int main()
{
    if (read())
        ;
    else
    {
        while (true)
        {
            cout << "Please Enter a valid name\n";
            if (read())
                break;
        }
    }

    for (int i = 0; i < components.size(); ++i)
    {
        if (components[i].node1 > nodes_num)
            nodes_num = components[i].node1;
        if (components[i].node2 > nodes_num)
            nodes_num = components[i].node2;
    }

    int Eqs = nodes_num;
    for (int i = 0; i < components.size(); ++i)
    {
        if (components[i].name == "Vs")
        {
            Eqs++;
            components[i].VS_num = Eqs;
        }
    }

    Eigen::MatrixXcd Ys(Eqs, Eqs);
    Eigen::MatrixXcd VS(Eqs, 1);
    Eigen::MatrixXcd IS(Eqs, 1);

    for (int i = 0; i < Eqs; ++i)
        for (int j = 0; j < Eqs; ++j)
            Ys(i, j).imag(0), Ys(i, j).real(0);

	for (int i = 0; i < Eqs; ++i)
    {
        VS(i, 0).imag(0);
        VS(i, 0).real(0);

        IS(i, 0).imag(0);
        IS(i, 0).real(0);
    }

	vector<complex<double> > Vnodes(nodes_num);
    for (int i = 0; i < components.size(); ++i)
    {
        // first of all, if we have a voltage source
        if (components[i].node1 == 0 && components[i].name == "Vs")
        {
            // Vnodes[components[i].node2] = components[i].voltage;
            Ys(components[i].node2 - 1, components[i].VS_num - 1) += complex<double>(-1, 0);
            Ys(components[i].VS_num - 1, components[i].node2 - 1) += complex<double>(-1, 0);
            VS(components[i].VS_num - 1, 0) += components[i].voltage;
        }
        else if (components[i].node2 == 0 && components[i].name == "Vs")
        {
            Ys(components[i].node1 - 1, components[i].VS_num - 1) += complex<double>(1, 0);
            Ys(components[i].VS_num - 1, components[i].node1 - 1) += complex<double>(1, 0);
            VS(components[i].VS_num - 1, 0) += components[i].voltage;
        }
        else if (components[i].node1 != 0 && components[i].node2 != 0
            && components[i].name == "Vs") // same as previus but we have two additional equations
        {
            Ys(components[i].node1 - 1, components[i].VS_num - 1) += complex<double>(1, 0);
            Ys(components[i].node2 - 1, components[i].VS_num - 1) += complex<double>(-1, 0);
            Ys(components[i].VS_num - 1, components[i].node1 - 1) += complex<double>(1, 0);
            Ys(components[i].VS_num - 1, components[i].node2 - 1) += complex<double>(-1, 0);
            VS(components[i].VS_num - 1, 0) += components[i].voltage;
        }

        // If we have current sources
        else if (components[i].name == "Cs" && components[i].node1 == 0)
        {
            VS(components[i].node2 - 1, 0) -= components[i].current;
        }
        else if (components[i].name == "Cs" && components[i].node2 == 0)
        {
            VS(components[i].node1 - 1, 0) += components[i].current;
        }
        else if (components[i].name == "Cs" && components[i].node1 != 0 && components[i].node2 != 0)
        {
            VS(components[i].node2 - 1, 0) -= components[i].current;
            VS(components[i].node1 - 1, 0) += components[i].current;
        }

        // Now if we have passive components
        else if (components[i].name[0] == 'R' || components[i].name[0] == 'C'
            || components[i].name[0] == 'L')
        {
            if (components[i].node1 == 0)
            {
                Ys(components[i].node2 - 1, components[i].node2 - 1) += components[i].Y;
            }
            else if (components[i].node2 == 0)
            {
                Ys(components[i].node1 - 1, components[i].node1 - 1) += components[i].Y;
            }
            else if (components[i].node1 != 0 && components[i].node2 != 0)
            {
                Ys(components[i].node1 - 1, components[i].node1 - 1) += components[i].Y;
                Ys(components[i].node2 - 1, components[i].node1 - 1) -= components[i].Y;
                Ys(components[i].node2 - 1, components[i].node2 - 1) += components[i].Y;
                Ys(components[i].node1 - 1, components[i].node2 - 1) += components[i].Y;
            }
        }
    }

    IS += Ys.jacobiSvd(ComputeThinU | ComputeThinV).solve(VS);
    cout << "\n";

    cout << Ys << "\n\n\n";
    cout << VS << "\n\n\n";
    cout << IS;
    cout << "\n";

    for (int i = 0; i < Eqs; i++)
    {

        IS(i, 0).real(int(IS(i, 0).real() * 1000) / 1000.0);
        IS(i, 0).imag(int(IS(i, 0).imag() * 1000) / 1000.0);
    }
    int i = 0;
    for (; i < nodes_num; i++)
    {
        cout << "V(" << i + 1 << ")"
             << " " << abs(IS(i, 0)) << " " << (arg(IS(i, 0)) * (180 / 3.141592654));
        cout << endl;
    }
    for (int j = 0; j < nodes_num; j++)
    {
        if (components[j].name == "Vs")
        {
            cout << "I(" << components[j].node1 << " " << components[j].node2 << ")"
                 << " " << abs(IS(i, 0)) << " " << (arg(IS(i, 0)) * (180 / 3.141592654));
            i++;
            cout << endl;
        }
        if (components[j].name == "Is")
        {
            cout << "I(" << components[j].node2 << " " << components[j].node1 << ")"
                 << " " << abs(components[j].current) << " "
                 << (arg(components[j].current) * (180 / 3.141592654));
            cout << endl;
        }
        if (components[j].name[0] == 'R' || components[j].name[0] == 'L'
            || components[j].name[0] == 'C')
        {
            if (components[j].node1 != 0 && components[j].node1 != 0)
            {
                complex<double> I
                    = ((IS(components[j].node1 - 1, 0) - IS(components[j].node2 - 1, 0))
                        / components[j].Z);
                cout << "I(" << components[j].node1 << " " << components[j].node2 << ")"
                     << " " << abs(I) << " " << (arg(I) * (180 / 3.141592654));
                cout << endl;
            }
            else
            {
                if (components[j].node1 == 0)
                {
                    complex<double> I = ((complex<double>(0, 0) - IS(components[j].node2 - 1, 0))
                        / components[j].Z);
                    cout << "I(" << components[j].node1 << " " << components[j].node2 << ")"
                         << " " << abs(I) << " " << (arg(I) * (180 / 3.141592654));
                    cout << endl;
                }
                else if (components[j].node2 == 0)
                {
                    complex<double> I = ((IS(components[j].node1 - 1, 0)) / components[j].Z);
                    cout << "I(" << components[j].node1 << " " << components[j].node2 << ")"
                         << " " << abs(I) << " " << (arg(I) * (180 / 3.141592654));
                    cout << endl;
                }
            }
        }
    }
    system("PAUSE");
    return 0;
}


#include <Eigen/Dense>
#include <iostream>

using namespace Eigen;
using namespace std;

int main(int, char**)
{
  cout.precision(3);
  MatrixXcf a = MatrixXcf::Random(2,2);
cout << "Here is the matrix a\n" << a << endl;

cout << "Here is the matrix a^T\n" << a.transpose() << endl;

cout << "Here is the conjugate of a\n" << a.conjugate() << endl;

cout << "Here is the matrix a^*\n" << a.adjoint() << endl;

  return 0;
}


https://www.boost.org/doc/libs/1_72_0/libs/multiprecision/doc/html/boost_multiprecision/tut/ints/egs/factorials.html

#include <iostream>
#include <cassert>
#include <boost/multiprecision/cpp_int.hpp>

using boost::multiprecision::cpp_int;

using namespace std;

cpp_int factorial(int n)
{
    assert(n >= 0);
    if (n <= 1)
        return 1;
    else
        return n * factorial(n - 1);
}

int main()
{
    for (int i = 0; i < 10; i++)
        cout << i << "!=" << factorial(i) << endl;
}

/*

*/


#include <iostream>
#include <algorithm>
#include <iterator>
#include <cmath>
#include <fstream>
#include <valarray>
#include <complex>

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

#include "vlasovpp/field.h"
#include "vlasovpp/weno.h"
#include "vlasovpp/fft.h"
#include "vlasovpp/array_view.h"
#include "vlasovpp/poisson.h"

namespace math = boost::math::constants;
const std::complex<double> & I = std::complex<double>(0.,1.);

#define SQ(X) ((X)*(X))
#define Xi(i) (i*f0.step.dx+f0.range.x_min)
#define Vk(k) (k*f0.step.dv+f0.range.v_min)

int main(int,char**)
{
  const std::size_t NumDimV = 1;
	const int Nx = 64, Nv = 128, Nb_iter=100;

	field<double,NumDimV> f0( boost::extents[Nv][Nx] );

	f0.range.v_min = -10.; f0.range.v_max = 10.;
	f0.step.dv = (f0.range.v_max-f0.range.v_min)/Nv;
	f0.range.x_min = -10.; f0.range.x_max = 10.;
	f0.step.dx = (f0.range.x_max-f0.range.x_min)/Nx;

  double Tf = 2.*math::pi<double>();

	ublas::vector<double> v (Nv);
  ublas::vector<double> E (Nx);
  for ( std::size_t k=0 ; k<Nv ; ++k ) { v[k] = Vk(k); }
  for ( std::size_t i=0 ; i<Nx ; ++i ) { E[i] = -Xi(i); }

	const double lx = f0.range.x_max-f0.range.x_min;
  const double lv = f0.range.v_max-f0.range.v_min;
	ublas::vector<double> kx(Nx),kv(Nv);
  for ( int i=0  ; i<Nx/2 ; ++i ) { kx[i]    = 2.*math::pi<double>()*i/lx; }
  for ( int i=-Nx/2 ; i<0 ; ++i ) { kx[Nx+i] = 2.*math::pi<double>()*i/lx; }

  for ( int k=0  ; k<Nv/2 ; ++k ) { kv[k]    = 2.*math::pi<double>()*k/lv; }
  for ( int k=-Nv/2 ; k<0 ; ++k ) { kv[Nv+k] = 2.*math::pi<double>()*k/lv; }

  for (field<double,NumDimV>::size_type k=0 ; k<f0.size(0) ; ++k ) {
    for (field<double,NumDimV>::size_type i=0 ; i<f0.size(1) ; ++i ) {
      //f[k][i] = std::cos(Xi(i)*0.2)*std::cos(math::pi<double>()*2.*Vk(k)/20);
      f0[k][i] = std::exp( -SQ(Xi(i)-1.)/2. )*std::exp( -Vk(k)*Vk(k)/1.);
      //f[k][i] = ( std::exp(-0.5*SQ(Vk(k)))*np/std::sqrt(2.*math::pi<double>()) )*(1.+0.04*std::cos(0.3*Xi(i)));
    }
  }
  f0.write("init.dat");

  fft::spectrum_ hxf(Nx);
  fft::spectrum_ hvf(Nv);

  for ( int nb_iter=10 ; nb_iter<100 ; nb_iter+=10 ){
    double dt = Tf/nb_iter;

    int i_t=0;
    field<double,1> f=f0;
    while ( i_t*dt < Tf ) {
      //std::cout<<" \r"<<i_t<<" "<<std::flush;

      field<double,1> f1 = f;
      field<double,1> f2 = f;

  	  for ( std::size_t k=0 ; k<Nv ; ++k ) {
  	  	hxf.fft(&(f[k][0]));
  	  	for ( std::size_t i=0 ; i<Nx ; ++i ) { hxf[i] = std::exp( -I*v(k)*kx[i]*0.5*dt )*hxf[i]; }
  	  	hxf.ifft(&(f1[k][0]));
  	  }

      for ( std::size_t i=0 ; i<Nx ; ++i ) {
        std::valarray<double> fi(Nv); for ( std::size_t k=0 ; k<Nv ; ++k ) { fi[k] = f1[k][i]; }
        hvf.fft(&(fi[0]));
        for ( std::size_t k=0 ; k<Nv ; ++k ) { hvf[k] = std::exp( -I*E(i)*kv[k]*dt )*hvf[k]; }
        hvf.ifft(&(fi[0]));
        for ( std::size_t k=0 ; k<Nv ; ++k ) { f2[k][i] = fi[k]; }
      }

  	  for ( auto k=0 ; k<f.size(0) ; ++k ) {
  	  	hxf.fft(&(f2[k][0]));
  	  	for ( auto i=0 ; i<Nx ; ++i ) { hxf[i] = std::exp( -I*v(k)*kx[i]*0.5*dt )*hxf[i]; }
  	  	hxf.ifft(&(f[k][0]));
  	  }

      ++i_t;
  	}
    //std::cout << " \r" << nb_iter << " : " << i_t << std::endl;
    field<double,1> diff=f;
    for ( std::size_t k=0 ; k<Nv ; ++k ) {
      for ( std::size_t i=0 ; i<Nx ; ++i ) {
        diff[k][i] -= f0[k][i];
      }
    }
    double e_1  = std::accumulate( diff.origin() , diff.origin()+diff.num_elements() , 0. , [&](double a,double b){return a+std::abs(b)*f0.step.dx*f0.step.dv;} );
    double e_oo = std::abs(*std::max_element( diff.origin() , diff.origin()+diff.num_elements() , [](double a,double b){return (std::abs(a) < std::abs(b));} ));
    std::cout << dt << " " << e_1 << " " << e_oo << std::endl;
  }

	return 0;
}


#pragma once

#include <Eigen/Dense>

#include "aabb.hpp"
#include "euclidean-transform.hpp"
#include "ordered-pair.hpp"
#include "perceive/utils/math.hpp"
#include "quaternion.hpp"
#include "vector-2.hpp"
#include "vector-3.hpp"
#include "vector-4.hpp"

namespace perceive
{
// --------------------------------------------------------------------- Lp-norm

enum class Lp_norm_t : int { L_INF = 0, L1 = 1, L2 = 2, L3 = 3, L4 = 4 };

inline const char* str(Lp_norm_t method)
{
   switch(method) {
   case Lp_norm_t::L_INF: return "L-inf";
   case Lp_norm_t::L1: return "L1";
   case Lp_norm_t::L2: return "L2";
   case Lp_norm_t::L3: return "L3";
   case Lp_norm_t::L4: return "L4";
   }
   return "<error>";
}

// --------------------------------------------------------------------- Vectors

typedef Vector2T<float> Vector2f;
typedef Vector3T<float> Vector3f;
typedef Vector4T<float> Vector4f;
typedef Vector4T<float> Plane4f;
typedef QuaternionT<float> QuaternionF;

typedef Vector2T<real> Vector2;
typedef Vector3T<real> Vector3;
typedef Vector4T<real> Vector4;
typedef Vector4T<real> Plane;
typedef QuaternionT<real> Quaternion;
typedef AABBT<real> AABB;

typedef Vector2T<int> Point2;
typedef Vector3T<int> Point3;
typedef Vector4T<int> Point4;
typedef AABBT<int> AABBi;
typedef OrderedPairT<int> OrderedPair;

typedef EuclideanTransformT<float> EuclideanTransformF;
typedef EuclideanTransformT<real> EuclideanTransform;

using RealRange = std::pair<real, real>;

// #define EIGEN_ALIGN Eigen::DontAlign
#define EIGEN_ALIGN Eigen::AutoAlign

template<typename T> using EigenVector4T = Eigen::Matrix<T, 4, 1, EIGEN_ALIGN>;

using Vector2r = Eigen::Vector2d;
using Vector3r = Eigen::Vector3d;
using Vector4r = Eigen::Vector4d;
using Vector6r = Eigen::Matrix<real, 6, 1, EIGEN_ALIGN>;
using VectorXr = Eigen::Matrix<real, Eigen::Dynamic, 1, EIGEN_ALIGN>;

template<typename T> using Matrix4T = Eigen::Matrix<T, 4, 4, EIGEN_ALIGN>;

using Matrix3r  = Eigen::Matrix<real, 3, 3, EIGEN_ALIGN>;
using Matrix4r  = Eigen::Matrix<real, 4, 4, EIGEN_ALIGN>;
using Matrix34r = Eigen::Matrix<real, 3, 4, EIGEN_ALIGN>;
using MatrixXr  = Eigen::Matrix<real, Eigen::Dynamic, Eigen::Dynamic>;

using Matrix3f  = Eigen::Matrix<float, 3, 3, EIGEN_ALIGN>;
using Matrix4f  = Eigen::Matrix<float, 4, 4, EIGEN_ALIGN>;
using Matrix34f = Eigen::Matrix<float, 3, 4, EIGEN_ALIGN>;
using MatrixXf  = Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic>;

using MatrixXi = Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic>;

// ----------------------------------------------------------------------- Round

// template<> inline Vector2 round<Vector2>(const Vector2& v) noexcept
// {
//    return v.round();
// }
// template<> inline Vector3 round<Vector3>(const Vector3& v) noexcept
// {
//    return v.round();
// }
// template<> inline Vector4 round<Vector4>(const Vector4& v) noexcept
// {
//    return v.round();
// }

// ------------------------------------------------------------------ Conversion

inline Vector2 to_vec2(const Point2& v) noexcept { return Vector2(v(0), v(1)); }
inline Vector2 to_vec2(const Vector2& v) noexcept
{
   return Vector2(v(0), v(1));
}
inline Vector2 to_vec2(const Vector2r& v) noexcept
{
   return Vector2(v(0), v(1));
}
inline Vector2 to_vec2(const Vector2f& v) noexcept
{
   return Vector2(real(v(0)), real(v(1)));
}
inline Vector2r to_vec2r(const Point2& v) noexcept
{
   return Vector2r(v(0), v(1));
}
inline Vector2r to_vec2r(const Vector2& v) noexcept
{
   return Vector2r(v(0), v(1));
}
inline Vector2r to_vec2r(const Vector2r& v) noexcept
{
   return Vector2r(v(0), v(1));
}
inline Vector2r to_vec2r(const Vector2f& v) noexcept
{
   return Vector2r(v(0), v(1));
}
inline Vector2f to_vec2f(const Point2& v) noexcept
{
   return Vector2f(float(v(0)), float(v(1)));
}
inline Vector2f to_vec2f(const Vector2& v) noexcept
{
   return Vector2f(float(v(0)), float(v(1)));
}
inline Vector2f to_vec2f(const Vector2r& v) noexcept
{
   return Vector2f(float(v(0)), float(v(1)));
}
inline Vector2f to_vec2f(const Vector2f& v) noexcept
{
   return Vector2f(v(0), v(1));
}

inline Vector3 to_vec3(const Point3& v) noexcept
{
   return Vector3(v(0), v(1), v(2));
}
inline Vector3 to_vec3(const Vector3& v) noexcept
{
   return Vector3(v(0), v(1), v(2));
}
inline Vector3 to_vec3(const Vector3r& v) noexcept
{
   return Vector3(v(0), v(1), v(2));
}
inline Vector3 to_vec3(const Vector3f& v) noexcept
{
   return Vector3(real(v(0)), real(v(1)), real(v(2)));
}
inline Vector3r to_vec3r(const Point3& v) noexcept
{
   return Vector3r(v(0), v(1), v(2));
}
inline Vector3r to_vec3r(const Vector3& v) noexcept
{
   return Vector3r(v(0), v(1), v(2));
}
inline Vector3r to_vec3r(const Vector3r& v) noexcept
{
   return Vector3r(v(0), v(1), v(2));
}
inline Vector3r to_vec3r(const Vector3f& v) noexcept
{
   return Vector3r(real(v(0)), real(v(1)), real(v(2)));
}
inline Vector3f to_vec3f(const Point3& v) noexcept
{
   return Vector3f(float(v(0)), float(v(1)), float(v(2)));
}
inline Vector3f to_vec3f(const Vector3& v) noexcept
{
   return Vector3f(float(v(0)), float(v(1)), float(v(2)));
}
inline Vector3f to_vec3f(const Vector3r& v) noexcept
{
   return Vector3f(float(v(0)), float(v(1)), float(v(2)));
}
inline Vector3f to_vec3f(const Vector3f& v) noexcept
{
   return Vector3f(v(0), v(1), v(2));
}

inline Vector4 to_vec4(const Point4& v) noexcept
{
   return Vector4(v(0), v(1), v(2), v(3));
}
inline Vector4 to_vec4(const Vector4& v) noexcept
{
   return Vector4(v(0), v(1), v(2), v(3));
}
inline Vector4 to_vec4(const Vector4r& v) noexcept
{
   return Vector4(v(0), v(1), v(2), v(3));
}
inline Vector4 to_vec4(const Vector4f& v) noexcept
{
   return Vector4(real(v(0)), real(v(1)), real(v(2)), real(v(3)));
}
inline Vector4r to_vec4r(const Point4& v) noexcept
{
   return Vector4r(v(0), v(1), v(2), v(3));
}
inline Vector4r to_vec4r(const Vector4& v) noexcept
{
   return Vector4r(v(0), v(1), v(2), v(3));
}
inline Vector4r to_vec4r(const Vector4r& v) noexcept
{
   return Vector4r(v(0), v(1), v(2), v(3));
}
inline Vector4r to_vec4r(const Vector4f& v) noexcept
{
   return Vector4r(real(v(0)), real(v(1)), real(v(2)), real(v(3)));
}
inline Vector4f to_vec4f(const Point4& v) noexcept
{
   return Vector4f(float(v(0)), float(v(1)), float(v(2)), float(v(3)));
}
inline Vector4f to_vec4f(const Vector4& v) noexcept
{
   return Vector4f(float(v(0)), float(v(1)), float(v(2)), float(v(3)));
}
inline Vector4f to_vec4f(const Vector4r& v) noexcept
{
   return Vector4f(float(v(0)), float(v(1)), float(v(2)), float(v(3)));
}
inline Vector4f to_vec4f(const Vector4f& v) noexcept
{
   return Vector4f(float(v(0)), float(v(1)), float(v(2)), float(v(3)));
}

inline Point2 to_pt2(const std::pair<int, int>& v) noexcept
{
   return Point2(v.first, v.second);
}
inline Point2 to_pt2(const Vector2& v) noexcept
{
   return Point2(int(v.x), int(v.y));
}
inline Point2 to_pt2(const Vector2f& v) noexcept
{
   return Point2(int(v.x), int(v.y));
}
inline Point3 to_pt3(const Vector3& v) noexcept
{
   return Point3(int(v.x), int(v.y), int(v.z));
}
inline Point3 to_pt3(const Vector3f& v) noexcept
{
   return Point3(int(v.x), int(v.y), int(v.z));
}
inline Point4 to_pt4(const Vector4& v) noexcept
{
   return Point4(int(v.x), int(v.y), int(v.z), int(v.w));
}
inline Point4 to_pt4(const Vector4f& v) noexcept
{
   return Point4(int(v.x), int(v.y), int(v.z), int(v.w));
}

inline Quaternion to_quaternion(const QuaternionF& q) noexcept
{
   return Quaternion(real(q.x), real(q.y), real(q.z), real(q.w));
}

inline QuaternionF to_quaternionf(const Quaternion& q) noexcept
{
   return QuaternionF(float(q.x), float(q.y), float(q.z), float(q.w));
}

template<typename T> inline Vector2T<T> clip_to_xy(const Vector3T<T>& X)
{
   return Vector2T<T>(X.x, X.y);
}

// ------------------------------------------------------------------- is-finite

inline bool is_finite(const Vector2& x) noexcept { return x.is_finite(); }
inline bool is_finite(const Vector3& x) noexcept { return x.is_finite(); }
inline bool is_finite(const Vector4& x) noexcept { return x.is_finite(); }
inline bool is_finite(const Vector2f& x) noexcept { return x.is_finite(); }
inline bool is_finite(const Vector3f& x) noexcept { return x.is_finite(); }
inline bool is_finite(const Vector4f& x) noexcept { return x.is_finite(); }
inline bool is_finite(float x) noexcept { return std::isfinite(x); }
inline bool is_finite(double x) noexcept { return std::isfinite(x); }

// --------------------------------------------------------- matrix-mult-helpers
// P3
inline Vector4r mult_homgen(const Matrix4r& M, const Vector4r& X) noexcept
{
   Vector4r Y = M * Vector4r(X(0), X(1), X(2), X(3));
   if(fabs(Y(3)) < 1e-9) Y /= Y(3);
   return Y;
}

inline Vector4 mult_homgen(const Matrix4r& M, const Vector4& X) noexcept
{
   return to_vec4(mult_homgen(M, to_vec4r(X)));
}

inline Vector3r mult_homgen(const Matrix4r& M, const Vector3r& X) noexcept
{
   auto Y = mult_homgen(M, Vector4r(X(0), X(1), X(2), 1.0));
   return Vector3r(Y(0), Y(1), Y(2));
}

inline Vector3 mult_homgen(const Matrix4r& M, const Vector3& X) noexcept
{
   return to_vec3(mult_homgen(M, to_vec3r(X)));
}

// P2
inline Vector3r mult_homgen(const Matrix3r& M, const Vector3r& X) noexcept
{
   Vector3r Y = M * X;
   if(fabs(Y(2)) < 1e-9) Y /= Y(2);
   return Y;
}

inline Vector3 mult_homgen(const Matrix3r& M, const Vector3& X) noexcept
{
   return to_vec3(mult_homgen(M, to_vec3r(X)));
}

inline Vector2r mult_homgen(const Matrix3r& M, const Vector2r& X) noexcept
{
   auto Y = mult_homgen(M, Vector3r(X(0), X(1), 1.0));
   return Vector2r(Y(0), Y(1));
}

inline Vector2 mult_homgen(const Matrix3r& M, const Vector2& X) noexcept
{
   return to_vec2(mult_homgen(M, to_vec2r(X)));
}

// --------------------------------------------------------------- AABB => AABBi

inline AABBi aabb_to_aabbi(const AABB& aabb)
{
   return AABBi(int(floor(aabb.left)),
                int(floor(aabb.top)),
                int(ceil(aabb.right)),
                int(ceil(aabb.bottom)));
}

// -------------------------------------------------------- Matrix3r => Matrix3d

template<typename U, typename V> void matrixU_to_V(const U& S, V& D) noexcept
{
   using T               = typename V::Scalar;
   const unsigned n_rows = unsigned(S.rows());
   const unsigned n_cols = unsigned(S.cols());
   if(D.rows() != S.rows() || D.cols() != S.cols())
      D = V::Zero(S.rows(), S.cols());
   assert(D.rows() == n_rows && D.cols() == n_cols);
   for(unsigned row = 0; row < n_rows; ++row)
      for(unsigned col = 0; col < n_cols; ++col) D(row, col) = T(S(row, col));
}

// static void matrix3r_to_3d(const Matrix3r& S, Matrix3d& D) {matrixU_to_V(S,
// D);} static void matrix3d_to_3r(const Matrix3d& S, Matrix3r& D)
// {matrixU_to_V(S, D);} static void matrixXr_to_Xd(const MatrixXr& S, MatrixXd&
// D) {matrixU_to_V(S, D);} static void matrixXd_to_Xr(const MatrixXd& S,
// MatrixXr& D) {matrixU_to_V(S, D);}

// --------------------------------------------------------------------- Generic

template<typename T> typename T::value_type quadrance(const T& v) noexcept
{
   return v.quadrance();
}
template<typename T> typename T::value_type norm(const T& v) noexcept
{
   return v.norm();
}
template<typename T> typename T::value_type dot(const T& u, const T& v) noexcept
{
   return u.dot(v);
}
template<typename T>
typename T::value_type distance(const T& u, const T& v) noexcept
{
   return u.distance(v);
}

template<typename T>
inline Vector3T<T> cross(const Vector3T<T>& u, const Vector3T<T>& v)
{
   return u.cross(v);
}

inline Matrix3r make_skew_symmetric(const Vector3& t) noexcept
{
   Matrix3r Tx = Matrix3r::Zero();
   Tx(0, 1)    = -t(2);
   Tx(0, 2)    = t(1);
   Tx(1, 2)    = -t(0);
   Tx(1, 0)    = t(2);
   Tx(2, 0)    = -t(1);
   Tx(2, 1)    = t(0);
   return Tx;
}

// --------------------------------------------------------------------- Vector2

inline Vector2 cartesian_to_polar(const Vector2& x) noexcept
{
   Vector2 ret;
   if(fabs(x.quadrance() - real(1.0)) < real(1e-9)) {
      ret.mag()   = real(1.0);
      ret.theta() = acos(x.y >= real(0.0) ? x.x : -x.x);
   } else {
      ret.mag()   = x.norm();
      ret.theta() = acos((x.y >= real(0.0) ? x.x : -x.x) / ret.mag());
   }
   return ret;
}

inline Vector2 angle_to_cartesian(const real theta) noexcept
{
   return Vector2(cos(theta), sin(theta));
}

inline Vector2 polar_to_cartesian(const Vector2& x) noexcept
{
   return angle_to_cartesian(x.theta()) * x.mag();
}

// ------------------------------------------------------------------- Spherical

inline Vector3f
spherical_to_cartesian(float inc, float azi, float dist) noexcept
{
   const float sinx = std::sin(inc);
   const float cosx = std::cos(inc);
   const float siny = std::sin(azi);
   const float cosy = std::cos(azi);
   //
   return Vector3f(sinx * cosy, sinx * siny, cosx) * dist;
}

inline Vector3 spherical_to_cartesian(real inc, real azi, real dist) noexcept
{
   const Vector3::value_type sinx = sin(inc);
   const Vector3::value_type cosx = cos(inc);
   const Vector3::value_type siny = sin(azi);
   const Vector3::value_type cosy = cos(azi);
   //
   return Vector3(sinx * cosy, sinx * siny, cosx) * dist;
}

inline Vector3 spherical_to_cartesian(const Vector3& s) noexcept
{
   return spherical_to_cartesian(s.x, s.y, s.z);
}

inline Vector3f spherical_to_cartesian(const Vector3f& s) noexcept
{
   return spherical_to_cartesian(s.x, s.y, s.z);
}

inline Vector3 cartesian_to_spherical(real x, real y, real z) noexcept
{
   auto r           = sqrt(x * x + y * y + z * z);
   auto inclination = acos(z / r);
   auto azimuth     = atan2(y, x);
   return Vector3(inclination, azimuth, r);
}

inline Vector3 cartesian_to_spherical(const Vector3& n) noexcept
{
   return cartesian_to_spherical(n.x, n.y, n.z);
}

inline Vector3f cartesian_to_spherical(const Vector3f& n) noexcept
{
   return to_vec3f(cartesian_to_spherical(real(n.x), real(n.y), real(n.z)));
}

// ----------------------------------------------------------------- Homogeneous

inline Vector3r normalized_P2(const Vector3r& x) noexcept
{
   Vector3r v = x;
   v /= v(2);
   if((fabs(v(0)) > 1e20) || (fabs(v(1)) > 1e20))
      return x; // tending towards an ideal point
   return v;
}

template<typename T>
inline Vector2T<T> homgen_P2_to_R2(const Vector3T<T>& x) noexcept
{
   if(std::fabs(x.z) < T(1e-20)) {
      T sign = (x.z < T(0.0)) ? -T(1.0) : T(1.0);
      return Vector2T<T>(sign * x.x / T(1e-20), sign * x.y / T(1e-20));
   }
   auto inv = T(1.0) / x.z;
   return Vector2T<T>(x.x * inv, x.y * inv);
}

template<typename T>
inline Vector3T<T> homgen_R2_to_P2(const Vector2T<T>& x) noexcept
{
   return Vector3T<T>(x.x, x.y, T(1.0));
}

template<typename T>
inline Vector2T<T> line_line_isect(const Vector3T<T>& u,
                                   const Vector3T<T>& v) noexcept
{
   return homgen_P2_to_R2(cross(u, v));
}

template<typename T>
inline Vector3T<T> to_homgen_line(const Vector2T<T>& u,
                                  const Vector2T<T>& v) noexcept
{
   Vector3T<T> ret(u.y - v.y, v.x - u.x, u.x * v.y - u.y * v.x);
   ret.normalise_line();
   return ret;
}

template<typename T>
inline Vector3T<T> to_homgen_line(const Vector3T<T>& u,
                                  const Vector3T<T>& v) noexcept
{
   Vector3T<T> ret = cross(u, v);
   ret.normalise_line();
   return ret;
}

inline Vector3r to_homgen_line(const Vector3r& u, const Vector3r& v) noexcept
{
   return to_vec3r(to_homgen_line(to_vec3(u), to_vec3(v)));
}

template<typename T>
inline T dot_line_point(const Vector3T<T>& line,
                        const Vector3T<T>& point) noexcept
{
   return dot(line, point);
}

template<typename T>
inline T dot_line_point(const Vector3T<T>& line,
                        const Vector2T<T>& point) noexcept
{
   return line.x * point.x + line.y * point.y + line.z;
}

// Project 'point' onto 'line'
template<typename T>
inline Vector2T<T> project_point_line(const Vector3T<T>& line,
                                      const Vector2T<T>& point) noexcept
{
   return point - dot_line_point(line, point) * Vector2T<T>(line.x, line.y);
}

inline bool is_orthogonal(const Vector3& a, const Vector3& b) noexcept
{
   return fabs(dot(a, b)) < 1e-9;
}

template<typename T>
inline typename T::value_type uv_cos_theta(const T& u, const T& v)
{
   using V = typename T::value_type;
   return std::clamp<V>(u.normalised().dot(v.normalised()), V(-1.0), V(1.0));
}

// ---------------------------------------------------------- Lifted Coordinates

inline Vector6r lift_xy(real x, real y) noexcept
{
   Vector6r X;
   X(0) = square(x);
   X(1) = x * y;
   X(2) = square(y);
   X(3) = x;
   X(4) = y;
   X(5) = 1.0;
   return X;
}

inline Vector6r lift_xy(const Vector2& x) noexcept { return lift_xy(x.x, x.y); }
inline Vector6r lift_xy(const Vector2r& x) noexcept
{
   return lift_xy(x(0), x(1));
}
inline Vector6r lift_xy(const Vector3& x) noexcept
{
   return lift_xy(homgen_P2_to_R2(x));
}
inline Vector6r lift_xy(const Vector3r& x) noexcept
{
   return lift_xy(homgen_P2_to_R2(to_vec3(x)));
}

// ------------------------------------------------------------------------ Rays

inline Vector3 to_ray(const MatrixXr& A, real x, real y) noexcept
{
   return to_vec3(A * lift_xy(x, y)).normalized();
}

inline Vector3 to_ray(const MatrixXr& A, const Vector2& x) noexcept
{
   return to_ray(A, x.x, x.y);
}
inline Vector3 to_ray(const MatrixXr& A, const Vector2r& x) noexcept
{
   return to_ray(A, x(0), x(1));
}
inline Vector3 to_ray(const MatrixXr& A, const Vector3r& x) noexcept
{
   return to_ray(A, homgen_P2_to_R2(to_vec3(x)));
}

// ---------------------------------------------------------------- 2D Iteration

static constexpr array<std::pair<int, int>, 4> four_connected
    = {{{-1, 0}, {0, -1}, {0, 1}, {1, 0}}};

static constexpr array<std::pair<int, int>, 8> eight_connected
    = {{{-1, 0}, {0, -1}, {0, 1}, {1, 0}, {-1, -1}, {-1, 1}, {1, -1}, {1, 1}}};

static constexpr array<std::pair<int, int>, 20> twenty_connected
    = {{{-2, -1}, {-2, 0}, {-2, 1}, {-1, -2}, {-1, -1}, {-1, 0}, {-1, 1},
        {-1, 2},  {0, -2}, {0, -1}, {0, 1},   {0, 2},   {1, -2}, {1, -1},
        {1, 0},   {1, 1},  {1, 2},  {2, -1},  {2, 0},   {2, 1}}};

static constexpr array<std::pair<int, int>, 21> twentyone_connected
    = {{{-2, -1}, {-2, 0}, {-2, 1}, {-1, -2}, {-1, -1}, {-1, 0}, {-1, 1},
        {-1, 2},  {0, -2}, {0, -1}, {0, 1},   {0, 0},   {0, 2},  {1, -2},
        {1, -1},  {1, 0},  {1, 1},  {1, 2},   {2, -1},  {2, 0},  {2, 1}}};

static constexpr array<std::pair<int, int>, 24> twentyfour_connected
    = {{{-2, -2}, {-2, -1}, {-2, 0}, {-2, 1}, {-2, 2}, {-1, -2},
        {-1, -1}, {-1, 0},  {-1, 1}, {-1, 2}, {0, -2}, {0, -1},
        {0, 1},   {0, 2},   {1, -2}, {1, -1}, {1, 0},  {1, 1},
        {1, 2},   {2, -2},  {2, -1}, {2, 0},  {2, 1},  {2, 2}}};

// -------------------------------------------------------------------- feedback

template<typename T>
inline string vec_feedback(const Vector2T<T>& u, const Vector2T<T>& v) noexcept
{
   return format("|{} - {}| = {}", str(u), str(v), (u - v).norm());
}

template<typename T>
inline string vec_feedback(const Vector3T<T>& u, const Vector3T<T>& v) noexcept
{
   return format("|{} - {}| = {}", str(u), str(v), (u - v).norm());
}

template<typename T>
inline string vec_feedback(const Vector4T<T>& u, const Vector4T<T>& v) noexcept
{
   return format("|{} - {}| = {}", str(u), str(v), (u - v).norm());
}

// ----------------------------------------------------------- Quantize Funciton
std::array<std::pair<Point2, float>, 4> quantize(const Vector2& X) noexcept;

} // namespace perceive

namespace std
{
// OrderedPair
template<> struct hash<perceive::OrderedPair>
{
   size_t operator()(const perceive::OrderedPair& v) const { return v.hash(); }
};

// ---------------------------------------------------------------------- Vector

// Vector2
template<> struct hash<perceive::Vector2>
{
   size_t operator()(const perceive::Vector2& v) const { return v.hash(); }
};

// Vector3
template<> struct hash<perceive::Vector3>
{
   size_t operator()(const perceive::Vector3& v) const { return v.hash(); }
};

// Vector4
template<> struct hash<perceive::Vector4>
{
   size_t operator()(const perceive::Vector4& v) const { return v.hash(); }
};

// ----------------------------------------------------------------------- Point

// Point2
template<> struct hash<perceive::Point2>
{
   size_t operator()(const perceive::Point2& v) const { return v.hash(); }
};

// Point3
template<> struct hash<perceive::Point3>
{
   size_t operator()(const perceive::Point3& v) const { return v.hash(); }
};

// Point4
template<> struct hash<perceive::Point4>
{
   size_t operator()(const perceive::Point4& v) const { return v.hash(); }
};

} // namespace std


#include <boost/lexical_cast.hpp>
#include <iostream>
#include <vector>

class hamiltonian {
public:
  // z[0]: position, z[1]: momentum
  double energy(double t, std::vector<double> const& z) const {
    return potential_energy(t, z) + kinetic_energy(t, z);
  }
  double potential_energy(double /* t */, std::vector<double> const& z) const {
    //return -std::cos(2*M_PI*z[0]);
    return -std::cos(z[0]);
  }
  double kinetic_energy(double /* t */, std::vector<double> const& z) const {
    return 0.5 * z[1] * z[1];
  }
  // "force" calculation
  void operator()(double /* t */, std::vector<double> const& z, std::vector<double>& force) const {
    force[0] = z[1];
    //force[1] = -2*M_PI*std::sin(2*M_PI*z[0]);
    force[1] = -std::sin(z[0]);
  }
};

int main(int argc, char **argv) {
  double t_init = 0;
  double t_final = 10;
  double dt = 0.001;
  if (argc >= 2) t_final = boost::lexical_cast<double>(argv[1]);
  if (argc >= 3) dt = boost::lexical_cast<double>(argv[2]);
  std::cout << "# " << integrator_t::name() << std::endl
            << "# t_init  = " << t_init << std::endl
            << "# t_final = " << t_final << std::endl
            << "# dt      = " << dt << std::endl;

  std::vector<double> z(2);
  z[0] = M_PI*0.5;
  z[1] = 0;

  hamiltonian ham;

  integrator_t integrator(2);
  for (double t = t_init; t <= t_final; t += dt) {
    std::cout <<  t << ' ' << z[0] << ' ' << z[1] << ' ' << ham.energy(t,z) << ' ' << ham.kinetic_energy(t,z) << ' ' << ham.potential_energy(t,z) << std::endl;
    integrator.step(t, dt, z, ham);
  }
}