// Copyright (C) 2016 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#define BOOST_TEST_MODULE testSparseInterpolator
#define BOOST_TEST_DYN_LINK
#include <fstream>
#include <functional>
#include <boost/test/unit_test.hpp>
#include <boost/random.hpp>
#include <boost/random/uniform_01.hpp>
#include <Eigen/Dense>
#include "StOpt/core/grids/SparseSpaceGridBound.h"
#include "StOpt/core/grids/SparseGridBoundIterator.h"
#include "StOpt/core/grids/SparseSpaceGridNoBound.h"
#include "StOpt/core/grids/SparseGridNoBoundIterator.h"
#include "StOpt/core/grids/SparseInterpolatorSpectral.h"

using namespace std;
using namespace Eigen;
using namespace StOpt;

double accuracyEqual = 1e-10;
double accuracyEqualFirst = 1e-7;
double accuracyNearEqual = 0.5;

#if defined   __linux
#include <fenv.h>
#define enable_abort_on_floating_point_exception() feenableexcept(FE_DIVBYZERO | FE_INVALID)
#endif


/// For Clang < 3.7 (and above ?) to be compatible GCC 5.1 and above
namespace boost
{
namespace unit_test
{
namespace ut_detail
{
std::string normalize_test_case_name(const_string name)
{
    return (name[0] == '&' ? std::string(name.begin() + 1, name.size() - 1) : std::string(name.begin(), name.size()));
}
}
}
}

/// \class FunctionExp testHierarchizationBound.cpp
/// Test Hierarchization on exponential
class FunctionExp
{
public :
    double operator()(const Eigen::ArrayXd &p_x) const
    {
        double ret = 1.;
        for (int id = 0; id < p_x.size(); ++id)
            ret *=  exp(p_x(id));
        return ret;
    }
};


/// test cases by exact boundary treatment
void testSparseGridInterpolationBound(int p_level, int p_degree, const Eigen::ArrayXd &p_weight)
{

#if defined   __linux
    enable_abort_on_floating_point_exception();
#endif

    function< double(const Eigen::ArrayXd &) >  f =  FunctionExp() ;

    Eigen::ArrayXd  lowValues = Eigen::ArrayXd::Zero(p_weight.size());
    Eigen::ArrayXd  sizeDomain = Eigen::ArrayXd::Constant(p_weight.size(), 1.);

    // sparse grid generation
    SparseSpaceGridBound sparseGrid(lowValues, sizeDomain, p_level, p_weight, p_degree);

    // test grid iterators
    shared_ptr<GridIterator > iterGrid = sparseGrid.getGridIterator();

    ArrayXXd valuesFunction(4, sparseGrid.getNbPoints());
    while (iterGrid->isValid())
    {
        Eigen::ArrayXd pointCoord = iterGrid->getCoordinate();
        valuesFunction.col(iterGrid->getCount()).setConstant(f(pointCoord)) ;
        iterGrid->next();
    }

    // Hieriarchize
    ArrayXXd hieraValues = valuesFunction;
    sparseGrid.toHierarchizeVec(hieraValues);


    shared_ptr<GridIterator > iterGridNew = sparseGrid.getGridIterator();
    while (iterGridNew->isValid())
    {
        Eigen::ArrayXd pointCoord = iterGridNew->getCoordinate();
        // interpolator
        shared_ptr<Interpolator > interpolator = sparseGrid.createInterpolator(pointCoord);
        ArrayXd  interVal  = interpolator->applyVec(hieraValues);
        BOOST_CHECK_CLOSE(interVal(0), valuesFunction(0, iterGridNew->getCount()), accuracyEqual);
        iterGridNew->next();
    }

}

BOOST_AUTO_TEST_CASE(testCheckSparseInterpolationBound)
{
    // 1D , level 3, linear
    ArrayXd  weight1D = ArrayXd::Constant(1, 1.);
    testSparseGridInterpolationBound(3, 1, weight1D);

    // 3D , level 4, linear
    ArrayXd  weight3D = ArrayXd::Constant(3, 1.);
    testSparseGridInterpolationBound(4, 1, weight3D);

    // 3D , level 4, quadratic
    testSparseGridInterpolationBound(4, 2, weight3D);

    // 3D , level 4, cubic
    testSparseGridInterpolationBound(4, 3, weight3D);

    // 5D , level 4, cubic
    ArrayXd  weight5D = ArrayXd::Constant(5, 1.);
    testSparseGridInterpolationBound(4, 3, weight5D);

}

// test with dimension
void testSpectralInterpolatorBound(int p_nDim, int p_level)
{
    ArrayXd lowValues = ArrayXd::Constant(p_nDim, 1.); // bottom of the domain
    ArrayXd sizeDomain = ArrayXd::Constant(p_nDim, 5.); // size of the mesh

    // linear
    int degree = 1;
    // isotropic
    ArrayXd  weight = ArrayXd::Constant(p_nDim, 1.);
    SparseSpaceGridBound sparseGrid(lowValues, sizeDomain, p_level, weight, degree);

    // Data
    ArrayXd data(sparseGrid.getNbPoints());
    shared_ptr<GridIterator> iterSparseGrid  =  sparseGrid.getGridIterator();
    while (iterSparseGrid->isValid())
    {
        ArrayXd pointCoord = iterSparseGrid->getCoordinate();
        data(iterSparseGrid->getCount()) = pow(1. + pointCoord(0), 2.);
        for (int id = 1; id < p_nDim; ++id)
            data(iterSparseGrid->getCount()) *= pow(1. + pointCoord(id), 2.);
        iterSparseGrid->next();
    }


    // spectral interpolator
    SparseInterpolatorSpectral interpolator(&sparseGrid, data);
    boost::mt19937 generator;
    boost::uniform_01<double> alea;
    boost::variate_generator<boost::mt19937 &, boost::uniform_01<double> > uniform(generator, alea);

    int nsimul = 1000;
    for (int is = 0; is < nsimul; ++is)
    {
        ArrayXd pointCoord(p_nDim);
        for (int id = 0; id < p_nDim; ++id)
            pointCoord(id) = lowValues(id) + sizeDomain(id) * uniform();
        double val = pow(1. + pointCoord(0), 2.);
        for (int id = 1; id < p_nDim; ++id)
            val *= pow(1. + pointCoord(id), 2.);

        double vInterp = interpolator.apply(pointCoord);
        // check exact  interpolation for polynomials
        BOOST_CHECK_CLOSE(vInterp, val,  accuracyNearEqual);

    }
}


BOOST_AUTO_TEST_CASE(testCheckSpectralInterpolatorBound)
{
    // 1D, level 5
    testSpectralInterpolatorBound(1, 5);

    // 3D level 5
    testSpectralInterpolatorBound(3, 5);

}

/// test cases eliminating boundary points
void testSparseGridInterpolationNoBound(int p_level, int p_degree, const Eigen::ArrayXd &p_weight)
{

#if defined   __linux
    enable_abort_on_floating_point_exception();
#endif

    function< double(const Eigen::ArrayXd &) >  f =  FunctionExp() ;

    Eigen::ArrayXd  lowValues = Eigen::ArrayXd::Zero(p_weight.size());
    Eigen::ArrayXd  sizeDomain = Eigen::ArrayXd::Constant(p_weight.size(), 1.);

    // sparse grid generation
    SparseSpaceGridNoBound sparseGrid(lowValues, sizeDomain, p_level, p_weight, p_degree);

    // test grid iterators
    shared_ptr<GridIterator > iterGrid = sparseGrid.getGridIterator();

    ArrayXXd valuesFunction(4, sparseGrid.getNbPoints());
    while (iterGrid->isValid())
    {
        Eigen::ArrayXd pointCoord = iterGrid->getCoordinate();
        valuesFunction.col(iterGrid->getCount()).setConstant(f(pointCoord)) ;
        iterGrid->next();
    }

    // Hieriarchize
    ArrayXXd hieraValues = valuesFunction;
    sparseGrid.toHierarchizeVec(hieraValues);

    shared_ptr<GridIterator > iterGridNew = sparseGrid.getGridIterator();
    while (iterGridNew->isValid())
    {
        Eigen::ArrayXd pointCoord = iterGridNew->getCoordinate();
        // interpolator
        shared_ptr<Interpolator > interpolator = sparseGrid.createInterpolator(pointCoord);
        ArrayXd  interVal  = interpolator->applyVec(hieraValues);
        BOOST_CHECK_CLOSE(interVal(0), valuesFunction(0, iterGridNew->getCount()), accuracyEqual);
        iterGridNew->next();
    }
}

BOOST_AUTO_TEST_CASE(testCheckSparseInterpolationNoBound)
{
    // 1D , level 3, linear
    ArrayXd  weight1D = ArrayXd::Constant(1, 1.);
    testSparseGridInterpolationNoBound(3, 1, weight1D);

    // 1D , level 5, Cubic
    testSparseGridInterpolationNoBound(5, 3, weight1D);

    // 3D , level 4, linear
    ArrayXd  weight3D = ArrayXd::Constant(3, 1.);
    testSparseGridInterpolationNoBound(4, 1, weight3D);

    // 3D , level 4, quadratic
    testSparseGridInterpolationNoBound(4, 2, weight3D);

    // 3D , level 4, cubic
    testSparseGridInterpolationNoBound(4, 3, weight3D);

    // 5D , level 4, cubic
    ArrayXd  weight5D = ArrayXd::Constant(5, 1.);
    testSparseGridInterpolationNoBound(4, 3, weight5D);

    // 7D , level 5,  quadartic
    ArrayXd  weight7D = ArrayXd::Constant(7, 1.);
    testSparseGridInterpolationNoBound(5, 2, weight7D);

}

// test with dimension
void testSpectralInterpolatorNoBound(int p_nDim, int p_level)
{
    ArrayXd lowValues = ArrayXd::Constant(p_nDim, 1.); // bottom of the domain
    ArrayXd sizeDomain = ArrayXd::Constant(p_nDim, 5.); // size of the mesh

    // linear
    int degree = 1;
    // isotropic
    ArrayXd  weight = ArrayXd::Constant(p_nDim, 1.);
    SparseSpaceGridNoBound  sparseGrid(lowValues, sizeDomain, p_level, weight, degree);

    // Data
    ArrayXd data(sparseGrid.getNbPoints());
    shared_ptr<GridIterator> iterSparseGrid  =  sparseGrid.getGridIterator();
    while (iterSparseGrid->isValid())
    {
        ArrayXd pointCoord = iterSparseGrid->getCoordinate();
        data(iterSparseGrid->getCount()) = pow(1. + pointCoord(0), 2.);
        for (int id = 1; id < p_nDim; ++id)
            data(iterSparseGrid->getCount()) *= pow(1. + pointCoord(id), 2.);
        iterSparseGrid->next();
    }


    // spectral interpolator
    SparseInterpolatorSpectral interpolator(&sparseGrid, data);
    boost::mt19937 generator;
    boost::uniform_01<double> alea;
    boost::variate_generator<boost::mt19937 &, boost::uniform_01<double> > uniform(generator, alea);

    int nsimul = 1000;
    for (int is = 0; is < nsimul; ++is)
    {
        ArrayXd pointCoord(p_nDim);
        // avoid near boundary
        for (int id = 0; id < p_nDim; ++id)
            pointCoord(id) = lowValues(id) + sizeDomain(id) * (1. + uniform()) / 4.;
        double val = pow(1. + pointCoord(0), 2.);
        for (int id = 1; id < p_nDim; ++id)
            val *= pow(1. + pointCoord(id), 2.);

        double vInterp = interpolator.apply(pointCoord);
        // check exact  interpolation for polynomials
        BOOST_CHECK_CLOSE(vInterp, val,  accuracyNearEqual);

    }
}


BOOST_AUTO_TEST_CASE(testCheckSpectralInterpolatorNoBound)
{
    // 1D, level 5
    testSpectralInterpolatorNoBound(1, 5);

    // 3D level 5
    testSpectralInterpolatorNoBound(3, 5);

}
