// Copyright (C) 2016 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#define BOOST_TEST_MODULE testSparseHierarchization
#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"

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

    //  test iterator on each level
    ArrayXd valuesFunctionTest(sparseGrid.getNbPoints());
    shared_ptr<SparseSet> dataSet =  sparseGrid.getDataSet();
    for (SparseSet::const_iterator iterLevel = dataSet->begin(); iterLevel != dataSet->end(); ++iterLevel)
    {
        // get back iterator on this level
        shared_ptr<SparseGridIterator> iterGridLevel = sparseGrid.getLevelGridIterator(iterLevel);
        while (iterGridLevel->isValid())
        {
            Eigen::ArrayXd pointCoord = iterGridLevel->getCoordinate();
            valuesFunctionTest(iterGridLevel->getCount()) = f(pointCoord);
            iterGridLevel->next();
        }
    }

    for (int i = 0 ; i < valuesFunctionTest.size(); ++i)
        BOOST_CHECK_CLOSE(valuesFunctionTest(i), valuesFunction(0, i), accuracyEqual);

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

    // test hierarchization point by point
    vector<SparsePoint> sparsePoints(sparseGrid.getNbPoints());
    // iterate on points
    for (SparseSet::const_iterator iterLevel = dataSet->begin(); iterLevel != dataSet->end(); ++iterLevel)
        for (SparseLevel::const_iterator iterPosition = iterLevel->second.begin(); iterPosition != iterLevel->second.end(); ++iterPosition)
            sparsePoints[iterPosition->second] = make_tuple(iterLevel->first, iterPosition->first, iterPosition->second);

    ArrayXXd hieraValuesBis(hieraValues.rows(), hieraValues.cols());
    sparseGrid.toHierarchizePByPVec(valuesFunction, sparsePoints, hieraValuesBis);
    for (int j = 0 ; j < hieraValuesBis.cols(); ++j)
        for (int i = 0; i < hieraValuesBis.rows(); ++i)
            BOOST_CHECK_CLOSE(hieraValuesBis(i, j), hieraValues(i, j), accuracyEqualFirst);

    // now test level by level
    ArrayXXd hieraValuesTer(valuesFunction.rows(), valuesFunction.cols());
    for (SparseSet::const_iterator iterLevel = dataSet->begin(); iterLevel != dataSet->end(); ++iterLevel)
        sparseGrid.toHierarchizePByPLevelVec(valuesFunction, iterLevel, hieraValuesTer);

    // verifiy equality
    for (int j = 0 ; j < hieraValuesBis.cols(); ++j)
        for (int i = 0; i < hieraValuesBis.rows(); ++i)
            BOOST_CHECK_CLOSE(hieraValuesBis(i, j), hieraValuesTer(i, j), accuracyEqual);

}

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

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

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

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

}


/// test cases eliminating boundary points
void testSparseGridHierarNoBound(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();
    }

    //  test iterator on each level
    ArrayXd valuesFunctionTest(sparseGrid.getNbPoints());
    shared_ptr<SparseSet> dataSet =  sparseGrid.getDataSet();
    for (SparseSet::const_iterator iterLevel = dataSet->begin(); iterLevel != dataSet->end(); ++iterLevel)
    {
        // get back iterator on this level
        shared_ptr<SparseGridIterator> iterGridLevel = sparseGrid.getLevelGridIterator(iterLevel);
        while (iterGridLevel->isValid())
        {
            Eigen::ArrayXd pointCoord = iterGridLevel->getCoordinate();
            valuesFunctionTest(iterGridLevel->getCount()) = f(pointCoord);
            iterGridLevel->next();
        }
    }
    // Hieriarchize
    ArrayXXd hieraValues = valuesFunction;
    sparseGrid.toHierarchizeVec(hieraValues);

    // test hierarchization point by point
    vector<SparsePoint> sparsePoints(sparseGrid.getNbPoints());
    // iterate on points
    for (SparseSet::const_iterator iterLevel = dataSet->begin(); iterLevel != dataSet->end(); ++iterLevel)
        for (SparseLevel::const_iterator iterPosition = iterLevel->second.begin(); iterPosition != iterLevel->second.end(); ++iterPosition)
            sparsePoints[iterPosition->second] = make_tuple(iterLevel->first, iterPosition->first, iterPosition->second);

    ArrayXXd hieraValuesBis(hieraValues.rows(), hieraValues.cols());
    sparseGrid.toHierarchizePByPVec(valuesFunction, sparsePoints, hieraValuesBis);
    for (int j = 0 ; j < hieraValuesBis.cols(); ++j)
        for (int i = 0; i < hieraValuesBis.rows(); ++i)
            BOOST_CHECK_CLOSE(hieraValuesBis(i, j), hieraValues(i, j), accuracyEqualFirst);


    // now test level by level
    ArrayXXd hieraValuesTer(valuesFunction.rows(), valuesFunction.cols());
    for (SparseSet::const_iterator iterLevel = dataSet->begin(); iterLevel != dataSet->end(); ++iterLevel)
        sparseGrid.toHierarchizePByPLevelVec(valuesFunction, iterLevel, hieraValuesTer);

    // verifiy equality
    for (int j = 0 ; j < hieraValuesBis.cols(); ++j)
        for (int i = 0; i < hieraValuesBis.rows(); ++i)
            BOOST_CHECK_CLOSE(hieraValuesBis(i, j), hieraValuesTer(i, j), accuracyEqual);

}

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

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

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

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

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

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

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

}
