// Copyright (C) 2019 Fime
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#ifndef USE_MPI
#define BOOST_TEST_MODULE testGasStorageSDDPTree
#endif
#define BOOST_TEST_DYN_LINK
#ifdef USE_MPI
#include <boost/mpi.hpp>
#endif
#include <functional>
#include <array>
#include <memory>
#define _USE_MATH_DEFINES
#include <math.h>
#include <boost/test/unit_test.hpp>
#include <Eigen/Dense>
#include "geners/Record.hh"
#include "StOpt/core/grids/OneDimRegularSpaceGrid.h"
#include "StOpt/core/grids/OneDimData.h"
#include "StOpt/core/grids/RegularSpaceGridGeners.h"
#include "StOpt/core/utils/eigenGeners.h"
#include "StOpt/regression/LocalLinearRegression.h"
#include "StOpt/sddp/SDDPFinalCutTree.h"
#include "StOpt/sddp/backwardForwardSDDPTree.h"
#include "StOpt/sddp/backwardSDDPTree.h"
#include "test/c++/tools/simulators/MeanRevertingSimulator.h"
#include "test/c++/tools/simulators/TrinomialTreeOUSimulator.h"
#include "test/c++/tools/simulators/DeterTreeSimulator.h"
#include "test/c++/tools/simulators/MeanRevertingSimulatorTree.h"
#include "test/c++/tools/sddp/OptimizeGasStorageSDDP.h"
#include "test/c++/tools/dp/OptimizeGasStorage.h"
#include "test/c++/tools/dp/DynamicProgrammingByRegression.h"
#include "test/c++/tools/dp/DynamicProgrammingByRegressionDist.h"
#include "test/c++/tools/dp/SimulateRegression.h"
#include "test/c++/tools/dp/SimulateRegressionDist.h"

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


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


class ZeroFunction
{
public:
    ZeroFunction() {}
    double operator()(const int &, const ArrayXd &, const ArrayXd &) const
    {
        return 0. ;
    }
};


// low accuracy to have not too long test cases (percnetage)
double accuracyClose =  0.5;
double accuracyCClose =  2.;
double accuracyStopSDDP =  5.;

/// valorisaton par DP et local linear regressions
/// \param p_nbStorage             number of storage
/// \param p_nbStep                number of time step for storage valuation
/// \param p_nbsimulOpt            number of simulations in backward resolution for DP resolution
/// \param p_nMeshDP               number of mesh used in DP by regresson methods
/// \param p_maxLevelStorage       storage level
/// \param p_injectionRateStorage  injecton rate
/// \param p_withdrawalCostStorage withdrawal rate
/// \param p_maturity              maturity
/// \param p_futureGrid            future curve
/// \param p_mr                    mean reverting
/// \param p_sig                   volatility
/// \param p_nbPtGrid              number of grid points
/// \param p_nbSimulcheck          number of simulations to check convergence
/// \param p_initStorage           initial storage level
/// \param p_world       MPI communicator
/// \param p_strAdd      to make difference for file with  different communicators
double valorizationDP(const size_t &p_nbStep,  const int &p_nbsimulOpt, const int   &p_nMeshDP,
                      const double &p_maxLevelStorage, const double &p_injectionRateStorage, const double &p_withdrawalRateStorage,
                      const double &p_injectionCostStorage, const   double &p_withdrawalCostStorage, const double &p_maturity,
                      shared_ptr<OneDimData<OneDimRegularSpaceGrid, double> > &p_futureGrid,
                      const double &p_mr, const double &p_sig, const int &p_nbPtGrid, const int &p_nbSimulcheck,
                      const double   &p_initStorage
#ifdef USE_MPI
                      , const boost::mpi::communicator &p_world, const  string &p_strAdd
#endif
                     )
{
    // one dimensional factors
    int nDim = 1;
    VectorXd sigma = VectorXd::Constant(nDim, p_sig);
    VectorXd mr = VectorXd::Constant(nDim, p_mr);

    // initial values
    ArrayXd initialStockDP = ArrayXd::Constant(1, p_initStorage);

    // grid
    //////
    int nGrid = p_nbPtGrid;
    ArrayXd lowValues = ArrayXd::Constant(1, 0.);
    ArrayXd step = ArrayXd::Constant(1, p_maxLevelStorage / nGrid);
    ArrayXi nbStep = ArrayXi::Constant(1, nGrid);
    shared_ptr<RegularSpaceGrid> grid = make_shared<RegularSpaceGrid>(lowValues, step, nbStep);

    // no actualization
    double r  = 0 ;
    // a backward simulator
    ///////////////////////
    bool bForward = false;
    int nbSimulDP = p_nbsimulOpt;
    shared_ptr< MeanRevertingSimulator< OneDimData<OneDimRegularSpaceGrid, double> > > backSimulatorDP = make_shared<MeanRevertingSimulator< OneDimData<OneDimRegularSpaceGrid, double> > >(p_futureGrid, sigma, mr, r,  p_maturity, p_nbStep, nbSimulDP, bForward);

    bForward = true;
    shared_ptr< MeanRevertingSimulator< OneDimData<OneDimRegularSpaceGrid, double> > > forSimulator = make_shared<MeanRevertingSimulator< OneDimData<OneDimRegularSpaceGrid, double> > > (p_futureGrid, sigma, mr, r, p_maturity, p_nbStep, p_nbSimulcheck, bForward);
    // optimizer
    ///////////
    shared_ptr< OptimizeGasStorage< MeanRevertingSimulator< OneDimData<OneDimRegularSpaceGrid, double> > > > storage = make_shared< OptimizeGasStorage< MeanRevertingSimulator< OneDimData<OneDimRegularSpaceGrid, double> > > > (p_injectionRateStorage, p_withdrawalRateStorage, p_injectionCostStorage, p_withdrawalCostStorage);
    // regressor
    ///////////
    int nMeshDP = p_nMeshDP ;
    ArrayXi nbMeshDP = ArrayXi::Constant(1, nMeshDP);
    shared_ptr< BaseRegression > regressor = make_shared<LocalLinearRegression>(nbMeshDP);
    // final value
    function<double(const int &, const ArrayXd &, const ArrayXd &)>   vFunction = ZeroFunction();

    // initial regime
    int initialRegime = 0; // only one regime

    // Optimize
    ///////////
    string fileToDump = "CondExpGasStorageSDDPTree";
#ifdef USE_MPI
    fileToDump = fileToDump + p_strAdd;
#endif
    // link the simulations to the optimizer
    storage->setSimulator(backSimulatorDP);

#ifdef USE_MPI
    bool bOneFile = true;
    double valueOptim =  DynamicProgrammingByRegressionDist(grid, storage, regressor, vFunction, initialStockDP, initialRegime, fileToDump, bOneFile, p_world);
    // link the simulations to the optimizer
    storage->setSimulator(forSimulator);
    double valSimu = SimulateRegressionDist(grid, storage, vFunction, initialStockDP, initialRegime, fileToDump, bOneFile, p_world) ;
#else
    double valueOptim =  DynamicProgrammingByRegression(grid, storage, regressor, vFunction, initialStockDP, initialRegime, fileToDump);
    // link the simulations to the optimizer
    storage->setSimulator(forSimulator);
    double valSimu = SimulateRegression(grid, storage, vFunction, initialStockDP, initialRegime, fileToDump) ;

#endif

#ifdef USE_MPI
    if (p_world.rank() == 0)
#endif
        cout << " Value by DP " << valueOptim << " AND  sim " <<  valSimu << endl ;

    return valSimu;


}

/// Valorisation par DP with  cuts : so each transition problem is solved with a LP resolution
/// \param p_nbStorage            number of storage
/// \param p_maxLevelStorage       storage level
/// \param p_injectionRateStorage  injecton rate
/// \param p_withdrawalCostStorage withdrawal rate
/// \param p_maturity              maturity
/// \param p_futureGrid            future curve
/// \param p_name                  name of archive for tree
/// \param p_mr                    mean reverting
/// \param p_sig                   volatility
/// \param p_nbPtGrid              number of grid points
/// \param p_nbSimul               number od sample in simulation
/// \return value and optimization and simulation
/// \param p_initStorage           initial storage level
/// \param p_world       MPI communicator
/// \param p_strAdd      to make difference for file with  different communicators
double valorizationDPCuts(const int &p_nbStorage,
                          const double &p_maxLevelStorage, const double &p_injectionRateStorage, const double &p_withdrawalRateStorage,
                          const double &p_injectionCostStorage, const   double &p_withdrawalCostStorage,
                          shared_ptr<OneDimData<OneDimRegularSpaceGrid, double> > &p_futureGrid,
                          const string &p_name,
                          const double &p_mr, const double &p_sig, const int &p_nbPtGrid, const int &p_nbSimul,
                          const double   &p_initStorage
#ifdef USE_MPI
                          , const boost::mpi::communicator &p_world, const  string &p_strAdd
#endif
                         )
{

#ifdef USE_MPI
    int iTask = p_world.rank();
    p_world.barrier();
#else
    int iTask = 0;
#endif

    // create a grid of points
    ArrayXd grid = ArrayXd::LinSpaced(p_nbPtGrid + 1, 0., p_maxLevelStorage);

    // initial values
    ArrayXd initialStock = ArrayXd::Constant(p_nbStorage, p_initStorage);


    shared_ptr<gs::BinaryFileArchive> binArxiv = make_shared<gs::BinaryFileArchive>(p_name.c_str(), "r");

    // create mean reverting backward simulator
    shared_ptr<MeanRevertingSimulatorTree<OneDimData<OneDimRegularSpaceGrid, double> > >   backSimulator = make_shared<MeanRevertingSimulatorTree<OneDimData<OneDimRegularSpaceGrid, double> > >(binArxiv, p_futureGrid, p_sig, p_mr);

    // a forward simulator
    shared_ptr<MeanRevertingSimulatorTree< OneDimData<OneDimRegularSpaceGrid, double> > > forSimulator = make_shared<MeanRevertingSimulatorTree< OneDimData<OneDimRegularSpaceGrid, double> > >(binArxiv, p_futureGrid, p_sig, p_mr, p_nbSimul);

    // SDDP one step optimizer
    shared_ptr< OptimizerSDDPBase> optimizer =   make_shared< OptimizeGasStorageSDDP< MeanRevertingSimulatorTree< OneDimData<OneDimRegularSpaceGrid, double> > > > (p_maxLevelStorage, p_injectionRateStorage,
            p_withdrawalRateStorage, p_injectionCostStorage,
            p_withdrawalCostStorage, p_nbStorage,
            backSimulator, forSimulator);

    /// final cut
    ArrayXXd finalCut =  ArrayXXd::Zero(1 + p_nbStorage, 1);
    SDDPFinalCutTree finCut(finalCut);

    // optimisation dates
    ArrayXd dates = backSimulator->getDates();

    // names for archive
    string nameCut = "CutGasStorageForTree";
    string nameVisitedStates = "VisitedStateGasStorageForTree";
#ifdef USE_MPI
    nameCut = nameCut + p_strAdd;
    nameVisitedStates = nameVisitedStates + p_strAdd;
#endif

    // archive for cuts read write
    std::shared_ptr<gs::BinaryFileArchive> archiveForCuts;

    // cast fro backwardSDDPTree call
    std::shared_ptr<SimulatorSDDPBaseTree>  simBack = static_pointer_cast<SimulatorSDDPBaseTree>(backSimulator);

    if (iTask == 0)
    {
        archiveForCuts = std::make_shared<gs::BinaryFileArchive>(nameCut.c_str(), "w+");

        // create a first set of admissible states
        gs::BinaryFileArchive archiveForInitialState(nameVisitedStates.c_str(), "w");

        // create admissible states
        for (int idate = 0 ; idate < dates.size() - 1; ++idate)
        {
            // actualize time
            simBack->updateDateIndex(idate);
            // get number of nodes
            int nbNodes = simBack-> getNbNodes();
            // store all states
            SDDPVisitedStatesTree setOfStates(nbNodes);

            if (idate == 0)
            {
                std::shared_ptr< Eigen::ArrayXd > state =  std::make_shared< Eigen::ArrayXd >(initialStock);
                setOfStates.addVisitedStateForAll(state, nbNodes);
            }
            else
            {
                for (int ig = 0; ig < grid.size(); ++ig)
                {
                    std::shared_ptr< Eigen::ArrayXd > state = make_shared<Eigen::ArrayXd>(1);
                    (*state)(0) = grid(ig);
                    setOfStates.addVisitedStateForAll(state, nbNodes);
                }
            }
            archiveForInitialState << gs::Record(setOfStates, "States", "Top");
        }
    }
#ifdef USE_MPI
    p_world.barrier();
#endif

    // reinitialize simulator
    simBack->resetTime();
    ostringstream stringStream;
    double  value = backwardSDDPTree(optimizer, simBack, dates, initialStock, finCut, nameVisitedStates, archiveForCuts
#ifdef USE_MPI
                                     , p_world
#endif
                                    );

#ifdef USE_MPI
    if (p_world.rank() == 0)
#endif
        cout << "End of backward with cuts" << endl ;
#ifdef USE_MPI
    p_world.barrier();
#endif

    // value in simulaton
    std::shared_ptr<SimulatorSDDPBaseTree>  simFor = static_pointer_cast<SimulatorSDDPBaseTree>(forSimulator);

    double valSimul = forwardSDDPTree(optimizer, simFor, dates, initialStock, finCut, false, archiveForCuts, nameVisitedStates
#ifdef USE_MPI
                                      , p_world
#endif
                                     );


#ifdef USE_MPI
    p_world.barrier();
    if (p_world.rank() == 0)
#endif
    {
        cout << stringStream.str() << endl ;
        cout << " Value Optim " <<  value << "Value simul " <<  valSimul <<  endl ;
    }

    return valSimul;
    return 0;
}



/// Valorisation with  SDDP : iterations between forward and backward resolutions
/// \param p_nbStorage             number of storage
/// \param p_nbStep                number of time step for storage valuation
/// \param p_nbSamSimul            number of sample used in simulation to derive new path used in the next backward resolution
/// \param p_nSimulCheck           number of simulations to check convergence in SDDP
/// \param p_maxLevelStorage       storage level
/// \param p_injectionRateStorage  injecton rate
/// \param p_withdrawalCostStorage withdrawal rate
/// \param p_maturity              maturity
/// \param p_futureGrid            future curve
/// \param p_name                  name of archive for tree
/// \param p_mr                    mean reverting
/// \param p_sig                   volatility
/// \param p_initStorage           initial storage level
/// \param p_world       MPI communicator
/// \param p_strAdd      to make difference for file with  different communicators
/// \return value and optimization and simulation
pair<double, double> valorizationSDDP(const int &p_nbStorage, const size_t &p_nbStep, const int   &p_nbSamSimul, const int &p_nSimulCheck,
                                      const double &p_maxLevelStorage, const double &p_injectionRateStorage, const double &p_withdrawalRateStorage,
                                      const double &p_injectionCostStorage, const   double &p_withdrawalCostStorage, const double &p_maturity,
                                      shared_ptr<OneDimData<OneDimRegularSpaceGrid, double> > &p_futureGrid,
                                      const string &p_name,
                                      const double &p_mr, const double &p_sig,
                                      const double   &p_initStorage
#ifdef USE_MPI
                                      , const boost::mpi::communicator &p_world, const  string &p_strAdd
#endif
                                     )
{

#ifdef USE_MPI
    p_world.barrier();
#endif
    // initial values
    ArrayXd initialStock = ArrayXd::Constant(p_nbStorage, p_initStorage);

    shared_ptr<gs::BinaryFileArchive> binArxiv = make_shared<gs::BinaryFileArchive>(p_name.c_str(), "r");

    // create mean reverting backward simulator
    shared_ptr<MeanRevertingSimulatorTree<OneDimData<OneDimRegularSpaceGrid, double> > >   backSimulator = make_shared<MeanRevertingSimulatorTree<OneDimData<OneDimRegularSpaceGrid, double> > >(binArxiv, p_futureGrid, p_sig, p_mr);

    // a forward simulator
    shared_ptr<MeanRevertingSimulatorTree< OneDimData<OneDimRegularSpaceGrid, double> > > forSimulator = make_shared<MeanRevertingSimulatorTree< OneDimData<OneDimRegularSpaceGrid, double> > >(binArxiv, p_futureGrid, p_sig, p_mr, p_nbSamSimul);

    // value by SDDP
    //**************
    // SDDP one step optimizer
    shared_ptr< OptimizerSDDPBase> optimizer =   make_shared< OptimizeGasStorageSDDP< MeanRevertingSimulatorTree< OneDimData<OneDimRegularSpaceGrid, double> > > > (p_maxLevelStorage, p_injectionRateStorage,
            p_withdrawalRateStorage, p_injectionCostStorage,
            p_withdrawalCostStorage, p_nbStorage,
            backSimulator, forSimulator);

    /// final cut
    ArrayXXd finalCut =  ArrayXXd::Zero(1 + p_nbStorage, 1);
    SDDPFinalCutTree finCut(finalCut);

    // optimisation dates
    ArrayXd dates = ArrayXd::LinSpaced(p_nbStep + 1, 0., p_maturity);

    // names for archive
    string nameCut = "CutGasStorageTree";
    string nameVisitedStates = "CutVisitedStateGasStorageTree";
#ifdef USE_MPI
    nameCut = nameCut + p_strAdd;
    nameVisitedStates = nameVisitedStates + p_strAdd;
#endif

    // precision parameter
    int nIterMax = 200;
    double accuracy = accuracyStopSDDP / 100;
    int nstepIterations = 40; // take values between optimization n and optimization n+ nstepIterations for convergence criterion
    ostringstream stringStream;
    pair<double, double>  values = backwardForwardSDDPTree(optimizer, p_nSimulCheck, initialStock, finCut, dates,   nameCut, nameVisitedStates, nIterMax,
                                   accuracy, nstepIterations, stringStream
#ifdef USE_MPI
                                   , p_world
#endif
                                                          );
#ifdef USE_MPI
    if (p_world.rank() == 0)
#endif
    {
        cout << stringStream.str() << endl ;
        cout << " Value Optim " <<  values.first << " and Simulation " << values.second << " Iteration " << nIterMax << endl ;
    }

    return values;
}



/// \brief Comparison SDDP , DP for gas storage
/// \param p_nbStorage            number of storage
/// \param p_nbStep               number of time step for storage valuation
/// \param p_nbStepTreePerStep    for each time step, number of time step for tree discretization with trinomial tree,
/// \param p_nbSamSimul           number of sample used in simulation to derive new path used in the next backward resolution
/// \param p_nSimulCheck          number of simulations to check convergence in SDDP
/// \param p_nbsimulOpt           number of simulations in backward resolution for DP resolution
/// \param p_nMeshDP              number of mesh used in DP by regresson methods
/// \param p_world                 MPI communicator
/// \param p_strAdd                to make difference for file with  different communicators
void testStorageSDDPTree(const int &p_nbStorage, const size_t &p_nbStep,  const int   &p_nbStepTreePerStep, const int   &p_nbSamSimul, const int &p_nSimulCheck,
                         const int &p_nbsimulOpt,
                         const int   &p_nMeshDP
#ifdef USE_MPI
                         , const boost::mpi::communicator &p_world, const  string &p_strAdd
#endif
                        )
{


    // storage
    /////////
    double maxLevelStorage  = 360000;
    double injectionRateStorage = 60000;
    double withdrawalRateStorage = 45000;
    double injectionCostStorage = 0.35;
    double withdrawalCostStorage = 0.35;
    double initLevel = withdrawalRateStorage;
    double maturity = 1.;

    // mean reverting parameters
    double mr = 0.29;
    double sigma =  0.94 ;

    int nbPtGrid = 100;

    // Dynamic of the future
    //*********************

    // define a a time grid
    shared_ptr<OneDimRegularSpaceGrid> timeGrid(new OneDimRegularSpaceGrid(0., maturity / p_nbStep, p_nbStep));
    // future values
    shared_ptr<vector< double > > futValues(new vector<double>(p_nbStep + 1));
    // periodicity factor
    int iPeriod = 52;
    for (size_t i = 0; i < p_nbStep + 1; ++i)
        (*futValues)[i] = 50. + 10 * sin((M_PI * i * iPeriod) / p_nbStep);
    // define the future curve
    shared_ptr<OneDimData<OneDimRegularSpaceGrid, double> > futureGrid(new OneDimData< OneDimRegularSpaceGrid, double> (timeGrid, futValues));

    // value by DP
    //**************
    double valueSimu = valorizationDP(p_nbStep,  p_nbsimulOpt, p_nMeshDP, maxLevelStorage,
                                      injectionRateStorage, withdrawalRateStorage, injectionCostStorage, withdrawalCostStorage,
                                      maturity, futureGrid, mr, sigma, nbPtGrid, p_nSimulCheck, initLevel, p_world, p_strAdd);


#ifdef USE_MPI
    p_world.barrier();
#endif


    // simulation dates for tree
    ArrayXd ddates =  ArrayXd::LinSpaced(p_nbStep * p_nbStepTreePerStep + 1, 0., 1.);

    TrinomialTreeOUSimulator tree(mr, sigma, ddates);

    // sub array for dates
    ArrayXi indexT(p_nbStep + 1);
    for (size_t i = 0; i < p_nbStep; ++i)
        indexT(i) = i * p_nbStepTreePerStep;
    indexT(p_nbStep) = ddates.size() - 1;


    // create archive
    string nameTree = "Tree";
#ifdef USE_MPI
    if (p_world.rank() == 0)
#endif
        tree.dump(nameTree, indexT);
#ifdef USE_MPI
    p_world.barrier();
#endif


    // solve SDDP
    pair<double, double> values = valorizationSDDP(p_nbStorage, p_nbStep, p_nbSamSimul, p_nSimulCheck, maxLevelStorage, injectionRateStorage, withdrawalRateStorage,
                                  injectionCostStorage, withdrawalCostStorage, maturity, futureGrid, nameTree, mr, sigma, initLevel, p_world, p_strAdd);


#ifdef USE_MPI
    p_world.barrier();
    if (p_world.rank() == 0)
#endif
    {
        BOOST_CHECK_CLOSE(valueSimu * p_nbStorage, values.second, accuracyCClose);
    }
}


/// \brief  DP for gas storage with and without cuts for one storage
///         Here deterministic tree (one node)
/// \param p_nbStep               number of time step for storage valuation
/// \param p_nbPtGrid             number of grid points
/// \param p_world       MPI communicator
/// \param p_strAdd      to make difference for file with  different communicators
void testStorageDPDeterTreeCut(const size_t &p_nbStep, const int &p_nbPtGrid
#ifdef USE_MPI
                               , const boost::mpi::communicator &p_world, const  string &p_strAdd
#endif
                              )
{

    // storage
    /////////
    double maxLevelStorage  = 360000;
    double injectionRateStorage = 60000;
    double withdrawalRateStorage = 45000;
    double injectionCostStorage = 0.35;
    double withdrawalCostStorage = 0.35;
    double initLevel = withdrawalRateStorage;
    double maturity = 1.;

    // parameters of the tree
    double mr = 1;
    double sig = 0.00001 ;


    // Dynamic of the future
    //*********************

    // define a a time grid
    shared_ptr<OneDimRegularSpaceGrid> timeGrid(new OneDimRegularSpaceGrid(0., maturity / p_nbStep, p_nbStep));
    // future values
    shared_ptr<vector< double > > futValues(new vector<double>(p_nbStep + 1));
    // periodicity factor
    int iPeriod = 52;
    for (size_t i = 0; i < p_nbStep + 1; ++i)
        (*futValues)[i] = 50. + 10 * sin((M_PI * i * iPeriod) / p_nbStep);
    // define the future curve
    shared_ptr<OneDimData<OneDimRegularSpaceGrid, double> > futureGrid(new OneDimData< OneDimRegularSpaceGrid, double> (timeGrid, futValues));

    // value by DP
    //**************
    int nbsim = 4;
    int nbmesh = 1;
    double valueSimu = valorizationDP(p_nbStep, nbsim, nbmesh, maxLevelStorage,
                                      injectionRateStorage, withdrawalRateStorage, injectionCostStorage, withdrawalCostStorage,
                                      maturity, futureGrid, mr, sig, p_nbPtGrid, 1, initLevel, p_world, p_strAdd);


#ifdef USE_MPI
    p_world.barrier();
#endif


    // simulation dates for tree
    ArrayXd ddates =  ArrayXd::LinSpaced(p_nbStep + 1, 0., 1.);

    // simulation tree
    DeterTreeSimulator tree(ddates);

    // sub array for dates
    ArrayXi indexT(p_nbStep + 1);
    for (size_t i = 0; i <= p_nbStep; ++i)
        indexT(i) = i ;

    // create arxiv
    string nameTree = "Tree";

#ifdef USE_MPI
    nameTree = nameTree + p_strAdd;
    if (p_world.rank() == 0)
#endif
        tree.dump(nameTree, indexT);

#ifdef USE_MPI
    p_world.barrier();
#endif

    // solve DP with cuts
    double valDPCut = valorizationDPCuts(1, maxLevelStorage, injectionRateStorage, withdrawalRateStorage,
                                         injectionCostStorage,  withdrawalCostStorage,  futureGrid, nameTree, mr, sig, p_nbPtGrid, 1, initLevel, p_world, p_strAdd);

#ifdef USE_MPI
    p_world.barrier();
    if (p_world.rank() == 0)
#endif
    {
        BOOST_CHECK_CLOSE(valueSimu, valDPCut, accuracyClose);
    }

#ifdef USE_MPI
    p_world.barrier();
#endif


}

/// \brief Comparison  DP  classical and cuts in stochastic
/// \param p_nbStep               number of time step for storage valuation
/// \param p_nbStepTreePerStep    for each time step, number of time step for tree discretization with trinomial tree,
//// \param p_nSimulCheck         number of simulations to check convergence in SDDP
/// \param p_nbsimulOpt           number of simulations in backward resolution for DP resolution
/// \param p_nMeshDP              number of mesh used in DP by regresson methods
/// \param p_nbPtGrid             number of points on grid
/// \param p_world       MPI communicator
/// \param p_strAdd      to make difference for file with  different communicators
void testStorageCutTree(const size_t &p_nbStep,  const int   &p_nbStepTreePerStep,  const int &p_nSimulCheck,
                        const int &p_nbsimulOpt,
                        const int   &p_nMeshDP, int p_nbPtGrid
#ifdef USE_MPI
                        , const boost::mpi::communicator &p_world, const  string &p_strAdd
#endif

                       )
{


    // storage
    /////////
    double maxLevelStorage  = 360000;
    double injectionRateStorage = 60000;
    double withdrawalRateStorage = 45000;
    double injectionCostStorage = 0.35;
    double withdrawalCostStorage = 0.35;
    double initLevel = withdrawalRateStorage;
    double maturity = 1.;

    // mean reverting parameters
    double mr = 0.29;
    double sigma =  0.94 ;


    // Dynamic of the future
    //*********************

    // define a a time grid
    shared_ptr<OneDimRegularSpaceGrid> timeGrid(new OneDimRegularSpaceGrid(0., maturity / p_nbStep, p_nbStep));
    // future values
    shared_ptr<vector< double > > futValues(new vector<double>(p_nbStep + 1));
    // periodicity factor
    int iPeriod = 52;
    for (size_t i = 0; i < p_nbStep + 1; ++i)
        (*futValues)[i] = 50. + 10 * sin((M_PI * i * iPeriod) / p_nbStep);
    // define the future curve
    shared_ptr<OneDimData<OneDimRegularSpaceGrid, double> > futureGrid(new OneDimData< OneDimRegularSpaceGrid, double> (timeGrid, futValues));

    // value by DP
    //**************
    double valueSim = valorizationDP(p_nbStep,  p_nbsimulOpt, p_nMeshDP, maxLevelStorage,
                                     injectionRateStorage, withdrawalRateStorage, injectionCostStorage, withdrawalCostStorage,
                                     maturity, futureGrid, mr, sigma, p_nbPtGrid, p_nSimulCheck, initLevel, p_world, p_strAdd);


#ifdef USE_MPI
    p_world.barrier();
#endif


    // simulation dates for tree
    ArrayXd ddates =  ArrayXd::LinSpaced(p_nbStep * p_nbStepTreePerStep + 1, 0., 1.);

    TrinomialTreeOUSimulator tree(mr, sigma, ddates);

    // sub array for dates
    ArrayXi indexT(p_nbStep + 1);
    for (size_t i = 0; i < p_nbStep; ++i)
        indexT(i) = i * p_nbStepTreePerStep;
    indexT(p_nbStep) = ddates.size() - 1;


    // create arxiv
    string nameTree = "Tree";
#ifdef USE_MPI
    nameTree = nameTree + p_strAdd;
    if (p_world.rank() == 0)
#endif
        tree.dump(nameTree, indexT);

#ifdef USE_MPI
    p_world.barrier();
#endif

    // solve DP with cuts
    double valDPCut = valorizationDPCuts(1, maxLevelStorage, injectionRateStorage, withdrawalRateStorage,
                                         injectionCostStorage,  withdrawalCostStorage,  futureGrid, nameTree,
                                         mr, sigma, p_nbPtGrid, p_nSimulCheck, initLevel, p_world, p_strAdd);

#ifdef USE_MPI
    if (p_world.rank() == 0)
#endif
    {
        BOOST_CHECK_CLOSE(valueSim, valDPCut, accuracyCClose);
    }

}



BOOST_AUTO_TEST_CASE(testSimpleStorageDeterministicCutTree)
{
    int nbStep = 30 ;
    int nbPtGrid = 200;
#ifdef USE_MPI
    boost::mpi::communicator world ;
    world.barrier();
#endif
    testStorageDPDeterTreeCut(nbStep, nbPtGrid
#ifdef USE_MPI
                              , world, ""
#endif
                             );
}

#ifdef USE_MPI
BOOST_AUTO_TEST_CASE(testSimpleStorageDeterministicCutTreeCommunicator)
{
    int nbStep = 30 ;
    int nbPtGrid = 200;
    boost::mpi::communicator world ;
    world.barrier();
    if (world.size() > 1)
    {
        // create 2 communicators
        bool bSeparate = (2 * world.rank() < world.size());
        boost::mpi::communicator worldLoc = world.split(bSeparate ? 0 : 1);
        string strToAdd = (bSeparate ? "0" : "1");
        testStorageDPDeterTreeCut(nbStep, nbPtGrid, worldLoc, strToAdd);
    }
    world.barrier();
}
#endif


BOOST_AUTO_TEST_CASE(testSimpleStorageCutTree)
{
#ifdef USE_MPI
    boost::mpi::communicator world ;
    world.barrier();
#endif
    int nbsimulLinear = 10000;
    int nbStep = 30 ;
    int nbStepTreePerStep = 1;
    int nbMeshLinear = 6 ;
    int nSimulCheck = 10000;
    int nbPtGrid = 200;
    testStorageCutTree(nbStep, nbStepTreePerStep,   nSimulCheck,  nbsimulLinear, nbMeshLinear, nbPtGrid
#ifdef USE_MPI
                       , world, ""
#endif
                      );
}


BOOST_AUTO_TEST_CASE(testSimpleStorageSDDPTree1D1Step)
{
#ifdef USE_MPI
    boost::mpi::communicator world ;
    world.barrier();
#endif
    int nbsimulLinear = 20000;
    int nbSamSimulLinear = 5;
    int nbStep = 30 ;
    int nbStepTreePerStep = 1;
    int nbMeshLinear = 6 ;
    int nSimulCheck = 10000;
    testStorageSDDPTree(1, nbStep, nbStepTreePerStep,  nbSamSimulLinear, nSimulCheck,  nbsimulLinear, nbMeshLinear
#ifdef USE_MPI
                        , world, ""
#endif
                       );
}



#ifdef USE_MPI
// (empty) Initialization function. Can't use testing tools here.
bool init_function()
{
    return true;
}

int main(int argc, char *argv[])
{
#if defined   __linux
    enable_abort_on_floating_point_exception();
#endif
    boost::mpi::environment env(argc, argv);
    return ::boost::unit_test::unit_test_main(&init_function, argc, argv);
}
#endif
