// Copyright (C) 2025 EDF
// All Rights Reserved
// This code is published under the GNU Lesser General Public License (GNU LGPL)
#include <list>
#include <map>
#include <algorithm>
#include "StOpt/regression/ContinuationCutsGridAdaptNonConcave.h"
#include "StOpt/core/utils/comparisonUtils.h"
#include "StOpt/core/utils/constant.h"
#include "StOpt/core/utils/eigenComparison.h"
#include "StOpt/core/grids/GridAdapt1D.h"
#include "StOpt/core/grids/GridAdapt2D.h"

using namespace std;
using namespace Eigen;

namespace StOpt
{

ContinuationCutsGridAdaptNonConcave::ContinuationCutsGridAdaptNonConcave(const  shared_ptr< GridAdaptBase >   &p_grid,
        const shared_ptr< BaseRegression >   &p_condExp,
        const ArrayXXd &p_values) : m_grid(p_grid), m_condExp(p_condExp), m_regressedCutCoeff(3)

{
    const int dimGrid = p_grid->getDim();
    m_regressedCutCoeff.resize(dimGrid + 1, 1);
    // nest on cuts
    for (int ic = 0; ic < dimGrid + 1 ; ++ic)
    {
        // coefficients of regressed functions (nb stock points, nb function basis)
        // From Eigen 3.4  ArrayXXd  valLoc = p_values(seqN(ic*p_condExp->getNbSimul(),p_condExp->getNbSimul()),all);
        ArrayXXd  valLoc = p_values.block(ic * p_condExp->getNbSimul(), 0, p_condExp->getNbSimul(), p_values.cols());
        m_regressedCutCoeff(ic) = m_condExp->getCoordBasisFunctionMultiple(valLoc.transpose()).transpose();
    }
    if (dimGrid == 1)
    {
        // for first coefficient cuts calculate  \f$ \bar a_0  = a_0 - \sum_{i=1}^1 a_i \bar x_i \f$
        vector< ArrayXd > points = p_grid->getPoints();
        ArrayXd pointCoordReg(1);
        for (size_t ipoint = 0; ipoint  < points.size(); ++ipoint)
        {
            // coordinates
            pointCoordReg(0) = points[ipoint][0];
        // cout << " LIST OF CIUTS " << pointCoordReg.transpose() <<  " AFF " << m_regressedCutCoeff(0).col(ipoint) << "DV1 " << m_regressedCutCoeff(1).col(ipoint) ;
            // grid cuts
            for (int id = 0 ; id < pointCoordReg.size(); ++id)
                m_regressedCutCoeff(0).col(ipoint) -= m_regressedCutCoeff(id + 1).col(ipoint) * pointCoordReg(id);
        // cout << " EQ CUT " << m_regressedCutCoeff(0).col(ipoint) << endl ;  
        }
    }
    else // dimGrid == 2
    {
        // for first coefficient cuts calculate  \f$ \bar a_0  = a_0 - \sum_{i=1}^2 a_i \bar x_i \f$
        vector< ArrayXd > points = p_grid->getPoints();
        ArrayXd pointCoordReg(2);
        for (size_t ipoint = 0; ipoint  < points.size(); ++ipoint)
        {
            // coordinates
            pointCoordReg(0) = points[ipoint][0];
            pointCoordReg(1) = points[ipoint][1];
        // cout << " LIST OF CIUTS " << pointCoordReg.transpose() <<  " AFF " << m_regressedCutCoeff(0).col(ipoint) << "DV1 " << m_regressedCutCoeff(1).col(ipoint) << "DV2 " << m_regressedCutCoeff(2).col(ipoint)  ;
            // grid cuts
            for (int id = 0 ; id < pointCoordReg.size(); ++id)
                m_regressedCutCoeff(0).col(ipoint) -= m_regressedCutCoeff(id + 1).col(ipoint) * pointCoordReg(id);
        // cout << " EQ CUT " << m_regressedCutCoeff(0).col(ipoint) << endl ;  
        }
    }
}

bool ContinuationCutsGridAdaptNonConcave::areTheTwoGridsMergable1D(const   shared_ptr< GridAdaptBase> &p_G1, const  shared_ptr< GridAdaptBase> &p_G2) const
{
    // can we merge meshes
    bool bMerge = true; // default merge
    shared_ptr<GridAdapt1D> const pGrid1 = dynamic_pointer_cast<GridAdapt1D>(p_G1);
    shared_ptr<GridAdapt1D> const pGrid2 = dynamic_pointer_cast<GridAdapt1D>(p_G2);
    // Cells are side by side: by construction grid1 is on left part of grid2
    if ((std::abs(pGrid1->getXMax() - pGrid2->getXMin()) > StOpt::tiny)  && (std::abs(pGrid2->getXMax() - pGrid1->getXMin()) > StOpt::tiny))
    {
        bMerge = false;
    }
    return bMerge;
}

bool ContinuationCutsGridAdaptNonConcave::areTheTwoGridsMergable2D(const ArrayXi &p_indexG1, const ArrayXi &p_indexG2, const   shared_ptr< GridAdaptBase> &p_G1, const  shared_ptr< GridAdaptBase> &p_G2) const
{
    // can we merge meshes
    bool bMerge = true; // default merge
    shared_ptr<GridAdapt2D> const pGrid1 = dynamic_pointer_cast<GridAdapt2D>(p_G1);
    shared_ptr<GridAdapt2D> const pGrid2 = dynamic_pointer_cast<GridAdapt2D>(p_G2);
    if ((p_indexG1[0] != p_indexG2[0]) && (p_indexG1[1] != p_indexG2[1]))
    {
        // case where cells  are   :
        // 0     meshj   or    meshi   0
        // meshi 0              0      meshj
        bMerge = false;
    }
    else if (p_indexG1[1] == p_indexG2[1])
    {
        // case where
        // 0     0        or     meshi  meshj
        // meshi meshj            0      0
        // calls are side by side in x direction : by construction grid1 is on left part of grid2
        if (((std::abs(pGrid1->getXMax() - pGrid2->getXMin()) > StOpt::tiny)  && (std::abs(pGrid2->getXMax() - pGrid1->getXMin()) > StOpt::tiny)) || (std::abs(pGrid1->getYMin() - pGrid2->getYMin()) > StOpt::tiny) || \
                (std::abs(pGrid1->getYMax() - pGrid2->getYMax()) > StOpt::tiny))
        {
            bMerge = false;
        }
    }
    else if (p_indexG1[0] == p_indexG2[0])
    {
        // case where
        // mesh2     0        or     0  mesh2
        // mesh1     0               0  mesh1
        // calls are side by side in y direction
        if (((std::abs(pGrid1->getYMax() - pGrid2->getYMin()) > StOpt::tiny)  && (std::abs(pGrid2->getYMax() - pGrid1->getYMin()) > StOpt::tiny)) || (std::abs(pGrid1->getXMin() - pGrid2->getXMin()) > StOpt::tiny) || \
                (std::abs(pGrid1->getXMax() - pGrid2->getXMax()) > StOpt::tiny))
        {
            bMerge = false;
        }
    }
    return bMerge;
}


vector< pair <shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd>  > >  ContinuationCutsGridAdaptNonConcave::getCutsASim(const  ArrayXXd &p_hypStock,  const ArrayXd &p_coordinates) const
{
    const int dimGrid = m_grid->getDim();
    int nbCutsCoeff = dimGrid + 1;
    // for return
    vector<pair <shared_ptr<GridAdaptBase>, shared_ptr<ArrayXXd>  > >  ret;
    if (dimGrid == 1)
    {
        ArrayXXd  cuts(nbCutsCoeff, 2);
        ArrayXd pointCoordReg(1);
        ArrayXi ptList(2);
        shared_ptr<GridAdapt1D> const pGrid1D = dynamic_pointer_cast<GridAdapt1D>(m_grid);
        list<  pair< shared_ptr< Mesh1D>, shared_ptr<vector<ArrayXi > > > > theMeshesAndPos  = pGrid1D->intersect(p_hypStock(0), p_hypStock(1));
        ret.reserve(theMeshesAndPos.size());
        for (const auto &meshAndPos : theMeshesAndPos)
        {
            shared_ptr< Mesh1D>  mesh  = meshAndPos.first;
            ptList(0) = mesh->getVerticeL();
            ptList(1) = mesh->getVerticeR();
            vector< ArrayXd > points;
            for (int i = 0; i < 2; ++i)
            {
                // coordinates
                pointCoordReg(0) = pGrid1D->getXCoord(ptList(i));
                points.push_back(pointCoordReg);
                for (int jc = 0; jc < nbCutsCoeff; ++jc)
                {
                    // reconstruct the value for all simulations
                    cuts(jc, i) =  m_condExp->getValue(p_coordinates, m_regressedCutCoeff(jc).col(ptList(i)));
                }
            }
            // create grid without meshes
            shared_ptr<GridAdaptBase>  grid1D = make_shared<GridAdapt1D>(mesh->getXL(), mesh->getXR(), points);
            ret.push_back(make_pair(grid1D, make_shared<ArrayXXd>(cuts)));
        }
    }
    else // dimGrid == 2
    {
        ArrayXXd  cuts(nbCutsCoeff, 4);
        ArrayXd pointCoordReg(2);
        ArrayXi ptList(4);
        shared_ptr<GridAdapt2D> const pGrid2D = dynamic_pointer_cast<GridAdapt2D>(m_grid);
        list<  pair< shared_ptr< Mesh2D>, shared_ptr<vector<ArrayXi > > > > theMeshesAndPos  = pGrid2D->intersect(p_hypStock(0, 0), p_hypStock(0, 1), p_hypStock(1, 0), p_hypStock(1, 1));
        ret.reserve(theMeshesAndPos.size());
        for (const auto &meshAndPos : theMeshesAndPos)
        {
            shared_ptr< Mesh2D>  mesh  = meshAndPos.first;
            ptList(0) = mesh->getVerticeLT();
            ptList(1) = mesh->getVerticeLB();
            ptList(2) = mesh->getVerticeRB();
            ptList(3) = mesh->getVerticeRT();
            vector< ArrayXd > points;
            for (int i = 0; i < 4; ++i)
            {
                // coordinates
                pointCoordReg(0) = pGrid2D->getXCoord(ptList(i));
                pointCoordReg(1) = pGrid2D->getYCoord(ptList(i));
                points.push_back(pointCoordReg);
                for (int jc = 0; jc < nbCutsCoeff; ++jc)
                {
                    // reconstruct the value for all simulations
                    cuts(jc, i) =  m_condExp->getValue(p_coordinates, m_regressedCutCoeff(jc).col(ptList(i)));
                }
            }
            // create grid without meshes
            shared_ptr<GridAdaptBase>  grid2D = make_shared<GridAdapt2D>(mesh->getXL(), mesh->getXR(),  mesh->getYB(), mesh->getYT(), points);
            ret.push_back(make_pair(grid2D, make_shared<ArrayXXd>(cuts)));
        }
    }
    return ret;
}

vector< pair <shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd>  > >  ContinuationCutsGridAdaptNonConcave::getCutsASim(const  ArrayXXd &p_hypStock,  const int   &p_isim) const
{
    const int dimGrid = m_grid->getDim();
    int nbCutsCoeff = dimGrid + 1;
    // for return
    vector<pair <shared_ptr<GridAdaptBase>, shared_ptr<ArrayXXd>  > >  ret;
    if (dimGrid == 1)
    {
        ArrayXXd  cuts(nbCutsCoeff, 2);
        ArrayXd pointCoordReg(1);
        ArrayXi ptList(2);
        shared_ptr<GridAdapt1D> const pGrid1D = dynamic_pointer_cast<GridAdapt1D>(m_grid);
        list<  pair< shared_ptr< Mesh1D>, shared_ptr<vector<ArrayXi > > > > thesMeshesAndPos  = pGrid1D->intersect(p_hypStock(0), p_hypStock(1));

        ret.reserve(thesMeshesAndPos.size());
        for (const auto &meshAndPos : thesMeshesAndPos)
        {
            shared_ptr< Mesh1D>  mesh  = meshAndPos.first;
            ptList(0) = mesh->getVerticeL();
            ptList(1) = mesh->getVerticeR();
            vector< ArrayXd > points;
            points.reserve(2);
            for (int i = 0; i < 2; ++i)
            {
                // coordinates
                pointCoordReg(0) = pGrid1D->getXCoord(ptList(i));
                points.push_back(pointCoordReg);
                for (int jc = 0; jc < nbCutsCoeff; ++jc)
                {
                    // reconstruct the value for all simulations
                    cuts(jc, i) =  m_condExp->reconstructionASim(p_isim, m_regressedCutCoeff(jc).col(ptList(i)));
                }
            }
            // create grid
            // Here keep level of grid above
            shared_ptr<GridAdaptBase>  grid1D = make_shared<GridAdapt1D>(mesh->getXL(), mesh->getXR(), points);
            ret.push_back(make_pair(grid1D, make_shared<ArrayXXd>(cuts)));
        }
    }
    else // dimGrid == 2
    {
        ArrayXXd  cuts(nbCutsCoeff, 4);
        ArrayXd pointCoordReg(2);
        ArrayXi ptList(4);
        shared_ptr<GridAdapt2D> const pGrid2D = dynamic_pointer_cast<GridAdapt2D>(m_grid);
        list<  pair< shared_ptr< Mesh2D>, shared_ptr<vector<ArrayXi > > > > thesMeshesAndPos  = pGrid2D->intersect(p_hypStock(0, 0), p_hypStock(0, 1), p_hypStock(1, 0), p_hypStock(1, 1));

        ret.reserve(thesMeshesAndPos.size());
        for (const auto &meshAndPos : thesMeshesAndPos)
        {
            shared_ptr< Mesh2D>  mesh  = meshAndPos.first;
            ptList(0) = mesh->getVerticeLT();
            ptList(1) = mesh->getVerticeLB();
            ptList(2) = mesh->getVerticeRB();
            ptList(3) = mesh->getVerticeRT();
            vector< ArrayXd > points;
            points.reserve(4);
            for (int i = 0; i < 4; ++i)
            {
                // coordinates
                pointCoordReg(0) = pGrid2D->getXCoord(ptList(i));
                pointCoordReg(1) = pGrid2D->getYCoord(ptList(i));
                points.push_back(pointCoordReg);
                for (int jc = 0; jc < nbCutsCoeff; ++jc)
                {
                    // reconstruct the value for all simulations
                    cuts(jc, i) =  m_condExp->reconstructionASim(p_isim, m_regressedCutCoeff(jc).col(ptList(i)));
                }
            }
            // create grid
            // Here keep level of grid above
            shared_ptr<GridAdaptBase>  grid2D = make_shared<GridAdapt2D>(mesh->getXL(), mesh->getXR(),  mesh->getYB(), mesh->getYT(), points);
            ret.push_back(make_pair(grid2D, make_shared<ArrayXXd>(cuts)));
        }
    }
    return ret;
}


vector< pair <shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd>  > >  ContinuationCutsGridAdaptNonConcave::getCutsAllSimulations(const ArrayXXd &p_hypStock) const
{
    const int dimGrid = m_grid->getDim();
    int nbCutsCoeff = dimGrid + 1;
    // for return
    vector<pair <shared_ptr<GridAdaptBase>, shared_ptr<ArrayXXd>  > >  ret;
    if (dimGrid == 1)
    {
        ArrayXXd  cuts(nbCutsCoeff * m_condExp->getNbSimul(), 2);
        ArrayXd pointCoordReg(1);
        ArrayXi ptList(2);
        shared_ptr<GridAdapt1D> const pGrid1D = dynamic_pointer_cast<GridAdapt1D>(m_grid);
        list<  pair< shared_ptr< Mesh1D>, shared_ptr<vector<ArrayXi > > > > thesMeshesAndPos  = pGrid1D->intersect(p_hypStock(0), p_hypStock(1));
        ret.reserve(thesMeshesAndPos.size());
        for (const auto &meshAndPos : thesMeshesAndPos)
        {
            shared_ptr< Mesh1D>  mesh  = meshAndPos.first;
            ptList(0) = mesh->getVerticeL();
            ptList(1) = mesh->getVerticeR();
            vector< ArrayXd > points;
            points.reserve(2);
            for (int i = 0; i < 2; ++i)
            {
                // coordinates
                pointCoordReg(0) = pGrid1D->getXCoord(ptList(i));
                points.push_back(pointCoordReg);
                for (int jc = 0; jc < nbCutsCoeff; ++jc)
                {
                    // reconstruct the value for all simulations
                    cuts.col(i).segment(jc * m_condExp->getNbSimul(), m_condExp->getNbSimul()) = m_condExp->reconstruction(m_regressedCutCoeff(jc).col(ptList(i)));
                }
            }
            // create grid
            shared_ptr<GridAdaptBase>  grid1D = make_shared<GridAdapt1D>(mesh->getXL(), mesh->getXR(), points);
            ret.push_back(make_pair(grid1D, make_shared<ArrayXXd>(cuts)));
        }
    }
    else // dimGrid == 2
    {
        ArrayXXd  cuts(nbCutsCoeff * m_condExp->getNbSimul(), 4);
        ArrayXd pointCoordReg(2);
        ArrayXi ptList(4);
        shared_ptr<GridAdapt2D> const pGrid2D = dynamic_pointer_cast<GridAdapt2D>(m_grid);
        list<  pair< shared_ptr< Mesh2D>, shared_ptr<vector<ArrayXi > > > > thesMeshesAndPos  = pGrid2D->intersect(p_hypStock(0, 0), p_hypStock(0, 1), p_hypStock(1, 0), p_hypStock(1, 1));
        ret.reserve(thesMeshesAndPos.size());
        for (const auto &meshAndPos : thesMeshesAndPos)
        {
            shared_ptr< Mesh2D>  mesh  = meshAndPos.first;
            ptList(0) = mesh->getVerticeLT();
            ptList(1) = mesh->getVerticeLB();
            ptList(2) = mesh->getVerticeRB();
            ptList(3) = mesh->getVerticeRT();
            vector< ArrayXd > points;
            points.reserve(4);
            for (int i = 0; i < 4; ++i)
            {
                // coordinates
                pointCoordReg(0) = pGrid2D->getXCoord(ptList(i));
                pointCoordReg(1) = pGrid2D->getYCoord(ptList(i));
                points.push_back(pointCoordReg);
                for (int jc = 0; jc < nbCutsCoeff; ++jc)
                {
                    // reconstruct the value for all simulations
                    cuts.col(i).segment(jc * m_condExp->getNbSimul(), m_condExp->getNbSimul()) = m_condExp->reconstruction(m_regressedCutCoeff(jc).col(ptList(i)));
                }
            }
            // create grid
            shared_ptr<GridAdaptBase>  grid2D = make_shared<GridAdapt2D>(mesh->getXL(), mesh->getXR(),  mesh->getYB(), mesh->getYT(), points);
            ret.push_back(make_pair(grid2D, make_shared<ArrayXXd>(cuts)));
        }
    }
    return ret;
}

pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> >  ContinuationCutsGridAdaptNonConcave::mergeGridAndCuts1D(const vector< map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> >, cmpVectorArrayXi >::iterator > &p_vecGridToMerge) const
{
    double xMin = 1e10;
    double xMax = -1e10;
    for (const auto &elem : p_vecGridToMerge)
    {
        shared_ptr<GridAdapt1D> const pGrid1D = dynamic_pointer_cast<GridAdapt1D>(elem->second.first);
        xMin = min(pGrid1D->getXMin(), xMin);
        xMax = max(pGrid1D->getXMax(), xMax);
    }
    vector< ArrayXd> vecOfCuts;
    int nbCutMax = 0;
    for (size_t i = 0; i < p_vecGridToMerge.size(); ++i)
        nbCutMax +=  p_vecGridToMerge[i]->second.second->cols();
    vecOfCuts.reserve(nbCutMax);
    vector< ArrayXd > vecOfPoint;
    vecOfPoint.reserve(nbCutMax);
    // first
    vector< ArrayXd >  ptFirst = p_vecGridToMerge[0]->second.first->getPoints();
    for (size_t ic = 0; ic < ptFirst.size(); ++ic)
    {
        vecOfPoint.push_back(ptFirst[ic]);
        vecOfCuts.push_back(p_vecGridToMerge[0]->second.second->col(ic));
    }
    // now iterate on folowing vectors
    for (size_t iv = 1; iv < p_vecGridToMerge.size(); ++iv)
    {
        vector< ArrayXd >  ptInVEc = p_vecGridToMerge[iv]->second.first->getPoints();
        for (size_t ic = 0; ic < ptInVEc.size(); ++ic)
        {
            bool bAdd = true;
            for (const auto &pt : vecOfPoint)
            {
                if (fabs(pt[0] - ptInVEc[ic][0]) < tiny)
                {
                    bAdd = false;
                    break;
                }
            }
            if (bAdd)
            {
                vecOfPoint.push_back(ptInVEc[ic]);
                vecOfCuts.push_back(p_vecGridToMerge[iv]->second.second->col(ic));
            }
        }
    }
    ArrayXXd cutsGlob(2, vecOfPoint.size());
    for (size_t ic = 0; ic < vecOfCuts.size(); ++ic)
        cutsGlob.col(ic) = vecOfCuts[ic];

    assert(vecOfCuts.size() == vecOfPoint.size());

    return make_pair(make_shared<GridAdapt1D >(xMin, xMax, vecOfPoint), make_shared<ArrayXXd>(cutsGlob));

}

pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> >  ContinuationCutsGridAdaptNonConcave::mergeGridAndCuts2D(const vector< map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> >, cmpVectorArrayXi >::iterator > &p_vecGridToMerge) const
{
    double xMin = 1e10;
    double yMin = 1e10;
    double xMax = -1e10;
    double yMax = -1e10;
    for (const auto &elem : p_vecGridToMerge)
    {
        shared_ptr<GridAdapt2D> const pGrid2D = dynamic_pointer_cast<GridAdapt2D>(elem->second.first);
        xMin = min(pGrid2D->getXMin(), xMin);
        xMax = max(pGrid2D->getXMax(), xMax);
        yMin = min(pGrid2D->getYMin(), yMin);
        yMax = max(pGrid2D->getYMax(), yMax);
    }
    vector< ArrayXd> vecOfCuts;
    int nbCutMax = 0;
    for (size_t i = 0; i < p_vecGridToMerge.size(); ++i)
        nbCutMax +=  p_vecGridToMerge[i]->second.second->cols();
    vecOfCuts.reserve(nbCutMax);
    vector< ArrayXd > vecOfPoint;
    vecOfPoint.reserve(nbCutMax);
    // first
    vector< ArrayXd >  ptFirst = p_vecGridToMerge[0]->second.first->getPoints();
    for (size_t ic = 0; ic < ptFirst.size(); ++ic)
    {
        vecOfPoint.push_back(ptFirst[ic]);
        vecOfCuts.push_back(p_vecGridToMerge[0]->second.second->col(ic));
    }
    // now iterate on folowing vectors
    for (size_t iv = 1; iv < p_vecGridToMerge.size(); ++iv)
    {
        vector< ArrayXd >  ptInVEc = p_vecGridToMerge[iv]->second.first->getPoints();
        for (size_t ic = 0; ic < ptInVEc.size(); ++ic)
        {
            bool bAdd = true;
            for (const auto &pt : vecOfPoint)
            {
                if (fabs(pt[0] - ptInVEc[ic][0]) + fabs(pt[1] - ptInVEc[ic][1]) < tiny)
                {
                    bAdd = false;
                    break;
                }
            }
            if (bAdd)
            {
                vecOfPoint.push_back(ptInVEc[ic]);
                vecOfCuts.push_back(p_vecGridToMerge[iv]->second.second->col(ic));
            }
        }
    }
    ArrayXXd cutsGlob(3, vecOfPoint.size());
    for (size_t ic = 0; ic < vecOfCuts.size(); ++ic)
        cutsGlob.col(ic) = vecOfCuts[ic];

    assert(vecOfCuts.size() == vecOfPoint.size());

    return make_pair(make_shared<GridAdapt2D >(xMin, xMax, yMin, yMax, vecOfPoint), make_shared<ArrayXXd>(cutsGlob));

}

int   ContinuationCutsGridAdaptNonConcave::separateNonConcaveConcaveGrids1D(const list<  pair< shared_ptr< Mesh1D>,
									    shared_ptr<vector<ArrayXi > > > >   &p_thesMeshesAndPos,
									    const int &p_isim,  vector< pair <shared_ptr<GridAdaptBase>, shared_ptr<ArrayXXd>  > > &p_nonConvaVec,
									    map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi> &p_mapMesh,
									    const bool & p_bStrict) const
{
    int nbCutsCoeff = 2;
    ArrayXXd  cuts(nbCutsCoeff, 2);
    ArrayXi ptList(2);
    ArrayXd pointCoordReg(1);
    // max level
    int maxLevel = 0;
    for (const auto &meshAndPos : p_thesMeshesAndPos)
    {
        shared_ptr< Mesh1D>  mesh  = meshAndPos.first;
        maxLevel = max(static_cast<int>(meshAndPos.second->size()), maxLevel);
        ptList(0) = mesh->getVerticeL();
        ptList(1) = mesh->getVerticeR();
        vector< ArrayXd > points;
        points.reserve(2);
        for (int i = 0; i < 2; ++i)
        {
            // coordinates
            pointCoordReg(0) = dynamic_pointer_cast<GridAdapt1D>(m_grid)->getXCoord(ptList(i));
            points.push_back(pointCoordReg);
            for (int jc = 0; jc < nbCutsCoeff; ++jc)
            {
                // reconstruct the value for all simulations
                cuts(jc, i) =  m_condExp->reconstructionASim(p_isim, m_regressedCutCoeff(jc).col(ptList(i)));
            }
        }
        // check if concave  on mesh
        bool bConcave = isConcave1D(cuts, mesh->getXL(), mesh->getXR(),p_bStrict);
        // create grid withe mesh
        shared_ptr<GridAdaptBase>  grid1D = make_shared<GridAdapt1D>(mesh->getXL(), mesh->getXR(), points);

        if (bConcave)
        {
            // store
            p_mapMesh[*meshAndPos.second] = make_pair(grid1D, make_shared<ArrayXXd>(cuts));
        }
        else
        {
            p_nonConvaVec.push_back(make_pair(grid1D,  make_shared<ArrayXXd>(cuts)));
        }
    }
    return maxLevel;
}

int   ContinuationCutsGridAdaptNonConcave::separateNonConcaveConcaveGrids2D(const list<  pair< shared_ptr< Mesh2D>,
									    shared_ptr<vector<ArrayXi > > > >   &p_thesMeshesAndPos,
									    const int &p_isim,  vector< pair <shared_ptr<GridAdaptBase>, shared_ptr<ArrayXXd>  > > &p_nonConvaVec,
									    map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi> &p_mapMesh,
									    const bool & p_bStrict) const
{
    int nbCutsCoeff = 3;
    ArrayXXd  cuts(nbCutsCoeff, 4);
    ArrayXi ptList(4);
    ArrayXd pointCoordReg(2);
    // max level
    int maxLevel = 0;
    for (const auto &meshAndPos : p_thesMeshesAndPos)
    {
        shared_ptr< Mesh2D>  mesh  = meshAndPos.first;
        maxLevel = max(static_cast<int>(meshAndPos.second->size()), maxLevel);
        ptList(0) = mesh->getVerticeLT();
        ptList(1) = mesh->getVerticeLB();
        ptList(2) = mesh->getVerticeRB();
        ptList(3) = mesh->getVerticeRT();
        vector< ArrayXd > points;
        points.reserve(4);
        for (int i = 0; i < 4; ++i)
        {
            // coordinates
            shared_ptr<GridAdapt2D> const pGrid2D = dynamic_pointer_cast<GridAdapt2D>(m_grid);
            pointCoordReg(0) = pGrid2D->getXCoord(ptList(i));
            pointCoordReg(1) = pGrid2D->getYCoord(ptList(i));
            points.push_back(pointCoordReg);
            for (int jc = 0; jc < nbCutsCoeff; ++jc)
            {
                // reconstruct the value for all simulations
                cuts(jc, i) =  m_condExp->reconstructionASim(p_isim, m_regressedCutCoeff(jc).col(ptList(i)));
            }
        }
        // check if concave  on mesh
        bool bConcave = isConcave2D(cuts, mesh->getXL(), mesh->getXR(), mesh->getYB(), mesh->getYT(),p_bStrict);
        // create grid withe mesh
        shared_ptr<GridAdaptBase>  grid2D = make_shared<GridAdapt2D>(mesh->getXL(), mesh->getXR(),  mesh->getYB(), mesh->getYT(), points);

        if (bConcave)
        {
            // store
            p_mapMesh[*meshAndPos.second] = make_pair(grid2D, make_shared<ArrayXXd>(cuts));
        }
        else
        {
            p_nonConvaVec.push_back(make_pair(grid2D,  make_shared<ArrayXXd>(cuts)));
        }
    }
    return maxLevel;
}

int   ContinuationCutsGridAdaptNonConcave::separateNonConcaveConcaveGrids1D(const list<  pair< shared_ptr< Mesh1D>,
        shared_ptr<vector<ArrayXi > > > >   &p_thesMeshesAndPos,
        const ArrayXd &p_coordinates,
        vector< pair <shared_ptr<GridAdaptBase>, shared_ptr<ArrayXXd>  > > &p_nonConvaVec,
        map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi> &p_mapMesh,
									    const bool & p_bStrict) const
{
    int nbCutsCoeff = 2;
    ArrayXXd  cuts(nbCutsCoeff, 2);
    ArrayXi ptList(2);
    ArrayXd pointCoordReg(1);
    // max level
    int maxLevel = 0;
    for (const auto &meshAndPos : p_thesMeshesAndPos)
    {
        shared_ptr< Mesh1D>  mesh  = meshAndPos.first;
        maxLevel = max(static_cast<int>(meshAndPos.second->size()), maxLevel);
        ptList(0) = mesh->getVerticeL();
        ptList(1) = mesh->getVerticeR();
        vector< ArrayXd > points;
        points.reserve(2);
        for (int i = 0; i < 2; ++i)
        {
            // coordinates
            pointCoordReg(0) = dynamic_pointer_cast<GridAdapt1D>(m_grid)->getXCoord(ptList(i));
            points.push_back(pointCoordReg);
            for (int jc = 0; jc < nbCutsCoeff; ++jc)
            {
                // reconstruct the value for all simulations
                cuts(jc, i) =  m_condExp->getValue(p_coordinates, m_regressedCutCoeff(jc).col(ptList(i)));
            }
        }
        // check if concave  on mesh
        bool bConcave = isConcave1D(cuts, mesh->getXL(), mesh->getXR(),p_bStrict);
        // create grid withe mesh
        shared_ptr<GridAdaptBase>  grid1D = make_shared<GridAdapt1D>(mesh->getXL(), mesh->getXR(), points);

        if (bConcave)
        {
            // store
            p_mapMesh[*meshAndPos.second] = make_pair(grid1D, make_shared<ArrayXXd>(cuts));
        }
        else
        {
            p_nonConvaVec.push_back(make_pair(grid1D,  make_shared<ArrayXXd>(cuts)));
        }
    }
    return maxLevel;
}

int   ContinuationCutsGridAdaptNonConcave::separateNonConcaveConcaveGrids2D(const list<  pair< shared_ptr< Mesh2D>,
        shared_ptr<vector<ArrayXi > > > >   &p_thesMeshesAndPos,
        const ArrayXd &p_coordinates,
        vector< pair <shared_ptr<GridAdaptBase>, shared_ptr<ArrayXXd>  > > &p_nonConvaVec,
        map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi> &p_mapMesh,
									    const bool & p_bStrict) const
{
    int nbCutsCoeff = 3;
    ArrayXXd  cuts(nbCutsCoeff, 4);
    ArrayXi ptList(4);
    ArrayXd pointCoordReg(2);
    // max level
    int maxLevel = 0;
    for (const auto &meshAndPos : p_thesMeshesAndPos)
    {
        shared_ptr< Mesh2D>  mesh  = meshAndPos.first;
        maxLevel = max(static_cast<int>(meshAndPos.second->size()), maxLevel);
        ptList(0) = mesh->getVerticeLT();
        ptList(1) = mesh->getVerticeLB();
        ptList(2) = mesh->getVerticeRB();
        ptList(3) = mesh->getVerticeRT();
        vector< ArrayXd > points;
        points.reserve(4);
        for (int i = 0; i < 4; ++i)
        {
            // coordinates
            shared_ptr<GridAdapt2D> const pGrid2D = dynamic_pointer_cast<GridAdapt2D>(m_grid);
            pointCoordReg(0) = pGrid2D->getXCoord(ptList(i));
            pointCoordReg(1) = pGrid2D->getYCoord(ptList(i));
            points.push_back(pointCoordReg);
            for (int jc = 0; jc < nbCutsCoeff; ++jc)
            {
                // reconstruct the value for all simulations
                cuts(jc, i) =  m_condExp->getValue(p_coordinates, m_regressedCutCoeff(jc).col(ptList(i)));
            }
        }
        // check if concave  on mesh
        bool bConcave = isConcave2D(cuts, mesh->getXL(), mesh->getXR(), mesh->getYB(),  mesh->getYT(),p_bStrict);
        // create grid withe mesh
        shared_ptr<GridAdaptBase>  grid2D = make_shared<GridAdapt2D>(mesh->getXL(), mesh->getXR(),  mesh->getYB(), mesh->getYT(), points);

        if (bConcave)
        {
            // store
            p_mapMesh[*meshAndPos.second] = make_pair(grid2D, make_shared<ArrayXXd>(cuts));
        }
        else
        {
            p_nonConvaVec.push_back(make_pair(grid2D,  make_shared<ArrayXXd>(cuts)));
        }
    }
    return maxLevel;
}

void  ContinuationCutsGridAdaptNonConcave::treatMergableGrids1D(const vector<ArrayXi >   &p_levelCut,
        map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi> &p_mergableMapMesh,
        map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi> &p_newMapMeshPM,
        vector<  vector<ArrayXi > >   &p_deletedKeysPM,
        vector< pair <shared_ptr<GridAdaptBase>, shared_ptr<ArrayXXd>  > > &p_mergeGridsVec) const
{
    vector<ArrayXi > vecTest = {{{0}}, {{1}}};
    // check if  all sub grid in a grid (2 in a row) are present
    vector< map< vector<ArrayXi >,  pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> >, cmpVectorArrayXi >::iterator   > vecGridPToMerge;
    vecGridPToMerge.reserve(2);
    for (int ia = 0; ia < 2; ++ia)
    {
        vector<ArrayXi > levelNext = p_levelCut;
        levelNext.push_back(vecTest[ia]);
        auto  iter = p_mergableMapMesh.find(levelNext);
        if (iter != p_mergableMapMesh.end())
        {
            vecGridPToMerge.push_back(iter);
            // keys should be added to delete list
            p_deletedKeysPM.push_back(iter->first);
        }
    }
    // now try to merge  mergable meshes together
    // first case : only one mesh -> remove it because nothing to merge
    if (vecGridPToMerge.size() == 1)
    {
        p_mergeGridsVec.push_back(vecGridPToMerge[0]->second);
    }
    else if (vecGridPToMerge.size() == 2)
    {
        bool bMerge = areTheTwoGridsMergable1D(vecGridPToMerge[0]->second.first, vecGridPToMerge[1]->second.first);
        if (bMerge)
        {
            pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> > newGridAndCuts =  mergeGridAndCuts1D(vecGridPToMerge);
            // and give it to mergable  with an index above
            p_newMapMeshPM[p_levelCut] = newGridAndCuts;
        }
        else
        {
            // remove mesh
            p_mergeGridsVec.push_back(vecGridPToMerge[0]->second);
            p_mergeGridsVec.push_back(vecGridPToMerge[1]->second);
        }
    }
}

void  ContinuationCutsGridAdaptNonConcave::treatMergableGrids2D(const vector<ArrayXi >   &p_levelCut,
        map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi> &p_mergableMapMesh,
        map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi> &p_newMapMeshPM,
        vector<  vector<ArrayXi > >   &p_deletedKeysPM,
        vector< pair <shared_ptr<GridAdaptBase>, shared_ptr<ArrayXXd>  > > &p_mergeGridsVec) const
{
    vector<ArrayXi > vecTest = {{{0, 0}}, {{1, 0}}, {{0, 1}}, {{1, 1}}};
    // check if  all sub grid in a grid (2 by 2) are present
    vector< map< vector<ArrayXi >,  pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> >, cmpVectorArrayXi >::iterator   > vecGridPToMerge;
    vecGridPToMerge.reserve(4);
    for (int ia = 0; ia < 4; ++ia)
    {
        vector<ArrayXi > levelNext = p_levelCut;
        levelNext.push_back(vecTest[ia]);
        auto  iter = p_mergableMapMesh.find(levelNext);
        if (iter != p_mergableMapMesh.end())
        {
            vecGridPToMerge.push_back(iter);
            // keys should be added to delete list
            p_deletedKeysPM.push_back(iter->first);
        }
    }
    // now try to merge  mergable meshes together
    // first case : only one mesh -> remove it because nothing to merge
    if (vecGridPToMerge.size() == 1)
    {
        p_mergeGridsVec.push_back(vecGridPToMerge[0]->second);
    }
    else if (vecGridPToMerge.size() == 2)
    {
        bool bMerge = areTheTwoGridsMergable2D(vecGridPToMerge[0]->first[vecGridPToMerge[0]->first.size() - 1], vecGridPToMerge[1]->first[vecGridPToMerge[1]->first.size() - 1],
                                             vecGridPToMerge[0]->second.first, vecGridPToMerge[1]->second.first);
        if (bMerge)
        {
            pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> > newGridAndCuts =  mergeGridAndCuts2D(vecGridPToMerge);
            // and give it to mergable  with an index above
            p_newMapMeshPM[p_levelCut] = newGridAndCuts;
        }
        else
        {
            // remove mesh
            p_mergeGridsVec.push_back(vecGridPToMerge[0]->second);
            p_mergeGridsVec.push_back(vecGridPToMerge[1]->second);

        }
    }
    else if (vecGridPToMerge.size() == 3)
    {
        // can we merge them 2 by 2
        bool bMerge12 = areTheTwoGridsMergable2D(vecGridPToMerge[0]->first[vecGridPToMerge[0]->first.size() - 1], vecGridPToMerge[1]->first[vecGridPToMerge[1]->first.size() - 1],
                                               vecGridPToMerge[0]->second.first, vecGridPToMerge[1]->second.first);
        bool bMerge13 = areTheTwoGridsMergable2D(vecGridPToMerge[0]->first[vecGridPToMerge[0]->first.size() - 1], vecGridPToMerge[2]->first[vecGridPToMerge[2]->first.size() - 1],
                                               vecGridPToMerge[0]->second.first, vecGridPToMerge[2]->second.first);
        bool bMerge23 = areTheTwoGridsMergable2D(vecGridPToMerge[1]->first[vecGridPToMerge[1]->first.size() - 1], vecGridPToMerge[2]->first[vecGridPToMerge[2]->first.size() - 1],
                                               vecGridPToMerge[1]->second.first, vecGridPToMerge[2]->second.first);

        // Only the two first are merged if possible to get a convex set
        if (bMerge12)
        {
            pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> > newGridAndCuts =  mergeGridAndCuts2D({vecGridPToMerge[0], vecGridPToMerge[1]});
            // and give it to mergable  with an index above
            p_newMapMeshPM[p_levelCut] = newGridAndCuts;
            // remove last grid
            p_mergeGridsVec.push_back(vecGridPToMerge[2]->second);
        }
        else if (bMerge13)
        {
            pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> > newGridAndCuts =  mergeGridAndCuts2D({vecGridPToMerge[0], vecGridPToMerge[2]});
            // and give it to mergable  with an index above
            p_newMapMeshPM[p_levelCut] = newGridAndCuts;
            // remove last grid
            p_mergeGridsVec.push_back(vecGridPToMerge[1]->second);
        }
        else if (bMerge23)
        {
            pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> > newGridAndCuts =  mergeGridAndCuts2D({vecGridPToMerge[1], vecGridPToMerge[2]});
            // and give it to mergable  with an index above
            p_newMapMeshPM[p_levelCut] = newGridAndCuts;
            // remove last grid
            p_mergeGridsVec.push_back(vecGridPToMerge[0]->second);
        }
        else
        {
            // nothing to merge
            p_mergeGridsVec.push_back(vecGridPToMerge[0]->second);
            p_mergeGridsVec.push_back(vecGridPToMerge[1]->second);
            p_mergeGridsVec.push_back(vecGridPToMerge[2]->second);
        }

    }
    else if (vecGridPToMerge.size() == 4)
    {
        // only possibilities (grid1 and grid2) merge , or (grid1 and grid3), or (grid2 and grid 4) or ( grid3 and grid 4) or the 4
        bool bMerge12 = areTheTwoGridsMergable2D(vecGridPToMerge[0]->first[vecGridPToMerge[0]->first.size() - 1], vecGridPToMerge[1]->first[vecGridPToMerge[1]->first.size() - 1],
                                               vecGridPToMerge[0]->second.first, vecGridPToMerge[1]->second.first);
        bool bMerge13 = areTheTwoGridsMergable2D(vecGridPToMerge[0]->first[vecGridPToMerge[0]->first.size() - 1], vecGridPToMerge[2]->first[vecGridPToMerge[2]->first.size() - 1],
                                               vecGridPToMerge[0]->second.first, vecGridPToMerge[2]->second.first);
        bool bMerge24 = areTheTwoGridsMergable2D(vecGridPToMerge[1]->first[vecGridPToMerge[1]->first.size() - 1], vecGridPToMerge[3]->first[vecGridPToMerge[3]->first.size() - 1],
                                               vecGridPToMerge[1]->second.first, vecGridPToMerge[3]->second.first);
        bool bMerge34 = areTheTwoGridsMergable2D(vecGridPToMerge[2]->first[vecGridPToMerge[2]->first.size() - 1], vecGridPToMerge[3]->first[vecGridPToMerge[3]->first.size() - 1],
                                               vecGridPToMerge[2]->second.first, vecGridPToMerge[3]->second.first);

        // Only the two first are merge if possible to get a convex set
        // either merge the 4
        if (bMerge12 && bMerge13 && bMerge24 && bMerge34)
        {
            pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> > newGridAndCuts =  mergeGridAndCuts2D(vecGridPToMerge);
            // and give it to mergable  with an index above
            p_newMapMeshPM[p_levelCut] = newGridAndCuts;
        }
        else if (bMerge12)
        {
            pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> > newGridAndCuts =  mergeGridAndCuts2D({vecGridPToMerge[0], vecGridPToMerge[1]});
            // decide to send  back grid1 and grid2 in potential merge
            p_newMapMeshPM[p_levelCut] = newGridAndCuts;
            if (bMerge34)
            {
                pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> > newGridAndCuts =  mergeGridAndCuts2D({vecGridPToMerge[2], vecGridPToMerge[3]});
                // remove it
                p_mergeGridsVec.push_back(newGridAndCuts);
            }
            else
            {
                // remove both
                p_mergeGridsVec.push_back(vecGridPToMerge[2]->second);
                p_mergeGridsVec.push_back(vecGridPToMerge[3]->second);
            }
        }
        else if (bMerge13)
        {
            pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> > newGridAndCuts =  mergeGridAndCuts2D({ vecGridPToMerge[0], vecGridPToMerge[2]});
            // decide to send  back grid1 and grid2 in potential merge
            p_newMapMeshPM[p_levelCut] = newGridAndCuts;
            if (bMerge24)
            {
                pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> > newGridAndCuts =  mergeGridAndCuts2D({vecGridPToMerge[1], vecGridPToMerge[3]});
                // remove it
                p_mergeGridsVec.push_back(newGridAndCuts);
            }
            else
            {
                // remove both
                p_mergeGridsVec.push_back(vecGridPToMerge[1]->second);
                p_mergeGridsVec.push_back(vecGridPToMerge[3]->second);
            }
        }
        else if (bMerge34)
        {
            pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> > newGridAndCuts =  mergeGridAndCuts2D({vecGridPToMerge[2], vecGridPToMerge[3] });
            // decide to send  back grid1 and grid2 in potential merge
            p_newMapMeshPM[p_levelCut] = newGridAndCuts;
        }
        else if (bMerge24)
        {
            pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> > newGridAndCuts =  mergeGridAndCuts2D({vecGridPToMerge[1], vecGridPToMerge[3]});
            // decide to send  back grid1 and grid2 in potential merge
            p_newMapMeshPM[p_levelCut] = newGridAndCuts;
        }
        else
        {
            // nothing to merge : remove all mesh
            p_mergeGridsVec.push_back(vecGridPToMerge[0]->second);
            p_mergeGridsVec.push_back(vecGridPToMerge[1]->second);
            p_mergeGridsVec.push_back(vecGridPToMerge[2]->second);
            p_mergeGridsVec.push_back(vecGridPToMerge[3]->second);
        }

    }
}

void  ContinuationCutsGridAdaptNonConcave::mergeGridsByLevel1D(const int &p_maxLevel, vector< pair <shared_ptr<GridAdaptBase>, shared_ptr<ArrayXXd>  > > &p_mergeGridsVec,
        map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi> &p_mapMesh) const
{
    // first check : if not
    vector<ArrayXi > vecTest = {{{0}}, {{1}}};
    ArrayXi iMerge = ArrayXi::Zero(2);

    // operate by level
    for (int iLevel = p_maxLevel; iLevel > 1 ; --iLevel)

    {
        // map for merges
        map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi> newMapMesh;
        // list of deleted keys
        vector<  vector<ArrayXi > > deletedKeys;
        deletedKeys.reserve(p_mapMesh.size());
        for (auto &mapMeshEl : p_mapMesh)
        {
            vector<ArrayXi >  level = mapMeshEl.first;
            //if ((static_cast<int>(level.size()) == iLevel) && (find(deletedKeys.begin(), deletedKeys.end(), level) == deletedKeys.end()))
            if ((static_cast<int>(level.size()) == iLevel) && (find_if(deletedKeys.begin(), deletedKeys.end(), [&](vector<ArrayXi> vec) {return vectorArrayXiEq(vec, level);}) == deletedKeys.end()))
            {
                vector<ArrayXi >  levelCut = level;
                levelCut.pop_back();
                // check that all sub grid in a grid (2 by 1) are present
                vector< map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> >, cmpVectorArrayXi >::iterator > vecGridToMerge;
                vecGridToMerge.reserve(2);
                for (int ia = 0; ia < 2; ++ia)
                {
                    vector<ArrayXi > levelNext = levelCut;
                    levelNext.push_back(vecTest[ia]);
                    auto iter = p_mapMesh.find(levelNext);
                    if (iter == p_mapMesh.end())
                    {
                        iMerge(ia) = 0;
                    }
                    else
                    {
                        // potentially mergeable
                        iMerge(ia) = 1;
                        vecGridToMerge.push_back(iter);
                        // keys should be added to delete list
                        deletedKeys.push_back(iter->first);
                    }
                }
                // number of cell concave
                int nbConcave = iMerge.sum();
                if (nbConcave < 2)
                {
                    // either  1 mesh at least is non concave
                    //    0  0   or  1 0    or   0 1    so impossible to merge
                    for (const auto   &iter : vecGridToMerge)
                    {
                        p_mergeGridsVec.push_back(iter->second);
                    }
                }
                else // nbConcave == 2
                {
                    // case  1 1
                    // merge all cells
                    pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> > newGridAndCuts =  mergeGridAndCuts1D(vecGridToMerge);
                    // these cells can be merge on level above (keep it in map)
                    newMapMesh[levelCut] = newGridAndCuts;
                }
            }
        }

        // now delete keys in initial map
        for (const auto &iterKey : deletedKeys)
        {

            p_mapMesh.erase(iterKey);
        }
        // merge new map and old map
        p_mapMesh.insert(newMapMesh.begin(), newMapMesh.end());
    }
    int nbptglob = 0;
    for (auto &mapMeshEl : p_mapMesh)
    {
        nbptglob +=  mapMeshEl.second.first->getNbPoints();
    }

    // We should be on initial  grid with level 1 only
    while (p_mapMesh.size() > 0)
    {
        // pick up first element
        const auto &aGridAndCut = p_mapMesh.begin();
        vector< map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> >, cmpVectorArrayXi >::iterator> vecToGroup;
        vecToGroup.reserve(p_mapMesh.size());
        vecToGroup.push_back(aGridAndCut);
        // go max left
        // Store left maximal coordinate  in initial grid (level 0)
        bool bContLeft =  true;
        vector<ArrayXi > levelLeft = aGridAndCut->first;
        while (bContLeft)
        {
            bContLeft = false;
            levelLeft[0][0] -= 1;
            auto  elem = p_mapMesh.find(levelLeft);
            if (elem != p_mapMesh.end())
            {
                // add element to vector
                vecToGroup.push_back(elem);
                bContLeft = true;
            }
        }
        // go max right
        bool bContRight = true;
        // Store right maximal coordinate  in initial grid (level 0)
        vector<ArrayXi > levelRight = aGridAndCut->first;
        while (bContRight)
        {
            bContRight = false;
            levelRight[0][0] += 1;
            auto  elem = p_mapMesh.find(levelRight);
            if (elem != p_mapMesh.end())
            {
                // add element to vector and keys to delete
                vecToGroup.push_back(elem);
                bContRight = true;
            }
        }
        // A line [iLeft, iRight] has been defined
        // create grid and add
        p_mergeGridsVec.push_back(mergeGridAndCuts1D(vecToGroup));
        // delete meshes
        for (const auto &elem : vecToGroup)
            p_mapMesh.erase(elem);
    }
}

void  ContinuationCutsGridAdaptNonConcave::mergeGridsByLevel2D(const int &p_maxLevel, vector< pair <shared_ptr<GridAdaptBase>, shared_ptr<ArrayXXd>  > > &p_mergeGridsVec,
        map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi> &p_mapMesh) const
{
    // first check : if not
    vector<ArrayXi > vecTest = {{{0, 0}}, {{1, 0}}, {{0, 1}}, {{1, 1}}};
    ArrayXi iMerge = ArrayXi::Zero(4);

    // map for cells  than are not completely merged  but can that potentially can merge with a cell of the level above
    // ie :  case nbConcave = 3 or 2 below
    map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi>  mergableMapMesh;
    // operate by level
    for (int iLevel = p_maxLevel; iLevel > 1 ; --iLevel)

    {
        // map for merges
        map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi> newMapMesh;
        // list of deleted keys
        vector<  vector<ArrayXi > > deletedKeys;
        deletedKeys.reserve(p_mapMesh.size());
        for (auto &mapMeshEl : p_mapMesh)
        {
            vector<ArrayXi >  level = mapMeshEl.first;
            if ((static_cast<int>(level.size()) == iLevel) && (find_if(deletedKeys.begin(), deletedKeys.end(), [&](vector<ArrayXi> vec) {return vectorArrayXiEq(vec, level);}) == deletedKeys.end()))
            {
                vector<ArrayXi >  levelCut = level;
                levelCut.pop_back();
                // check that all sub grid in a grid (2 by 2) are present
                vector< map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> >, cmpVectorArrayXi >::iterator > vecGridToMerge;
                vecGridToMerge.reserve(4);
                for (int ia = 0; ia < 4; ++ia)
                {
                    vector<ArrayXi > levelNext = levelCut;
                    levelNext.push_back(vecTest[ia]);
                    auto iter = p_mapMesh.find(levelNext);
                    if (iter == p_mapMesh.end())
                    {
                        iMerge(ia) = 0;
                    }
                    else
                    {
                        // potentially mergeable
                        iMerge(ia) = 1;
                        vecGridToMerge.push_back(iter);
                        // keys should be added to delete list
                        deletedKeys.push_back(iter->first);
                    }
                }
                // number of cell concave
                int nbConcave = iMerge.sum();
                if ((nbConcave < 2)  || ((iMerge(0) == 0) && (iMerge(3) == 0)) || ((iMerge(1) == 0) && (iMerge(2) == 0)))
                {
                    // either  3 meshes at least are non concave
                    //    0  0   or  1 0    or   0 1     or  0 0   or  0 0
                    //    0  0       0 0         0 0         1 0       0 1
                    // or
                    //    0  1    or  1 0
                    //    1  0        0 1   so impossible to merge
                    for (const auto   &iter : vecGridToMerge)
                    {
                        p_mergeGridsVec.push_back(iter->second);
                    }
                }
                else if (nbConcave == 4)
                {
                    // case  1 1   or
                    //       1 1
                    // merge all cells
                    pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> > newGridAndCuts =  mergeGridAndCuts2D(vecGridToMerge);
                    // these cell can be merge on level above (keep it in map)
                    newMapMesh[levelCut] = newGridAndCuts;
                }
                else if (nbConcave == 3)
                {
                    // case
                    // 1 1   or  1 0  or  0 1   or  1 1
                    // 0 1       1 1      1 1       1 0
                    // merge the two first
                    pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> > newGridAndCuts =  mergeGridAndCuts2D({ vecGridToMerge[0], vecGridToMerge[1]});
                    // the grid is not remove but stored as potentially mergable
                    //p_mergeGridsVec.push_back(newGridAndCuts);
                    map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> >, cmpVectorArrayXi >::iterator iter = mergableMapMesh.find(levelCut);
                    if (iter != mergableMapMesh.end())
                    {
                        // remove existing  potentially mergable mesh that  cannot be merged anymore
                        p_mergeGridsVec.push_back(mergableMapMesh[levelCut]);
                    }
                    mergableMapMesh[levelCut] =  newGridAndCuts;
                    // last
                    p_mergeGridsVec.push_back(vecGridToMerge[2]->second);
                }
                else
                {
                    // case
                    // 1 1   or  1 0  or  0 0   or  0 1
                    // 0 0       1 0      1 1       0 1
                    // merge the  cells
                    assert(vecGridToMerge.size() == 2);
                    pair<shared_ptr<GridAdaptBase >, shared_ptr<ArrayXXd> > newGridAndCuts =  mergeGridAndCuts2D(vecGridToMerge);
                    // the grid is not remove but stored as potentially mergable
                    //p_mergeGridsVec.push_back(newGridAndCuts);
                    map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> >, cmpVectorArrayXi >::iterator iter = mergableMapMesh.find(levelCut);
                    if (iter != mergableMapMesh.end())
                    {
                        // remove existing  potentially mergable mesh that  cannot be merged anymore
                        p_mergeGridsVec.push_back(mergableMapMesh[levelCut]);
                    }
                    mergableMapMesh[levelCut] =  newGridAndCuts;
                }
            }
        }


        // now delete keys in initial map
        for (const auto &iterKey : deletedKeys)
        {
            p_mapMesh.erase(iterKey);
        }
        // merge new map and old map
        p_mapMesh.insert(newMapMesh.begin(), newMapMesh.end());
        // now try to merge  "mergable mesh only" between them on level above
        if (iLevel > 1)
        {
            vector<  vector<ArrayXi > > deletedKeysPM;
            deletedKeysPM.reserve(mergableMapMesh.size());
            // new mergable map
            map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi> newMapMeshPM;
            for (auto &mapMeshEl : mergableMapMesh)
            {
                vector<ArrayXi >  level = mapMeshEl.first;
                // test level above
                if ((static_cast<int>(level.size()) == iLevel - 1) && (find_if(deletedKeysPM.begin(), deletedKeysPM.end(), [&](vector<ArrayXi> vec) {return vectorArrayXiEq(vec, level);}) == deletedKeysPM.end()))
                {
                    vector<ArrayXi >  levelCut = level;
                    levelCut.pop_back();
                    // try to merge mragble mesh on grid of curretn level
                    treatMergableGrids2D(levelCut, mergableMapMesh, newMapMeshPM, deletedKeysPM, p_mergeGridsVec);

                }
            }
            // remove the mesh treated
            for (const auto &iterKey : deletedKeysPM)
            {
                mergableMapMesh.erase(iterKey);
            }
            // merge new map and old map
            mergableMapMesh.insert(newMapMeshPM.begin(), newMapMeshPM.end());
        }
    }
    int nbptglob = 0;
    for (auto &mapMeshEl : p_mapMesh)
    {
        nbptglob +=  mapMeshEl.second.first->getNbPoints();
    }

    // We should be on initial  grid with level 1 only
    while (p_mapMesh.size() > 0)
    {
        // pick up first element
        const auto &aGridAndCut = p_mapMesh.begin();
        vector< map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> >, cmpVectorArrayXi >::iterator> vecToGroup;
        vecToGroup.reserve(p_mapMesh.size());
        vecToGroup.push_back(aGridAndCut);
        // go max left
        // Store left maximal coordinate  in initial grid (level 0)
        int iLeft = aGridAndCut->first[0][0];
        bool bContLeft =  true;
        vector<ArrayXi > levelLeft = aGridAndCut->first;
        while (bContLeft)
        {
            bContLeft = false;
            levelLeft[0][0] -= 1;
            auto  elem = p_mapMesh.find(levelLeft);
            if (elem != p_mapMesh.end())
            {
                // add element to vector
                vecToGroup.push_back(elem);
                iLeft = levelLeft[0][0];
                bContLeft = true;
            }
        }
        // go max right
        bool bContRight = true;
        // Store right maximal coordinate  in initial grid (level 0)
        int iRight = aGridAndCut->first[0][0];
        vector<ArrayXi > levelRight = aGridAndCut->first;
        while (bContRight)
        {
            bContRight = false;
            levelRight[0][0] += 1;
            auto  elem = p_mapMesh.find(levelRight);
            if (elem != p_mapMesh.end())
            {
                // add element to vector and keys to delete
                vecToGroup.push_back(elem);
                iRight = levelRight[0][0];
                bContRight = true;
            }
        }
        // A line [iLeft, iRight] has been defined
        // first try to go down
        int iBottom = aGridAndCut->first[0][1] - 1;
        bool bContBottom = (iBottom >= 0);
        while (bContBottom)
        {
            // vector of iterators
            vector< map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> >, cmpVectorArrayXi >::iterator> vecToGroupLoc;
            vecToGroupLoc.reserve(iRight + 1 - iLeft);
            for (int i = iLeft; i <= iRight; ++i)
            {
                vector<ArrayXi > levelLoc = {{{i, iBottom}}};
                auto  elem = p_mapMesh.find(levelLoc);
                if (elem == p_mapMesh.end())
                {
                    bContBottom = false;
                    break;
                }
                else
                {
                    vecToGroupLoc.push_back(elem);
                }
            }
            if (bContBottom)
            {
                iBottom -= 1;
                vecToGroup.insert(vecToGroup.end(), vecToGroupLoc.begin(), vecToGroupLoc.end());
            }
            if (iBottom < 0)
                bContBottom = false;
        }
        // then try to go up
        int iTop = aGridAndCut->first[0][1] + 1;
        bool bContTop = true;
        while (bContTop)
        {
            // vector of iterators
            vector< map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> >, cmpVectorArrayXi >::iterator> vecToGroupLoc;
            vecToGroupLoc.reserve(iRight + 1 - iLeft);
            for (int i = iLeft; i <= iRight; ++i)
            {
                vector<ArrayXi > levelLoc = {{{i, iTop}}};
                auto  elem = p_mapMesh.find(levelLoc);
                if (elem == p_mapMesh.end())
                {
                    bContTop = false;
                    break;
                }
                else
                {
                    vecToGroupLoc.push_back(elem);
                }
            }
            if (bContTop)
            {
                iTop += 1;
                vecToGroup.insert(vecToGroup.end(), vecToGroupLoc.begin(), vecToGroupLoc.end());
            }
        }
        // create grid and add
        p_mergeGridsVec.push_back(mergeGridAndCuts2D(vecToGroup));
        // delete meshes
        for (const auto &elem : vecToGroup)
            p_mapMesh.erase(elem);
    }
    //  potentially mergable grid are just removed if left
    for (auto &mapMeshEl : mergableMapMesh)
    {
        p_mergeGridsVec.push_back(mapMeshEl.second);
    }
}

vector< pair <shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd>  > >  ContinuationCutsGridAdaptNonConcave::getCutsConcGatherASim(const  ArrayXXd &p_hypStock,  const int   &p_isim, const bool & p_bStrict) const
{
    const int dimGrid = m_grid->getDim();
    vector<pair <shared_ptr<GridAdaptBase>, shared_ptr<ArrayXXd>  > >  ret;
    // to store mesh potentially eligible to gathering
    map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi> mapMesh;
    if (dimGrid == 1)
    {
        list<  pair< shared_ptr< Mesh1D>, shared_ptr<vector<ArrayXi > > > > thesMeshesAndPos  = dynamic_pointer_cast<GridAdapt1D>(m_grid)->intersect(p_hypStock(0), p_hypStock(1));
        ret.reserve(thesMeshesAndPos.size());
        // divide in non concaev grids and concaves ones
        int maxLevel = separateNonConcaveConcaveGrids1D(thesMeshesAndPos, p_isim, ret, mapMesh, p_bStrict) ;

        mergeGridsByLevel1D(maxLevel, ret, mapMesh);
    }
    else // dimGrid == 2
    {
        list<  pair< shared_ptr< Mesh2D>, shared_ptr<vector<ArrayXi > > > > thesMeshesAndPos  = dynamic_pointer_cast<GridAdapt2D>(m_grid)->intersect(p_hypStock(0, 0), p_hypStock(0, 1), p_hypStock(1, 0), p_hypStock(1, 1));
        ret.reserve(thesMeshesAndPos.size());
        // divide in non concaev grids and concaves ones
        int maxLevel = separateNonConcaveConcaveGrids2D(thesMeshesAndPos, p_isim, ret, mapMesh, p_bStrict) ;

        mergeGridsByLevel2D(maxLevel, ret, mapMesh);
    }

    return ret;
}


vector< pair <shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd>  > >  ContinuationCutsGridAdaptNonConcave::getCutsConcGatherASim(const  ArrayXXd &p_hypStock,  const ArrayXd &p_coordinates, const bool & p_bStrict) const
{
    const int dimGrid = m_grid->getDim();
    vector<pair <shared_ptr<GridAdaptBase>, shared_ptr<ArrayXXd>  > >  ret;
    // to store mesh potentially eligible to gathering
    map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi> mapMesh;
    if (dimGrid == 1)
    {
        list<  pair< shared_ptr< Mesh1D>, shared_ptr<vector<ArrayXi > > > > thesMeshesAndPos  = dynamic_pointer_cast<GridAdapt1D>(m_grid)->intersect(p_hypStock(0), p_hypStock(1));
        ret.reserve(thesMeshesAndPos.size());
        // divide in non concaev grids and concaves ones
        int maxLevel = separateNonConcaveConcaveGrids1D(thesMeshesAndPos, p_coordinates, ret, mapMesh, p_bStrict) ;
        mergeGridsByLevel1D(maxLevel, ret, mapMesh);
    }
    else // dimGrid == 2
    {
        list<  pair< shared_ptr< Mesh2D>, shared_ptr<vector<ArrayXi > > > > thesMeshesAndPos  = dynamic_pointer_cast<GridAdapt2D>(m_grid)->intersect(p_hypStock(0, 0), p_hypStock(0, 1), p_hypStock(1, 0), p_hypStock(1, 1));
        ret.reserve(thesMeshesAndPos.size());
        // divide in non concaev grids and concaves ones
        int maxLevel = separateNonConcaveConcaveGrids2D(thesMeshesAndPos, p_coordinates, ret, mapMesh, p_bStrict) ;
        mergeGridsByLevel2D(maxLevel, ret, mapMesh);
    }

    return ret;
}


vector<   shared_ptr<GridAdaptBase>>    ContinuationCutsGridAdaptNonConcave::getMeshNotConcASim(const  ArrayXXd   &p_hypStock, const int &p_isim, const bool & p_bStrict) const
{
    const int dimGrid = m_grid->getDim();
    vector<shared_ptr<GridAdaptBase> >  ret;
    vector<pair <shared_ptr<GridAdaptBase>, shared_ptr<ArrayXXd>  > >  vecMeshAndCuts;
    // to store mesh potentially eligible to gathering
    map< vector<ArrayXi >, pair< shared_ptr< GridAdaptBase>, shared_ptr<ArrayXXd> > , cmpVectorArrayXi> mapMesh;

    if (dimGrid == 1)
    {
        list<  pair< shared_ptr< Mesh1D>, shared_ptr<vector<ArrayXi > > > > thesMeshesAndPos  = dynamic_pointer_cast<GridAdapt1D>(m_grid)->intersect(p_hypStock(0), p_hypStock(1));

        vecMeshAndCuts.reserve(thesMeshesAndPos.size());
        // divide in non concave grids and concaves ones
        int maxLevel = separateNonConcaveConcaveGrids1D(thesMeshesAndPos, p_isim, vecMeshAndCuts, mapMesh, p_bStrict) ;
        // set maxlevel to zero to avoid warning
        maxLevel = max(0, maxLevel);
    }
    else // dimGrid == 2
    {
        list<  pair< shared_ptr< Mesh2D>, shared_ptr<vector<ArrayXi > > > > thesMeshesAndPos  = dynamic_pointer_cast<GridAdapt2D>(m_grid)->intersect(p_hypStock(0, 0), p_hypStock(0, 1), p_hypStock(1, 0), p_hypStock(1, 1));

        vecMeshAndCuts.reserve(thesMeshesAndPos.size());
        // divide in non concave grids and concaves ones
        int maxLevel = separateNonConcaveConcaveGrids2D(thesMeshesAndPos, p_isim, vecMeshAndCuts, mapMesh, p_bStrict) ;
        // set maxlevel to zero to avoid warning
        maxLevel = max(0, maxLevel);
    }
    ret.reserve(vecMeshAndCuts.size());
    for (const  auto &meshAndCuts : vecMeshAndCuts)
        ret.push_back(meshAndCuts.first);

    return ret;
}
  
pair< double, ArrayXd> ContinuationCutsGridAdaptNonConcave::getVBAndVU(const ArrayXd &p_aParticle, const ArrayXd &p_stock) const
{
   // mesh associated to the point
  if (m_grid->getDim()==2)
    {
      shared_ptr<GridAdapt2D>  grid2D = static_pointer_cast<GridAdapt2D>(m_grid);
      std::shared_ptr< Mesh2D> meshPointBelong = grid2D->getMeshWithPoint(p_stock(0), p_stock(1)) ;
      ArrayXi ptList(4);
      ArrayXd pointCoordReg(2);
      ptList(0) = meshPointBelong->getVerticeLT();
      ptList(1) = meshPointBelong->getVerticeLB();
      ptList(2) = meshPointBelong->getVerticeRB();
      ptList(3) = meshPointBelong->getVerticeRT();
      vector< array<double, 2 > > points;
      ArrayXXd  cuts(3, 4); // size (number of cut coeff, number of points)
      for (int i = 0; i < 4; ++i)
	{
	  for (int jc = 0; jc < 3; ++jc)
	    {
	      // reconstruct the value for all simulations
	      cuts(jc, i) =  m_condExp->getValue(p_aParticle, m_regressedCutCoeff(jc).col(ptList(i)));
	    }
	}
      // cut value
      int iAct = 0;
      double valOpt = StOpt::infty;
      for (int i = 0; i < 4; ++i)
	{
	  double val = cuts(0, i) + cuts(1, i) * p_stock(0) + cuts(2, i) * p_stock(1);
	  if (val < valOpt)
	    {
	      valOpt = val;
	      iAct = i;
	    }
	}
      ArrayXd VU(2);
      VU(0) = cuts(1, iAct);
      VU(1) = cuts(2, iAct);
      return make_pair(valOpt, VU);
    }
  else
    {
      shared_ptr<GridAdapt1D>  grid1D = static_pointer_cast<GridAdapt1D>(m_grid);
      std::shared_ptr< Mesh1D> meshPointBelong = grid1D->getMeshWithPoint(p_stock(0)) ;
      ArrayXi ptList(2);
      ArrayXd pointCoordReg(2);
      ptList(0) = meshPointBelong->getVerticeL();
      ptList(1) = meshPointBelong->getVerticeR();
      vector< array<double, 1 > > points;
      ArrayXXd  cuts(2, 2); // size (number of cut coeff, number of points)
      for (int i = 0; i < 2; ++i)
	{
	  for (int jc = 0; jc < 2; ++jc)
	    {
	      // reconstruct the value for all simulations
	      cuts(jc, i) =  m_condExp->getValue(p_aParticle, m_regressedCutCoeff(jc).col(ptList(i)));
	    }
	}
      // cut value
      int iAct = 0;
      double valOpt = StOpt::infty;
      for (int i = 0; i < 2; ++i)
	{
	  double val = cuts(0, i) + cuts(1, i) * p_stock(0);
	  if (val < valOpt)
	    {
	      valOpt = val;
	      iAct = i;
	    }
	}
      ArrayXd VU(1);
      VU(0) = cuts(1, iAct);
      return make_pair(valOpt, VU);
    }
}
}
