// Copyright (C) 2016 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#ifndef SPARSEREGRESSION_H
#define SPARSEREGRESSION_H
#include <Eigen/Dense>
#include <functional>
#ifdef _OPENMP
#include <omp.h>
#endif
#include "StOpt/core/grids/SparseSpaceGridNoBound.h"
#include "StOpt/regression/BaseRegression.h"

/** \file SparseRegression.h
 * \brief Compute conditional expectation with sparse grids
 *        As  in article "Monte-Carlo valorisation of American options: facts and new algorithms to improve existing methods"
 *          by Bouchard, Warin in "Numerical methods in finance", Springer,2012
 *        particle are sorted in each dimension such that the basis support have particles
 *       First draft is without adaptation.
 *       \author Xavier Warin
 */

namespace StOpt
{
/**
 * \defgroup Regression with sparse grids with linear, quadratic and cubic polynomials
 *@{
 */

class SparseRegression : public BaseRegression
{

private:

    std::shared_ptr< SparseSpaceGridNoBound > m_spGrid ; ///< to store the grid
    Eigen::ArrayXXd  m_partRescaled;  ///< Particles used to regress but rescaled according to transformToUnitSquare : dimension of the problem , second dimension : the  number of particles
    std::vector< std::vector< double> > m_mesh ; ///< For each direction, give the coordinate of the mesh
    std::vector< std::vector< std::function< double(const double &) > > > m_functionScal ;   ///< for each position, for  the level used, defines the  basis function
    Eigen::LLT<Eigen::MatrixXd>  m_llt; ///< llt factorization by eigen
    Eigen::MatrixXd m_yReg ; ///< utilitarian to calculate pseudo inverse if the matrix is singular
    bool  m_bSingular ; ///< true if the regression matrix is singular
    bool m_bNoRescale ; //< Don't rescale if true

    /// \brief create and factorize the regression matrix
    /// \param p_levelMax    maximum level of the sparse grid
    /// \param p_weight      weight for the anisotropy : the level \f$ (l_i)_i\f$  satisfy  \f$\sum_i weight[i] l_i \le NDIM + levelMax -1 \f$
    /// \param p_degree      Degree of the interpolation sparse grid
    void createAndFactorize(const int &p_levelMax, const Eigen::ArrayXd &p_weight, const int &p_degree);

    /// \brief rescale the particle values
    /// \param  p_aParticle  The particle to rescale according the mesh
    /// \param  p_aPartRescaled  The same particle rescaled
    void rescaleAParticle(const Eigen::Ref< const Eigen::ArrayXd>  p_aParticle, Eigen::Ref< Eigen::ArrayXd>  p_aPartRescaled) const;

    /// \brief Transform each direction to unit square : in each direction , the same number number of particles in each mesh.
    /// \param p_levelMax    maximum level of the sparse grid
    /// \param p_weight      weight for the anisotropy : the level \f$ (l_i)_i\f$  satisfy  \f$\sum_i weight[i] l_i \le NDIM + levelMax -1 \f$
    void transformToUnitSquare(const int &p_levelMax,  const Eigen::ArrayXd &p_weight);

    /// \brief Rescale simply
    /// \param p_weight      weight for the anisotropy : the level \f$ (l_i)_i\f$  satisfy \f$ \sum_i weight[i] l_i \le NDIM + levelMax -1 \f$
    void rescale(const Eigen::ArrayXd &p_weight);

    /// \brief Recursive calculation function basis at a point  using "son"" knowledge
    /// \param p_levelCurrent                      Current index of levels of the point
    /// \param p_positionCurrent                   Current position of the point, at the given level
    /// \param p_ipoint                            Point number
    /// \param p_xMiddle                           Position in [0,1] of current node in each dimension
    /// \param p_dx                                Semi mesh size
    /// \param p_x                                 Evaluation point
    /// \param p_idimMin                           Minimal dimension search (to avoid to go twice at same node)
    /// \param p_funcVal                           Function basis values at current node for all dimensions
    /// \param p_son                               Son array (first dimension is the node number, second is the dimension , 0 in array corresponds to left, 1 to right)
    /// \param p_nonNullFunctionValues            To fill in : values of the different non null function basis
    /// \param  p_associatedFunctionNumber        Cell number associated to each previous non num value
    void recursiveFillFunctionBasisWithSon(Eigen::ArrayXc &p_levelCurrent,
                                           Eigen::ArrayXui   &p_positionCurrent,
                                           const int &p_ipoint,
                                           Eigen::ArrayXd &p_xMiddle,
                                           Eigen::ArrayXd &p_dx,
                                           const Eigen::ArrayXd &p_x,
                                           const unsigned short int &p_idimMin,
                                           Eigen::ArrayXd &p_funcVal,
                                           const Eigen::Array< std::array<int, 2 >, Eigen::Dynamic, Eigen::Dynamic >    &p_son,
                                           std::vector<double> &p_nonNullFunctionValues,
                                           std::vector<int>    &p_associatedFunctionNumber) const;

    /// \brief  For a particle asses the value of all basis functions
    /// \param  p_particle                         particle rescaled coordinates
    ///  \param p_nonNullFunctionValues            To fill in : values of the different non null function basis
    ///  \param  p_associatedFunctionNumber        Cell number associated to each previous non num value
    void assessFuncBasis(const Eigen::ArrayXd &p_particle, std::vector<double> &p_nonNullFunctionValues, std::vector<int>    &p_associatedFunctionNumber) const ;


    /// \brief fill in matrix regression using the minimum of memory possible
    ///    Each thread fills part of the matrix for the simulations it uses, then the matrix is reconstructed
    /// \param  p_matReg  matrix to fill in
    void fillInRegressionMatrix(Eigen::MatrixXd &p_matReg);


    /// \brief Fill in second member  with minimal cost of storage
    /// \param  p_fToRegress  functions to regress associated to each simulation used in optimization (number of functions to regress times the simulations)
    /// \return   second members to  construct (number of functions to regress times the number of basis functions)
    Eigen::MatrixXd  fillInSecondMember(const Eigen::MatrixXd &p_fToRegress) const ;


    /// \brief Reconstruct the solution using the basis coordinates
    /// \param p_basisCoeff  basis function coefficients (number)
    /// \param  p_fRegressed  the result of the regression
    void reconstruct(const Eigen::ArrayXXd &p_basisCoeff, Eigen::ArrayXXd &p_fRegressed) const ;

    /// \brief Reconstruct the solution using the basis coordinates
    /// \param p_basisCoeff  basis function coefficients
    /// \param  p_aParticle  coordinates of the point where to reconstruct the value function
    /// \return regressed value
    double  reconstructOneParticle(const Eigen::ArrayXd &p_basisCoeff, const Eigen::ArrayXd &p_aParticle) const ;

public :

    /// \brief default constructor
    SparseRegression() {}

    /// \brief Constructor (for constructor achieved once)
    /// \param p_levelMax   Level max associated to the sparse grid
    /// \param p_weight     Weight for anisotropic sparse grids
    /// \param p_degree     Degree of the interpolator
    /// \param p_bNoRescale Don't use a complex rescaling
    SparseRegression(const int &p_levelMax, const Eigen::ArrayXd &p_weight, const int &p_degree, bool p_bNoRescale = false);

    /// \brief Constructor for object constructed at each time step
    /// \param  p_bZeroDate    first date is 0?
    /// \param  p_particles    particles used for the meshes.
    ///                        First dimension  : dimension of the problem,
    ///                        second dimension : the  number of particles
    /// \param p_levelMax      Level max associated to the sparse grid
    /// \param p_weight        Weight for anisotropic sparse grids
    /// \param p_degree     Degree of the interpolator
    SparseRegression(const bool &p_bZeroDate,
                     const Eigen::ArrayXXd  &p_particles,
                     const int &p_levelMax, const Eigen::ArrayXd &p_weight,
                     const int &p_degree);

    /// \brief constructor to recreate the dumped object (for simulation)
    /// \param p_bZeroDate first date is 0?
    /// \param p_spGrid    sparse grid
    /// \param p_mesh      mesh used for rescaling
    /// \param  p_meanX            scaled factor in each direction (average of particles values in each direction)
    /// \param  p_etypX            scaled factor in each direction (standard deviation of particles in each direction)
    /// \param  p_svdMatrix        svd matrix transposed  used to transform particles
    SparseRegression(const bool &p_bZeroDate, std::shared_ptr< SparseSpaceGridNoBound>  p_spGrid,  const std::vector< std::vector< double> > &p_mesh, const   Eigen::ArrayXd &p_meanX,
                     const   Eigen::ArrayXd   &p_etypX, const   Eigen::MatrixXd   &p_svdMatrix);


    /// \brief update the particles used in regression  and construct the matrices
    /// \param  p_bZeroDate    first date is 0?
    /// \param  p_particles    particles used for the meshes.
    ///                        First dimension  : dimension of the problem,
    ///                        second dimension : the  number of particles
    void updateSimulations(const bool &p_bZeroDate, const Eigen::ArrayXXd  &p_particles);

    /// \brief conditional expectation basis function coefficient calculation
    /// \param  p_fToRegress  function to regress associated to each simulation used in optimization
    /// \return regression coordinates on the basis  (size : number of meshes multiplied by the dimension plus one)
    /// @{
    Eigen::ArrayXd getCoordBasisFunction(const Eigen::ArrayXd &p_fToRegress) const;
    Eigen::ArrayXXd getCoordBasisFunctionMultiple(const Eigen::ArrayXXd &p_fToRegress) const;
    ///@}

    /// \brief conditional expectation calculation
    /// \param  p_fToRegress  simulations  to regress used in optimization
    /// \return regressed value function
    /// @{
    Eigen::ArrayXd getAllSimulations(const Eigen::ArrayXd &p_fToRegress) const ;
    Eigen::ArrayXXd getAllSimulationsMultiple(const Eigen::ArrayXXd &p_fToRegress) const;
    ///@}

    /// \brief Use basis functions to reconstruct the solution
    /// \param p_basisCoefficients basis coefficients
    ///@{
    Eigen::ArrayXd reconstruction(const Eigen::ArrayXd   &p_basisCoefficients) const ;
    Eigen::ArrayXXd reconstructionMultiple(const Eigen::ArrayXXd   &p_basisCoefficients) const ;
    /// @}

    /// \brief use basis function to reconstruct a given simulation
    /// \param p_isim               simulation number
    /// \param p_basisCoefficients  basis coefficients to reconstruct a given conditional expectation
    double reconstructionASim(const int &p_isim, const Eigen::ArrayXd   &p_basisCoefficients) const ;

    /// \brief conditional expectation reconstruction
    /// \param  p_coordinates        coordinates to interpolate (uncertainty sample)
    /// \param  p_coordBasisFunction regression coordinates on the basis  (size: number of meshes multiplied by the dimension plus one)
    /// \return regressed value function reconstructed for each simulation
    double getValue(const Eigen::ArrayXd   &p_coordinates,
                    const Eigen::ArrayXd   &p_coordBasisFunction) const ;

    /// \brief permits to reconstruct a function with basis functions coefficients values given on a grid
    /// \param  p_coordinates            coordinates  (uncertainty sample)
    /// \param  p_ptOfStock              grid point
    /// \param   p_interpFuncBasis       spectral interpolator to interpolate the basis functions  coefficients used in regression on the grid (given for each basis function)
    double getAValue(const Eigen::ArrayXd &p_coordinates,  const Eigen::ArrayXd &p_ptOfStock,
                     const std::vector< std::shared_ptr<InterpolatorSpectral> > &p_interpFuncBasis) const;


    /// \brief Accessor
    ///@{
    std::shared_ptr<SparseSpaceGridNoBound> getSpGrid() const
    {
        return m_spGrid ;   ///< grid
    }
    const std::vector< std::vector< double> > &getMesh() const
    {
        return m_mesh;    ///< mesh to rescale particles;
    }
    ///@}

    /// \brief get the number of basis functions
    inline int getNumberOfFunction() const
    {
        return m_spGrid->getNbPoints();
    }
    /// \brief Clone the regressor
    std::shared_ptr<BaseRegression> clone() const
    {
        return std::static_pointer_cast<BaseRegression>(std::make_shared<SparseRegression>(*this));
    }

};
/**@}*/
}
#endif /* SPARSEREGRESSION */


