// Copyright (C) 2017 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#include <iostream>
#include <vector>
#include <list>
#include <iomanip>
#include <memory>
#include <array>
#include <limits>
#include <array>
#include <Eigen/Dense>
#include <boost/predef.h>
#include <boost/multi_array.hpp>
#include <boost/array.hpp>
#include <Eigen/Cholesky>
#include "StOpt/regression/gridKernelConstruction.h"
#include "StOpt/regression/gridKernelIndexHelper.h"
#include "StOpt/core/utils/comparisonUtils.h"
#include "StOpt/core/grids/InterpolatorSpectral.h"


using namespace Eigen;
using namespace std;

namespace StOpt
{

inline double round(double d)
{
    return floor(d + 0.5);
}

// to iterate over boost array element and get associated index in one direction
template< typename  T, int NDIM>
typename boost::multi_array<T, NDIM>::index getIndex(const boost::multi_array<T, NDIM> &p_m, const T *p_requestedElement, const unsigned short int p_direction)
{
    int offset = p_requestedElement - p_m.origin();
    return (offset / p_m.strides()[p_direction] % p_m.shape()[p_direction] +  p_m.index_bases()[p_direction]);
}


// to iterate over boost array element and get associated index
template< typename  T, int NDIM>
boost::array< typename boost::multi_array<T, NDIM>::index, NDIM>  getIndexArray(const  boost::multi_array<T, NDIM> &p_m, const T *p_requestedElement)
{
    boost::array< typename boost::multi_array<T, NDIM>::index, NDIM>  indexRet;
    for (unsigned int dir = 0; dir < NDIM; dir++)
    {
        indexRet[dir] = getIndex<T, NDIM>(p_m, p_requestedElement, dir);
    }
    return indexRet;
}



/// \brief  Given          permits to calculate the regression coefficients used in the kernel method : constant regression
///  \param  p_zl          permits to locate  in each direction  the first left 'slice' in the method  belonging to the kernel associated to a grid point in z
///  \param  p_zr          permits to locate  in each direction the first right  'slice' in the method not belonging to the kernel associated to a grid point in z
///  \param  p_z           grid points in each direction
///  \param  p_h           kernel bandwidth in in each direction for each point
///  \param  p_sA          stores the \f$  \sum (x^{(m)})^i  \f$  in each dimension for each slice and is  used to contruct the regression matrices
///  \param  p_sB          stores the \f$  \sum (x^{(m)})^i p_y^i \f$  in each dimension for each slice and is used to construct the second member of the regression matrix.
///  \param  p_dimS        structure of the p_sA et  p_sB boost array
///  \param  p_idx         index used for grid points while iteration on dimensions
///  \param  p_zVal        associated coordinates values for current point with p_idex index
///  \param  p_zc          permits to store coefficient assocated to the considered z point and appearing while expanding the Epanechnikov  kernel in each direction
///  \param  p_p           get regressed values at p_z points
template<  int ND, int NDIM >
class GridValConst
{
public:
    void val(const vector< shared_ptr<ArrayXi> >    &p_zl,
             const vector< shared_ptr<ArrayXi> >    &p_zr,
             const vector< shared_ptr<ArrayXd> > &p_z,
             const vector< shared_ptr<ArrayXd> > &p_h,
             const boost::multi_array < double, ND - NDIM + 2 > & p_sA,
             const boost::multi_array < double, ND - NDIM + 2 > & p_sB,
             const boost::array < typename boost::multi_array < double, ND - NDIM + 2 >::index, ND - NDIM + 2  >  & p_dimS,
             boost::array< typename  boost::multi_array<double, ND>::index, ND> &p_idx,
             ArrayXd &p_zVal,
             ArrayXXd &p_zc,
             boost::multi_array<double, ND> &p_p,
             const double &p_tick
            )
    {
        boost::array < typename  boost::multi_array < double, ND - NDIM + 1 >::index, ND - NDIM + 1 > newDimS;
        for (int id = 1; id < ND - NDIM + 2; ++id)
            newDimS[id - 1] = p_dimS[id];
        boost::multi_array < double, ND - NDIM + 1 >  newSA(newDimS) ;
        for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA)
            *iterSA = 0. ;
        boost::multi_array < double, ND - NDIM + 1 >  newSB(newDimS) ;
        for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB)
            *iterSB = 0. ;
        const ArrayXi &zl = *p_zl[NDIM - 1];
        const ArrayXi &zr = *p_zr[NDIM - 1];
        const ArrayXd &h = *p_h[NDIM - 1];
        int ip = 0;
        int im = 0;
        for (int i = 0; i < p_z[NDIM - 1]->size(); ++i)
        {
            double z = (*p_z[NDIM - 1])(i);
            p_idx[NDIM - 1] = i;
            p_zVal(NDIM - 1) = z;
            double h2d = h(i) * h(i) * ND;
            p_zc(0, NDIM - 1) = 0.75 * (1. / ND - z * z / h2d);
            p_zc(1, NDIM - 1) = 0.75 * (2.*z / h2d);
            p_zc(2, NDIM - 1) = 0.75 * (-1. / h2d);
            while (ip < zr(i))
            {
                auto iterpsA = p_sA[ip].origin();
                for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA, ++iterpsA)
                    *iterSA += *iterpsA;
                auto iterpsB = p_sB[ip].origin();
                for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB, ++iterpsB)
                    *iterSB += *iterpsB;
                ip += 1;
            }
            while (im < zl(i))
            {
                auto iterpsA = p_sA[im].origin();
                for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA, ++iterpsA)
                    *iterSA -= *iterpsA;
                auto iterpsB = p_sB[im].origin();
                for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB, ++iterpsB)
                    *iterSB -= *iterpsB;
                im += 1;
            }

            GridValConst < ND, NDIM + 1 > ().val(p_zl,  p_zr, p_z, p_h, newSA, newSB, newDimS,  p_idx, p_zVal,  p_zc, p_p, p_tick);
        }
    }
};



/// \brief  Given          permits to calculate the regression coefficients used in the kernel method : specialized template for constant regression
///  \param  p_zl          permits to locate  in each direction  the first left 'slice' in the method  belonging to the kernel associated to a grid point in z
///  \param  p_zr          permits to locate  in each direction the first right  'slice' in the method not belonging to the kernel associated to a grid point in z
///  \param  p_z           grid points in each direction
///  \param  p_h           kernel bandwidth in in each direction for each point
///  \param  p_sA          stores the \f$  \sum (x^{(m)})^i (x^{(m)})^j (x^{(m)})^l \f$  in each dimension for each slice and is  used to contruct the regression matrices
///  \param  p_sB          stores the \f$  \sum (x^{(m)})^i (x^{(m)})^j \f$  in each dimension for each slice and is used to construct the second member of the regression matrix.
///  \param  p_dimS        structure of the p_sA and  p_sB boost array
///  \param  p_idx         index used for grid points while iteration on dimensions
///  \param  p_p           get regressed values at p_z points
///  \param  p_zc          permits to store coefficient assocated to the considered z point and appearing while exanding the Epanechnikov  kernel in each direction
///  \param  p_p           get regressed values at p_z points

template<  int ND >
class GridValConst<ND, ND>
{
public:
    void val(const vector< shared_ptr<ArrayXi> >    &p_zl,
             const vector< shared_ptr<ArrayXi> >    &p_zr,
             const vector< shared_ptr<ArrayXd> > &p_z,
             const vector< shared_ptr<ArrayXd> > &p_h,
             const boost::multi_array<double, 2> &p_sA,
             const boost::multi_array<double, 2> &p_sB,
             const boost::array< typename boost::multi_array<double, 2>::index, 2>   &p_dimS,
             boost::array< typename  boost::multi_array<double, ND>::index, ND> &p_idx,
             ArrayXd &p_zVal,
             ArrayXXd &p_zc,
             boost::multi_array<double, ND> &p_p,
             const double &p_tick
            )
    {
        boost::array< typename  boost::multi_array<double, 1>::index, 1> newDimS;
        newDimS[0] = p_dimS[1];
        boost::multi_array<double, 1>  newSA(newDimS) ;
        for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA)
            *iterSA = 0. ;
        boost::multi_array<double, 1>  newSB(newDimS) ;
        for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB)
            *iterSB = 0. ;
        const ArrayXi &zl = *p_zl[ND - 1];
        const ArrayXi &zr = *p_zr[ND - 1];
        const ArrayXd &h = *p_h[ND - 1];
        int ip = 0;
        int im = 0;
        for (int i = 0; i < p_z[ND - 1]->size(); ++i)
        {
            double z = (*p_z[ND - 1])(i);
            p_idx[ND - 1] = i;
            p_zVal(ND - 1) = z;
            double h2d = h(i) * h(i) * ND;
            p_zc(0, ND - 1) = 0.75 * (1. / ND - z * z / h2d);
            p_zc(1, ND - 1) = 0.75 * (2.*z / h2d);
            p_zc(2, ND - 1) = 0.75 * (-1. / h2d);
            while (ip < zr(i))
            {
                auto iterpsA = p_sA[ip].origin();
                for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA, ++iterpsA)
                {
                    *iterSA += *iterpsA;
                }
                auto iterpsB = p_sB[ip].origin();
                for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB, ++iterpsB)
                    *iterSB += *iterpsB;
                ip += 1;
            }
            while (im < zl(i))
            {
                auto iterpsA = p_sA[im].origin();
                for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA, ++iterpsA)
                    *iterSA -= *iterpsA;
                auto iterpsB = p_sB[im].origin();
                for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB, ++iterpsB)
                    *iterSB -= *iterpsB;
                im += 1;
            }

            if (std::fabs(*newSA.origin()) < 1e-20)
                p_p(p_idx) = 1e30;
            else
            {
                // regression
                double B = 0; // second member
                double A  = p_tick ; // constant 1D regressoin matrix
                for (int kk = 0 ; kk < ND; ++kk)
                {
                    B += p_zc(0, kk) * newSB[0] + p_zc(1, kk) * newSB[1 + 2 * kk] +  p_zc(2, kk) * newSB[2 * (kk + 1)];
                    A += p_zc(0, kk) * newSA[0] +  p_zc(1, kk) *  newSA[1 + 2 * kk] +  p_zc(2, kk) * newSA[2 * (kk + 1)] ;
                }
                double val = B / A;
                p_p(p_idx) = val ;
            }
        }
    }
};



/// \brief Perform  a ND regression
/// \param  p_x       particles (possibly with a diminution for the dimension)
/// \param  p_y       function value to regress (renormalized)
/// \param  p_z       grid points for regressions (in each dimension)
/// \param  p_h       bandwith for each point in p_z (in each dimension)
/// \param  p_g       storing all point in p_z + /- p_h (in each dimension)
/// \param  p_xG      for each dimension the mesh used in p_g for all simulations
/// \param  p_zl      for each dimension, indicate in which point in p_g  correspond a  point  in  p_z-p_h
/// \param  p_zr      for each dimension, indicate in which point in p_g   correspond a  point  in  p_z+p_h
/// \param  p_tick    tick to help regresssion matrix
/// \return  An array of the all the regressed values at the p_z points
template< int ND >
boost::multi_array< double, ND>  performNdRegConst(const ArrayXXd   &p_x, const VectorXd   &p_y,
        const vector< shared_ptr<ArrayXd> > &p_z,
        const vector< shared_ptr<ArrayXd> > &p_h,
        const vector< shared_ptr<ArrayXd> > &p_g,
        const ArrayXXi &p_xG,
        const vector< shared_ptr<ArrayXi> > &p_zl,
        const vector< shared_ptr<ArrayXi> > &p_zr,
        const double &p_tick)
{
    assert(ND == p_x.rows());
    boost::array< typename boost::multi_array<double, ND>::index, ND  > nz;
    for (int id = 0; id < ND; ++id)
        nz[id] = p_z[id]->size();
    boost::array< typename boost::multi_array<double, ND>::index, ND  > ng;
    for (int id = 0; id < ND; ++id)
        ng[id] = p_g[id]->size();

    ArrayXXd xPow = ArrayXXd::Zero(3, ND + 1);
    boost::array < typename boost::multi_array < double, ND + 3 >::index, ND + 1  > dimS;
    for (int id = 0; id < ND; ++id)
        dimS[id] = ng[id] - 1;
    dimS[ND] = 1 + 2 * ND;

    boost::multi_array < double, ND + 1 > sA(dimS);
    for (auto iterSA = sA.origin(); iterSA != (sA.origin() + sA.num_elements()); ++iterSA)
        *iterSA = 0. ;
    boost::multi_array < double, ND + 1 > sB(dimS);
    for (auto iterSB = sB.origin(); iterSB != (sB.origin() + sB.num_elements()); ++iterSB)
        *iterSB = 0. ;
    boost::array <  typename boost::multi_array < double, ND + 1 >::index, ND + 1  > idxS;
    for (int i = 0; i < p_x.cols(); ++i)
    {
        for (int idim = 0; idim < ND; ++idim)
        {
            idxS[idim] = p_xG(idim, i);
        }
        for (int k = 0; k < 3; ++k)
            xPow(k, 0) = 1. ;
        for (int j = 0; j < p_x.rows(); ++j)
        {
            double curr = 1;
            for (int k = 0; k < 3; ++k)
            {
                xPow(k, j + 1) = curr;
                curr *= p_x(j, i);
            }
        }
        // constants
        int indexS = 0;
        idxS[ND] = indexS++;
        sB(idxS) += p_y(i);
        sA(idxS) += 1;
        // degre 1 and 2 in each dimension
        for (size_t id = 0; id < ND; ++id)
        {
            idxS[ND] = indexS++;
            sB(idxS) += xPow(1, id + 1) * p_y(i);
            sA(idxS) += xPow(1, id + 1);
            idxS[ND] = indexS++;
            sB(idxS) += xPow(2, id + 1) * p_y(i);
            sA(idxS) += xPow(2, id + 1);
        }
    }

    boost::array< typename  boost::multi_array<double, ND>::index, ND>   idx;
    for (int id = 0; id < ND; ++id)
        idx[id] = 0. ;
    ArrayXd  zVal = ArrayXd::Zero(ND);

    boost::multi_array< double, ND> p(nz);
    ArrayXXd zc = ArrayXXd::Zero(3, ND);

    GridValConst<ND, 1>().val(p_zl, p_zr, p_z, p_h, sA, sB, dimS,  idx, zVal, zc, p, p_tick);

    return p;
}


/// \brief  Given          permits to calculate the regression coefficients used in the kernel method : linear regression
///  \param  p_zl          permits to locate  in each direction  the first left 'slice' in the method  belonging to the kernel associated to a grid point in z
///  \param  p_zr          permits to locate  in each direction the first right  'slice' in the method not belonging to the kernel associated to a grid point in z
///  \param  p_z           grid points in each direction
///  \param  p_h           kernel bandwidth in in each direction for each point
///  \param  p_sA          stores the \f$  \sum (x^{(m)})^i (x^{(m)})^j (x^{(m)})^l \f$  in each dimension for each slice and is  used to contruct the regression matrices
///  \param  p_sB          stores the \f$  \sum (x^{(m)})^i (x^{(m)})^j \f$  in each dimension for each slice and is used to construct the second member of the regression matrix.
///  \param  p_dimSA       structure of the p_sA boost array
///  \param  p_dimSB       structure of the p_sB boost array
///  \param  p_tabToIndexA utilitary to get power of coordinates of the particle used by regression matrix reconstruction
///  \param  p_tabToIndexB utilitary to get power of coordinates of the particle used by second member reconstruction
///  \param  p_idx         index used for grid points while iteration on dimensions
///  \param  p_zVal        associated coordinates values for current point with p_idex index
///  \param  p_rsize       regression matrix size is (p_rsize,p_rsize)
///  \param  p_zc          permits to store coefficient assocated to the considered z point and appearing while expanding the Epanechnikov  kernel in each direction
///  \param  p_p           get regressed values at p_z points
template<  int ND, int NDIM >
class GridValLin
{
public:
    void val(const vector< shared_ptr<ArrayXi> >    &p_zl,
             const vector< shared_ptr<ArrayXi> >    &p_zr,
             const vector< shared_ptr<ArrayXd> > &p_z,
             const vector< shared_ptr<ArrayXd> > &p_h,
             const boost::multi_array < double, ND - NDIM + 2 > & p_sA,
             const boost::multi_array < double, ND - NDIM + 2 > & p_sB,
             const boost::array < typename boost::multi_array < double, ND - NDIM + 2 >::index, ND - NDIM + 2  >  & p_dimSA,
             const boost::array < typename boost::multi_array < double, ND - NDIM + 2 >::index, ND - NDIM + 2  >  & p_dimSB,
             const boost::multi_array < int, 3> &p_tabToIndexA,
             const boost::multi_array < int, 2> &p_tabToIndexB,
             boost::array< typename  boost::multi_array<double, ND>::index, ND> &p_idx,
             ArrayXd &p_zVal,
             const int &p_rsize,
             ArrayXXd &p_zc,
             boost::multi_array<double, ND> &p_p,
             const double &p_tick
            )
    {
        boost::array < typename  boost::multi_array < double, ND - NDIM + 1 >::index, ND - NDIM + 1 > newDimSA;
        for (int id = 1; id < ND - NDIM + 2; ++id)
            newDimSA[id - 1] = p_dimSA[id];
        boost::array < typename  boost::multi_array < double, ND - NDIM + 1 >::index, ND - NDIM + 1 > newDimSB;
        for (int id = 1; id < ND - NDIM + 2; ++id)
            newDimSB[id - 1] = p_dimSB[id];
        boost::multi_array < double, ND - NDIM + 1 >  newSA(newDimSA) ;
        for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA)
            *iterSA = 0. ;
        boost::multi_array < double, ND - NDIM + 1 >  newSB(newDimSB) ;
        for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB)
            *iterSB = 0. ;
        const ArrayXi &zl = *p_zl[NDIM - 1];
        const ArrayXi &zr = *p_zr[NDIM - 1];
        const ArrayXd &h = *p_h[NDIM - 1];
        int ip = 0;
        int im = 0;
        for (int i = 0; i < p_z[NDIM - 1]->size(); ++i)
        {
            double z = (*p_z[NDIM - 1])(i);
            p_idx[NDIM - 1] = i;
            p_zVal(NDIM - 1) = z;
            double h2d = h(i) * h(i) * ND;
            p_zc(0, NDIM - 1) = 0.75 * (1. / ND - z * z / h2d);
            p_zc(1, NDIM - 1) = 0.75 * (2.*z / h2d);
            p_zc(2, NDIM - 1) = 0.75 * (-1. / h2d);
            while (ip < zr(i))
            {
                auto iterpsA = p_sA[ip].origin();
                for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA, ++iterpsA)
                {
                    *iterSA += *iterpsA;
                }
                auto iterpsB = p_sB[ip].origin();
                for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB, ++iterpsB)
                {
                    *iterSB += *iterpsB;
                }
                ip += 1;
            }
            while (im < zl(i))
            {
                auto iterpsA = p_sA[im].origin();
                for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA, ++iterpsA)
                {
                    *iterSA -= *iterpsA;
                }
                auto iterpsB = p_sB[im].origin();
                for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB, ++iterpsB)
                {
                    *iterSB -= *iterpsB;
                }
                im += 1;
            }

            GridValLin < ND, NDIM + 1 > ().val(p_zl,  p_zr, p_z, p_h, newSA, newSB, newDimSA, newDimSB, p_tabToIndexA, p_tabToIndexB, p_idx, p_zVal, p_rsize, p_zc, p_p, p_tick);
        }
    }
};



/// \brief  Given          permits to calculate the regression coefficients used in the kernel method : specialized template for linear regression
///  \param  p_zl          permits to locate  in each direction  the first left 'slice' in the method  belonging to the kernel associated to a grid point in z
///  \param  p_zr          permits to locate  in each direction the first right  'slice' in the method not belonging to the kernel associated to a grid point in z
///  \param  p_z           grid points in each direction
///  \param  p_h           kernel bandwidth in in each direction for each point
///  \param  p_sA          stores the \f$  \sum (x^{(m)})^i (x^{(m)})^j (x^{(m)})^l \f$  in each dimension for each slice and is  used to contruct the regression matrices
///  \param  p_sB          stores the \f$  \sum (x^{(m)})^i (x^{(m)})^j \f$  in each dimension for each slice and is used to construct the second member of the regression matrix.
///  \param  p_dimSA       structure of the p_sA boost array
///  \param  p_dimSB       structure of the p_sB boost array
///  \param  p_tabToIndexA utilitary to get power of coordinates of the particle used by regression matrix reconstruction
///  \param  p_tabToIndexB utilitary to get power of coordinates of the particle used by second member reconstruction
///  \param  p_idx         index used for grid points while iteration on dimensions
///  \param  p_p           get regressed values at p_z points
///  \param  p_rsize       regression matrix size is (p_rsize,p_rsize)
///  \param  p_zc          permits to store coefficient assocated to the considered z point and appearing while exanding the Epanechnikov  kernel in each direction
///  \param  p_p           get regressed values at p_z points

template<  int ND >
class GridValLin<ND, ND>
{
public:
    void val(const vector< shared_ptr<ArrayXi> >    &p_zl,
             const vector< shared_ptr<ArrayXi> >    &p_zr,
             const vector< shared_ptr<ArrayXd> > &p_z,
             const vector< shared_ptr<ArrayXd> > &p_h,
             const boost::multi_array<double, 2> &p_sA,
             const boost::multi_array<double, 2> &p_sB,
             const boost::array< typename boost::multi_array<double, 2>::index, 2>   &p_dimSA,
             const boost::array< typename boost::multi_array<double, 2>::index, 2>   &p_dimSB,
             const boost::multi_array < int, 3> &p_tabToIndexA,
             const boost::multi_array < int, 2> &p_tabToIndexB,
             boost::array< typename  boost::multi_array<double, ND>::index, ND> &p_idx,
             ArrayXd &p_zVal,
             const int &p_rsize,
             ArrayXXd &p_zc,
             boost::multi_array<double, ND> &p_p,
             const double &p_tick
            )
    {
        boost::array< typename  boost::multi_array<double, 1>::index, 1> newDimSA;
        newDimSA[0] = p_dimSA[1];
        boost::array< typename  boost::multi_array<double, 1>::index, 1> newDimSB;
        newDimSB[0] = p_dimSB[1];
        boost::multi_array<double, 1>  newSA(newDimSA) ;
        for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA)
            *iterSA = 0. ;
        boost::multi_array<double, 1>  newSB(newDimSB) ;
        for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB)
            *iterSB = 0. ;
        const ArrayXi &zl = *p_zl[ND - 1];
        const ArrayXi &zr = *p_zr[ND - 1];
        const ArrayXd &h = *p_h[ND - 1];
        int ip = 0;
        int im = 0;
        for (int i = 0; i < p_z[ND - 1]->size(); ++i)
        {
            double z = (*p_z[ND - 1])(i);
            p_idx[ND - 1] = i;
            p_zVal(ND - 1) = z;
            double h2d = h(i) * h(i) * ND;
            p_zc(0, ND - 1) = 0.75 * (1. / ND - z * z / h2d);
            p_zc(1, ND - 1) = 0.75 * (2.*z / h2d);
            p_zc(2, ND - 1) = 0.75 * (-1. / h2d);
            while (ip < zr(i))
            {
                auto iterpsA = p_sA[ip].origin();
                for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA, ++iterpsA)
                {
                    *iterSA += *iterpsA;
                }
                auto iterpsB = p_sB[ip].origin();
                for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB, ++iterpsB)
                {
                    *iterSB += *iterpsB;
                }
                ip += 1;
            }
            while (im < zl(i))
            {
                auto iterpsA = p_sA[im].origin();
                for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA, ++iterpsA)
                {
                    *iterSA -= *iterpsA;
                }
                auto iterpsB = p_sB[im].origin();
                for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB, ++iterpsB)
                {
                    *iterSB -= *iterpsB;
                }
                im += 1;
            }

            if (std::fabs(*newSA.origin()) < 1e-20)
                p_p(p_idx) = 1e30;
            else
            {
                // regression
                MatrixXd A = MatrixXd::Zero(p_rsize, p_rsize);
                VectorXd B = VectorXd::Zero(p_rsize);
                for (int ii = 0; ii < p_rsize; ++ii)
                {

                    for (int kk = 0 ; kk < ND; ++kk)
                    {
                        B(ii) += p_zc(0, kk) * newSB[p_tabToIndexB[ii][0]] + p_zc(1, kk) * newSB[p_tabToIndexB[ii][1 + kk]] +  p_zc(2, kk) * newSB[p_tabToIndexB[ii][1 + ND + kk]];
                    }
                    for (int jj = 0 ; jj < p_rsize; ++jj)
                        for (int kk = 0 ; kk < ND; ++kk)
                        {
                            A(ii, jj) += p_zc(0, kk) * newSA[p_tabToIndexA[ii][jj][0]] +  p_zc(1, kk) *  newSA[p_tabToIndexA[ii][jj][1 + kk]] +  p_zc(2, kk) * newSA[p_tabToIndexA[ii][jj][1 + kk + ND]] ;
                        }
                }
                for (int kk = 0; kk < p_rsize; ++kk)
                {
                    A(kk, kk) += p_tick;
                }
                LLT<MatrixXd>  lltA(A);
                VectorXd coeff = lltA.solve(B);
                double val = coeff(0);
                for (int id = 0; id < ND; ++id)
                    val += p_zVal(id) * coeff(id + 1);
                p_p(p_idx) = val ;
            }
        }
    }
};



/// \brief Perform  a ND regression
/// \param  p_x       particles (possibly with a diminution for the dimension)
/// \param  p_y       function value to regress (renormalized)
/// \param  p_z       grid points for regressions (in each dimension)
/// \param  p_h       bandwith for each point in p_z (in each dimension)
/// \param  p_g       storing all point in p_z + /- p_h (in each dimension)
/// \param  p_xG      for each dimension the mesh used in p_g for all simulations
/// \param  p_zl      for each dimension, indicate in which point in p_g  correspond a  point  in  p_z-p_h
/// \param  p_zr      for each dimension, indicate in which point in p_g   correspond a  point  in  p_z+p_h
/// \param  p_tick    tick to help regresssion matrix
/// \return  An array of the all the regressed values at the p_z points
template< int ND >
boost::multi_array< double, ND>  performNdRegLin(const ArrayXXd   &p_x, const VectorXd   &p_y,
        const vector< shared_ptr<ArrayXd> > &p_z,
        const vector< shared_ptr<ArrayXd> > &p_h,
        const vector< shared_ptr<ArrayXd> > &p_g,
        const ArrayXXi &p_xG,
        const vector< shared_ptr<ArrayXi> > &p_zl,
        const vector< shared_ptr<ArrayXi> > &p_zr,
        const double &p_tick)
{
    assert(ND == p_x.rows());
    boost::array< typename boost::multi_array<double, ND>::index, ND  > nz;
    for (int id = 0; id < ND; ++id)
        nz[id] = p_z[id]->size();
    boost::array< typename boost::multi_array<double, ND>::index, ND  > ng;
    for (int id = 0; id < ND; ++id)
        ng[id] = p_g[id]->size();

    int  rsize = 1 + ND;

    ArrayXXd xPow = ArrayXXd::Zero(5, ND + 1);
    boost::array < typename boost::multi_array < double, ND + 1 >::index, ND + 1  > dimSA;
    for (int id = 0; id < ND; ++id)
        dimSA[id] = ng[id] - 1;
    dimSA[ND] = 1 + 3 * ND * ND + ND + 2 * (ND * (ND - 1) * (ND - 2) / 3);
    boost::array < typename boost::multi_array < double, ND + 1 >::index, ND + 1  > dimSB;
    for (int id = 0; id < ND; ++id)
        dimSB[id] = ng[id] - 1;
    dimSB[ND] = 3 * ((ND * (ND + 1)) / 2) + 1;
    // create helper
    vector<array<size_t, 4> >  indexB;
    indexB.reserve(3 * ((ND * (ND + 1)) / 2) + 1);
    vector<array<size_t, 6> >  indexA;
    indexB.reserve(3 * ((ND * (ND + 1)) / 2) + 1);
    boost::multi_array < int, 2> tabToIndexB(boost::extents [1 + ND] [1 + 2 * ND]);
    createLinearIndexB<ND>(indexB, tabToIndexB);
    indexA.reserve(1 + 3 * ND * ND + ND + 2 * (ND * (ND - 1) * (ND - 2) / 3));
    boost::multi_array < int, 3> tabToIndexA(boost::extents [1 + ND] [1 + ND] [1 + 2 * ND]);
    createLinearIndexA<ND>(indexA, tabToIndexA);

    boost::multi_array < double, ND + 1 > sA(dimSA);
    for (auto iterSA = sA.origin(); iterSA != (sA.origin() + sA.num_elements()); ++iterSA)
        *iterSA = 0. ;
    boost::multi_array < double, ND + 1 > sB(dimSB);
    for (auto iterSB = sB.origin(); iterSB != (sB.origin() + sB.num_elements()); ++iterSB)
        *iterSB = 0. ;
    boost::array <  typename boost::multi_array < double, ND + 1 >::index, ND + 1  > idxSA;
    boost::array <  typename boost::multi_array < double, ND + 1 >::index, ND + 1  > idxSB;
    for (int i = 0; i < p_x.cols(); ++i)
    {
        for (int idim = 0; idim < ND; ++idim)
        {
            idxSA[idim] = p_xG(idim, i);
            idxSB[idim] = p_xG(idim, i);
        }
        for (int k = 0; k < 5; ++k)
            xPow(k, 0) = 1. ;
        for (int j = 0; j < p_x.rows(); ++j)
        {
            double curr = 1;
            for (int k = 0; k < 5; ++k)
            {
                xPow(k, j + 1) = curr;
                curr *= p_x(j, i);
            }
        }
        for (size_t iB = 0; iB < indexB.size(); ++iB)
        {
            idxSB[ND] = iB;
            sB(idxSB) += xPow(indexB[iB][1], indexB[iB][0]) * xPow(indexB[iB][3], indexB[iB][2]) * p_y(i);
        }
        for (size_t iA = 0; iA < indexA.size(); ++iA)
        {
            idxSA[ND] = iA;
            sA(idxSA) += xPow(indexA[iA][1], indexA[iA][0]) * xPow(indexA[iA][3], indexA[iA][2]) * xPow(indexA[iA][5], indexA[iA][4]);
        }
    }

    boost::array< typename  boost::multi_array<double, ND>::index, ND>   idx;
    for (int id = 0; id < ND; ++id)
        idx[id] = 0. ;
    ArrayXd  zVal = ArrayXd::Zero(ND);

    boost::multi_array< double, ND> p(nz);
    ArrayXXd zc = ArrayXXd::Zero(3, ND);

    GridValLin<ND, 1>().val(p_zl, p_zr, p_z, p_h, sA, sB, dimSA, dimSB,  tabToIndexA, tabToIndexB, idx, zVal, rsize, zc, p, p_tick);

    return p;
}



#if BOOST_COMP_GNUC
#pragma GCC push_options
#pragma GCC optimize ("O1")
#elif BOOST_COMP_MSVC || BOOST_COMP_INTEL
#pragma optimize( "", off )
#endif
/// \brief utilitary to prevent optimization
inline void addFastKernel(const double   &p_iterpsA, double &p_intersARound, double &p_iterSA)
{
    double aux = p_iterpsA - p_intersARound;
    double temp = p_iterSA + aux;
    if (fabs(p_iterSA) > fabs(aux))
    {
        p_intersARound = (temp - p_iterSA) - aux;
    }
    else
    {
        p_intersARound = (temp - aux) -  p_iterSA;
    }
    p_iterSA = temp;
}
/// \brief utilitary to prevent optimization
inline void minusFastKernel(const double   &p_iterpsA, double &p_intersARound, double &p_iterSA)
{
    double aux = -p_iterpsA - p_intersARound;
    double temp = p_iterSA + aux;
    if (fabs(p_iterSA) > fabs(aux))
    {
        p_intersARound = (temp - p_iterSA) - aux;
    }
    else
    {
        p_intersARound = (temp - aux) -  p_iterSA;
    }
    p_iterSA = temp;
}
#if BOOST_COMP_GNUC
#pragma GCC pop_options
#elif BOOST_COMP_MSVC || BOOST_COMP_INTEL
#pragma optimize( "", on )
#endif


/// \brief  Given     permits to calculate the regression coefficients used in the kernel method
///  \param  p_zl     permits to locate  in each direction  the first left 'slice' in the method  belonging to the kernel associated to a grid point in z
///  \param  p_zr     permits to locate  in each direction the first right  'slice' in the method not belonging to the kernel associated to a grid point in z
///  \param  p_z      grid points in each direction
///  \param  p_h      kernel bandwidth in in each direction for each point
///  \param  p_sA     stores the \f$  \sum (x^{(m)})^i (x^{(m)})^j (x^{(m)})^l \f$  in each dimension for each slice and is  used to contruct the regression matrices
///  \param  p_sB     stores the \f$  \sum (x^{(m)})^i (x^{(m)})^j \f$  in each dimension for each slice and is used to construct the second member of the regression matrix.
///  \param  p_dimSA  structure of the p_sA boost array
///  \param  p_dimSB  structure of the p_sB boost array
///  \param  p_tabToIndexA utilitary to get power of coordinates of the particle used by regression matrix reconstruction
///  \param  p_tabToIndexB utilitary to get power of coordinates of the particle used by second member reconstruction
///  \param  p_idx    index used for grid points while iteration on dimensions
///  \param  p_zVal   associated coordinates values for current point with p_idex index
///  \param  p_rsize  regression matrix size is (p_rsize,p_rsize)
///  \param  p_zc     permits to store coefficient assocated to the considered z point and appearing while exanding the Epanechnikov  kernel in each direction
///  \param  p_p      get regressed values at p_z points
#if BOOST_COMP_GNUC
#pragma GCC push_options
#pragma GCC optimize ("O1")
#elif BOOST_COMP_MSVC || BOOST_COMP_INTEL
#pragma optimize( "", off )
#endif
template<  int ND, int NDIM >
class GridValLinStable
{
public:
    void val(const vector< shared_ptr<ArrayXi> >    &p_zl,
             const vector< shared_ptr<ArrayXi> >    &p_zr,
             const vector< shared_ptr<ArrayXd> > &p_z,
             const vector< shared_ptr<ArrayXd> > &p_h,
             const boost::multi_array < double, ND - NDIM + 2 > & p_sA,
             const boost::multi_array < double, ND - NDIM + 2 > & p_sB,
             const boost::array < typename boost::multi_array < double, ND - NDIM + 2 >::index, ND - NDIM + 2  >  & p_dimSA,
             const boost::array < typename boost::multi_array < double, ND - NDIM + 2 >::index, ND - NDIM + 2  >  & p_dimSB,
             const boost::multi_array < int, 3> &p_tabToIndexA,
             const boost::multi_array < int, 2> &p_tabToIndexB,
             boost::array< typename  boost::multi_array<double, ND>::index, ND> &p_idx,
             ArrayXd &p_zVal,
             const int &p_rsize,
             ArrayXXd &p_zc,
             boost::multi_array<double, ND> &p_p,
             const double &p_tick
            )
    {
        boost::array < typename  boost::multi_array < double, ND - NDIM + 1 >::index, ND - NDIM + 1 > newDimSA;
        for (int id = 1; id < ND - NDIM + 2; ++id)
            newDimSA[id - 1] = p_dimSA[id];
        boost::array < typename  boost::multi_array < double, ND - NDIM + 1 >::index, ND - NDIM + 1 > newDimSB;
        for (int id = 1; id < ND - NDIM + 2; ++id)
            newDimSB[id - 1] = p_dimSB[id];
        boost::multi_array < double, ND - NDIM + 1 >  newSA(newDimSA) ;
        for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA)
            *iterSA = 0. ;
        boost::multi_array < double, ND - NDIM + 1 >  newSB(newDimSB) ;
        for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB)
            *iterSB = 0. ;
        const ArrayXi &zl = *p_zl[NDIM - 1];
        const ArrayXi &zr = *p_zr[NDIM - 1];
        const ArrayXd &h = *p_h[NDIM - 1];
        int ip = 0;
        int im = 0;
        // initialization round error
        boost::multi_array < double, ND - NDIM + 1 >  newSARound(newDimSA) ;
        {
            for (auto iterSA = newSARound.origin(); iterSA != (newSARound.origin() + newSARound.num_elements()); ++iterSA)
                *iterSA = 0. ;
        }
        boost::multi_array < double, ND - NDIM + 1 >  newSBRound(newDimSB) ;
        {
            for (auto iterSB = newSBRound.origin(); iterSB != (newSBRound.origin() + newSBRound.num_elements()); ++iterSB)
                *iterSB = 0. ;
        }
        for (int i = 0; i < p_z[NDIM - 1]->size(); ++i)
        {
            double z = (*p_z[NDIM - 1])(i);
            p_idx[NDIM - 1] = i;
            p_zVal(NDIM - 1) = z;
            double h2d = h(i) * h(i) * ND;
            p_zc(0, NDIM - 1) = 0.75 * (1. / ND - z * z / h2d);
            p_zc(1, NDIM - 1) = 0.75 * (2.*z / h2d);
            p_zc(2, NDIM - 1) = 0.75 * (-1. / h2d);
            while (ip < zr(i))
            {
                auto iterpsA = p_sA[ip].origin();
                auto intersARound = newSARound.origin();
                for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA, ++iterpsA, ++intersARound)
                {
                    addFastKernel(*iterpsA, *intersARound, *iterSA);
                }
                auto iterpsB = p_sB[ip].origin();
                auto intersBRound = newSBRound.origin();
                for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB, ++iterpsB, ++intersBRound)
                {
                    addFastKernel(*iterpsB, *intersBRound, *iterSB);
                }
                ip += 1;
            }
            while (im < zl(i))
            {
                auto iterpsA = p_sA[im].origin();
                auto intersARound = newSARound.origin();
                for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA, ++iterpsA, ++intersARound)
                {
                    minusFastKernel(*iterpsA, *intersARound, *iterSA);
                }
                auto iterpsB = p_sB[im].origin();
                auto intersBRound = newSBRound.origin();
                for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB, ++iterpsB, ++intersBRound)
                {
                    minusFastKernel(*iterpsB, *intersBRound, *iterSB);
                }
                im += 1;
            }
            GridValLinStable < ND, NDIM + 1 > ().val(p_zl,  p_zr, p_z, p_h, newSA, newSB, newDimSA, newDimSB, p_tabToIndexA, p_tabToIndexB, p_idx, p_zVal, p_rsize, p_zc, p_p, p_tick);
        }
    }
};
#if BOOST_COMP_GNUC
#pragma GCC pop_options
#elif BOOST_COMP_MSVC || BOOST_COMP_INTEL
#pragma optimize( "", on )
#endif


/// \brief  Given     permits to calculate the regression coefficients used in the kernel method : specialized template
///  \param  p_zl     permits to locate  in each direction  the first left 'slice' in the method  belonging to the kernel associated to a grid point in z
///  \param  p_zr     permits to locate  in each direction the first right  'slice' in the method not belonging to the kernel associated to a grid point in z
///  \param  p_z      grid points in each direction
///  \param  p_h      kernel bandwidth in in each direction for each point
///  \param  p_sA     stores the \f$  \sum (x^{(m)})^i (x^{(m)})^j (x^{(m)})^l \f$  in each dimension for each slice and is  used to contruct the regression matrices
///  \param  p_sB     stores the \f$  \sum (x^{(m)})^i (x^{(m)})^j \f$  in each dimension for each slice and is used to construct the second member of the regression matrix.
///  \param  p_dimSA  structure of the p_sA boost array
///  \param  p_dimSB  structure of the p_sB boost array
///  \param  p_tabToIndexA utilitary to get power of coordinates of the particle used by regression matrix reconstruction
///  \param  p_tabToIndexB utilitary to get power of coordinates of the particle used by second member reconstruction
///  \param  p_idx    index used for grid points while iteration on dimensions
///  \param  p_p      get regressed values at p_z points
///  \param  p_rsize  regression matrix size is (p_rsize,p_rsize)
///  \param  p_zc     permits to store coefficient assocated to the considered z point and appearing while exanding the Epanechnikov  kernel in each direction
///  \param  p_p      get regressed values at p_z points
#if BOOST_COMP_GNUC
#pragma GCC push_options
#pragma GCC optimize ("O1")
#elif BOOST_COMP_MSVC || BOOST_COMP_INTEL
#pragma optimize( "", off )
#endif
template<  int ND >
class GridValLinStable<ND, ND>
{
public:
    void val(const vector< shared_ptr<ArrayXi> >    &p_zl,
             const vector< shared_ptr<ArrayXi> >    &p_zr,
             const vector< shared_ptr<ArrayXd> > &p_z,
             const vector< shared_ptr<ArrayXd> > &p_h,
             const boost::multi_array<double, 2> &p_sA,
             const boost::multi_array<double, 2> &p_sB,
             const boost::array< typename boost::multi_array<double, 2>::index, 2>   &p_dimSA,
             const boost::array< typename boost::multi_array<double, 2>::index, 2>   &p_dimSB,
             const boost::multi_array < int, 3> &p_tabToIndexA,
             const boost::multi_array < int, 2> &p_tabToIndexB,
             boost::array< typename  boost::multi_array<double, ND>::index, ND> &p_idx,
             ArrayXd &p_zVal,
             const int &p_rsize,
             ArrayXXd &p_zc,
             boost::multi_array<double, ND> &p_p,
             const double &p_tick
            )
    {
        boost::array< typename  boost::multi_array<double, 1>::index, 1> newDimSA;
        newDimSA[0] = p_dimSA[1];
        boost::array< typename  boost::multi_array<double, 1>::index, 1> newDimSB;
        newDimSB[0] = p_dimSB[1];
        boost::multi_array<double, 1>  newSA(newDimSA) ;
        for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA)
            *iterSA = 0. ;
        boost::multi_array<double, 1>  newSB(newDimSB) ;
        for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB)
            *iterSB = 0. ;
        const ArrayXi &zl = *p_zl[ND - 1];
        const ArrayXi &zr = *p_zr[ND - 1];
        const ArrayXd &h = *p_h[ND - 1];
        int ip = 0;
        int im = 0;

        // initialization round error
        boost::multi_array < double,  1 >  newSARound(newDimSA) ;
        {
            for (auto iterSA = newSARound.origin(); iterSA != (newSARound.origin() + newSARound.num_elements()); ++iterSA) //,++intersARound)
                *iterSA = 0. ;
        }
        boost::multi_array < double,  1 >  newSBRound(newDimSB) ;
        {
            for (auto iterSB = newSBRound.origin(); iterSB != (newSBRound.origin() + newSBRound.num_elements()); ++iterSB) //,++intersBRound)
                *iterSB = 0. ;
        }


        for (int i = 0; i < p_z[ND - 1]->size(); ++i)
        {
            double z = (*p_z[ND - 1])(i);
            p_idx[ND - 1] = i;
            p_zVal(ND - 1) = z;
            double h2d = h(i) * h(i) * ND;
            p_zc(0, ND - 1) = 0.75 * (1. / ND - z * z / h2d);
            p_zc(1, ND - 1) = 0.75 * (2.*z / h2d);
            p_zc(2, ND - 1) = 0.75 * (-1. / h2d);

            while (ip < zr(i))
            {
                auto iterpsA = p_sA[ip].origin();
                auto intersARound = newSARound.origin();
                for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA, ++iterpsA, ++intersARound)
                {
                    addFastKernel(*iterpsA, *intersARound, *iterSA);
                }
                auto iterpsB = p_sB[ip].origin();
                auto intersBRound = newSBRound.origin();
                for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB, ++iterpsB, ++intersBRound)
                {
                    addFastKernel(*iterpsB, *intersBRound, *iterSB);
                }
                ip += 1;
            }
            while (im < zl(i))
            {
                auto iterpsA = p_sA[im].origin();
                auto intersARound = newSARound.origin();
                for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA, ++iterpsA, ++intersARound)
                {
                    minusFastKernel(*iterpsA, *intersARound, *iterSA);
                }
                auto iterpsB = p_sB[im].origin();
                auto intersBRound = newSBRound.origin();
                for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB, ++iterpsB, ++intersBRound)
                {
                    minusFastKernel(*iterpsB, *intersBRound, *iterSB);
                }
                im += 1;
            }

            if (std::fabs(*newSA.origin()) < 1e-20)
                p_p(p_idx) = 1e30;
            else
            {
                // regression
                MatrixXd A = MatrixXd::Zero(p_rsize, p_rsize);
                VectorXd B = VectorXd::Zero(p_rsize);
                for (int ii = 0; ii < p_rsize; ++ii)
                {

                    for (int kk = 0 ; kk < ND; ++kk)
                    {
                        B(ii) += p_zc(0, kk) * newSB[p_tabToIndexB[ii][0]] + p_zc(1, kk) * newSB[p_tabToIndexB[ii][1 + kk]] +  p_zc(2, kk) * newSB[p_tabToIndexB[ii][1 + ND + kk]];
                    }
                    for (int jj = 0 ; jj < p_rsize; ++jj)
                        for (int kk = 0 ; kk < ND; ++kk)
                        {
                            A(ii, jj) += p_zc(0, kk) * newSA[p_tabToIndexA[ii][jj][0]] +  p_zc(1, kk) *  newSA[p_tabToIndexA[ii][jj][1 + kk]] +  p_zc(2, kk) * newSA[p_tabToIndexA[ii][jj][1 + kk + ND]] ;
                        }
                }

                for (int kk = 0; kk < p_rsize; ++kk)
                {
                    A(kk, kk) += p_tick;
                }
                LLT<MatrixXd>  lltA(A);
                VectorXd coeff = lltA.solve(B);
                double val = coeff(0);
                for (int id = 0; id < ND; ++id)
                    val += p_zVal(id) * coeff(id + 1);
                p_p(p_idx) = val ;
            }
        }
    }
};
#if BOOST_COMP_GNUC
#pragma GCC pop_options
#elif BOOST_COMP_MSVC || BOOST_COMP_INTEL
#pragma optimize( "", on )
#endif

/// \brief Perform  a ND regression
/// \param  p_x       particles (possibly with a diminution for the dimension)
/// \param  p_y       function value to regress (renormalized)
/// \param  p_z       grid points for regressions (in each dimension)
/// \param  p_h       bandwith for each point in p_z (in each dimension)
/// \param  p_g       storing all point in p_z + /- p_h (in each dimension)
/// \param  p_xG      for each dimension the mesh used in p_g for all simulations
/// \param  p_zl      for each dimension, indicate in which point in p_g  correspond a  point  in  p_z-p_h
/// \param  p_zr      for each dimension, indicate in which point in p_g   correspond a  point  in  p_z+p_h
/// \param  p_tick    tick to help regresssion matrix
/// \return  An array of the all the regressed values at the p_z points
template< int ND >
boost::multi_array< double, ND>  performNdRegLinStable(const ArrayXXd   &p_x, const VectorXd   &p_y,
        const vector< shared_ptr<ArrayXd> > &p_z,
        const vector< shared_ptr<ArrayXd> > &p_h,
        const vector< shared_ptr<ArrayXd> > &p_g,
        const ArrayXXi &p_xG,
        const vector< shared_ptr<ArrayXi> > &p_zl,
        const vector< shared_ptr<ArrayXi> > &p_zr,
        const double &p_tick)
{
    assert(ND == p_x.rows());
    boost::array< typename boost::multi_array<double, ND>::index, ND  > nz;
    for (int id = 0; id < ND; ++id)
        nz[id] = p_z[id]->size();
    boost::array< typename boost::multi_array<double, ND>::index, ND  > ng;
    for (int id = 0; id < ND; ++id)
        ng[id] = p_g[id]->size();

    int  rsize = 1 + ND;

    ArrayXXd xPow = ArrayXXd::Zero(5, ND + 1);
    boost::array < typename boost::multi_array < double, ND + 1 >::index, ND + 1  > dimSA;
    for (int id = 0; id < ND; ++id)
        dimSA[id] = ng[id] - 1;
    dimSA[ND] = 1 + 3 * ND * ND + ND + 2 * (ND * (ND - 1) * (ND - 2) / 3);
    boost::array < typename boost::multi_array < double, ND + 1 >::index, ND + 1  > dimSB;
    for (int id = 0; id < ND; ++id)
        dimSB[id] = ng[id] - 1;
    dimSB[ND] = 3 * ((ND * (ND + 1)) / 2) + 1;
    boost::multi_array < double, ND + 1 > sA(dimSA);
    for (auto iterSA = sA.origin(); iterSA != (sA.origin() + sA.num_elements()); ++iterSA)
        *iterSA = 0. ;
    boost::multi_array < double, ND + 1 > sB(dimSB);
    for (auto iterSB = sB.origin(); iterSB != (sB.origin() + sB.num_elements()); ++iterSB)
        *iterSB = 0. ;
    // create helper
    vector<array<size_t, 4> >  indexB;
    indexB.reserve(3 * ((ND * (ND + 1)) / 2) + 1);
    boost::multi_array < int, 2> tabToIndexB(boost::extents [1 + ND] [1 + 2 * ND]);
    createLinearIndexB<ND>(indexB, tabToIndexB);
    vector<array<size_t, 6> >  indexA;
    indexA.reserve(1 + 3 * ND * ND + ND + 2 * (ND * (ND - 1) * (ND - 2) / 3));
    boost::multi_array < int, 3> tabToIndexA(boost::extents [1 + ND] [1 + ND] [1 + 2 * ND]);
    createLinearIndexA<ND>(indexA, tabToIndexA);

    boost::array <  typename boost::multi_array < double, ND + 1 >::index, ND + 1  > idxSA;
    boost::array <  typename boost::multi_array < double, ND + 1 >::index, ND + 1  > idxSB;
    for (int i = 0; i < p_x.cols(); ++i)
    {
        for (int idim = 0; idim < ND; ++idim)
        {
            idxSA[idim] = p_xG(idim, i);
            idxSB[idim] = p_xG(idim, i);
        }
        for (int k = 0; k < 5; ++k)
            xPow(k, 0) = 1. ;
        for (int j = 0; j < p_x.rows(); ++j)
        {
            double curr = 1;
            for (int k = 0; k < 5; ++k)
            {
                xPow(k, j + 1) = curr;
                curr *= p_x(j, i);
            }
        }
        for (size_t iB = 0; iB < indexB.size(); ++iB)
        {
            idxSB[ND] = iB;
            sB(idxSB) += xPow(indexB[iB][1], indexB[iB][0]) * xPow(indexB[iB][3], indexB[iB][2]) * p_y(i);
        }
        for (size_t iA = 0; iA < indexA.size(); ++iA)
        {
            idxSA[ND] = iA;
            sA(idxSA) += xPow(indexA[iA][1], indexA[iA][0]) * xPow(indexA[iA][3], indexA[iA][2]) * xPow(indexA[iA][5], indexA[iA][4]);
        }
    }


    boost::array< typename  boost::multi_array<double, ND>::index, ND>   idx;
    for (int id = 0; id < ND; ++id)
        idx[id] = 0. ;
    ArrayXd  zVal = ArrayXd::Zero(ND);

    boost::multi_array< double, ND> p(nz);
    ArrayXXd zc = ArrayXXd::Zero(3, ND);

    GridValLinStable<ND, 1>().val(p_zl, p_zr, p_z, p_h, sA, sB,  dimSA, dimSB,  tabToIndexA, tabToIndexB, idx, zVal, rsize, zc, p, p_tick);

    return p;
}



/// \brief  Given          permits to calculate the regression coefficients used in the kernel method : constant regression. Stable version.
///  \param  p_zl          permits to locate  in each direction  the first left 'slice' in the method  belonging to the kernel associated to a grid point in z
///  \param  p_zr          permits to locate  in each direction the first right  'slice' in the method not belonging to the kernel associated to a grid point in z
///  \param  p_z           grid points in each direction
///  \param  p_h           kernel bandwidth in in each direction for each point
///  \param  p_sA          stores the \f$  \sum (x^{(m)})^i  \f$  in each dimension for each slice and is  used to contruct the regression matrices
///  \param  p_sB          stores the \f$  \sum (x^{(m)})^i p_y^i \f$  in each dimension for each slice and is used to construct the second member of the regression matrix.
///  \param  p_dimS        structure of the p_sA et  p_sB boost array
///  \param  p_idx         index used for grid points while iteration on dimensions
///  \param  p_zVal        associated coordinates values for current point with p_idex index
///  \param  p_zc          permits to store coefficient assocated to the considered z point and appearing while expanding the Epanechnikov  kernel in each direction
///  \param  p_p           get regressed values at p_z points
#if BOOST_COMP_GNUC
#pragma GCC push_options
#pragma GCC optimize ("O1")
#elif BOOST_COMP_MSVC || BOOST_COMP_INTEL
#pragma optimize( "", off )
#endif
template<  int ND, int NDIM >
class GridValConstStable
{
public:
    void val(const vector< shared_ptr<ArrayXi> >    &p_zl,
             const vector< shared_ptr<ArrayXi> >    &p_zr,
             const vector< shared_ptr<ArrayXd> > &p_z,
             const vector< shared_ptr<ArrayXd> > &p_h,
             const boost::multi_array < double, ND - NDIM + 2 > & p_sA,
             const boost::multi_array < double, ND - NDIM + 2 > & p_sB,
             const boost::array < typename boost::multi_array < double, ND - NDIM + 2 >::index, ND - NDIM + 2  >  & p_dimS,
             boost::array< typename  boost::multi_array<double, ND>::index, ND> &p_idx,
             ArrayXd &p_zVal,
             ArrayXXd &p_zc,
             boost::multi_array<double, ND> &p_p,
             const double &p_tick
            )
    {
        boost::array < typename  boost::multi_array < double, ND - NDIM + 1 >::index, ND - NDIM + 1 > newDimS;
        for (int id = 1; id < ND - NDIM + 2; ++id)
            newDimS[id - 1] = p_dimS[id];
        boost::multi_array < double, ND - NDIM + 1 >  newSA(newDimS) ;
        for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA)
            *iterSA = 0. ;
        boost::multi_array < double, ND - NDIM + 1 >  newSB(newDimS) ;
        for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB)
            *iterSB = 0. ;
        const ArrayXi &zl = *p_zl[NDIM - 1];
        const ArrayXi &zr = *p_zr[NDIM - 1];
        const ArrayXd &h = *p_h[NDIM - 1];
        int ip = 0;
        int im = 0;

        // initialization round error
        boost::multi_array < double, ND - NDIM +  1 >  newSARound(newDimS) ;
        {
            for (auto iterSA = newSARound.origin(); iterSA != (newSARound.origin() + newSARound.num_elements()); ++iterSA) //,++intersARound)
                *iterSA = 0. ;
        }
        boost::multi_array < double, ND - NDIM +   1 >  newSBRound(newDimS) ;
        {
            for (auto iterSB = newSBRound.origin(); iterSB != (newSBRound.origin() + newSBRound.num_elements()); ++iterSB) //,++intersBRound)
                *iterSB = 0. ;
        }

        for (int i = 0; i < p_z[NDIM - 1]->size(); ++i)
        {
            double z = (*p_z[NDIM - 1])(i);
            p_idx[NDIM - 1] = i;
            p_zVal(NDIM - 1) = z;
            double h2d = h(i) * h(i) * ND;
            p_zc(0, NDIM - 1) = 0.75 * (1. / ND - z * z / h2d);
            p_zc(1, NDIM - 1) = 0.75 * (2.*z / h2d);
            p_zc(2, NDIM - 1) = 0.75 * (-1. / h2d);
            while (ip < zr(i))
            {
                auto iterpsA = p_sA[ip].origin();
                auto intersARound = newSARound.origin();
                for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA, ++iterpsA, ++intersARound)
                {
                    addFastKernel(*iterpsA, *intersARound, *iterSA);
                }
                auto iterpsB = p_sB[ip].origin();
                auto intersBRound = newSBRound.origin();
                for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB, ++iterpsB, ++intersBRound)
                {
                    addFastKernel(*iterpsB, *intersBRound, *iterSB);
                }
                ip += 1;
            }
            while (im < zl(i))
            {
                auto iterpsA = p_sA[im].origin();
                auto intersARound = newSARound.origin();
                for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA, ++iterpsA, ++intersARound)
                {
                    minusFastKernel(*iterpsA, *intersARound, *iterSA);
                }
                auto iterpsB = p_sB[im].origin();
                auto intersBRound = newSBRound.origin();
                for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB, ++iterpsB, ++intersBRound)
                {
                    minusFastKernel(*iterpsB, *intersBRound, *iterSB);
                }
                im += 1;
            }
            GridValConstStable < ND, NDIM + 1 > ().val(p_zl,  p_zr, p_z, p_h, newSA, newSB, newDimS,  p_idx, p_zVal,  p_zc, p_p, p_tick);
        }
    }
};
#if BOOST_COMP_GNUC
#pragma GCC pop_options
#elif BOOST_COMP_MSVC || BOOST_COMP_INTEL
#pragma optimize( "", on )
#endif





/// \brief  Given          permits to calculate the regression coefficients used in the kernel method : specialized template for constant regression
///  \param  p_zl          permits to locate  in each direction  the first left 'slice' in the method  belonging to the kernel associated to a grid point in z
///  \param  p_zr          permits to locate  in each direction the first right  'slice' in the method not belonging to the kernel associated to a grid point in z
///  \param  p_z           grid points in each direction
///  \param  p_h           kernel bandwidth in in each direction for each point
///  \param  p_sA          stores the \f$  \sum (x^{(m)})^i (x^{(m)})^j (x^{(m)})^l \f$  in each dimension for each slice and is  used to contruct the regression matrices
///  \param  p_sB          stores the \f$  \sum (x^{(m)})^i (x^{(m)})^j \f$  in each dimension for each slice and is used to construct the second member of the regression matrix.
///  \param  p_dimS        structure of the p_sA and  p_sB boost array
///  \param  p_idx         index used for grid points while iteration on dimensions
///  \param  p_p           get regressed values at p_z points
///  \param  p_zc          permits to store coefficient assocated to the considered z point and appearing while exanding the Epanechnikov  kernel in each direction
///  \param  p_p           get regressed values at p_z points

#if BOOST_COMP_GNUC
#pragma GCC push_options
#pragma GCC optimize ("O1")
#elif BOOST_COMP_MSVC || BOOST_COMP_INTEL
#pragma optimize( "", off )
#endif
template<  int ND >
class GridValConstStable<ND, ND>
{
public:
    void val(const vector< shared_ptr<ArrayXi> >    &p_zl,
             const vector< shared_ptr<ArrayXi> >    &p_zr,
             const vector< shared_ptr<ArrayXd> > &p_z,
             const vector< shared_ptr<ArrayXd> > &p_h,
             const boost::multi_array<double, 2> &p_sA,
             const boost::multi_array<double, 2> &p_sB,
             const boost::array< typename boost::multi_array<double, 2>::index, 2>   &p_dimS,
             boost::array< typename  boost::multi_array<double, ND>::index, ND> &p_idx,
             ArrayXd &p_zVal,
             ArrayXXd &p_zc,
             boost::multi_array<double, ND> &p_p,
             const double &p_tick
            )
    {
        boost::array< typename  boost::multi_array<double, 1>::index, 1> newDimS;
        newDimS[0] = p_dimS[1];
        boost::multi_array<double, 1>  newSA(newDimS) ;
        for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA)
            *iterSA = 0. ;
        boost::multi_array<double, 1>  newSB(newDimS) ;
        for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB)
            *iterSB = 0. ;
        const ArrayXi &zl = *p_zl[ND - 1];
        const ArrayXi &zr = *p_zr[ND - 1];
        const ArrayXd &h = *p_h[ND - 1];
        int ip = 0;
        int im = 0;

        // initialization round error
        boost::multi_array < double,  1 >  newSARound(newDimS) ;
        {
            for (auto iterSA = newSARound.origin(); iterSA != (newSARound.origin() + newSARound.num_elements()); ++iterSA) //,++intersARound)
                *iterSA = 0. ;
        }
        boost::multi_array < double,  1 >  newSBRound(newDimS) ;
        {
            for (auto iterSB = newSBRound.origin(); iterSB != (newSBRound.origin() + newSBRound.num_elements()); ++iterSB) //,++intersBRound)
                *iterSB = 0. ;
        }

        for (int i = 0; i < p_z[ND - 1]->size(); ++i)
        {
            double z = (*p_z[ND - 1])(i);
            p_idx[ND - 1] = i;
            p_zVal(ND - 1) = z;
            double h2d = h(i) * h(i) * ND;
            p_zc(0, ND - 1) = 0.75 * (1. / ND - z * z / h2d);
            p_zc(1, ND - 1) = 0.75 * (2.*z / h2d);
            p_zc(2, ND - 1) = 0.75 * (-1. / h2d);

            while (ip < zr(i))
            {
                auto iterpsA = p_sA[ip].origin();
                auto intersARound = newSARound.origin();
                for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA, ++iterpsA, ++intersARound)
                {
                    addFastKernel(*iterpsA, *intersARound, *iterSA);
                }
                auto iterpsB = p_sB[ip].origin();
                auto intersBRound = newSBRound.origin();
                for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB, ++iterpsB, ++intersBRound)
                {
                    addFastKernel(*iterpsB, *intersBRound, *iterSB);
                }
                ip += 1;
            }
            while (im < zl(i))
            {
                auto iterpsA = p_sA[im].origin();
                auto intersARound = newSARound.origin();
                for (auto iterSA = newSA.origin(); iterSA != (newSA.origin() + newSA.num_elements()); ++iterSA, ++iterpsA, ++intersARound)
                {
                    minusFastKernel(*iterpsA, *intersARound, *iterSA);
                }
                auto iterpsB = p_sB[im].origin();
                auto intersBRound = newSBRound.origin();
                for (auto iterSB = newSB.origin(); iterSB != (newSB.origin() + newSB.num_elements()); ++iterSB, ++iterpsB, ++intersBRound)
                {
                    minusFastKernel(*iterpsB, *intersBRound, *iterSB);
                }
                im += 1;
            }

            if (std::fabs(*newSA.origin()) < 1e-20)
                p_p(p_idx) = 1e30;
            else
            {
                // regression
                double B = 0; // second member
                double A  = p_tick ; // constant 1D regressoin matrix
                for (int kk = 0 ; kk < ND; ++kk)
                {
                    B += p_zc(0, kk) * newSB[0] + p_zc(1, kk) * newSB[1 + 2 * kk] +  p_zc(2, kk) * newSB[2 * (kk + 1)];
                    A += p_zc(0, kk) * newSA[0] +  p_zc(1, kk) *  newSA[1 + 2 * kk] +  p_zc(2, kk) * newSA[2 * (kk + 1)] ;
                }
                double val = B / A;
                p_p(p_idx) = val ;
            }
        }
    }
};
#if BOOST_COMP_GNUC
#pragma GCC pop_options
#elif BOOST_COMP_MSVC || BOOST_COMP_INTEL
#pragma optimize( "", on )
#endif


/// \brief Perform  a ND regression
/// \param  p_x       particles (possibly with a diminution for the dimension)
/// \param  p_y       function value to regress (renormalized)
/// \param  p_z       grid points for regressions (in each dimension)
/// \param  p_h       bandwith for each point in p_z (in each dimension)
/// \param  p_g       storing all point in p_z + /- p_h (in each dimension)
/// \param  p_xG      for each dimension the mesh used in p_g for all simulations
/// \param  p_zl      for each dimension, indicate in which point in p_g  correspond a  point  in  p_z-p_h
/// \param  p_zr      for each dimension, indicate in which point in p_g   correspond a  point  in  p_z+p_h
/// \param  p_tick    tick to help regresssion matrix
/// \return  An array of the all the regressed values at the p_z points
template< int ND >
boost::multi_array< double, ND>  performNdRegConstStable(const ArrayXXd   &p_x, const VectorXd   &p_y,
        const vector< shared_ptr<ArrayXd> > &p_z,
        const vector< shared_ptr<ArrayXd> > &p_h,
        const vector< shared_ptr<ArrayXd> > &p_g,
        const ArrayXXi &p_xG,
        const vector< shared_ptr<ArrayXi> > &p_zl,
        const vector< shared_ptr<ArrayXi> > &p_zr,
        const double &p_tick)
{
    assert(ND == p_x.rows());
    boost::array< typename boost::multi_array<double, ND>::index, ND  > nz;
    for (int id = 0; id < ND; ++id)
        nz[id] = p_z[id]->size();
    boost::array< typename boost::multi_array<double, ND>::index, ND  > ng;
    for (int id = 0; id < ND; ++id)
        ng[id] = p_g[id]->size();

    ArrayXXd xPow = ArrayXXd::Zero(3, ND + 1);
    boost::array < typename boost::multi_array < double, ND + 3 >::index, ND + 1  > dimS;
    for (int id = 0; id < ND; ++id)
        dimS[id] = ng[id] - 1;
    dimS[ND] = 1 + 2 * ND;

    boost::multi_array < double, ND + 1 > sA(dimS);
    for (auto iterSA = sA.origin(); iterSA != (sA.origin() + sA.num_elements()); ++iterSA)
        *iterSA = 0. ;
    boost::multi_array < double, ND + 1 > sB(dimS);
    for (auto iterSB = sB.origin(); iterSB != (sB.origin() + sB.num_elements()); ++iterSB)
        *iterSB = 0. ;
    boost::array <  typename boost::multi_array < double, ND + 1 >::index, ND + 1  > idxS;
    for (int i = 0; i < p_x.cols(); ++i)
    {
        for (int idim = 0; idim < ND; ++idim)
        {
            idxS[idim] = p_xG(idim, i);
        }
        for (int k = 0; k < 3; ++k)
            xPow(k, 0) = 1. ;
        for (int j = 0; j < p_x.rows(); ++j)
        {
            double curr = 1;
            for (int k = 0; k < 3; ++k)
            {
                xPow(k, j + 1) = curr;
                curr *= p_x(j, i);
            }
        }
        // constants
        int indexS = 0;
        idxS[ND] = indexS++;
        sB(idxS) += p_y(i);
        sA(idxS) += 1;
        // degre 1 and 2 in each dimension
        for (size_t id = 0; id < ND; ++id)
        {
            idxS[ND] = indexS++;
            sB(idxS) += xPow(1, id + 1) * p_y(i);
            sA(idxS) += xPow(1, id + 1);
            idxS[ND] = indexS++;
            sB(idxS) += xPow(2, id + 1) * p_y(i);
            sA(idxS) += xPow(2, id + 1);
        }
    }

    boost::array< typename  boost::multi_array<double, ND>::index, ND>   idx;
    for (int id = 0; id < ND; ++id)
        idx[id] = 0. ;
    ArrayXd  zVal = ArrayXd::Zero(ND);

    boost::multi_array< double, ND> p(nz);
    ArrayXXd zc = ArrayXXd::Zero(3, ND);

    GridValConstStable<ND, 1>().val(p_zl, p_zr, p_z, p_h, sA, sB, dimS,  idx, zVal, zc, p, p_tick);

    return p;
}

/// \brief  calculate the regresses values on a grid
/// \param  p_newX    renormalized particle
/// \param  p_Y       second member to regress
/// \param  p_z       grid points for regressions (in each dimension)
/// \param  p_h       bandwith for each point in p_z (in each dimension)
/// \param  p_g       storing all point in p_z + /- p_h (in each dimension)
/// \param  p_xG      permits to affect each particle to a slice defined by p_g in each direction
/// \param  p_zl      for each dimension, indicate in which point in p_g  correspond a  point  in  p_z-p_h
/// \param  p_zr      for each dimension, indicate in which point in p_g   correspond a  point  in  p_z+p_h
/// \param  p_bLinear true if we use linear regressions
/// \param  p_tick    tick to help regresssion matrix
/// \return An array with all the regressed value at p_z points  + meanY + sigmY
ArrayXd  gridKernelRegressedValuesOnGrid(const ArrayXXd   &p_newX,
        const ArrayXd &p_y,
        const vector< shared_ptr<ArrayXd> >   &p_z,
        const vector< shared_ptr<ArrayXd> >   &p_h,
        const vector<  shared_ptr<ArrayXd> > &p_g,
        const ArrayXXi &p_xG,
        const vector< shared_ptr<ArrayXi> > &p_zl,
        const vector< shared_ptr<ArrayXi> > &p_zr,
        const bool &p_bLinear,
        double  p_tick)

{
    // renormalized
    double meanY = p_y.mean();
    double stdY = sqrt((p_y - meanY).pow(2).mean());
    ArrayXd newY = p_y - meanY;
    if (stdY >  1e3 * std::numeric_limits<double>::epsilon())
        newY /= stdY;

    switch (p_newX.rows())
    {
    case (1):
    {
        boost::multi_array< double, 1>  yHatZ = (p_bLinear ? performNdRegLin<1>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConst<1>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }

    case (2):
    {
        boost::multi_array< double, 2>  yHatZ = (p_bLinear ? performNdRegLin<2>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConst<2>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (3):
    {
        boost::multi_array< double, 3>  yHatZ = (p_bLinear ? performNdRegLin<3>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConst<3>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));

        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (4):
    {
        boost::multi_array<double, 4>  yHatZ = (p_bLinear ? performNdRegLin<4>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConst<4>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (5):
    {
        boost::multi_array<double, 5>  yHatZ = (p_bLinear ? performNdRegLin<5>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConst<5>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (6):
    {
        boost::multi_array<double, 6>  yHatZ = (p_bLinear ? performNdRegLin<6>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConst<6>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (7):
    {
        boost::multi_array<double, 7>  yHatZ = (p_bLinear ? performNdRegLin<7>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConst<7>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (8):
    {
        boost::multi_array<double, 8>  yHatZ = (p_bLinear ? performNdRegLin<8>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConst<8>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    default:
    {
        cout << " ERROR : DIM SHOULD BELOW 9" << std::endl ;
        abort();
    }
    }
    return ArrayXd();
}


/// \brief  calculate the regresses values on a grid
/// \param  p_newX    renormalized particle
/// \param  p_Y       second member to regress
/// \param  p_z       grid points for regressions (in each dimension)
/// \param  p_h       bandwith for each point in p_z (in each dimension)
/// \param  p_g       storing all point in p_z + /- p_h (in each dimension)
/// \param  p_xG      permits to affect each particle to a slice defined by p_g in each direction
/// \param  p_zl      for each dimension, indicate in which point in p_g  correspond a  point  in  p_z-p_h
/// \param  p_zr      for each dimension, indicate in which point in p_g   correspond a  point  in  p_z+p_h
/// \param  p_bLinear true if we use linear regressions
/// \param  p_tick    tick to help regresssion matrix
/// \return An array with all the regressed value at p_z points  + meanY + sigmY
ArrayXd  gridKernelRegressedValuesOnGridStable(const ArrayXXd   &p_newX,
        const ArrayXd &p_y,
        const vector< shared_ptr<ArrayXd> >   &p_z,
        const vector< shared_ptr<ArrayXd> >   &p_h,
        const vector<  shared_ptr<ArrayXd> > &p_g,
        const ArrayXXi &p_xG,
        const vector< shared_ptr<ArrayXi> > &p_zl,
        const vector< shared_ptr<ArrayXi> > &p_zr,
        const bool &p_bLinear,
        double  p_tick)

{

    // renormalized
    double meanY = p_y.mean();
    double stdY = sqrt((p_y - meanY).pow(2).mean());
    ArrayXd newY = p_y - meanY;
    if (stdY >  1e3 * std::numeric_limits<double>::epsilon())
        newY /= stdY;

    switch (p_newX.rows())
    {
    case (1):
    {
        boost::multi_array< double, 1>  yHatZ = (p_bLinear ? performNdRegLinStable<1>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConstStable<1>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }

    case (2):
    {
        boost::multi_array< double, 2>  yHatZ = (p_bLinear ? performNdRegLinStable<2>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConstStable<2>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (3):
    {
        boost::multi_array< double, 3>  yHatZ = (p_bLinear ? performNdRegLinStable<3>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConstStable<3>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (4):
    {
        boost::multi_array<double, 4>  yHatZ = (p_bLinear ? performNdRegLinStable<4>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConstStable<4>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (5):
    {
        boost::multi_array<double, 5>  yHatZ = (p_bLinear ? performNdRegLinStable<5>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConstStable<5>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (6):
    {
        boost::multi_array<double, 6>  yHatZ = (p_bLinear ? performNdRegLinStable<6>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConstStable<6>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (7):
    {
        boost::multi_array<double, 7>  yHatZ = (p_bLinear ? performNdRegLinStable<7>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConstStable<7>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (8):
    {
        boost::multi_array<double, 8>  yHatZ = (p_bLinear ? performNdRegLinStable<8>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConstStable<8>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    default:
    {
        cout << " ERROR : DIM BELOW 9" << std::endl ;
        abort();
    }
    }
    return ArrayXd();
}


/// \brief Interpolation for one particule by Shepard kernel interpolation
/// \param  p_x   particles values (normalized)
/// \param  p_y   regressed values on the grid p_z
/// \param  p_z   grid values
/// \param  p_nz  dimension of the grid p_z
/// \param  p_xz  position of the particle in the grid
template<int ND>
double interpNdShephard(const ArrayXd  &p_x, const ArrayXd    &p_y,
                        const vector< shared_ptr<ArrayXd> > &p_z,
                        const array<int, ND> &p_nz,
                        const array<int, ND> &p_xz)
{
    double numer = 0;
    double denom = 0;

    // position in y
    boost::array < int, ND  >  idec ;
    int iposY = p_xz[ND - 1];
    idec[ND - 1] = 1;
    for (int id = ND - 2; id >= 0  ; --id)
    {
        idec[id] = idec[id + 1] * p_nz[id + 1];
        iposY += p_xz[id] * idec[id];
    }
    // precompute weights
    Array<double, ND, 2>  weights;
    for (int id = 0; id < ND; ++id)
    {
        double dz = (*p_z[id])(p_xz[id] + 1) - (*p_z[id])(p_xz[id]);
        weights(id, 0) = 1. - pow(((*p_z[id])(p_xz[id]) - p_x(id)) / dz, 2.);
        weights(id, 1) = 1. - pow(((*p_z[id])(p_xz[id] + 1) - p_x(id)) / dz, 2.);
        assert(weights(id, 0) >= - 1e3 * std::numeric_limits<double>::epsilon());
        assert(weights(id, 1) >= - 1e3 * std::numeric_limits<double>::epsilon());
    }
    for (int j = 0 ; j < pow(2, ND) ; ++j)
    {
        unsigned int ires = j ;
        int iposYNext = iposY;
        double weightToAdd = 0;
        for (int id = ND - 1 ; id >= 0  ; --id)
        {
            unsigned int iShift = (ires >> id) ;
            iposYNext += iShift * idec[id];
            // add weight
            weightToAdd += weights(id, iShift);
            ires -= (iShift << id);
        }
        // nest on dimension
        numer += weightToAdd * p_y(iposYNext);
        denom += weightToAdd;
    }
    return numer / denom;
}


/// \brief Interpolation based on a grid with values
/// \param  p_x   particles values (normalized)
/// \param  p_y   regressed values on the grid p_z
/// \param  p_z   grid values
/// \param   p_iSort sorted particles by their number
template<int ND>
ArrayXd interpNd(const ArrayXXd &p_x, const ArrayXd    &p_y,  const vector< shared_ptr<ArrayXd> > &p_z,
                 const ArrayXXi &p_iSort)
{
    array< int, ND  > nz;
    for (int id = 0; id < ND; ++id)
        nz[id] = p_z[id]->size();
    // assign each point to a slice
    ArrayXXi xz(ND, p_x.cols());
    for (int k = 0; k < ND; ++k)
    {
        int iz = 0;
        int nk = nz[k];
        for (int i = 0; i < p_x.cols(); ++i)
        {
            int isort = p_iSort(k, i);
            double pxUp = p_x(k, isort) - std::fabs(p_x(k, isort)) * 1e3 * std::numeric_limits<double>::epsilon();
            while (((iz + 1) < nk) && ((*p_z[k])(iz + 1) < pxUp))
            {
                iz = iz + 1;
            }
            xz(k, isort) = iz ;
        }
    }

    ArrayXd ret(p_x.cols());

    for (int  is = 0; is < ret.size(); ++is)
    {

        array<int, ND> xzLoc;
        for (int id = 0; id < ND; ++id)
            xzLoc[id] = xz(id, is);
        ret(is) = interpNdShephard<ND>(p_x.col(is), p_y, p_z, nz, xzLoc);
    }
    return ret;
}

/// \brief Interpolation for one point : without fast kernel summation
/// \param  p_x   a particles (normalized)
/// \param  p_y   regressed values on the grid p_z
/// \param  p_z   grd values
/// \return interpolated value
template<int ND>
double  interpNdAParticle(const ArrayXd &p_x, const ArrayXd  &p_y,  const vector< shared_ptr<ArrayXd> > &p_z)
{
    array< int, ND  > nz;
    for (int id = 0; id < ND; ++id)
        nz[id] = p_z[id]->size();

    array < int, ND  > indexMesh;
    for (int id = 0; id < ND; ++id)
    {
        int nk = nz[id];
        double pxUp = p_x(id) - std::fabs(p_x(id)) * 1e3 * std::numeric_limits<double>::epsilon();
        int iz1 ;
        int izMin = 0;
        int izMax = nk - 2;
        if ((*p_z[id])(izMax + 1) <  pxUp)
        {
            iz1 = izMax;
        }
        else if ((*p_z[id])(izMin + 1) > pxUp)
        {
            iz1 = 0;
        }
        else
        {
            while (izMax > izMin + 1)
            {
                int izMed = (izMin + izMax) / 2;
                if ((*p_z[id])(izMed + 1) < pxUp)
                    izMin = izMed;
                else
                    izMax = izMed;
            }
            iz1 = izMax;
        }
        indexMesh[id] = iz1;
    }

    return interpNdShephard<ND>(p_x, p_y, p_z, nz, indexMesh);
}

/// \brief Interpolation for one point : without fast kernel summation.
///        Used to avoid costly interpolation on all function basis
/// \param  p_x   a particles (normalized)
/// \param  p_z   grid values
/// \param  p_ptOfStock            grid point
/// \param  p_interpFuncBasis      spectral interpolator to interpolate on the basis the function value (regressed functions on all grid with
///                                spectral representation)
/// \return interpolated value
template<int ND>
double  interpNdAParticleOnBasis(const ArrayXd &p_x,   const vector< shared_ptr<ArrayXd> > &p_z,
                                 const ArrayXd &p_ptOfStock, const vector< shared_ptr<InterpolatorSpectral> > &p_interpFuncBasis)
{
    boost::array< typename boost::multi_array<std::list<int>, ND>::index, ND  > nz;
    for (int id = 0; id < ND; ++id)
        nz[id] = p_z[id]->size();

    boost::array < int, ND  > indexMesh;
    for (int id = 0; id < ND; ++id)
    {
        int nk = nz[id];
        double pxUp = p_x(id) - std::fabs(p_x(id)) * 1e3 * std::numeric_limits<double>::epsilon();
        int iz1 ;
        int izMin = 0;
        int izMax = nk - 2;
        if ((*p_z[id])(izMax + 1) <  pxUp)
        {
            iz1 = izMax;
        }
        else if ((*p_z[id])(izMin + 1) > pxUp)
        {
            iz1 = 0;
        }
        else
        {
            while (izMax > izMin + 1)
            {
                int izMed = (izMin + izMax) / 2;
                if ((*p_z[id])(izMed + 1) < pxUp)
                    izMin = izMed;
                else
                    izMax = izMed;
            }
            iz1 = izMax;
        }
        indexMesh[id] = iz1;
    }

    // position in y
    boost::array < int, ND  >  idec ;
    int iposY = indexMesh[ND - 1];
    idec[ND - 1] = 1;
    for (int id = ND - 2; id >= 0  ; --id)
    {
        idec[id] = idec[id + 1] * nz[id + 1];
        iposY += indexMesh[id] * idec[id];
    }
    double numer = 0;
    double denom = 0;

    // precompute weights
    Array<double, ND, 2>  weights;
    for (int id = 0; id < ND; ++id)
    {
        double dz = (*p_z[id])(indexMesh[id] + 1) - (*p_z[id])(indexMesh[id]);
        weights(id, 0) = 1. - pow(((*p_z[id])(indexMesh[id]) - p_x(id)) / dz, 2.);
        weights(id, 1) = 1. - pow(((*p_z[id])(indexMesh[id] + 1) - p_x(id)) / dz, 2.);
#ifndef NOCHECK_GRID
        assert(weights(id, 0) >= - 1e3 * std::numeric_limits<double>::epsilon());
        assert(weights(id, 1) >= - 1e3 * std::numeric_limits<double>::epsilon());
#endif
    }
    for (int j = 0 ; j < pow(2, ND) ; ++j)
    {
        unsigned int ires = j ;
        int iposYNext = iposY;
        double weightToAdd = 0;
        for (int id = ND - 1 ; id >= 0  ; --id)
        {
            unsigned int iShift = (ires >> id) ;
            iposYNext += iShift * idec[id];
            // add weight
            weightToAdd += weights(id, iShift);
            ires -= (iShift << id);
        }
        // interpolation for regressed function
        double yReg = p_interpFuncBasis[iposYNext]->apply(p_ptOfStock);
        // nest on dimension
        numer += weightToAdd * yReg;
        denom += weightToAdd;
    }
    return numer / denom;
}


/// \brief  calculate the regresses values  associated to the p_y points
/// \param  p_newX    renormalized particle
/// \param  p_Y       second member to regress
/// \param  p_z       grid points for regressions (in each dimension)
/// \param  p_h       bandwith for each point in p_z (in each dimension)
/// \param  p_g       storing all point in p_z + /- p_h (in each dimension)
/// \param  p_xG      permits to affect each particle to a slice defined by p_g in each direction
/// \param  p_zl      for each dimension, indicate in which point in p_g  correspond a  point  in  p_z-p_h
/// \param  p_zr      for each dimension, indicate in which point in p_g   correspond a  point  in  p_z+p_h
/// \param  p_iSort   in each direction sorted particles by their number
/// \param  p_bLinear true if linear regression
/// \param  p_tick    tick to help regresssion matrix
/// \return An array with all the regressed value for the particle p_newX
ArrayXd  gridKernelRegressedValues(const ArrayXXd   &p_newX,
                                   const ArrayXd &p_y,
                                   const vector< shared_ptr<ArrayXd> >   &p_z,
                                   const vector< shared_ptr<ArrayXd> >   &p_h,
                                   const vector<  shared_ptr<ArrayXd> > &p_g,
                                   const ArrayXXi &p_xG,
                                   const vector< shared_ptr<ArrayXi> > &p_zl,
                                   const vector< shared_ptr<ArrayXi> > &p_zr,
                                   const ArrayXXi &p_iSort,
                                   const bool &p_bLinear,
                                   double  p_tick)

{
    // renormalized
    double meanY = p_y.mean();
    double stdY = sqrt((p_y - meanY).pow(2).mean());
    ArrayXd newY = (p_y - meanY) / stdY;

    ArrayXd yReg(p_y.size());

    switch (p_newX.rows())
    {
    case (1):
    {
        boost::multi_array< double, 1>  yHatZ = (p_bLinear ? performNdRegLin<1>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConst<1>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
            yReg(p_iSort(0, ipos++)) = meanY + stdY * (*iterY);
        break;
    }

    case (2):
    {
        boost::multi_array< double, 2>  yHatZ = (p_bLinear ? performNdRegLin<2>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConst<2>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<2>(p_newX, regressed, p_z, p_iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (3):
    {
        boost::multi_array< double, 3>  yHatZ = (p_bLinear ? performNdRegLin<3>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConst<3>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<3>(p_newX, regressed, p_z, p_iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (4):
    {
        boost::multi_array<double, 4>  yHatZ = (p_bLinear ? performNdRegLin<4>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConst<4>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<4>(p_newX, regressed, p_z,  p_iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (5):
    {
        boost::multi_array<double, 5>  yHatZ = (p_bLinear ? performNdRegLin<5>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConst<5>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<5>(p_newX, regressed, p_z,  p_iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (6):
    {
        boost::multi_array<double, 6>  yHatZ = (p_bLinear ? performNdRegLin<6>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConst<6>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<6>(p_newX, regressed, p_z,  p_iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (7):
    {
        boost::multi_array<double, 7>  yHatZ = (p_bLinear ? performNdRegLin<7>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConst<7>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<7>(p_newX, regressed, p_z,  p_iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (8):
    {
        boost::multi_array<double, 8>  yHatZ = (p_bLinear ? performNdRegLin<8>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick) :
                                                performNdRegConst<8>(p_newX, newY, p_z, p_h, p_g, p_xG, p_zl, p_zr, p_tick));
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<8>(p_newX, regressed, p_z,  p_iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    default:
    {
        cout << " ERROR : DIM BELOW 9" << std::endl ;
        abort();
    }
    }
    return yReg;
}



/// \brief recursive for regression matrix calculation
/// \param  p_y       function value to regress (renormalized)
/// \param  p_z       grid points for regressions (in each dimension)
/// \param  p_h       bandwith for each point in p_z (in each dimension)
/// \param  p_tick    tick to help regresssion matrix
/// \param  p_idim    current dimension
/// \param  p_coordZ coordinates of current grid point
/// \param  p_hVal   current bandwidth in all directions
/// \param  p_indexZ      index of current point in p_z
/// \param  p_yRegressed  regressed values
/// \param  p_bLinear    true if leanr regression
template< int ND >
void RecurMat(const ArrayXXd   &p_x, const ArrayXd &p_y,
              const vector< shared_ptr<ArrayXd> > &p_z,
              const vector< shared_ptr<ArrayXd> > &p_h,
              const double &p_tick,
              const int &p_idim,
              ArrayXd &p_coordZ,
              ArrayXd &p_hVal,
              boost::array<  typename boost::multi_array<double, ND>::index, ND> &p_indexZ,
              boost::multi_array<double, ND>   &p_yRegressed,
              const bool &p_bLinear)
{
    for (int ip = 0; ip < p_z[p_idim]->size(); ++ip)
    {
        p_coordZ(p_idim) = (*p_z[p_idim])(ip);
        p_hVal(p_idim) = (*p_h[p_idim])(ip);
        if (p_idim != 0)
        {
            RecurMat<ND>(p_x, p_y, p_z, p_h, p_tick, p_idim - 1, p_coordZ, p_hVal, p_indexZ, p_yRegressed, p_bLinear);
        }
        else
        {
            if (p_bLinear)
            {
                MatrixXd A = p_tick * MatrixXd::Identity(1 + p_x.rows(), 1 + p_x.rows());
                VectorXd B = VectorXd::Zero(1 + p_x.rows());
                // nest on particle
                for (int is = 0; is < p_x.cols(); ++is)
                {
                    double kernelMatLoc = 0.;
                    for (int id = 0; id < p_x.rows() ; ++id)
                    {
                        double spread = std::fabs(p_x(id, is) - p_coordZ(id)) / p_hVal(id);
                        if (spread  <= 1.)
                        {
                            kernelMatLoc += 0.75 * (1 - spread * spread) / ND;
                        }
                        else
                        {
                            kernelMatLoc = 0;
                            break;
                        }
                    }
                    if (kernelMatLoc > std::numeric_limits<double>::epsilon())
                    {
                        // matrix
                        A(0, 0) += kernelMatLoc;
                        for (int id = 0 ; id <  p_x.rows(); ++id)
                        {
                            A(0, id + 1) += p_x(id, is) * kernelMatLoc;
                        }
                        for (int idd = 0; idd <  p_x.rows(); ++idd)
                        {
                            for (int id = idd; id < p_x.rows(); ++id)
                            {
                                A(idd + 1, id + 1) += p_x(id, is) * p_x(idd, is) * kernelMatLoc;
                            }
                        }
                        // second member
                        B(0) += kernelMatLoc * p_y(is);
                        for (int id = 0; id < p_x.rows(); ++id)
                        {
                            B(id + 1) += kernelMatLoc * p_y(is) * p_x(id, is);
                        }
                    }
                }
                // fill rest of matrix
                for (int id = 1; id <=  p_x.rows(); ++id)
                    for (int idd = 0; idd < id; ++idd)
                        A(id, idd) = A(idd, id);
                // invert
                LLT<MatrixXd>  lltA(A);
                VectorXd coeff = lltA.solve(B);
                // store
                p_yRegressed(p_indexZ) = coeff[0];
                for (int id = 0; id < ND; ++id)
                    p_yRegressed(p_indexZ) += coeff[id + 1] * p_coordZ(id);
            }
            else
            {
                double  A = p_tick;
                double B = 0;
                // nest on particle
                for (int is = 0; is < p_x.cols(); ++is)
                {
                    double kernelMatLoc = 0.;
                    for (int id = 0; id < p_x.rows() ; ++id)
                    {
                        double spread = std::fabs(p_x(id, is) - p_coordZ(id)) / p_hVal(id);
                        if (spread  <= 1.)
                        {
                            kernelMatLoc += 0.75 * (1 - spread * spread) / ND;
                        }
                        else
                        {
                            kernelMatLoc = 0;
                            break;
                        }
                    }
                    if (kernelMatLoc > std::numeric_limits<double>::epsilon())
                    {
                        // matrix
                        A += kernelMatLoc;
                        // second member
                        B += kernelMatLoc * p_y(is);
                    }
                }
                // store
                p_yRegressed(p_indexZ) = B / A;
            }
        }
        p_indexZ[p_idim] += 1;
    }
    p_indexZ[p_idim] = 0;
}



/// \brief  Perform naive kernel  regression on grid points
/// \param  p_x       particles (possibly with a diminution for the dimension)
/// \param  p_y       function value to regress (renormalized)
/// \param  p_z       grid points for regressions (in each dimension)
/// \param  p_h       bandwith for each point in p_z (in each dimension)
/// \param  p_bLinear do we use linera regressions
/// \param  p_tick    tick to help regresssion matrix
/// \return regressed values on grid points
template< int ND >
boost::multi_array<double, ND> performRegressionNaive(const ArrayXXd   &p_x, ArrayXd   &p_y,
        const vector< shared_ptr<ArrayXd> > &p_z,
        const vector< shared_ptr<ArrayXd> > &p_h,
        const bool &p_bLinear,
        const double &p_tick
                                                     )
{
    boost::array<  typename boost::multi_array<double, ND>::index, ND>  dimReg;
    for (int id = 0; id < ND; ++id)
        dimReg[id] = p_z[id]->size();
    boost::multi_array<double, ND> yRegressed(dimReg);

    ArrayXd coordZ(ND);;
    ArrayXd hVal(ND);
    boost::array<  typename boost::multi_array<double, ND>::index, ND>  zIndex;
    for (int id = 0; id < ND; ++id)
        zIndex[id] = 0;

    //  Recursive evaluation
    RecurMat<ND>(p_x, p_y, p_z, p_h, p_tick, ND - 1, coordZ, hVal, zIndex, yRegressed, p_bLinear);

    return  yRegressed;
}




/// \brief recursive for regression matrix calculation
/// \param  p_y       function value to regress (renormalized)
/// \param  p_z       grid points for regressions (in each dimension)
/// \param  p_h       bandwith for each point in p_z (in each dimension)
/// \param  p_tick    tick to help regresssion matrix
/// \param  p_idim    current dimension
/// \param  p_coordZ coordinates of current grid point
/// \param  p_hVal   current bandwidth in all directions
/// \param  p_indexZ      index of current point in p_z
/// \param  p_yRegressed  regressed values
/// \param  p_bLinear do we use linera regressions
template< int ND >
void RecurMatStab(const ArrayXXd   &p_x, const ArrayXd &p_y,
                  const vector< shared_ptr<ArrayXd> > &p_z,
                  const vector< shared_ptr<ArrayXd> > &p_h,
                  const double &p_tick,
                  const int &p_idim,
                  ArrayXd &p_coordZ,
                  ArrayXd &p_hVal,
                  boost::array<  typename boost::multi_array<double, ND>::index, ND> &p_indexZ,
                  boost::multi_array<double, ND>   &p_yRegressed,
                  const bool &p_bLinear)
{
    for (int ip = 0; ip < p_z[p_idim]->size(); ++ip)
    {
        p_coordZ(p_idim) = (*p_z[p_idim])(ip);
        p_hVal(p_idim) = (*p_h[p_idim])(ip);
        if (p_idim != 0)
        {
            RecurMatStab<ND>(p_x, p_y, p_z, p_h, p_tick, p_idim - 1, p_coordZ, p_hVal, p_indexZ, p_yRegressed, p_bLinear);
        }
        else
        {
            if (p_bLinear)
            {
                MatrixXd A = p_tick * MatrixXd::Identity(1 + p_x.rows(), 1 + p_x.rows());
                VectorXd B = VectorXd::Zero(1 + p_x.rows());
                MatrixXd AError = MatrixXd::Zero(1 + p_x.rows(), 1 + p_x.rows());
                VectorXd BError = VectorXd::Zero(1 + p_x.rows());
                // nest on particle
                for (int is = 0; is < p_x.cols(); ++is)
                {
                    double kernelMatLoc = 0.;
                    for (int id = 0; id < p_x.rows() ; ++id)
                    {
                        double spread = std::fabs(p_x(id, is) - p_coordZ(id)) / p_hVal(id);
                        if (spread  <= 1.)
                        {
                            kernelMatLoc += 0.75 * (1 - spread * spread) / ND;
                        }
                        else
                        {
                            kernelMatLoc = 0;
                            break;
                        }
                    }
                    if (kernelMatLoc > std::numeric_limits<double>::epsilon())
                    {
                        // matrix
                        addFastKernel(kernelMatLoc, AError(0, 0), A(0, 0));
                        //A(0, 0) += kernelMatLoc;
                        for (int id = 0 ; id <  p_x.rows(); ++id)
                        {
                            double toAdd = p_x(id, is) * kernelMatLoc;
                            addFastKernel(toAdd, AError(0, id + 1), A(0, id + 1));
                        }
                        for (int idd = 0; idd <  p_x.rows(); ++idd)
                        {
                            for (int id = idd; id < p_x.rows(); ++id)
                            {
                                double toAdd = p_x(id, is) * p_x(idd, is) * kernelMatLoc;
                                addFastKernel(toAdd, AError(idd + 1, id + 1), A(idd + 1, id + 1));
                            }
                        }
                        // second member
                        double toAddB = kernelMatLoc * p_y(is);
                        addFastKernel(toAddB, BError(0), B(0));
                        for (int id = 0; id < p_x.rows(); ++id)
                        {
                            double toAddBB = kernelMatLoc * p_y(is) * p_x(id, is);
                            addFastKernel(toAddBB, BError(id + 1), B(id + 1));
                        }
                    }
                }
                // fill rest of matrix
                for (int id = 1; id <=  p_x.rows(); ++id)
                    for (int idd = 0; idd < id; ++idd)
                        A(id, idd) = A(idd, id);
                // invert
                LLT<MatrixXd>  lltA(A);
                VectorXd coeff = lltA.solve(B);
                // store
                p_yRegressed(p_indexZ) = coeff[0];
                for (int id = 0; id < ND; ++id)
                    p_yRegressed(p_indexZ) += coeff[id + 1] * p_coordZ(id);
            }
            else
            {
                double  A = p_tick ;
                double B = 0.;
                double AError = 0.;
                double BError = 0.;
                // nest on particle
                for (int is = 0; is < p_x.cols(); ++is)
                {
                    double kernelMatLoc = 0.;
                    for (int id = 0; id < p_x.rows() ; ++id)
                    {
                        double spread = std::fabs(p_x(id, is) - p_coordZ(id)) / p_hVal(id);
                        if (spread  <= 1.)
                        {
                            kernelMatLoc += 0.75 * (1 - spread * spread) / ND;
                        }
                        else
                        {
                            kernelMatLoc = 0;
                            break;
                        }
                    }
                    if (kernelMatLoc > std::numeric_limits<double>::epsilon())
                    {
                        // matrix
                        addFastKernel(kernelMatLoc, AError, A);

                        // second member
                        double toAddB = kernelMatLoc * p_y(is);
                        addFastKernel(toAddB, BError, B);
                    }
                }
                // store
                p_yRegressed(p_indexZ) = B / A;
            }
        }
        p_indexZ[p_idim] += 1;
    }
    p_indexZ[p_idim] = 0;
}



/// \brief  Perform naive kernel  regression on grid points
/// \param  p_x       particles (possibly with a diminution for the dimension)
/// \param  p_y       function value to regress (renormalized)
/// \param  p_z       grid points for regressions (in each dimension)
/// \param  p_h       bandwith for each point in p_z (in each dimension)
/// \param  p_bLinear do we use linera regressions
/// \param  p_tick    tick to help regresssion matrix
/// \return regressed values on grid points
template< int ND >
boost::multi_array<double, ND> performRegressionNaiveStab(const ArrayXXd   &p_x, ArrayXd   &p_y,
        const vector< shared_ptr<ArrayXd> > &p_z,
        const vector< shared_ptr<ArrayXd> > &p_h,
        const bool &p_bLinear,
        const double &p_tick
                                                         )
{
    boost::array<  typename boost::multi_array<double, ND>::index, ND>  dimReg;
    for (int id = 0; id < ND; ++id)
        dimReg[id] = p_z[id]->size();
    boost::multi_array<double, ND> yRegressed(dimReg);

    ArrayXd coordZ(ND);;
    ArrayXd hVal(ND);
    boost::array<  typename boost::multi_array<double, ND>::index, ND>  zIndex;
    for (int id = 0; id < ND; ++id)
        zIndex[id] = 0;

    //  Recursive evaluation
    RecurMatStab<ND>(p_x, p_y, p_z, p_h, p_tick, ND - 1, coordZ, hVal, zIndex, yRegressed, p_bLinear);

    return  yRegressed;
}


/// \brief Prepare data for the kernel regression :
///        - SVD on data
///        - calculate the grid,
///        - calculate the bandwidth
/// \brief General Subroutine : naive implementation
/// \param p_X        sample point
/// \param p_Y        sample point to regress with respect to p_X
/// \param p_prop     Proportion of point taken by each local regression (bandwith)
/// \param p_q        p_q by number of simulations correspond to the number of points in the grid
/// \param p_newX     p_X values after rescaling and rotation
/// \param p_newY     p_Y values b after renormalization
/// \param  p_z       grid points for regressions (in each dimension)
/// \param  p_h       bandwith for each point in p_z (in each dimension)
/// \param  p_iSort   sorted particles by their number
/// \param  p_meanY   Y mean
/// \param  p_stdY    Y standard deviation
/// \param  p_zl          permits to locate  in each direction  the first left 'slice' in the method  belonging to the kernel associated to a grid point in z
/// \param  p_zr          permits to locate  in each direction the first right  'slice' in the method not belonging to the kernel associated to a grid point in z
/// \param  p_g       storing all point in p_z + /- p_h (in each dimension)
/// \param  p_xG      for each dimension the mesh used in p_g for all simulations
void prepareData(const ArrayXXd &p_X, const ArrayXd &p_Y, const double &p_prop,
                 const double &p_q, ArrayXXd &p_newX,  ArrayXd &p_newY,
                 vector< shared_ptr<ArrayXd> > &p_z, vector< shared_ptr<ArrayXd> > &p_h,
                 ArrayXXi &p_iSort, double &p_meanY, double &p_stdY,
                 vector< shared_ptr<ArrayXi> >     &p_zl,
                 vector< shared_ptr<ArrayXi> >     &p_zr,
                 vector< shared_ptr<ArrayXd> >     &p_g,
                 ArrayXXi &p_xG)
{
    p_meanY = p_Y.mean();
    p_stdY = sqrt((p_Y - p_meanY).pow(2).mean());
    p_newY = ((p_Y - p_meanY) / p_stdY);
    p_newX = p_X;
    ArrayXi aN, aK;
    int dEff;

    for (int id = 0; id < p_newX.rows(); ++id)
    {
        p_newX.row(id) -= p_newX.row(id).mean();
        double m_etypX = sqrt(p_newX.row(id).pow(2).mean());
        p_newX.row(id).array() /=  m_etypX;
    }
    BDCSVD<MatrixXd> svd(p_newX.matrix(), ComputeThinU);
    MatrixXd svdMatrix = svd.matrixU();
    for (int id = 0; id <  p_newX.rows(); ++id)
    {
        double vNrom = svdMatrix(id, id) / std::fabs(svdMatrix(id, id));
        for (int iid = 0; iid < p_newX.rows(); ++iid)
            svdMatrix(iid, id) *= vNrom;
    }
    p_newX  = (svdMatrix.transpose() * p_newX.matrix()).array();
    ArrayXd sing = svd.singularValues().array();

    preprocessData(p_newX, sing, p_prop, p_q, aN, aK, dEff);

    p_iSort.resize(p_newX.rows(), p_newX.cols());
    ArrayXXd sX(p_newX.rows(), p_newX.cols());
    // sort
    for (int id = 0 ; id <  p_newX.rows(); ++id)
    {
        vector<std::pair<double, int> > toSort(p_newX.cols());
        for (int is = 0; is < p_newX.cols(); ++is)
            toSort[is] = make_pair(p_newX(id, is), is);
        sort(toSort.begin(), toSort.end());
        for (int is = 0; is < p_newX.cols(); ++is)
        {
            p_iSort(id, is) = toSort[is].second;
            sX(id, is) = toSort[is].first;
        }
    }
    p_h.reserve(sX.rows());
    p_z.reserve(sX.rows());
    for (int id = 0; id < sX.rows(); ++id)
    {
        ArrayXd hFull = KNearestBandwithKPt1D(sX.row(id), aK(id));
        shared_ptr<ArrayXd> zLoc = make_shared<ArrayXd>(aN(id));
        shared_ptr<ArrayXd> hLoc = make_shared<ArrayXd>(aN(id));
        for (int i = 0; i <  aN(id); ++i)
        {
            int idec = static_cast<int>(round(i * (sX.cols() - 1) / (aN(id) - 1)));
            (*zLoc)(i) = sX(id, idec);
            (*hLoc)(i) = hFull(idec);
        }
        p_h.push_back(hLoc);
        p_z.push_back(zLoc);
    }
    //
    adaptiveBandwithNd(sX, p_iSort, aN, aK, p_h, p_z, p_zl, p_zr, p_g, p_xG);

}

/// \brief General Subroutine : naive implementation
/// \param p_X        sample point
/// \param p_Y        sample point to regress with respect to p_X
/// \param p_prop     Proportion of point taken by each local regression (bandwith)
/// \param p_bLinear do we use linera regressions
/// \param p_q        p_q by number of simulations correspond to the number of points in the grid
ArrayXd  locAdapRegNaive(const ArrayXXd &p_X, const ArrayXd &p_Y,
                         const double &p_prop, const bool &p_bLinear, double p_q, double p_tick)
{
    vector< shared_ptr<ArrayXd> > h, z ;
    ArrayXd newY;
    ArrayXXd  newX;
    ArrayXXi iSort;
    double meanY, stdY;
    vector< shared_ptr<ArrayXi> >    zl,  zr;
    ArrayXXi xG(p_X.rows(), p_X.cols());
    vector< shared_ptr<ArrayXd> >  g;

    // calculate grids, bandwidth
    prepareData(p_X, p_Y, p_prop, p_q, newX,  newY, z, h, iSort, meanY, stdY, zl,  zr,  g, xG);

    ArrayXd yReg(p_Y.size());
    // naive kernel
    switch (newX.rows())
    {
    case (1):
    {
        boost::multi_array<double, 1> yHatZ = performRegressionNaive<1>(newX, newY, z, h,  p_bLinear, p_tick);
        for (int is = 0; is < p_Y.size(); ++is)
            yReg(iSort(0, is)) = meanY + stdY * yHatZ[is];
        break;
    }

    case (2):
    {
        boost::multi_array<double, 2> yHatZ = performRegressionNaive<2>(newX, newY, z, h,  p_bLinear, p_tick);
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<2>(newX, regressed, z,  iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (3):
    {
        boost::multi_array<double, 3> yHatZ = performRegressionNaive<3>(newX, newY, z, h,  p_bLinear, p_tick);
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<3>(newX, regressed, z,  iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (4):
    {
        boost::multi_array<double, 4> yHatZ = performRegressionNaive<4>(newX, newY, z, h,  p_bLinear, p_tick);
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<4>(newX, regressed, z,  iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (5):
    {
        boost::multi_array<double, 5> yHatZ = performRegressionNaive<5>(newX, newY, z, h, p_bLinear, p_tick);
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<5>(newX, regressed, z,  iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (6):
    {
        boost::multi_array<double, 6> yHatZ = performRegressionNaive<6>(newX, newY, z, h,  p_bLinear, p_tick);
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<6>(newX, regressed, z,  iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (7):
    {
        boost::multi_array<double, 7> yHatZ = performRegressionNaive<7>(newX, newY, z, h,  p_bLinear, p_tick);
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<7>(newX, regressed, z,  iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (8):
    {
        boost::multi_array<double, 8> yHatZ = performRegressionNaive<8>(newX, newY, z, h,  p_bLinear, p_tick);
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<8>(newX, regressed, z,  iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    default:
    {
        cout << " ERROR : DIM BELOW 9" << std::endl ;
        abort();
    }
    }
    return yReg;
}

/// \brief General Subroutine for regression : fast sum  implementation used for direct binding
/// \param p_X        sample point
/// \param p_Y        sample point to regress with respect to p_X
/// \param p_prop     Proportion of point taken by each local regression (bandwith)
/// \param  p_bLinear do we use linera regressions
/// \param p_q        p_q by number of simulations correspond to the number of points in the grid
ArrayXd  locAdapReg(const ArrayXXd &p_X, const ArrayXd &p_Y,
                    const double &p_prop, const bool &p_bLinear, double p_q, double p_tick)
{
    vector< shared_ptr<ArrayXd> > h, z ;
    ArrayXd newY;
    ArrayXXd  newX;
    ArrayXXi iSort;
    double meanY, stdY;
    vector< shared_ptr<ArrayXi> >    zl,  zr;
    ArrayXXi xG(p_X.rows(), p_X.cols());
    vector< shared_ptr<ArrayXd> >  g;

    // calculate grids, bandwidth
    prepareData(p_X, p_Y, p_prop, p_q, newX,  newY, z, h, iSort, meanY, stdY, zl,  zr,  g, xG);

    ArrayXd yReg(p_Y.size());
    // naive kernel
    switch (newX.rows())
    {
    case (1):
    {
        boost::multi_array< double, 1>  yHatZ = (p_bLinear ? performNdRegLin<1>(newX, newY, z, h, g, xG, zl, zr, p_tick) :
                                                performNdRegConst<1>(newX, newY, z, h, g, xG, zl, zr, p_tick));
        for (int is = 0; is < p_Y.size(); ++is)
            yReg(iSort(0, is)) = meanY + stdY * yHatZ[is];
        break;
    }

    case (2):
    {
        boost::multi_array< double, 2>  yHatZ = (p_bLinear ? performNdRegLin<2>(newX, newY, z, h, g, xG, zl, zr, p_tick) :
                                                performNdRegConst<2>(newX, newY, z, h, g, xG, zl, zr, p_tick));
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<2>(newX, regressed, z,  iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (3):
    {
        boost::multi_array< double, 3>  yHatZ = (p_bLinear ? performNdRegLin<3>(newX, newY, z, h, g, xG, zl, zr, p_tick) :
                                                performNdRegConst<3>(newX, newY, z, h, g, xG, zl, zr, p_tick));

        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<3>(newX, regressed, z,  iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (4):
    {
        boost::multi_array<double, 4>  yHatZ = (p_bLinear ? performNdRegLin<4>(newX, newY, z, h, g, xG, zl, zr, p_tick) :
                                                performNdRegConst<4>(newX, newY, z, h, g, xG, zl, zr, p_tick));
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<4>(newX, regressed, z,  iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (5):
    {
        boost::multi_array<double, 5>  yHatZ = (p_bLinear ? performNdRegLin<5>(newX, newY, z, h, g, xG, zl, zr, p_tick) :
                                                performNdRegConst<5>(newX, newY, z, h, g, xG, zl, zr, p_tick));
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<5>(newX, regressed, z,  iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (6):
    {
        boost::multi_array<double, 6>  yHatZ = (p_bLinear ? performNdRegLin<6>(newX, newY, z, h, g, xG, zl, zr, p_tick) :
                                                performNdRegConst<6>(newX, newY, z, h, g, xG, zl, zr, p_tick));
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<6>(newX, regressed, z,  iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (7):
    {
        boost::multi_array<double, 7>  yHatZ = (p_bLinear ? performNdRegLin<7>(newX, newY, z, h, g, xG, zl, zr, p_tick) :
                                                performNdRegConst<7>(newX, newY, z, h, g, xG, zl, zr, p_tick));
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<7>(newX, regressed, z,  iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (8):
    {
        boost::multi_array<double, 8>  yHatZ = (p_bLinear ? performNdRegLin<8>(newX, newY, z, h, g, xG, zl, zr, p_tick) :
                                                performNdRegConst<8>(newX, newY, z, h, g, xG, zl, zr, p_tick));
        const Map<ArrayXd> regressed(yHatZ.data(), yHatZ.num_elements()) ;
        ArrayXd yHatX = interpNd<8>(newX, regressed, z,  iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    default:
    {
        cout << " ERROR : DIM BELOW 9" << std::endl ;
        abort();
    }
    }
    return yReg;
}

/// \brief General Subroutine : naive implementation  and calculate the regressed valeus only on the grid
/// \param p_X        sample point
/// \param p_Y        sample point to regress with respect to p_X
/// \param p_prop     Proportion of point taken by each local regression (bandwith)
/// \param p_bLinear  do we use linera regressions
/// \param p_q        p_q by number of simulations correspond to the number of points in the grid
ArrayXd  locAdapRegNaiveOnGrid(const ArrayXXd &p_X, const ArrayXd &p_Y, const double &p_prop, const bool &p_bLinear, double p_q, double p_tick)
{
    vector< shared_ptr<ArrayXd> > h, z ;
    ArrayXd newY;
    ArrayXXd  newX;
    ArrayXXi iSort;
    double meanY, stdY;
    vector< shared_ptr<ArrayXi> >    zl,  zr;
    ArrayXXi xG(p_X.rows(), p_X.cols());
    vector< shared_ptr<ArrayXd> >  g;

    // calculate grids, bandwidth
    prepareData(p_X, p_Y, p_prop, p_q, newX,  newY, z, h, iSort, meanY, stdY, zl,  zr,  g, xG);

    // naive kernel
    switch (newX.rows())
    {
    case (1):
    {
        boost::multi_array<double, 1> yHatZ = performRegressionNaive<1>(newX, newY, z, h, p_bLinear, p_tick);
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }

    case (2):
    {
        boost::multi_array<double, 2> yHatZ = performRegressionNaive<2>(newX, newY, z, h, p_bLinear, p_tick);
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (3):
    {
        boost::multi_array<double, 3> yHatZ = performRegressionNaive<3>(newX, newY, z, h, p_bLinear, p_tick);
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (4):
    {
        boost::multi_array<double, 4> yHatZ = performRegressionNaive<4>(newX, newY, z, h, p_bLinear, p_tick);
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (5):
    {
        boost::multi_array<double, 5> yHatZ = performRegressionNaive<5>(newX, newY, z, h, p_bLinear, p_tick);
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (6):
    {
        boost::multi_array<double, 6> yHatZ = performRegressionNaive<6>(newX, newY, z, h, p_bLinear, p_tick);
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (7):
    {
        boost::multi_array<double, 7> yHatZ = performRegressionNaive<7>(newX, newY, z, h, p_bLinear, p_tick);
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (8):
    {
        boost::multi_array<double, 8> yHatZ = performRegressionNaive<8>(newX, newY, z, h, p_bLinear, p_tick);
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    default:
    {
        cout << " ERROR : DIM BELOW 9" << std::endl ;
        abort();
    }
    }
    return ArrayXd();
}

/// \brief General Subroutine : naive implementation  and calculate the regressed valeus only on the grid
/// \param p_X        sample point
/// \param p_Y        sample point to regress with respect to p_X
/// \param p_prop     Proportion of point taken by each local regression (bandwith)
/// \param p_bLinear  do we use linera regressions
/// \param p_q        p_q by number of simulations correspond to the number of points in the grid
ArrayXd  locAdapRegNaiveOnGridStab(const ArrayXXd &p_X, const ArrayXd &p_Y, const double &p_prop, const bool &p_bLinear,  double p_q, double p_tick)
{
    vector< shared_ptr<ArrayXd> > h, z ;
    ArrayXd newY;
    ArrayXXd  newX;
    ArrayXXi iSort;
    double meanY, stdY;
    vector< shared_ptr<ArrayXi> >    zl,  zr;
    ArrayXXi xG(p_X.rows(), p_X.cols());
    vector< shared_ptr<ArrayXd> >  g;

    // calculate grids, bandwidth
    prepareData(p_X, p_Y, p_prop, p_q, newX,  newY, z, h, iSort, meanY, stdY, zl,  zr,  g, xG);

    // naive kernel
    switch (newX.rows())
    {
    case (1):
    {
        boost::multi_array<double, 1> yHatZ = performRegressionNaiveStab<1>(newX, newY, z, h, p_bLinear, p_tick);
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }

    case (2):
    {
        boost::multi_array<double, 2> yHatZ = performRegressionNaiveStab<2>(newX, newY, z, h, p_bLinear, p_tick);
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (3):
    {
        boost::multi_array<double, 3> yHatZ = performRegressionNaiveStab<3>(newX, newY, z, h, p_bLinear, p_tick);
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (4):
    {
        boost::multi_array<double, 4> yHatZ = performRegressionNaiveStab<4>(newX, newY, z, h, p_bLinear, p_tick);
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (5):
    {
        boost::multi_array<double, 5> yHatZ = performRegressionNaiveStab<5>(newX, newY, z, h, p_bLinear, p_tick);
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (6):
    {
        boost::multi_array<double, 6> yHatZ = performRegressionNaiveStab<6>(newX, newY, z, h, p_bLinear, p_tick);
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (7):
    {
        boost::multi_array<double, 7> yHatZ = performRegressionNaiveStab<7>(newX, newY, z, h, p_bLinear, p_tick);
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    case (8):
    {
        boost::multi_array<double, 8> yHatZ = performRegressionNaiveStab<8>(newX, newY, z, h, p_bLinear, p_tick);
        ArrayXd regressed(yHatZ.num_elements() + 2) ;
        int ipos = 0;
        for (auto iterY = yHatZ.origin(); iterY != (yHatZ.origin() + yHatZ.num_elements()); ++iterY)
        {
            regressed(ipos++) = *iterY;
        }
        regressed(regressed.size() - 2) = meanY;
        regressed(regressed.size() - 1) = stdY;
        return regressed;
    }
    default:
    {
        cout << " ERROR : DIM BELOW 9" << std::endl ;
        abort();
    }
    }
    return ArrayXd();
}



/// \brief  Calculate for all points used by the kernel method the regressed values from the values on the grid
/// \param  p_newX    renormalized particle
/// \param  p_yHatZ   regressed values on the grid  defined by p_z plus scaling (mean, std)
/// \param  p_z       grid points for regressions (in each dimension)
/// \param  p_iSort   in each direction sorted particles by their number
/// \return An array with all the regressed value for the particle p_newX
ArrayXd  fromGridValuesGetRegAllSim(const ArrayXXd   &p_newX,
                                    const ArrayXd   &p_yHatZ,
                                    const vector< shared_ptr<ArrayXd> >   &p_z,
                                    const ArrayXXi &p_iSort)
{
    ArrayXd yReg(p_newX.cols());

    double meanY = p_yHatZ(p_yHatZ.size() - 2);
    double stdY = p_yHatZ(p_yHatZ.size() - 1);
    switch (p_newX.rows())
    {
    case (1):
    {
        for (int is = 0; is < yReg.size(); ++is)
            yReg(p_iSort(0, is)) = meanY + stdY * p_yHatZ(is);
        break;
    }

    case (2):
    {
        ArrayXd yHatX = interpNd<2>(p_newX, p_yHatZ, p_z,  p_iSort);
        yReg = meanY + yHatX * stdY;
        break;

    }
    case (3):
    {
        ArrayXd yHatX = interpNd<3>(p_newX, p_yHatZ, p_z,  p_iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (4):
    {
        ArrayXd yHatX = interpNd<4>(p_newX, p_yHatZ, p_z,  p_iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (5):
    {
        ArrayXd yHatX = interpNd<5>(p_newX, p_yHatZ, p_z,  p_iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (6):
    {
        ArrayXd yHatX = interpNd<6>(p_newX, p_yHatZ, p_z, p_iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (7):
    {
        ArrayXd yHatX = interpNd<7>(p_newX, p_yHatZ, p_z, p_iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    case (8):
    {
        ArrayXd yHatX = interpNd<8>(p_newX, p_yHatZ, p_z, p_iSort);
        yReg = meanY + yHatX * stdY;
        break;
    }
    default:
    {
        cout << " ERROR : DIM BELOW 9" << std::endl ;
        abort();
    }
    }
    return yReg;
}


/// \brief  Calculate for one point  used by the kernel method the regressed value from the interpolated values on the grid
/// \param  p_newX    renormalized particle
/// \param  p_yHatZ   regressed values on the grid  defined by p_z plus scaling (mean, std)
/// \param  p_z       grid points for regressions (in each dimension)
/// \return  the interpolated value
double   fromGridValuesGetRegASim(const ArrayXd    &p_newX,
                                  const ArrayXd   &p_yHatZ,
                                  const vector< shared_ptr<ArrayXd> >   &p_z)
{

    double meanY = p_yHatZ(p_yHatZ.size() - 2);
    double stdY = p_yHatZ(p_yHatZ.size() - 1);

    switch (p_newX.size())
    {
    case (1):
    {
        double val = interpNdAParticle<1>(p_newX, p_yHatZ, p_z);
        return meanY + val * stdY;
    }
    case (2):
    {
        double val = interpNdAParticle<2>(p_newX, p_yHatZ, p_z);
        return meanY + val * stdY;
    }
    case (3):
    {
        double val = interpNdAParticle<3>(p_newX, p_yHatZ, p_z);
        return meanY + val * stdY;
    }
    case (4):
    {
        double val = interpNdAParticle<4>(p_newX, p_yHatZ, p_z);
        return meanY + val * stdY;
    }
    case (5):
    {
        double val = interpNdAParticle<5>(p_newX, p_yHatZ, p_z);
        return meanY + val * stdY;
    }
    case (6):
    {
        double val = interpNdAParticle<6>(p_newX, p_yHatZ, p_z);
        return meanY + val * stdY;
    }
    case (7):
    {
        double val = interpNdAParticle<7>(p_newX, p_yHatZ, p_z);
        return meanY + val * stdY;
    }
    case (8):
    {
        double val = interpNdAParticle<8>(p_newX, p_yHatZ, p_z);
        return meanY + val * stdY;
    }
    default:
    {
        cout << " ERROR : DIM BELOW 9" << std::endl ;
        abort();
    }
    }
}

/// \brief  Calculate for one point  used by the kernel method the regressed value from the interpolated values on the grid
///         Used to avoid costly interpolation on all function basis
/// \param  p_newX    renormalized particle
/// \param  p_yHatZ   regressed values on the grid  defined by p_z plus scaling (mean, std)
/// \param  p_z       grid points for regressions (in each dimension)
/// \param  p_ptOfStock            grid point
/// \param  p_interpFuncBasis      spectral interpolator to interpolate on the basis the function value (regressed functions on all grid with
///                                spectral representation)
/// \return  the interpolated value
double   fromGridValuesGetRegASimOnBasis(const ArrayXd    &p_newX,
        const vector< shared_ptr<ArrayXd> >   &p_z, const ArrayXd &p_ptOfStock,
        const vector< shared_ptr<InterpolatorSpectral> > &p_interpFuncBasis)
{
    // use an interpolation of the mean and the standard deviation
    double meanY = p_interpFuncBasis[p_interpFuncBasis.size() - 2]->apply(p_ptOfStock);
    double stdY = p_interpFuncBasis[p_interpFuncBasis.size() - 1]->apply(p_ptOfStock);

    switch (p_newX.size())
    {
    case (1):
    {
        double val = interpNdAParticleOnBasis<1>(p_newX,  p_z, p_ptOfStock, p_interpFuncBasis);
        return meanY + val * stdY;
    }
    case (2):
    {
        double val = interpNdAParticleOnBasis<2>(p_newX,  p_z, p_ptOfStock, p_interpFuncBasis);
        return meanY + val * stdY;
    }
    case (3):
    {
        double val = interpNdAParticleOnBasis<3>(p_newX,  p_z, p_ptOfStock, p_interpFuncBasis);
        return meanY + val * stdY;
    }
    case (4):
    {
        double val = interpNdAParticleOnBasis<4>(p_newX,  p_z, p_ptOfStock, p_interpFuncBasis);
        return meanY + val * stdY;
    }
    case (5):
    {
        double val = interpNdAParticleOnBasis<5>(p_newX,  p_z, p_ptOfStock, p_interpFuncBasis);
        return meanY + val * stdY;
    }
    case (6):
    {
        double val = interpNdAParticleOnBasis<6>(p_newX,  p_z, p_ptOfStock, p_interpFuncBasis);
        return meanY + val * stdY;
    }
    case (7):
    {
        double val = interpNdAParticleOnBasis<7>(p_newX,  p_z, p_ptOfStock, p_interpFuncBasis);
        return meanY + val * stdY;
    }
    case (8):
    {
        double val = interpNdAParticleOnBasis<8>(p_newX,  p_z, p_ptOfStock, p_interpFuncBasis);
        return meanY + val * stdY;
    }
    default:
    {
        cout << " ERROR : DIM BELOW 9" << std::endl ;
        abort();
    }
    }
}

}
