//rego: Automatic time series forecasting and missing value imputation.
//
//Copyright (C) Davide Altomare and David Loris <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>

#define uli unsigned long int

// #include <armadillo>
#ifndef language_R
 //#define OPTIM_ENABLE_ARMA_WRAPPERS
 #include <armadillo>
#endif

#ifdef language_R
 #define __GXX_EXPERIMENTAL_CXX0X__ 1

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

 #define ARMA_USE_CXX11
 #define ARMA_64BIT_WORD

 #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;

//------------------------------------------------------------------------------------------------------------------------
//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

double log_sum(colvec v)
{

  double lsum;

  if(accu(v>=0)>0){
   if(max(v)<=700){lsum=log(accu(exp(v)));} else{lsum=max(v);}
  } else{
   if(min(v)>=-700){lsum=log(accu(exp(v)));} else {lsum=max(v);}
  }

  return lsum;

} //end function

vec sub_elem_eq(vec v, vec w, double x)
{
  uvec iw;

  iw=find(w==x);
  if(!iw.is_empty()){v=v.elem(iw);}else{v=datum::nan;}

  return v;

} //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;

}

//------------------------------------------------------------------------------------------------------------------------
//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;
 uli 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;
 uli h;
 uli iM=datum::nan;

 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; uli k; uli z; uli h; uli iM2;
 double sumM;
 vec mov(lM+1); uvec imov; uvec umov; vec mov2; vec 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, Xty, invXtX;

  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=sub_mat(YtY,pa1,vv);
  XtX=sub_mat(YtY,pa1,pa1);
  betah=solve(XtX,Xty);
  //betah=inv_sympd(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(XtX,z1);
  //z1=inv_sympd(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=sub_mat(YtY,pa0,vv);
   XtX=sub_mat(YtY,pa0,pa0);
   betah=solve(XtX,Xty);
   //betah=inv_sympd(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

   if(univariate==0){

    limodR=t-1;
    s=lcv;

    while(t<n_tot_mod){ //while1
    //cout << t << ":" << n_tot_mod << endl;

     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{

       qh=pow_vec((M_q+C)/(1-M_q+C), (2*(1-G))-1);
       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

mat G_fin_fill(mat G, vec vr, uli ic, vec x)
{
  uli k, lvr;
  lvr=vr.n_elem;

  for(k=0; k<lvr; k++){
    G(vr(k),ic)=x(k);
  }

  return G;

} //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;
 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);

 //edges.print("edges");

 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));

 heartRes=FBF_heart(nobs_c, Corr_c*nobs_c, 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);

}

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);
}

vector< vector<double> >  arma_mat_to_std_mat(mat* A) {

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

    return V;
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//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;

    Col<double>* prt_vresid(){
     return(&vresid);
    }
};

str_output_reg reg(mat* MY0, mat* MY)
{

    mat X=(*MY).cols(1,(*MY).n_cols-1);
    vec Y=(*MY).col(0);
    mat XtX=trans(X)*X;
    vec Xty=trans(X)*Y;
    vec vbeta=solve(XtX,Xty);
    //vec vbeta=inv_sympd(XtX)*Xty;

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

    vec vactual0=(*MY0).col(0);
    vec vfitted0=(*MY0).cols(1,(*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;
};

str_out_uni_select model_univariate_selection(mat* Y, Col<uli>* vretard, double max_lag){

  str_out_uni_select str_out;

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

    uli from_lag=1;

    double cons_rows;
    uli max_lag0;

    cons_rows=(double) find_consecutive_finite(Y,0);
    max_lag0=(uli) min(cons_rows/2,cons_rows/log(cons_rows/10));
    if(max_lag==-1){
     max_lag=(double) max_lag0;
    }else if(max_lag>0){
     max_lag=(double) min(max_lag,(double)max_lag0);
    }

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

    double nvars=(*Y).n_cols-1+(*vretard).n_rows;
    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;
    Col<double> M_log_FBF;

    //estimate threshold

    nc=(*Y).n_cols;
    mat Ynorm = randu<mat>((*Y).n_rows,100);

    mat Y0=Ynorm;
    Col<uli> vretard0;
    tab_input=data_preparation(&Y0,vretard0);

    nr=tab_input.corY_nr;
    nc=tab_input.corY_nc;
    nvar_max=(uli)2*std::pow(tab_input.MY.n_rows,0.25);
    G_base_c=zeros<vec>(nc-1);

    res=FBF_RS(tab_input.corY,nr,G_base_c,h_c,C_c,Y0.n_cols-1+vretard0.n_rows,Y0.n_cols-1+vretard0.n_rows,univariate);
    M_log_FBF=res(3,0);

    uli nth=(uli) (0.99*M_log_FBF.size());
    M_log_FBF=sort(M_log_FBF);
    threshold=M_log_FBF(nth);

    //univariate

    tab_input=data_preparation(Y,*vretard);
    nr=tab_input.corY_nr;
    nc=tab_input.corY_nc;

    G_base_c=zeros<vec>(nc-1);
    n_tot_mod_c=(*Y).n_cols-1+(*vretard).n_rows;
    double n_hpp_c=(*Y).n_cols-1+(*vretard).n_rows;

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

    M_log_FBF=res(3,0);

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

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

    vec ids_vars=rvals-((*Y).n_cols-1);
    vec ids_vars_ar=ids_vars(find(ids_vars>=0));

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

	  Col<uli> tmp=(*vretard)(conv_to< uvec >::from(ids_vars_ar));
    str_out.vars_x_idx=ids_vars_x;
    str_out.vars_ar_idx=tmp;

    (*vretard)=(*vretard)(conv_to< uvec >::from(ids_vars_ar));

  }

  return(str_out);

}

struct str_out_multi_select
{
 vec resid;
 Col<uli> ids_vars_x;
 Col<uli> ids_vars_ar;
 vec vbeta;
};

str_out_multi_select model_multivariate_selection(mat* Y, Col<uli>* ids_vars_x_uni, Col<uli>* vretard){

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

    double nvars=(*Y).n_cols-1+(*vretard).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,(*vretard));

      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=(*vretard).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=(*vretard).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);

          str_output_reg tab_out_reg=reg(tab_input.prt_MY0(),tab_input.prt_MY());
          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);

    }

    str_out.resid=vresid;
    str_out.ids_vars_x=ids_vars_x;
    str_out.ids_vars_ar=v_ar;
    str_out.vbeta=vbeta_arx;

    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;
 double L=datum::nan;
 double L_adj=datum::nan;

};

str_pred_out sarimax_pred(mat* Y, Col<uli> ids_vars_x, Col<uli> ids_vars_ar, vec vbeta_arx, Col<uli> ids_vars_ma, vec vbeta_ma, bool flg_sim, vec vfitted, vec probs, uli nsim)
{

  str_pred_out str_out;
  vec vresid;

  if(flg_sim==1){
   vresid=(*Y).col(0)-vfitted;
   vresid=vresid(find_finite(vresid));
  }else{
    nsim=1;
  }

  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());
  }

  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 Mout;
  if(flg_sim==1){
   Mout.resize(nsim,(*Y).n_rows);
   Mout.fill(datum::nan);
  }else{
   Mout.resize((*Y).n_rows,2);
  }

  double pred_arx=0;
  double pred_ma=0;

  vec veps((*Y).n_rows);

  vec vy_pred((*Y).n_rows);

  double pred_err;

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

  double eps_tma;

  bool flg_na_ar=0;
  bool flg_na_ma=0;

  uli ri;
  random_device rd;
  //mt19937 gen(rd());
  mt19937 gen(1234567);
  uniform_int_distribution<> distrib(0, vresid.n_rows-1);

  uli t0;

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

    vy_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){

      ut(0)=t;

      //X

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

      flg_na_ar=0;
      flg_na_ma=0;

      //AR

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

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

       if(tar>0){
        if(isfinite((*Y)(tar,0))){
         pred_arx=pred_arx+(*Y)(tar,0)*vbeta_ar(p0);
        }else{
         pred_arx=pred_arx+vy_pred(tar)*vbeta_ar(p0);
        }
        flg_na_ar=0;
       }else{
        pred_arx=0;
        flg_na_ar=1;
       }

      }

      //MA

      if(q>0){

        pred_ma=0;

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

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

         if(tma>0){
          eps_tma=veps(tma);
          if(isfinite(eps_tma)){
           pred_ma=pred_ma+eps_tma*vbeta_ma(q0);
          }
          flg_na_ma=0;
         }else{
          pred_ma=0;
          flg_na_ma=1;
         }

        }

      }

      if((flg_na_ar==0) & (flg_na_ma==0)){
       vy_pred(t)=pred_arx+pred_ma;
      }else{
       vy_pred(t)=datum::nan;
      }

      if(flg_sim==1){
        //vz=randi<uvec>(1,distr_param(0, vresid.n_rows-1)); RcppArmadillo bug
        //pred_err=vresid(vz(0));
        ri=(uli) distrib(gen);
        pred_err=vresid(ri);

        if(isfinite((*Y)(t,0))){
          veps(t)=(*Y)(t,0)-pred_arx;
        }else{
          veps(t)=pred_ma+pred_err;
        }

        if((flg_na_ar==0) & (flg_na_ma==0)){
         vy_pred(t)=pred_arx+pred_ma+pred_err;
        }else{
         vy_pred(t)=datum::nan;
        }
        Mout(s,t)=vy_pred(t);
      }else{
        if(isfinite((*Y)(t,0))){
          veps(t)=(*Y)(t,0)-pred_arx;
        }else{
          veps(t)=pred_ma;
        }
      }

    }

  }

  if(flg_sim==1){

   Mout=quantile(Mout,probs);
   Mout=join_rows(vfitted,Mout.t());
   Mout=join_rows((*Y).col(0),Mout);

  }else{

   Mout.col(0)=(*Y).col(0);
   Mout.col(1)=vy_pred;

  }

  map<string,double> mp_idx_perf=performances((*Y).col(0), vy_pred, (uli)(ids_vars_x.n_rows+ids_vars_ar.n_rows+ids_vars_ma.n_rows));

  str_out.predictions=Mout;
  str_out.L=mp_idx_perf["L"];
  str_out.L_adj=mp_idx_perf["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;
};

bool CheckVisited(map<vector<uli>,uli>* mv, vector<uli>* v)
{
  bool res=0;
  if ((*mv).find(*v) != (*mv).end()) {
    res=1;
  }
  return(res);
}

pair < pair< vector<str_model_out>, vector<str_pred_out> > , mat > model_selection_prediction(mat* Y, double max_lag, vec probs, uli nsim)
{

  pair < pair< vector<str_model_out>, vector<str_pred_out> > , mat > res_out;

  str_model_out res_out_i;
  str_pred_out out_pred_i;

  str_out_uni_select out_uni_select_arx;
  str_out_multi_select out_multi_select_arx;

  str_out_uni_select out_uni_select_ma;
  str_out_multi_select out_multi_select_ma;

  map<vector<uli>,uli> visited_models;

  vec vresid;
  vec vfitted, vfitted_empty;
  Col<uli> vretard, vretard_empty, vretard1;
  vector<uli> vtmp;
  vector<double> vmin_ids_vars_ar;

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

  Col<uli> id_regressors;
  uvec u_id_regressors;

  //SARIX

  out_uni_select_arx=model_univariate_selection(Y, &vretard, max_lag);

  if((flg_x_only==0) & (vretard.n_rows>0)){ //if is a sarimax

   for(double i=-1; i<(double)(vretard.size()-1); ++i){

    vretard1=vretard;
    if(i>=0){
     vretard1.shed_rows(0,(uli)i);
    }

    out_multi_select_arx=model_multivariate_selection(Y, &out_uni_select_arx.vars_x_idx, &vretard1);

    vtmp=conv_to< vector<uli> >::from(out_multi_select_arx.ids_vars_ar);

    if(CheckVisited(&visited_models,&vtmp)==0){

       visited_models.insert(pair<vector<uli>,uli>(vtmp,0));

       //MA

       vresid=out_multi_select_arx.resid;
       vretard1.clear();

       out_uni_select_ma=model_univariate_selection(&vresid, &vretard1, max_lag);
       out_multi_select_ma=model_multivariate_selection(&vresid, &out_uni_select_ma.vars_x_idx, &vretard1);

       res_out_i.ids_vars_x=out_multi_select_arx.ids_vars_x;
       res_out_i.ids_vars_ar=out_multi_select_arx.ids_vars_ar;
       res_out_i.vbeta_arx=out_multi_select_arx.vbeta;
       res_out_i.ids_vars_ma=out_multi_select_ma.ids_vars_ar;
       res_out_i.vbeta_ma=out_multi_select_ma.vbeta;

       res_out.first.first.push_back(res_out_i);

       if(out_multi_select_arx.ids_vars_ar.n_rows>0){
        vmin_ids_vars_ar.push_back(out_multi_select_arx.ids_vars_ar.min());
       }else{
        vmin_ids_vars_ar.push_back(-1);
       }

       out_pred_i=sarimax_pred(Y, out_multi_select_arx.ids_vars_x, out_multi_select_arx.ids_vars_ar, out_multi_select_arx.vbeta, out_multi_select_ma.ids_vars_ar, out_multi_select_ma.vbeta, 0, vfitted_empty, probs, nsim);
       vfitted=out_pred_i.predictions.col(1);
       out_pred_i=sarimax_pred(Y, out_multi_select_arx.ids_vars_x, out_multi_select_arx.ids_vars_ar, out_multi_select_arx.vbeta, out_multi_select_ma.ids_vars_ar, out_multi_select_ma.vbeta, 1, vfitted, probs, nsim);

       res_out.first.second.push_back(out_pred_i);

       if(out_multi_select_arx.ids_vars_ar.n_rows==0){
        break;
       }

    }

   }//end for

  }else{

   flg_x_only=1;

  }

  if(flg_x_only==1){

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

    res_out_i.ids_vars_x=out_multi_select_arx.ids_vars_x;
    res_out_i.ids_vars_ar=out_multi_select_arx.ids_vars_ar;
    res_out_i.vbeta_arx=out_multi_select_arx.vbeta;
    res_out_i.ids_vars_ma=out_multi_select_ma.ids_vars_ar;
    res_out_i.vbeta_ma=out_multi_select_ma.vbeta;

    res_out.first.first.push_back(res_out_i);

    out_pred_i=sarimax_pred(Y, out_multi_select_arx.ids_vars_x, out_multi_select_arx.ids_vars_ar, out_multi_select_arx.vbeta, out_multi_select_ma.ids_vars_ar, out_multi_select_ma.vbeta, 0, vfitted_empty, probs, nsim);
    vfitted=out_pred_i.predictions.col(1);
    out_pred_i=sarimax_pred(Y, out_multi_select_arx.ids_vars_x, out_multi_select_arx.ids_vars_ar, out_multi_select_arx.vbeta, out_multi_select_ma.ids_vars_ar, out_multi_select_ma.vbeta, 1, vfitted, probs, nsim);

    res_out.first.second.push_back(out_pred_i);

    res_out.second=out_pred_i.predictions;

  }else{

    uvec idx_tmp;
    uli mod_sel;

    vec vmin_ids_vars_ar_col=conv_to< vec >::from(vmin_ids_vars_ar);

    mat final_predictions=res_out.first.second[0].predictions;

    double npred=0;
    for(uli j=0; j<(*Y).n_rows; ++j){

     if(j>0){
      if((isfinite((*Y)(j,0))==0) | ((isfinite((*Y)(j,0))==1) & (isfinite((*Y)(j-1,0))==0))){
       npred=npred+1;
      }else{
       npred=0;
      }
     }else{
      if(isfinite((*Y)(j,0))==0){
       npred=npred+1;
      }else{
       npred=0;
      }
     }

     if(npred>1)
     {

      if(vmin_ids_vars_ar_col(0)==-1){
        mod_sel=0;
      }else{
        idx_tmp=find(vmin_ids_vars_ar_col>=npred);
        if(idx_tmp.n_rows>0){
         mod_sel=(uli) idx_tmp(0);
        }else{
         idx_tmp=find(vmin_ids_vars_ar_col<npred);
         mod_sel=(uli) idx_tmp(idx_tmp.n_rows-1);
        }
      }

      final_predictions.row(j)=res_out.first.second[mod_sel].predictions.row(j);

     }

    }//end for

    res_out.second=final_predictions;

  }//end else

  return(res_out);

}

struct str_output
{

 mat predictions;
 double L=datum::nan;
 double L_adj=datum::nan;

 mat fw_predictions;
 vector<uli> fw_var_x_idx;
 vector<uli> fw_var_ar_idx;
 vector<uli> fw_var_ma_idx;
 double fw_L=datum::nan;
 double fw_L_adj=datum::nan;

 mat bw_predictions;
 vector<uli> bw_var_x_idx;
 vector<uli> bw_var_ar_idx;
 vector<uli> bw_var_ma_idx;
 double bw_L=datum::nan;
 double bw_L_adj=datum::nan;

};

str_output regpred_cpp(Mat<double>* Y, double max_lag, double alpha, uli nsim, bool flg_print, string direction="<->")
{

  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;

  pair < pair< vector<str_model_out>, vector<str_pred_out> > , mat > out_sel_pred;

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

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

    out_sel_pred=model_selection_prediction(Y, max_lag, probs, nsim);

    predictions=out_sel_pred.second;

    str_out.fw_predictions=predictions;

    str_out.fw_var_x_idx=conv_to< vector<uli> >::from(out_sel_pred.first.first[0].ids_vars_x);

    for(uli k=0; k<(uli)out_sel_pred.first.first.size(); ++k){
     vtmp_uli=join_vert(vtmp_uli,out_sel_pred.first.first[k].ids_vars_ar);
    }
    str_out.fw_var_ar_idx=conv_to< vector<uli> >::from(unique(vtmp_uli));

    for(uli k=0; k<(uli)out_sel_pred.first.first.size(); ++k){
     vtmp_uli=join_vert(vtmp_uli,out_sel_pred.first.first[k].ids_vars_ma);
    }
    str_out.fw_var_ma_idx=conv_to< vector<uli> >::from(unique(vtmp_uli));

    fw_k=(uli) (str_out.fw_var_x_idx.size() + str_out.fw_var_ar_idx.size() + str_out.fw_var_ma_idx.size());

    mp_idx_perf=performances(predictions.col(0), predictions.col(3), fw_k);

    str_out.fw_L=mp_idx_perf["L"];
    str_out.fw_L_adj=mp_idx_perf["L_adj"];

  }

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

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

    out_sel_pred=model_selection_prediction(&Yr, max_lag, probs, nsim);

    predictions_rev=reverse(out_sel_pred.second);

    str_out.bw_predictions=predictions_rev;

    str_out.bw_var_x_idx=conv_to< vector<uli> >::from(out_sel_pred.first.first[0].ids_vars_x);

    for(uli k=0; k<(uli)out_sel_pred.first.first.size(); ++k){
     vtmp_uli=join_vert(vtmp_uli,out_sel_pred.first.first[k].ids_vars_ar);
    }
    str_out.bw_var_ar_idx=conv_to< vector<uli> >::from(unique(vtmp_uli));

    for(uli k=0; k<(uli)out_sel_pred.first.first.size(); ++k){
     vtmp_uli=join_vert(vtmp_uli,out_sel_pred.first.first[k].ids_vars_ma);
    }
    str_out.bw_var_ma_idx=conv_to< vector<uli> >::from(unique(vtmp_uli));

    bw_k=(uli) (str_out.bw_var_x_idx.size() + str_out.bw_var_ar_idx.size() + str_out.bw_var_ma_idx.size());

    mp_idx_perf=performances(predictions_rev.col(0), predictions_rev.col(3), bw_k);

    str_out.bw_L=mp_idx_perf["L"];
    str_out.bw_L_adj=mp_idx_perf["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;

  mp_idx_perf=performances(predictions.col(0), predictions.col(3), max(bw_k,fw_k));

  str_out.L=mp_idx_perf["L"];
  str_out.L_adj=mp_idx_perf["L_adj"];

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

  return(str_out);

}

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

mat  std_mat_to_arma_mat(vector< vector<double> >* 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;

}

#ifdef language_py

pair < pair < list< vector<uli> >, list< vector<string> > > , pair < list< vector< vector<double> > >, list< double> > >
regpred_py(vector< vector<double> >& Y, double max_lag, double alpha, uli nsim, bool flg_print, string direction)
{

  pair < pair < list< vector<uli> >, list< vector<string> > > , pair < list< vector< vector<double> > >, list< double> > > res;
  mat Y0=std_mat_to_arma_mat(&Y);

  str_output str_out=regpred_cpp(&Y0, max_lag, alpha, nsim, flg_print, direction);

  res.first.first.push_back(str_out.fw_var_x_idx);
  res.first.first.push_back(str_out.fw_var_ar_idx);
  res.first.first.push_back(str_out.fw_var_ma_idx);
  res.first.first.push_back(str_out.bw_var_x_idx);
  res.first.first.push_back(str_out.bw_var_ar_idx);
  res.first.first.push_back(str_out.bw_var_ma_idx);

  // res.first.second.push_back(str_out.fw_var_x_names);
  // res.first.second.push_back(str_out.bw_var_x_names);

  res.second.first.push_back(arma_mat_to_std_mat(&str_out.predictions));
  res.second.first.push_back(arma_mat_to_std_mat(&str_out.fw_predictions));
  res.second.first.push_back(arma_mat_to_std_mat(&str_out.bw_predictions));

  res.second.second.push_back(str_out.L);
  res.second.second.push_back(str_out.L_adj);
  res.second.second.push_back(str_out.fw_L);
  res.second.second.push_back(str_out.fw_L_adj);
  res.second.second.push_back(str_out.bw_L);
  res.second.second.push_back(str_out.bw_L_adj);

  return(res);

}

#endif

#ifdef language_R

NumericMatrix  arma_mat_to_num_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;
}

RcppExport SEXP regpred_R(SEXP Y_p, SEXP max_lag_p, SEXP alpha_p, SEXP nsim_p, SEXP flg_print_p, SEXP direction_p)
{

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

  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);

  str_output str_out=regpred_cpp(&Y, max_lag, alpha, nsim, flg_print, direction);

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

  /*maximum 20 elements admitted for each level*/
  List res=List::create(
    Named("final")=List::create(
      Named("predictions") = predictions,
      Named("L") = str_out.L,
      Named("L_adj") = str_out.L_adj
    ),
    Named("forward")=List::create(
      Named("predictions") = fw_predictions,
      Named("var_x_names") = str_out.fw_var_x_idx,
      Named("var_ar_idx") = str_out.fw_var_ar_idx,
      Named("var_ma_idx") = str_out.fw_var_ma_idx,
      Named("L") = str_out.fw_L,
      Named("L_adj") = str_out.fw_L_adj
    ),
    Named("backward")=List::create(
      Named("predictions") = bw_predictions,
      Named("var_x_names") = str_out.bw_var_x_idx,
      Named("var_ar_idx") = str_out.bw_var_ar_idx,
      Named("var_ma_idx") = str_out.bw_var_ma_idx,
      Named("L") = str_out.bw_L,
      Named("L_adj") = str_out.bw_L_adj
    )
  );

  return(res);

}

#endif


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

#include "HE1NDecrypter.h"
#include <json/json.h>
#include <NTL/ZZ_p.h>

void HE1NDecrypter::setKey(NTL::vec_ZZ& key)
{
    p = key[0];
    kappa = key[1];
};

void HE1NDecrypter::readSecretsFromJSON(std::string& json)
{
	Json::Value root;   // will contains the root value after parsing.
	Json::Reader reader;
	bool parsingSuccessful = reader.parse(json,root);
	if (parsingSuccessful){
		p = NTL::conv<NTL::ZZ>(root["p"].asCString());
		kappa = NTL::conv<NTL::ZZ>(root["kappa"].asCString());
	}
};

NTL::ZZ HE1NDecrypter::decrypt(NTL::ZZ_p& ciphertext)
{
	NTL::ZZ ctext = rep(ciphertext);
    return (ctext % p) % kappa;
};


#ifndef CMAESAG_HPP
#define CMAESAG_HPP

#include <vector>
#include <string>
#include <type_traits>
#include <boost/serialization/list.hpp>
#include <boost/serialization/set.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/functional/hash.hpp>

#include "arch/ARLAgent.hpp"
#include "bib/Seed.hpp"
#include "bib/Utils.hpp"
#include <bib/MetropolisHasting.hpp>
#include <bib/XMLEngine.hpp>
#include <bib/Combinaison.hpp>
#include "cmaes_interface.h"

double expo_sum(const std::vector<double>& v){
  double sum = 0.f;
  for(double q : v)
    sum += exp(q);
  return sum;
}

class PIDControllerLearn : public arch::ARLAgent<arch::AgentProgOptions> {
 public:
  PIDControllerLearn(unsigned int _nb_motors, unsigned int _nb_sensors)
    : arch::ARLAgent<arch::AgentProgOptions>(_nb_motors, _nb_sensors), nb_sensors(_nb_sensors),
    current_param(dimension_problem())
    {

  }

  virtual ~PIDControllerLearn() {
    cmaes_exit(evo);
    delete evo;
    delete ac_informed_state;
  }

  void print_final_best(){
    LOG_DEBUG(cmaes_Get(evo, "fbestever"));
    const double* parameters = cmaes_GetPtr(evo, "xbestever");
    bib::Logger::PRINT_ELEMENTS(parameters, dimension_problem());
  }

  uint dimension_problem(){
//     return this->nb_motors * 3;
//     return this->nb_motors * 2;
//     return 2;
    return 3;
  }

  const std::vector<double>& _run(double, const std::vector<double>& sensors,
                                  bool, bool goal, bool) override {

    //force a small perturbation
    vector<double>* next_action = new std::vector<double>(this->nb_motors, 0.f);

//     std::vector<double> test = {-2.53168, -0.20791, 0.15605, 0.35428, -0.02659, 0.32720, -1.81081, -0.28895, -0.18864, -2.23505, -0.37164, 0.78502, -3.05356, -0.38279, -0.68477, -0.50092, -0.57394, -0.45777};//best sum max
//     std::vector<double> test = {-1.74053, 0.07579, 0.57802, -0.67090, -0.67155, 0.35731, -2.26236, 0.74043, -0.47473, -2.33031, -0.45227, 0.12848, -1.66925, 1.28023, -0.45736, 0.47006, -0.35110, 0.35893};
    //         std::vector<double> test = {-2.05891, -0.52004, -0.12384};//best old
//         std::vector<double> test = {-4.56065, -0.87200, 0.07312};//best sum max
//     std::vector<double> test = {-2.59888, -0.47748, 0.09128};//best sum expo
    std::vector<double> test = {-2.5, -0.5, 0.1};//best sum expo
    uint k=0;
    for(auto d : test)
      current_param[k++]=d;

    uint y=0;
    for(uint i=0; i<this->nb_motors; i++) {
      uint st_index = ac_informed_state->at(i*2);
      if(dimension_problem()== this->nb_motors * 2)
        next_action->at(i) = (2.0f/M_PI) * atan(current_param[y]*sensors[st_index] + current_param[y+1] * sensors[st_index+1]);
      else if(dimension_problem()== this->nb_motors * 3)
        next_action->at(i) = (2.0f/M_PI) * atan(current_param[y]*sensors[st_index] + current_param[y+1] * sensors[st_index+1])*current_param[y+2];
      else if(dimension_problem()==2)
        next_action->at(i) = (2.0f/M_PI) * atan(current_param[0]*sensors[st_index] + current_param[1] * sensors[st_index+1]);
      else if(dimension_problem()==3)
        next_action->at(i) = (2.0f/M_PI) * atan(current_param[0]*sensors[st_index] + current_param[1] * sensors[st_index+1])*current_param[2];

      if(dimension_problem()==this->nb_motors * 2)
        y+=2;
      else if(dimension_problem()==this->nb_motors * 3)
        y+=3;
    }

    for(uint i=0; i<ac_informed_state->size(); i++){
      if(i % 2 == 0 && episode_score_max[i/2] < fabs(sensors[ac_informed_state->at(i)])){
        episode_score_max[i/2] = fabs(sensors[ac_informed_state->at(i)]) ;
      }

      if(i % 2 == 0) // current angles
        episode_score += fabs(sensors[ac_informed_state->at(i)]);
//       else // derivative less important
//         episode_score += fabs(sensors[ac_informed_state->at(i)])*0.1;
    }

//     small action noise
    for (uint i = 0; i < next_action->size(); i++)
      next_action->at(i) += bib::Utils::randin(-0.05f, 0.05f);

    if(goal)
      episode_score += 100000;

//  CMA-ES already implement exploration in parameter space
    last_action.reset(next_action);

    return *next_action;
  }

  void _unique_invoke(boost::property_tree::ptree* pt, boost::program_options::variables_map*) override {
    ac_informed_state           = bib::to_array<uint>(pt->get<std::string>("devnn.ac_informed_state"));
    population                  = pt->get<uint>("agent.population");
    initial_deviation           = pt->get<double>("agent.initial_deviation");

    check_feasible = true;
    racing = false;
    error_count = 0;

    try {
      check_feasible = pt->get<bool>("agent.check_feasible");
    } catch(boost::exception const& ) {
    }

    try {
      racing = pt->get<bool>("agent.racing");
    } catch(boost::exception const& ) {
    }

    episode = 0;

    uint dimension = dimension_problem();
    double* startx  = new double[dimension];
    double* deviation  = new double[dimension];
    for(uint j=0; j< dimension; j++) {
      deviation[j] = initial_deviation;
      startx[j] = j % 2 == 0 ? -2.f : -0.05f;
    }

    evo = new cmaes_t;
    arFunvals = cmaes_init(evo, dimension, startx, deviation, 0, population, NULL/*"config.cmaes.ini"*/);
    delete[] startx;
    delete[] deviation;
//     evo->sp.stopTolFun = 1e-150;
//     evo->sp.stopTolFunHist = 1e-150;
//     evo->sp.stopTolUpXFactor = 1e50;

    printf("%s\n", cmaes_SayHello(evo));
    new_population();
    LOG_DEBUG(cmaes_Get(evo, "lambda") << " " << dimension << " " << population << " " << cmaes_Get(evo, "N"));
    if (population < 2)
      LOG_DEBUG("population too small, changed to : " << (4+(int)(3*log((double)dimension))));
  }

  bool is_feasible(const double* parameters) {
    for(uint i=0; i < (uint) cmaes_Get(evo, "dim"); i++)
      if(fabs(parameters[i]) >= 50.f) {
        return false;
      }

    return true;
  }

  void new_population() {
    const char * terminate =  cmaes_TestForTermination(evo);
    if(terminate) {
      LOG_INFO("mismatch "<< terminate);
      error_count++;

      if(error_count > 20 && racing) {
        LOG_FILE(DEFAULT_END_FILE, "-1");
        exit(0);
      }
    }
    //ASSERT(!cmaes_TestForTermination(evo), "mismatch "<< cmaes_TestForTermination(evo));

    current_individual = 0;
    pop = cmaes_SamplePopulation(evo);

    if(check_feasible) {
      //check that the population is feasible
      bool allfeasible = true;
      for (int i = 0; i < cmaes_Get(evo, "popsize"); ++i)
        while (!is_feasible(pop[i])) {
          cmaes_ReSampleSingle(evo, i);
          allfeasible = false;
        }

      if(!allfeasible)
        LOG_INFO("non feasible solution produced");
    }
  }

  void _start_episode(const std::vector<double>&, bool) override {
    episode_score = 0;

    episode_score_max.clear();
    for(uint i=0; i<ac_informed_state->size(); i++)
      if(i % 2 == 0){
        episode_score_max.push_back(std::numeric_limits<double>::lowest());
      }
  }

  void start_instance(bool learning) override {
    last_action = nullptr;
    scores.clear();

    //put individual into NN
    const double* parameters = nullptr;
    if(learning || !cmaes_UpdateDistribution_done_once)
      parameters = pop[current_individual];
    else
      parameters = cmaes_GetPtr(evo, "xbestever");

    loadPolicyParameters(parameters);

    if(learning)
      episode++;
  }

  void end_episode(bool) override {
    double sum = std::accumulate(episode_score_max.begin(), episode_score_max.end(), 0.f);
//     double sum =expo_sum(episode_score_max);
//     scores.push_back(episode_score);
    scores.push_back(sum);
  }

  void end_instance(bool learning) override {
    if(learning) {
      arFunvals[current_individual] = std::accumulate(scores.begin(), scores.end(), 0.f) / scores.size();

      current_individual++;
      if(current_individual >= cmaes_Get(evo, "lambda")) {
        cmaes_UpdateDistribution(evo, arFunvals);
        cmaes_UpdateDistribution_done_once=true;
        new_population();
      }
    }
  }

 protected:
  void _display(std::ostream& out) const override {
    out << std::setw(8) << std::fixed << std::setprecision(5) << episode_score << " " <<
    std::accumulate(episode_score_max.begin(), episode_score_max.end(), 0.f) << " " << expo_sum(episode_score_max);
  }

  void _dump(std::ostream& out) const override {
    out << std::setw(8) << std::fixed << std::setprecision(5) << episode_score << " " <<
    std::accumulate(episode_score_max.begin(), episode_score_max.end(), 0.f) << " "<< expo_sum(episode_score_max);
  }

 private:
  void loadPolicyParameters(const double* parameters) {
    for(uint i=0; i<current_param.size(); i++)
      current_param[i]=parameters[i];
  }

 private:
  //initilized by constructor
  uint nb_sensors;

  //initialized by invoke
  std::vector<uint>* ac_informed_state = nullptr;
  std::vector<double> current_param;
  uint population;
  double initial_deviation;
  bool check_feasible;
  bool racing;
  cmaes_t* evo;
  double *arFunvals;
  double episode_score;
  std::vector<double> episode_score_max;

  //internal mecanisms
  std::shared_ptr<std::vector<double>> last_action;
  std::list<double> scores;
  double *const *pop;
  bool cmaes_UpdateDistribution_done_once = false;
  uint current_individual;
  uint episode;
  uint error_count;

};

#endif


#ifndef STAN_MATH_REV_MAT_FUN_QUAD_FORM_HPP
#define STAN_MATH_REV_MAT_FUN_QUAD_FORM_HPP

#include <boost/utility/enable_if.hpp>
#include <boost/type_traits.hpp>
#include <stan/math/rev/core.hpp>
#include <stan/math/prim/mat/fun/Eigen.hpp>
#include <stan/math/prim/mat/fun/typedefs.hpp>
#include <stan/math/rev/mat/fun/typedefs.hpp>
#include <stan/math/prim/mat/fun/value_of.hpp>
#include <stan/math/prim/mat/fun/quad_form.hpp>
#include <stan/math/prim/mat/err/check_multiplicable.hpp>
#include <stan/math/prim/mat/err/check_square.hpp>
#include <stan/math/prim/mat/err/check_symmetric.hpp>

namespace stan {
  namespace math {

    namespace {
      template <typename TA, int RA, int CA, typename TB, int RB, int CB>
      class quad_form_vari_alloc : public chainable_alloc {
      private:
        inline void compute(const Eigen::Matrix<double, RA, CA>& A,
                            const Eigen::Matrix<double, RB, CB>& B) {
          Eigen::Matrix<double, CB, CB> Cd(B.transpose()*A*B);
          for (int j = 0; j < C_.cols(); j++) {
            for (int i = 0; i < C_.rows(); i++) {
              if (sym_) {
                C_(i, j) = var(new vari(0.5*(Cd(i, j) + Cd(j, i)), false));
              } else {
                C_(i, j) = var(new vari(Cd(i, j), false));
              }
            }
          }
        }

      public:
        quad_form_vari_alloc(const Eigen::Matrix<TA, RA, CA>& A,
                             const Eigen::Matrix<TB, RB, CB>& B,
                             bool symmetric = false)
          : A_(A), B_(B), C_(B_.cols(), B_.cols()), sym_(symmetric) {
          compute(value_of(A), value_of(B));
        }

        Eigen::Matrix<TA, RA, CA>  A_;
        Eigen::Matrix<TB, RB, CB>  B_;
        Eigen::Matrix<var, CB, CB> C_;
        bool sym_;
      };

      template <typename TA, int RA, int CA, typename TB, int RB, int CB>
      class quad_form_vari : public vari {
      protected:
        inline void chainA(Eigen::Matrix<double, RA, CA>& A,
                           const Eigen::Matrix<double, RB, CB>& Bd,
                           const Eigen::Matrix<double, CB, CB>& adjC) {}
        inline void chainB(Eigen::Matrix<double, RB, CB>& B,
                           const Eigen::Matrix<double, RA, CA>& Ad,
                           const Eigen::Matrix<double, RB, CB>& Bd,
                           const Eigen::Matrix<double, CB, CB>& adjC) {}

        inline void chainA(Eigen::Matrix<var, RA, CA>& A,
                           const Eigen::Matrix<double, RB, CB>& Bd,
                           const Eigen::Matrix<double, CB, CB>& adjC) {
          Eigen::Matrix<double, RA, CA>     adjA(Bd*adjC*Bd.transpose());
          for (int j = 0; j < A.cols(); j++) {
            for (int i = 0; i < A.rows(); i++) {
              A(i, j).vi_->adj_ += adjA(i, j);
            }
          }
        }
        inline void chainB(Eigen::Matrix<var, RB, CB>& B,
                           const Eigen::Matrix<double, RA, CA>& Ad,
                           const Eigen::Matrix<double, RB, CB>& Bd,
                           const Eigen::Matrix<double, CB, CB>& adjC) {
          Eigen::Matrix<double, RA, CA> adjB(Ad * Bd * adjC.transpose()
                                             + Ad.transpose()*Bd*adjC);
          for (int j = 0; j < B.cols(); j++)
            for (int i = 0; i < B.rows(); i++)
              B(i, j).vi_->adj_ += adjB(i, j);
        }

        inline void chainAB(Eigen::Matrix<TA, RA, CA>& A,
                            Eigen::Matrix<TB, RB, CB>& B,
                            const Eigen::Matrix<double, RA, CA>& Ad,
                            const Eigen::Matrix<double, RB, CB>& Bd,
                            const Eigen::Matrix<double, CB, CB>& adjC) {
          chainA(A, Bd, adjC);
          chainB(B, Ad, Bd, adjC);
        }

      public:
        quad_form_vari(const Eigen::Matrix<TA, RA, CA>& A,
                       const Eigen::Matrix<TB, RB, CB>& B,
                       bool symmetric = false)
          : vari(0.0) {
          impl_
            = new quad_form_vari_alloc<TA, RA, CA, TB, RB, CB>(A, B, symmetric);
        }

        virtual void chain() {
          Eigen::Matrix<double, CB, CB> adjC(impl_->C_.rows(),
                                             impl_->C_.cols());

          for (int j = 0; j < impl_->C_.cols(); j++)
            for (int i = 0; i < impl_->C_.rows(); i++)
              adjC(i, j) = impl_->C_(i, j).vi_->adj_;

          chainAB(impl_->A_, impl_->B_,
                  value_of(impl_->A_), value_of(impl_->B_),
                  adjC);
        }

        quad_form_vari_alloc<TA, RA, CA, TB, RB, CB> *impl_;
      };
    }

    template <typename TA, int RA, int CA, typename TB, int RB, int CB>
    inline typename
    boost::enable_if_c< boost::is_same<TA, var>::value ||
    boost::is_same<TB, var>::value,
                        Eigen::Matrix<var, CB, CB> >::type
      quad_form(const Eigen::Matrix<TA, RA, CA>& A,
                const Eigen::Matrix<TB, RB, CB>& B) {
      check_square("quad_form", "A", A);
      check_multiplicable("quad_form",
                          "A", A,
                          "B", B);

      quad_form_vari<TA, RA, CA, TB, RB, CB> *baseVari
        = new quad_form_vari<TA, RA, CA, TB, RB, CB>(A, B);

      return baseVari->impl_->C_;
    }
    template <typename TA, int RA, int CA, typename TB, int RB>
    inline typename
    boost::enable_if_c< boost::is_same<TA, var>::value ||
    boost::is_same<TB, var>::value,
                        var >::type
      quad_form(const Eigen::Matrix<TA, RA, CA>& A,
                const Eigen::Matrix<TB, RB, 1>& B) {
      check_square("quad_form", "A", A);
      check_multiplicable("quad_form",
                          "A", A,
                          "B", B);

      quad_form_vari<TA, RA, CA, TB, RB, 1> *baseVari
        = new quad_form_vari<TA, RA, CA, TB, RB, 1>(A, B);

      return baseVari->impl_->C_(0, 0);
    }

  }
}
#endif