// Copyright (C) 2016 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#ifndef OPTIMIZERSLCASE3_H
#define OPTIMIZERSLCASE3_H
#include <vector>
#include <functional>
#include <boost/random.hpp>
#include <Eigen/Dense>
#include "StOpt/core/grids/SpaceGrid.h"
#include "StOpt/core/grids/InterpolatorSpectral.h"
#include "StOpt/semilagrangien/OptimizerSLBase.h"
#include "StOpt/semilagrangien/SemiLagrangEspCond.h"

/** \file OptimizeSLCase3.h
 *  \brief Define a class for first case of Semi Lagrangian schemes
 *   \author Xavier Warin
 */

namespace StOpt
{

/// \class OptimizerSLCase3 OptimizeSLCase3.h
///  Optimizer for the resolution of a stochastic target problem
///  \f{eqnarray*}
///   \inf_{\alpha \in \mathbf{R}}  [ \frac{\partial u}{\partial t}(t,x) + \frac{\alpha^2}{2} \frac{\partial^2 u}{\partial x^2}(t,x) - \frac{ \alpha \mu}{\kappa} \frac{\partial u}{\partial x}   ] =0 \mbox{ for} (t,x) \in [0,T] \times [0,1].
/// \f}
class OptimizerSLCase3 : public OptimizerSLBase
{

private :

    double m_dt ; // time step
    double m_mu ; // asset trend
    double m_sig ; // asset volatility
    double m_alphaMax ; // maximal value for alpha tested
    double m_stepAlpha ; // step for alpha discretization

public :

    /// \brief Constructor
    /// \param p_mu        trend of the asset
    /// \param p_sig       volatility of the asset
    /// \param p_dt        resolution time step
    /// \param p_alphaMax  max of the alpha value used to test the control
    /// \param p_stepAlpha Step discretization of alpha
    OptimizerSLCase3(const double &p_mu, const double &p_sig, const double &p_dt, const double &p_alphaMax, const double &p_stepAlpha);

    /// \brief define the diffusion cone for parallelism
    /// \param  p_regionByProcessor         region (min max) treated by the processor for the different regimes treated
    /// \return returns in each dimension the min max values in the stock that can be reached from the grid p_gridByProcessor for each regime
    std::vector< std::array< double, 2> > getCone(const  std::vector<  std::array< double, 2>  > &p_regionByProcessor) const;


    /// \brief defines a step in optimization
    /// \param p_point     coordinate of the point to treat
    /// \param p_semiLag   semi Lagrangian operator for each regime for solution at the previous step
    /// \param p_time      current time
    /// \param p_phiInPt   value of the function at the previous time step at p_point for each regime
    /// \return a pair :
    ///          - first an array of the solution (for each regime)
    ///          - second an array of the optimal controls ( for each control)
    std::pair< Eigen::ArrayXd, Eigen::ArrayXd>   stepOptimize(const Eigen::ArrayXd   &p_point,
            const std::vector< std::shared_ptr<SemiLagrangEspCond> > &p_semiLag, const double &p_time,
            const Eigen::ArrayXd &p_phiInPt) const;


    /// \brief get back the dimension of the control
    virtual int getNbControl() const
    {
        return 1;
    }


    /// \brief get number of regimes
    inline  int getNbRegime() const
    {
        return 1 ;
    }

    /// \brief do we modify the volatility to stay in the domain
    inline  bool getBModifVol() const
    {
        return false;
    }

    /// \brief defines a step in simulation
    /// \param p_gridNext      grid at the next step
    /// \param p_semiLag       semi Lagrangian operator at the current step in each regime
    /// \param p_state         state array (can be modified)
    /// \param p_iReg          regime number
    /// \param p_brownian      Brownian realization
    /// \param p_phiInPt       value of the function at the next time step at p_point for each regime
    /// \param p_phiInOut      defines the value functions (modified)
    void stepSimulate(const StOpt::SpaceGrid   &p_gridNext,
                      const std::vector< std::shared_ptr< StOpt::SemiLagrangEspCond > > &p_semiLag,
                      Eigen::Ref<Eigen::ArrayXd>  p_state,
                      int &p_iReg,
                      const Eigen::ArrayXd &p_brownian,
                      const Eigen::ArrayXd &p_phiInPt, Eigen::Ref<Eigen::ArrayXd>  p_phiInOut)  const ;


    /// \brief defines a step in simulation using the control calculated in optimization
    /// \param p_gridNext      grid at the next step
    /// \param p_controlInterp the optimal controls interpolator
    /// \param p_state         state array (can be modified)
    /// \param p_iReg          regime number
    /// \param p_gaussian      unitary Gaussian realization
    /// \param p_phiInOut      defines the value function (modified)
    virtual void stepSimulateControl(const SpaceGrid    &p_gridNext,
                                     const  std::vector< std::shared_ptr< InterpolatorSpectral> >   &p_controlInterp,
                                     Eigen::Ref<Eigen::ArrayXd>  p_state,   int &p_iReg,
                                     const Eigen::ArrayXd &p_gaussian,
                                     Eigen::Ref<Eigen::ArrayXd>  p_phiInOut) const;

    /// \brief get the number of Brownians involved in semi Lagrangian for simulation
    inline int getBrownianNumber() const
    {
        return 1;
    }

    /// \brief Permit to deal with some boundary points that do not need boundary conditions
    ///        Return false if all points on the boundary need some boundary conditions
    inline  bool isNotNeedingBC(const Eigen::ArrayXd &)  const
    {
        return false;
    }

    /// \brief get size of the  function to follow in simulation
    inline int getSimuFuncSize() const
    {
        return 1;
    }

    /// \brief defines the dimension to split for MPI parallelism
    ///        For each dimension return true is the direction can be split
    Eigen::Array< bool, Eigen::Dynamic, 1> getDimensionToSplit() const
    {
        Eigen::Array< bool, Eigen::Dynamic, 1> bDim = Eigen::Array< bool, Eigen::Dynamic, 1>::Constant(1, true);
        return  bDim ;
    }

};
}
#endif /* OPTIMIZERSLCASE3_H */
