/*===========================================================================
 
 Copyright (C) 2006-2012 Yves Renard, Julien Pommier, Jeremie Lasry.
 
 This file is a part of GETFEM++
 
 Getfem++  is  free software;  you  can  redistribute  it  and/or modify it
 under  the  terms  of the  GNU  Lesser General Public License as published
 by  the  Free Software Foundation;  either version 3 of the License,  or
 (at your option) any later version along with the GCC Runtime Library
 Exception either version 3.1 or (at your option) any later version.
 This program  is  distributed  in  the  hope  that it will be useful,  but
 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or  FITNESS  FOR  A PARTICULAR PURPOSE.  See the GNU Lesser General Public
 License and GCC Runtime Library Exception for more details.
 You  should  have received a copy of the GNU Lesser General Public License
 along  with  this program;  if not, write to the Free Software Foundation,
 Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
 
===========================================================================*/

#include "crack_bilaplacian.h"
#include "getfem/getfem_regular_meshes.h"
#include "getfem/getfem_assembling.h" /* import assembly methods (and norms comp.) */
#include "getfem/getfem_fourth_order.h"
#include "getfem/getfem_model_solvers.h"
#include "getfem/getfem_superlu.h"

using std::endl; using std::cout; using std::cerr;
using std::ends; using std::cin;

// In this file :

// void asm_normal_derivative_dirichlet_constraints_bis
// void asm_constraint_gradient_vectorial_mult 
// void bilaplacian_crack_problem::set_matrix_mortar(sparse_matrix &H)
// void bilaplacian_crack_problem::init_mixed_elements(void) 


scalar_type crack_level(base_node P) {
return P[1];
}

scalar_type crack_tip_level(base_node P) {
return P[0];
}

// functions for assembling the constraints of the integral matching 
  
template<typename MAT, typename VECT1, typename VECT2>
void asm_normal_derivative_dirichlet_constraints_bis
(MAT &H, VECT1 &R, const getfem::mesh_im &mim, const getfem::mesh_fem &mf_u,
 const getfem::mesh_fem &mf_mult, const getfem::mesh_fem &mf_r,
 const VECT2 &r_data, const getfem::mesh_region &rg, bool R_must_be_derivated, 
 int version) {
  typedef typename gmm::linalg_traits<VECT1>::value_type value_type;
  typedef typename gmm::number_traits<value_type>::magnitude_type magn_type;
    
  rg.from_mesh(mim.linked_mesh()).error_if_not_faces();
  GMM_ASSERT1(mf_r.get_qdim() == 1, 
              "invalid data mesh fem (Qdim=1 required)");
  if (version & getfem::ASMDIR_BUILDH) {
    const char *s;
    if (mf_u.get_qdim() == 1 && mf_mult.get_qdim() == 1)
      s = "M(#1,#2)+=comp(Grad(#1).Normal().Grad(#2).Normal())(:,i,i,:,j,j)";
    else
      s = "M(#1,#2)+=comp(vGrad(#1).Normal().vGrad(#2).Normal())(:,j,i,i,:,j,k,k);";
      
    getfem::generic_assembly assem(s);
    assem.push_mi(mim);
    assem.push_mf(mf_mult);
    assem.push_mf(mf_u);
    assem.push_mat(H);
    assem.assembly(rg);
    gmm::clean(H, gmm::default_tol(magn_type())
               * gmm::mat_maxnorm(H) * magn_type(1000));
  }
  if (version & getfem::ASMDIR_BUILDR) {
    if (!R_must_be_derivated) {
      asm_normal_source_term(R, mim, mf_mult, mf_r, r_data, rg);
    } else {
      asm_real_or_complex_1_param
        (R, mim, mf_mult, mf_r, r_data, rg,
         "R=data(#2); V(#1)+=comp(Grad(#1).Normal().Grad(#2).Normal())(i,j,k,k).R(j)");
    }
  }
}
  
template<typename MAT>
void asm_constraint_gradient_vectorial_mult 
(MAT &H, const getfem::mesh_im &mim, const getfem::mesh_fem &mf_u,
 const getfem::mesh_fem &mf_mult, 
 const getfem::mesh_region &rg, int version) {
  typedef typename gmm::linalg_traits<MAT>::value_type value_type;
  typedef typename gmm::number_traits<value_type>::magnitude_type magn_type;
    
  rg.from_mesh(mim.linked_mesh()).error_if_not_faces();
  if (version & getfem::ASMDIR_BUILDH) {
    assert(mf_u.get_qdim() == 1 && mf_mult.get_qdim() == 2);
    const char *s = "M(#1,#2)+=comp(vBase(#1).Grad(#2))(:,i,:,i)";
    getfem::generic_assembly assem(s);
    assem.push_mi(mim);
    assem.push_mf(mf_mult);
    assem.push_mf(mf_u);
    assem.push_mat(H);
    assem.assembly(rg);
    gmm::clean(H, gmm::default_tol(magn_type())
               * gmm::mat_maxnorm(H) * magn_type(1000));
  }
}


    /*****************************************/
    // ASSEMBLING MATRIX FOR INTEGRAL MATCHING
    // (i.e. mortar on matching grids 
    /*****************************************/

void bilaplacian_crack_problem::set_matrix_mortar(sparse_matrix &H){

  int mult_with_H = int(PARAM.int_value("MULT_WITH_H")) ;
       mortar_type = int(PARAM.int_value("MORTAR_TYPE")) ;
       // MODIFICATION : trying to define 1-Dimensionnal 
       // multipliers on the boundary of enrichment zone.
//   cout << "Re-initialisation des mesh_fem mortar : " ;
//   mf_pre_mortar.set_finite_element(MORTAR_BOUNDARY_IN,
//              getfem::fem_descriptor(PARAM.string_value("MORTAR_FEM_TYPE")));
//   mf_pre_mortar_deriv.set_finite_element(MORTAR_BOUNDARY_IN,
//              getfem::fem_descriptor(PARAM.string_value("MORTAR_DERIV_FEM_TYPE")));
//   cout << " OK !! \n" ;
// 
//   mfls_mortar.adapt();
//   mfls_mortar_deriv.adapt();
       // END MODIFICATION
       getfem::mesh_fem &mf_mortar = (mult_with_H == 1) ? mfls_mortar : mf_pre_mortar;
       getfem::mesh_fem &mf_mortar_deriv = (mult_with_H == 1) ? mfls_mortar_deriv : mf_pre_mortar_deriv;

sparse_matrix H0(mf_mortar.nb_dof(), mf_u().nb_dof()) ;
    getfem::base_vector R(mf_mortar.nb_dof());

gmm::resize( H, mf_mortar_deriv.nb_dof(), mf_u().nb_dof());

 if (mortar_type == 1) {
      /* older version of integral matching (jan-feb 2007) :
       *
       *  \int_Gamma (u-v) \lambda + \partial_n ( u + v) \partial_n \lambda = 0, for all \lambda in \Lambda      
      */
 
      /* build the list of dof for the "(u-v) lambda" and for the  "\partial_n(u-v) \partial_n lambda" term in the mortar condition */  
      dal::bit_vector bv_mortar;
      dal::bit_vector bv_deriv;
      dal::bit_vector bv_union;
      sparse_matrix MM(mf_mortar.nb_dof(), mf_mortar.nb_dof());
      sparse_matrix MD(mf_mortar.nb_dof(), mf_mortar.nb_dof());
      std::vector<size_type> ind_mortar;
      std::vector<size_type> ind_deriv;
      std::vector<size_type> ind_union;
      getfem::asm_mass_matrix(MM, mim, mf_mortar, MORTAR_BOUNDARY_OUT);
      
      gmm::resize(R, gmm::mat_nrows(MD));
      asm_normal_derivative_dirichlet_constraints_bis(MD, R, mim, mf_mortar,
            mf_mortar, mf_pre_mortar, R, MORTAR_BOUNDARY_OUT, 0, getfem::ASMDIR_BUILDH) ;
      
      for (size_type i=0; i < mf_mortar.nb_dof(); ++i) {
	if ( (MM(i,i) > 1e-15) & (MD(i,i) > 1e-15) ) { 
	  bv_mortar.add(i);
	  bv_deriv.add(i); 
	  bv_union.add(i);
	}
	if ( (MM(i,i) > 1e-15) & (MD(i,i) <= 1e-15) ) { 
	  bv_mortar.add(i);
	  bv_union.add(i);
	}
	if ( (MM(i,i) <= 1e-15) & (MD(i,i) > 1e-15) ) { 
	  bv_deriv.add(i);
	  bv_union.add(i);
	}
      }
           
      for (dal::bv_visitor d(bv_mortar); !d.finished(); ++d)
	ind_mortar.push_back(d);
      for (dal::bv_visitor d(bv_deriv); !d.finished(); ++d)
	ind_deriv.push_back(d);
      for (dal::bv_visitor d(bv_union); !d.finished(); ++d)
	ind_union.push_back(d);
      
      /* This command was not suitable here :
	 dal::bit_vector bv_mortar = 
	 mf_mortar.dof_on_region(MORTAR_BOUNDARY_OUT);  
	 The reason for that is that unfortunately, the method "dof_on_region"
	 sometimes return too much dof when the mesh_fem is enriched. */
      
      
      /* building matrices */
      cout << "Handling mortar junction (" << ind_union.size() << 
	" dof for the lagrange multiplier)\n";    
      gmm::resize(H0, mf_mortar.nb_dof(), mf_u().nb_dof()) ;
      gmm::resize(H,  ind_union.size(),   mf_u().nb_dof()) ;
//       cout << "bv_mortar = " << bv_mortar << "\n";
//       cout << "bv_deriv = " << bv_deriv << "\n" ;
//       cout << "bv_union = " << bv_union << "\n" ;
      
      gmm::sub_index sub_i(ind_union);
      gmm::sub_index sub_i1(ind_mortar);
      gmm::sub_index sub_i2(ind_deriv);
      gmm::sub_interval sub_j(0, mf_u().nb_dof());
      // build sub_indices of dofs which are either value or derivatives in the matrix of constraints H
      std::vector<size_type> ind_val_H, ind_deriv_H ;
      for (unsigned i=0; i< ind_union.size(); ++i) {
	if ( bv_mortar[ind_union[i]] ) ind_val_H.push_back(i) ;
	if ( bv_deriv[ind_union[i]] ) ind_deriv_H.push_back(i) ;
      }   
      gmm::sub_index sub_val_H(ind_val_H) ;
      gmm::sub_index sub_deriv_H(ind_deriv_H) ;
      
      /* build the mortar constraint matrix -- note that the integration
	 method is conformal to the crack
      */
      getfem::asm_mass_matrix(H0, mim, mf_mortar, mf_u(), MORTAR_BOUNDARY_OUT);
      gmm::copy(gmm::sub_matrix(H0, sub_i1, sub_j), gmm::sub_matrix(H, sub_val_H, sub_j) );
      
      gmm::clear(H0);
      getfem::asm_mass_matrix(H0, mim, mf_mortar, mf_u(), MORTAR_BOUNDARY_IN);
      gmm::add(gmm::scaled(gmm::sub_matrix(H0, sub_i1, sub_j), -1), gmm::sub_matrix(H, sub_val_H, sub_j));
      
      
      gmm::clear(H0) ;
      gmm::resize(R, mat_nrows(H));
      asm_normal_derivative_dirichlet_constraints_bis(H0, R, mim, mf_u(),
                                                      mf_mortar, mf_pre_u, R, MORTAR_BOUNDARY_OUT, 0, getfem::ASMDIR_BUILDH) ;
      gmm::add(gmm::scaled(gmm::sub_matrix(H0, sub_i2, sub_j), 1. ), gmm::sub_matrix(H, sub_deriv_H, sub_j)) ;
      
      gmm::clear(H0) ;
      asm_normal_derivative_dirichlet_constraints_bis(H0, R, mim, mf_u(),
                                                      mf_mortar, mf_pre_u, R, MORTAR_BOUNDARY_IN, 0, getfem::ASMDIR_BUILDH) ;
      gmm::add(gmm::scaled(gmm::sub_matrix(H0, sub_i2, sub_j), -1.), gmm::sub_matrix(H, sub_deriv_H, sub_j)) ;
      
      // -----------------------------------
    }
    if (mortar_type == 2 || mortar_type == 3) {
      /* other version of the integral matching.
       * version 2 :
       *     \int_Gamma        (u-v) \lambda  = 0, for all \lambda in \Lambda
       *     \int_Gamma \nabla (u-v).\mu      = 0, for all \mu in M    (be careful : \mu is vectorial.
       * version 3 : only second constraint is different.   
       *     \int_Gamma \partial_n (u-v)\mu  = 0, for all \mu in M
      */
      if (mortar_type == 2){
          mf_mortar_deriv.set_qdim(2) ; //  \mu is vectorial !
      }
      /* build the list of dof for the "(u-v) lambda" condition  */  
      dal::bit_vector bv_mortar;
      dal::bit_vector bv_deriv;
      sparse_matrix MM(mf_mortar.nb_dof(), mf_mortar.nb_dof());
      sparse_matrix MD(mf_mortar_deriv.nb_dof(), mf_mortar_deriv.nb_dof());
      std::vector<size_type> ind_mortar;
      std::vector<size_type> ind_deriv;
      getfem::asm_mass_matrix(MM, mim, mf_mortar, MORTAR_BOUNDARY_OUT);
      getfem::asm_mass_matrix(MD, mim, mf_mortar_deriv, MORTAR_BOUNDARY_OUT);

      for (size_type i=0; i < mf_mortar.nb_dof(); ++i)
	if (MM(i,i) > 1e-15) { 
	  bv_mortar.add(i); 
	  ind_mortar.push_back(i);
	}
      for (size_type i=0; i < mf_mortar_deriv.nb_dof(); ++i)
	if (MD(i,i) > 1e-15) { 
	  bv_deriv.add(i); 
	  ind_deriv.push_back(i); 
        }
      
      // building matrices
      
      cout << "Handling mortar junction (" << ind_mortar.size() << 
	" dof for the lagrange multiplier of the displacement, " <<
	ind_deriv.size() << " dof for the lagrange multiplier of the derivative)\n";
      gmm::clear(H0);
      gmm::clear(H);
      gmm::resize(H0, mf_mortar.nb_dof(), mf_u().nb_dof()) ;
      gmm::resize(H,  ind_mortar.size() + ind_deriv.size(), mf_u().nb_dof()) ; 
      
      // Defining sub_indexes of the matrices calculated with the 
      // complete set of dofs.
      gmm::sub_index sub_i(ind_mortar);
      gmm::sub_index sub_i1(ind_deriv);
      gmm::sub_interval sub_j(0, mf_u().nb_dof());
      
      gmm::sub_interval sub_val_H(0, ind_mortar.size()) ;
      gmm::sub_interval sub_deriv_H(ind_mortar.size(), ind_deriv.size()) ;
      
      cout << "sub_indexes built\n" ;
      /* build the mortar constraint matrix -- note that the integration
	 method is conformal to the crack
      */
      getfem::asm_mass_matrix(H0, mim, mf_mortar, mf_u(), MORTAR_BOUNDARY_OUT);
      gmm::copy(gmm::sub_matrix(H0, sub_i, sub_j), gmm::sub_matrix(H, sub_val_H, sub_j) );
    
      gmm::clear(H0);
      getfem::asm_mass_matrix(H0, mim, mf_mortar, mf_u(), MORTAR_BOUNDARY_IN);
      gmm::add(gmm::scaled(gmm::sub_matrix(H0, sub_i, sub_j), -1), gmm::sub_matrix(H, sub_val_H, sub_j) );
      
      
      cout << "first contraint asm\n" ;
      gmm::resize(R, ind_deriv.size());
      gmm::clear(H0);
      gmm::resize(H0, mf_mortar_deriv.nb_dof(), mf_u().nb_dof() ) ;
      if (mortar_type == 2)
         asm_constraint_gradient_vectorial_mult
           (H0, mim, mf_u(), mf_mortar_deriv,
           MORTAR_BOUNDARY_OUT, getfem::ASMDIR_BUILDH) ;
      if (mortar_type == 3)
         getfem::asm_normal_derivative_dirichlet_constraints
	   (H0, R, mim, mf_u(), mf_mortar_deriv, mf_pre_u, R,
	    MORTAR_BOUNDARY_OUT, 0, getfem::ASMDIR_BUILDH) ;

      gmm::add(gmm::sub_matrix(H0, sub_i1, sub_j),
	       gmm::sub_matrix(H, sub_deriv_H, sub_j)) ;
      
      cout << "first step \n" ;
      gmm::clear(H0);
      if (mortar_type == 2) {
         asm_constraint_gradient_vectorial_mult
	   (H0, mim, mf_u(), mf_mortar_deriv, 
	   MORTAR_BOUNDARY_IN, getfem::ASMDIR_BUILDH) ;
      gmm::add(gmm::scaled(gmm::sub_matrix(H0, sub_i1, sub_j), - 1.0),
	       gmm::sub_matrix(H, sub_deriv_H, sub_j)) ; 
      }
      if (mortar_type == 3) {
         getfem::asm_normal_derivative_dirichlet_constraints
	   (H0, R, mim, mf_u(), mf_mortar_deriv, mf_pre_u, R,
	    MORTAR_BOUNDARY_IN, 0, getfem::ASMDIR_BUILDH) ;
      gmm::add(gmm::scaled(gmm::sub_matrix(H0, sub_i1, sub_j), + 1.0),
	       gmm::sub_matrix(H, sub_deriv_H, sub_j)) ;
      }


    }

}



    /************************************************/
    // INITIALISATION FOR MESHES WITH CUT QUADRANGLES 
    /************************************************/
    
    
void bilaplacian_crack_problem::init_mixed_elements(void) {
  std::string MESH_FILE = PARAM.string_value("MESH_FILE");
  std::string TRI_MESH_TYPE = PARAM.string_value("TRI_MESH_TYPE","Mesh type ");
  std::string QUAD_MESH_TYPE = PARAM.string_value("QUAD_MESH_TYPE","Mesh type ");
  std::string TRI_FEM_TYPE  = PARAM.string_value("TRI_FEM_TYPE","FEM name");
  std::string QUAD_FEM_TYPE  = PARAM.string_value("QUAD_FEM_TYPE","FEM name");
  std::string TRI_INTEGRATION = PARAM.string_value("TRI_INTEGRATION",
                                               "Name of integration method");
  std::string QUAD_INTEGRATION = PARAM.string_value("QUAD_INTEGRATION",
                                               "Name of integration method");
  
  cout << "TRI_MESH_TYPE=" << TRI_MESH_TYPE << "\n";
  cout << "TRI_FEM_TYPE="  << TRI_FEM_TYPE << "\n";
  cout << "TRI_INTEGRATION=" << TRI_INTEGRATION << "\n";
  cout << "QUAD_MESH_TYPE=" << QUAD_MESH_TYPE << "\n";
  cout << "QUAD_FEM_TYPE="  << QUAD_FEM_TYPE << "\n";
  cout << "QUAD_INTEGRATION=" << QUAD_INTEGRATION << "\n";
  
  size_type /*N_tri = 2, N_quad = 2,*/ N = 2 ;
    
  std::string SIMPLEX_INTEGRATION = PARAM.string_value("SIMPLEX_INTEGRATION",
                                         "Name of simplex integration method");
  std::string SINGULAR_INTEGRATION = PARAM.string_value("SINGULAR_INTEGRATION");
  enrichment_option = int(PARAM.int_value("ENRICHMENT_OPTION",
                                      "Enrichment option"));
    enr_area_radius = PARAM.real_value("RADIUS_ENR_AREA",
                                     "radius of the enrichment area");

  sol_ref = PARAM.int_value("SOL_REF") ;
  scalar_type angle_rot = PARAM.real_value("ANGLE_ROT") ;
  bgeot::pgeometric_trans pgt_tri = 
      bgeot::geometric_trans_descriptor(TRI_MESH_TYPE);
  bgeot::pgeometric_trans pgt_quad = 
      bgeot::geometric_trans_descriptor(QUAD_MESH_TYPE);
    
    /* First step : build the mesh */

 if (!MESH_FILE.empty()) { // The case of a quadrangular mesh in a file is not supported.
    mesh.read_from_file(MESH_FILE);
    cout << "WARNING : THE MESH FILES MUST DESIGN A MESH MADE OF TRIANGLES \n" ;
    base_small_vector tt(N); 
    tt[0] = PARAM.real_value("TRANSLAT_X") ;  
    tt[1] = PARAM.real_value("TRANSLAT_Y") ; 
    cout << "TRANSLAT_X = " << tt[0] << " ; TRANSLAT_Y = " << tt[1] << "\n" ;
    mesh.translation(tt); 
    if (mesh.points_of_convex(mesh.convex_index().first_true()).size() == 3){
       TRI_MESH_TYPE = bgeot::name_of_geometric_trans
          (mesh.trans_of_convex(mesh.convex_index().first_true()));
       cout << "TRI_MESH_TYPE=" << TRI_MESH_TYPE << "\n";
      }
    else if (mesh.points_of_convex(mesh.convex_index().first_true()).size() == 4){
       QUAD_MESH_TYPE = bgeot::name_of_geometric_trans
          (mesh.trans_of_convex(mesh.convex_index().first_true()));
       cout << "QUAD_MESH_TYPE=" << QUAD_MESH_TYPE << "\n";
      }
    else cout << "ERROR : elements are not triangles or quadrangles \n" ;
    pgt_tri = bgeot::geometric_trans_descriptor(TRI_MESH_TYPE);
    pgt_quad = bgeot::geometric_trans_descriptor(QUAD_MESH_TYPE);
    N = mesh.dim();
 } else {
    GMM_ASSERT1(N == 2, "For a plate problem, N should be 2");
    std::vector<size_type> nsubdiv(N);
    NX = PARAM.int_value("NX", "Number of space steps ") ;
    std::fill(nsubdiv.begin(),nsubdiv.end(), NX);
    if (sol_ref == 1)  nsubdiv[0] = NX / 2 ;
    if (sol_ref == 2) {
        size_type NY = PARAM.int_value("NY") ;
	nsubdiv[1] = NY ;
     }
    if (PARAM.int_value("QUAD") == 0)
       getfem::regular_unit_mesh(mesh, nsubdiv, pgt_tri, PARAM.int_value("MESH_NOISED") != 0);
    else
       getfem::regular_unit_mesh(mesh, nsubdiv, pgt_quad, PARAM.int_value("MESH_NOISED") != 0);
 
      base_small_vector tt(N); 
    switch (sol_ref) {
    case 0 : {
       tt[0] = - 0.5 + PARAM.real_value("TRANSLAT_X") ;
       tt[1] = - 0.5 + PARAM.real_value("TRANSLAT_Y") ;
    } break ;
    case 1 : {
       tt[0] = - PARAM.real_value("CRACK_SEMI_LENGTH") ;
       tt[1] = - 0.5 + PARAM.real_value("TRANSLAT_Y") ;
    } break ;
    case 2 : {
       tt[0] = - PARAM.real_value("CRACK_SEMI_LENGTH");
       tt[1] = - 0.5 + PARAM.real_value("TRANSLAT_Y") ;
    } break ;
    default : 
       GMM_ASSERT1(false, "SOL_REF parameter is undefined");
      break ;  
    }
    if (sol_ref == 1){
       tt[0] = - PARAM.real_value("CRACK_SEMI_LENGTH") ;
    }
    mesh.translation(tt);

    bgeot::base_matrix M(N,N);
    for (size_type i=0; i < N; ++i) {
      static const char *t[] = {"LX","LY","LZ"};
      M(i,i) = (i<3) ? PARAM.real_value(t[i],t[i]) : 1.0;
    }
    if (sol_ref == 2){
      M(0, 0) = 1.5 ;
      M(1, 1) = 1.0 ;
    }
    if (sol_ref == 0 || sol_ref == 3){
      M(0, 0) = cos(angle_rot) ; M(1, 1) = M(0, 0) ; 
      M(1, 0) = sin(angle_rot) ; M(0, 1) = - M(1, 0) ;
    }
    mesh.transformation(M);

    /* MODIFICATION RELATIVE TO THE USUAL WAY OF PROGRAMMING :
        In the other xfem programs, the level-set is initialized 
        after the mesh_fems. However, to define the mesh_fem, here, 
        we need to select the cut quadrangles. So, it is necessary to switch the orders here :
		- first, the level-set is initialized
		- second, the mesh_fem are initialized too.
    */

   // Setting the level-set
  ls.reinit();
//  scalar_type a = PARAM.real_value("CRACK_SEMI_LENGTH") ; 
  for (size_type d = 0; d < ls.get_mesh_fem().nb_dof(); ++d) {
    scalar_type x = ls.get_mesh_fem().point_of_basic_dof(d)[0];
    scalar_type y = ls.get_mesh_fem().point_of_basic_dof(d)[1];
    if (sol_ref == 0){
       ls.values(0)[d] = y  ; // + 1/4.*(x + .25);
       ls.values(1)[d] = x;}
    if (sol_ref == 1){
     ls.values(0)[d] = y  ;
     ls.values(1)[d] = x  ; //x * x - a * a ;
    }
    if (sol_ref == 2){
     ls.values(0)[d] = y ;
     ls.values(1)[d] = x ; 
    }
  }
  //ls.simplify(0.5);
  ls.touch(); 
  mls.add_level_set(ls); 
  mls.adapt();

    if (PARAM.int_value("MOVE_NODES")){
       cout << "deplacement des noeuds \n" ;
       //size_type nb_x_pos, nb_y_pos = 0 ;
       scalar_type seuil_select = PARAM.real_value("SEUIL_SELECT") ;
       //scalar_type seuil_move = PARAM.real_value("SEUIL_MOVE") ;


// for(dal::bv_visitor i(mesh.convex_index()) ; !i.finished() ; ++i){
//    nb_x_pos = 0 ; // nombre de noeuds d'abscisse positive
//    nb_y_pos = 0 ; // nombre de noeuds d'ordonnee positive
//    for (int j=0; j<4 ; ++j){
//        if (mesh.points_of_convex(i)[j][0] > 0.) 
//            nb_x_pos += 1 ;
//        if (mesh.points_of_convex(i)[j][1] > 0.) 
//            nb_y_pos += 1 ;
//    }
//  
//    if (nb_x_pos < 4){ // juste pour eviter de traiter des elements inutiles
//        if ( nb_y_pos == 1){
// 	  for (int j=0; j<4 ; ++j){
// 	      if ( (mesh.points_of_convex(i)[j][1] > 0.)
//                &&  (mesh.points_of_convex(i)[j][1] < seuil_select )  // 1./5./NX
// 	       ){
// 	         bgeot::base_node Q = mesh.points_of_convex(i)[j] ;
// 	         for (dal::bv_visitor ip(mesh.points().index()); !ip.finished(); ++ip) {
//                     bgeot::base_node& P = mesh.points()[ip];
// 	            if( gmm::vect_dist2(P, Q) < 1e-8){
// 		      cout << "deplace de (" << P[0] << " ; " << P[1] << ") a : " ;
// 		      //P[1] = 1./2./NX ;
// 		      //P[1] = 1./2./NX ;
// 		      P[1] = seuil_move ;
// 		      cout << P[1] << "\n" ;
// 	            }  
// 	        }
//               }
// 	  }
//        } 
//     if ( nb_y_pos == 3){
// 	  for (int j=0; j<4 ; ++j){
// 	      if ( (mesh.points_of_convex(i)[j][1] < 0.)
//                &&  (mesh.points_of_convex(i)[j][1] > -1.*seuil_select ) // -1./5./NX 
// 	       ){
// 	         bgeot::base_node Q = mesh.points_of_convex(i)[j] ;
// 	         for (dal::bv_visitor ip(mesh.points().index()); !ip.finished(); ++ip) {
//                     bgeot::base_node& P = mesh.points()[ip];
// 	            if( gmm::vect_dist2(P, Q) < 1e-8){
// 		      cout << "deplace de (" << P[0] << " ; " << P[1] << ") a : " ;
// 		      //P[1] = - 1./2./NX ;
// 		      //P[1] = -1./2./NX ;
// 		      P[1] = -1. *seuil_move ;
// 		      cout << P[1] << "\n" ;
// 	            }  
// 	        }
//               }
// 	  }
//        } 
// 
//        }
//    }
    
  
   for (dal::bv_visitor ip(mesh.points().index()); !ip.finished(); ++ip) {
                    bgeot::base_node& P = mesh.points()[ip];
	            if( gmm::abs(P[1]) < seuil_select){
		      cout << "deplace de (" << P[0] << " ; " << P[1] << ") a : " ;
		      P[1] = 0. ;
		      cout << P[1] << "\n" ;
	            }
    } 
  } // end of moving nodes		     
 } // end of else
 
    /* Separation of the crossed quadrangles in two triangles */
    

    if (PARAM.real_value("QUAD") == 1 ) {
       cout << "transforming quad in two triangles\n";
       // Selecting crossed quadrangles :
       dal::bit_vector quad_crossed ;
       for (dal::bv_visitor i(mesh.convex_index()); !i.finished(); ++i) {
           if (mls.is_convex_cut(i) && mesh.points_of_convex(i).size() == 4){
	      quad_crossed.add(i) ;
           }
       }
       cout << "quadrangles traverses par la fissure : \n" << quad_crossed << "\n" ;
       // Replace quadrangles by two triangles
       for (dal::bv_visitor i(quad_crossed) ; !i.finished() ; ++i){
              //cout << "Convex " << i << "to split ; values of the level_set : " << values_ls << "\n" ;
	      // First : save the global indexes of the 4 nodes
              std::vector<size_type> ind_sommets_cvx(4) ; 
              for (unsigned j = 0 ; j < (mesh.ind_points_of_convex(i)).size() ; ++j)
                  ind_sommets_cvx[j] = mesh.ind_points_of_convex(i)[j] ; 
              std::vector<bgeot::base_node> P(4) ;
	      // Second : save the coordinates, in order to calculate the length of the two diagonals
              for (unsigned node = 0 ; node < (mesh.points_of_convex(i)).size() ; ++node)
                  P[node] = mesh.points_of_convex(i)[node] ;
              mesh.sup_convex(i) ; // Delete convex now, we won't need it beyond this line.
              scalar_type dist1 = 0., dist2 = 0. ;
              for(int k = 0 ; k < 2 ; k++){ 
                  dist1 += (P[0][k] - P[3][k]) * (P[0][k] - P[3][k]) ;
                  dist2 += (P[2][k] - P[1][k]) * (P[2][k] - P[1][k]) ; 
              }
              // Cut the quadrangle along the shortest diagonal
	      std::vector<bgeot::size_type> ind(3) ;
              if (dist1 > dist2){ //then cut along the [s1 s2] diagonal
                 ind[0] = ind_sommets_cvx[0] ;
                 ind[1] = ind_sommets_cvx[1] ;
                 ind[2] = ind_sommets_cvx[2] ;
              }
              else{  // cut along the [s0 s3] diagonal
                 ind[0] = ind_sommets_cvx[0] ;
                 ind[1] = ind_sommets_cvx[2] ;
                 ind[2] = ind_sommets_cvx[3] ;
              }
              mesh.add_convex(pgt_tri, ind.begin());  // add first triangle
              if ( dist1 > dist2)
                 ind[0] = ind_sommets_cvx[3] ;
              else
                 ind[1] = ind_sommets_cvx[1] ;
              mesh.add_convex(pgt_tri, ind.begin());  // add second triangle 
       }
       // The procedure used beyond was made possible to use thanks to the
       // structure of the containers returned : the elements are ordered 
       // in accordance with the numerotation of the vertex and faces in a quadrangle:
       //         2____3        _2__
       //         |    |     1 |    |  0
       //         |____|       |____|
       //         0    1         3     
    }
    // Holding those informations is mandatory for the following
    dal::bit_vector quad_among_cvx, tri_among_cvx ;
    for (dal::bv_visitor i(mesh.convex_index()) ; !i.finished() ; ++i){
       if (mesh.points_of_convex(i).size() == 3)
          tri_among_cvx.add(i) ;
       else if (mesh.points_of_convex(i).size() == 4)
          quad_among_cvx.add(i) ;
       else cout << "WARNING : an element has nor 3 or 4 nodes ! \n" ;
    }
    
    scalar_type quality = 1.0, avg_area = 0. , min_area = 1. , max_area = 0., area ;
    scalar_type radius, avg_radius = 0., min_radius = 1., max_radius = 0. ;
    size_type cpt = 0 ; 
    for (dal::bv_visitor i(mesh.convex_index()); !i.finished(); ++i){
    	quality = std::min(quality, mesh.convex_quality_estimate(i));
	area = mesh.convex_area_estimate(i) ;
	radius = mesh.convex_radius_estimate(i) ;
	avg_area += area ;
	avg_radius += radius ;
	min_radius = std::min(radius, min_radius) ;
	max_radius = std::max(radius, max_radius) ;	
	min_area = std::min(min_area, area) ; 
	max_area = std::max(max_area, area) ;
	cpt++ ;
    }
    avg_area /= scalar_type(cpt) ;
    avg_radius /= scalar_type(cpt) ; 
    cout << "quality of mesh : " << quality << endl;
    cout << "average radius : " << avg_radius << endl;
    cout << "radius min : " << min_radius << " ; radius max : " << max_radius << endl;
    cout << "average area : " << avg_area << endl ;
    cout << "area min : " << min_area << " ; area max : " << max_area << endl;        
    
   /* read the parameters   */
  epsilon = PARAM.real_value("EPSILON", "thickness") ;
  nu = PARAM.real_value("NU", "nu") ;
  D = PARAM.real_value("D", "Flexure modulus") ;
  int dv = int(PARAM.int_value("DIRICHLET_VERSION", "Dirichlet version"));
  int mv = int(PARAM.int_value("MORTAR_VERSION", "Mortar version"));
  int cv = int(PARAM.int_value("CLOSING_VERSION"));  
  dirichlet_version = getfem::constraints_type(dv);
  mortar_version = getfem::constraints_type(mv);
  closing_version = getfem::constraints_type(cv);
  datafilename=PARAM.string_value("ROOTFILENAME","Base name of data files.");
  residual=PARAM.real_value("RESIDUAL"); if (residual == 0.) residual = 1e-10;
  KL = PARAM.int_value("KL", "Kirchhoff-Love model or not") != 0;
  D = PARAM.real_value("D", "Flexion modulus");
  if (KL) nu = PARAM.real_value("NU", "Poisson ratio");

  // Setting the integration methods
  getfem::pfem pf_u_tri = getfem::fem_descriptor(TRI_FEM_TYPE);
  getfem::pfem pf_u_quad = getfem::fem_descriptor(QUAD_FEM_TYPE);
  getfem::pintegration_method ppi_tri = 
    getfem::int_method_descriptor(TRI_INTEGRATION);
  getfem::pintegration_method ppi_quad = 
    getfem::int_method_descriptor(QUAD_INTEGRATION);
  getfem::pintegration_method sppi = 
    getfem::int_method_descriptor(SIMPLEX_INTEGRATION);
  getfem::pintegration_method sing_ppi = (SINGULAR_INTEGRATION.size() ?
		getfem::int_method_descriptor(SINGULAR_INTEGRATION) : 0);
  
  mim.set_integration_method(tri_among_cvx, ppi_tri);
  mim.set_integration_method(quad_among_cvx, ppi_quad);
  mim.set_simplex_im(sppi, sing_ppi);
  
    /* Setting the finite element on the mf_u */  
  mf_pre_u.set_finite_element(tri_among_cvx, pf_u_tri); 
  mf_pre_u.set_finite_element(quad_among_cvx, pf_u_quad);  
  getfem::pfem pf_partition_of_unity_tri = getfem::fem_descriptor(PARAM.string_value("TRI_PARTITION_OF_UNITY_FEM_TYPE")) ;
  getfem::pfem pf_partition_of_unity_quad = getfem::fem_descriptor(PARAM.string_value("QUAD_PARTITION_OF_UNITY_FEM_TYPE")) ; 
  mf_partition_of_unity.set_finite_element(tri_among_cvx, pf_partition_of_unity_tri);
  mf_partition_of_unity.set_finite_element(quad_among_cvx, pf_partition_of_unity_quad);

  mf_pre_mortar.set_finite_element(tri_among_cvx,
             getfem::fem_descriptor(PARAM.string_value("TRI_MORTAR_FEM_TYPE")));
  mf_pre_mortar_deriv.set_finite_element(tri_among_cvx,
             getfem::fem_descriptor(PARAM.string_value("TRI_MORTAR_DERIV_FEM_TYPE")));
  mf_pre_mortar.set_finite_element(quad_among_cvx,
             getfem::fem_descriptor(PARAM.string_value("QUAD_MORTAR_FEM_TYPE")));
  mf_pre_mortar_deriv.set_finite_element(quad_among_cvx,
             getfem::fem_descriptor(PARAM.string_value("QUAD_MORTAR_DERIV_FEM_TYPE")));  
             
  // set the mesh_fem of the multipliers (for the dirichlet condition)    
  std::string dirichlet_fem_name_tri = PARAM.string_value("TRI_DIRICHLET_FEM_TYPE");
  std::string dirichlet_fem_name_quad = PARAM.string_value("QUAD_DIRICHLET_FEM_TYPE");
  if (dirichlet_fem_name_tri.size() == 0 || dirichlet_fem_name_quad.size() == 0){
    mf_mult.set_finite_element(tri_among_cvx, pf_u_tri);
    mf_mult.set_finite_element(quad_among_cvx, pf_u_quad);    
    }
  else {
    cout << "TRI_DIRICHLET_FEM_TYPE="  << dirichlet_fem_name_tri << "\n";
    cout << "QUAD_DIRICHLET_FEM_TYPE="  << dirichlet_fem_name_quad << "\n";
    mf_mult.set_finite_element(tri_among_cvx, 
                               getfem::fem_descriptor(dirichlet_fem_name_tri));
    mf_mult.set_finite_element(quad_among_cvx, 
                               getfem::fem_descriptor(dirichlet_fem_name_quad));
  }
  std::string dirichlet_der_fem_name_tri
    = PARAM.string_value("TRI_DIRICHLET_DER_FEM_TYPE", "");
  std::string dirichlet_der_fem_name_quad
    = PARAM.string_value("QUAD_DIRICHLET_DER_FEM_TYPE", "");
  if (dirichlet_der_fem_name_tri.size() == 0 || dirichlet_der_fem_name_quad.size() == 0){
    mf_mult_d.set_finite_element(tri_among_cvx, pf_u_tri);
    mf_mult_d.set_finite_element(quad_among_cvx, pf_u_quad);
  }
  else {
    cout << "TRI_DIRICHLET_DER_FEM_TYPE="  << dirichlet_der_fem_name_tri << "\n";
    cout << "QUAD_DIRICHLET_DER_FEM_TYPE="  << dirichlet_der_fem_name_quad << "\n";
    mf_mult_d.set_finite_element(tri_among_cvx, 
                             getfem::fem_descriptor(dirichlet_der_fem_name_tri));
    mf_mult_d.set_finite_element(quad_among_cvx, 
                             getfem::fem_descriptor(dirichlet_der_fem_name_quad));
  }

  /* set the finite element on mf_rhs (same as mf_u if DATA_FEM_TYPE is
     not used in the .param file */
  std::string data_fem_name_tri = PARAM.string_value("TRI_DATA_FEM_TYPE");
  std::string data_fem_name_quad = PARAM.string_value("QUAD_DATA_FEM_TYPE");
  if (data_fem_name_tri.size() == 0 || data_fem_name_quad.size() == 0) {
    GMM_ASSERT1(pf_u_tri->is_lagrange(), "You are using a non-lagrange FEM. "
                << "In that case you need to set "
                << "DATA_FEM_TYPE in the .param file");
    mf_rhs.set_finite_element(tri_among_cvx, pf_u_tri);
    mf_rhs.set_finite_element(quad_among_cvx, pf_u_quad);
  } else {
    mf_rhs.set_finite_element(tri_among_cvx, 
                              getfem::fem_descriptor(data_fem_name_tri));
    mf_rhs.set_finite_element(quad_among_cvx, 
                              getfem::fem_descriptor(data_fem_name_quad));
  }

  /* set boundary conditions
   * (Neuman on the upper face, Dirichlet elsewhere) */
  cout << "Selecting Neumann and Dirichlet boundaries\n";
  getfem::mesh_region border_faces;
  getfem::outer_faces_of_mesh(mesh, border_faces);
  if (sol_ref == 0){
     for (getfem::mr_visitor i(border_faces); !i.finished(); ++i) {
         mesh.region(SIMPLE_SUPPORT_BOUNDARY_NUM).add(i.cv(), i.f());
         mesh.region(CLAMPED_BOUNDARY_NUM).add(i.cv(), i.f()); 
     }
  }
  if (sol_ref == 1 ){
     for (getfem::mr_visitor i(border_faces); !i.finished(); ++i) {
        base_node un = mesh.normal_of_face_of_convex(i.cv(), i.f());
        un /= gmm::vect_norm2(un);
        if ( (un[0] <= -0.9999) || (un[0] >= 0.9999) ) {
	//if  ( -un[0] >= 0.999  )  {
	   mesh.region(CLAMPED_BOUNDARY_NUM).add(i.cv(), i.f());
        
	}
	if  (gmm::abs(un[1]) >= 0.999)  {
	   mesh.region(SIMPLE_SUPPORT_BOUNDARY_NUM).add(i.cv(), i.f());
	   mesh.region(MOMENTUM_BOUNDARY_NUM).add(i.cv(), i.f());
	}  
     }
  }

    if (sol_ref == 2 ){
      for (getfem::mr_visitor i(border_faces); !i.finished(); ++i) {
        base_node un = mesh.normal_of_face_of_convex(i.cv(), i.f());
        un /= gmm::vect_norm2(un);
	if (un[0] < - 0.9999) { // symetry condition
	   mesh.region(CLAMPED_BOUNDARY_NUM).add(i.cv(), i.f());
	} 
	else{
	   mesh.region(SIMPLE_SUPPORT_BOUNDARY_NUM).add(i.cv(), i.f());
           mesh.region(MOMENTUM_BOUNDARY_NUM).add(i.cv(), i.f());
	}
      }
    }

//   if (sol_ref == 1 || sol_ref == 2){
//      for (getfem::mr_visitor i(border_faces); !i.finished(); ++i) {
//         base_node un = mesh.normal_of_face_of_convex(i.cv(), i.f());
//         un /= gmm::vect_norm2(un);
//         if ( (un[0] <= -0.9999) || (un[0] >= 0.9999) ) {
// 	//if  (gmm::abs(un[0]) >= 0.999)  {
// 	   mesh.region(CLAMPED_BOUNDARY_NUM).add(i.cv(), i.f());
//            //mesh.region(MOMENTUM_BOUNDARY_NUM).add(i.cv(), i.f());
// 	} else
//            mesh.region(FORCE_BOUNDARY_NUM).add(i.cv(), i.f());
//            mesh.region(MOMENTUM_BOUNDARY_NUM).add(i.cv(), i.f()); 
//      }
//   }

  // Updating things concerning the level-set
  // (Not sure this part is mandatory, however, the mesh_fem
  // were not initialised when we defined the level-set.
  // So we re-initialize to be sure.
  //scalar_type a = PARAM.real_value("CRACK_SEMI_LENGTH") ; 
  for (size_type d = 0; d < ls.get_mesh_fem().nb_dof(); ++d) {
    scalar_type x = ls.get_mesh_fem().point_of_basic_dof(d)[0];
    scalar_type y = ls.get_mesh_fem().point_of_basic_dof(d)[1];
    if (sol_ref == 0){
       ls.values(0)[d] = y  ; // + 1/4.*(x + .25);
       ls.values(1)[d] = x;}
    if (sol_ref == 1){
     ls.values(0)[d] = y  ;
     ls.values(1)[d] = x ; //x * x - a * a ;
    }
    if (sol_ref == 2){
     ls.values(0)[d] = y ;
     ls.values(1)[d] = x ; //x * x - a * a ;
    }
  }
  //ls.simplify(0.5);
  ls.touch(); 
  mls.add_level_set(ls); 
  mls.adapt();
  exact_sol.init(ls);
  mim.adapt();
  mfls_u.adapt();
  mfls_mortar.adapt();
  mfls_mortar_deriv.adapt();
  cout << "mfls_u.nb_dof()=" << mfls_u.nb_dof() << "\n";
}



    /************************************************/
    // MEMBER FOR EXPORTING SOLUTION, IN 2D AND 3D 
    /************************************************/


void bilaplacian_crack_problem::export_solution(const plain_vector &U){

    int VTK_EXPORT = int(PARAM.int_value("VTK_EXPORT"));
    int MATLAB_EXPORT = int(PARAM.int_value("MATLAB_EXPORT"));
    int DX_EXPORT = int(PARAM.int_value("DX_EXPORT"));
    
    getfem::mesh mcut;
    mls.global_cut_mesh(mcut);
    unsigned Q = mf_u().get_qdim();
    assert( Q == 1 ) ;
    getfem::mesh_fem mf(mcut, dim_type(Q));
    mf.set_classical_discontinuous_finite_element(3, 0.001);
    plain_vector V(mf.nb_dof()) ;
    getfem::interpolation(mf_u(), mf, U, V);

    size_type N = mcut.dim() ;
    getfem::mesh_fem mf_grad(mcut, dim_type(Q));
    mf_grad.set_classical_discontinuous_finite_element(2, 0.001);
        // mf.set_finite_element
    //	(getfem::fem_descriptor("FEM_PK_DISCONTINUOUS(2, 2, 0.0001)"));
    plain_vector GRAD(N*mf_grad.nb_dof()) ;
/*    if (PARAM.int_value("TRY") == 1 ){ 
    getfem::compute_gradient(mf, mf_grad, U, GRAD) ;
    }
    if (PARAM.int_value("TRY") == 1 ){ 
    getfem::compute_gradient(mf, mf_grad, U, GRAD) ;
    }  */  


//     // Printing the normal rotation (= derivative of the vertical displacement)
//     if (PARAM.int_value("SHOW_DX") == 1 ){  // au lieu d'afficher la solution, on affiche sa derivee en y
//        getfem::compute_gradient(mf, mf_grad, V, GRAD) ;
//        gmm::resize(V, mf_grad.nb_dof()) ;
//        for (unsigned i=0 ; i< mf_grad.nb_dof(); i++){
//            V[i] = GRAD[2*i+1] ;
//        }
//       cout << "export to " << datafilename + ".vtk" << "..\n";
//       getfem::vtk_export exp(datafilename + ".vtk",
// 			     PARAM.int_value("VTK_EXPORT")==1);
//       exp.exporting(mf_grad); 
//       exp.write_point_data(mf_grad, V, "gradient_second_coordinate");
//       cout << "export done, you can view the data file with (for example)\n"
// 	   << "mayavi -d " << datafilename
// 	   << ".vtk -m BandedSurfaceMap -m Outline -f WarpScalar\n";
//     }
//     else{
//       cout << "export to " << datafilename + ".vtk" << "..\n";
//       getfem::vtk_export exp(datafilename + ".vtk",
// 			     PARAM.int_value("VTK_EXPORT")==1);
//       exp.exporting(mf_u()); 
//       exp.write_point_data(mf, V, "vertical_displacement");
//       cout << "export done, you can view the data file with (for example)\n"
// 	   << "mayavi -d " << datafilename
// 	   << ".vtk -m BandedSurfaceMap -m Outline -f WarpScalar\n";
//     }

    getfem::stored_mesh_slice sl;
    getfem::mesh mcut_refined;

    unsigned nn;
    if (NX < 6) nn = 12;
    else if (NX < 12) nn = 8;
    else if (NX < 30) nn = 3;
    else nn = 1;

    /* choose an adequate slice refinement based on the distance to the crack tip */
    std::vector<bgeot::short_type> nrefine(mcut.convex_index().last_true()+1);
    for (dal::bv_visitor cv(mcut.convex_index()); !cv.finished(); ++cv) {
      scalar_type dmin=0, d;
      base_node Pmin,P;
      for (unsigned i=0; i < mcut.nb_points_of_convex(cv); ++i) {
	P = mcut.points_of_convex(cv)[i];
	//d = gmm::vect_norm2(ls_function(P));
	d = gmm::vect_norm2(P) ; 
	if (d < dmin || i == 0) { dmin = d; Pmin = P; }
      }

      if (dmin < 1e-5)
	nrefine[cv] = short_type(nn*2);
      else if (dmin < .1) 
	nrefine[cv] = short_type(nn*2);
      else nrefine[cv] = short_type(nn);
      /*if (dmin < .01)
	cout << "cv: "<< cv << ", dmin = " << dmin << "Pmin=" << Pmin << " " << nrefine[cv] << "\n";*/
    }

    getfem::mesh_slicer slicer(mcut);
    getfem::slicer_build_mesh bmesh(mcut_refined);
    slicer.push_back_action(bmesh);
    slicer.exec(nrefine, getfem::mesh_region::all_convexes());
     
    /*
      sl.build(mcut, 
      getfem::slicer_build_mesh(mcut_refined), nrefine);*/

    getfem::mesh_im mim_refined(mcut_refined); 
    mim_refined.set_integration_method(getfem::int_method_descriptor
				       ("IM_TRIANGLE(6)"));

    getfem::mesh_fem mf_refined(mcut_refined, dim_type(Q));
    mf_refined.set_classical_discontinuous_finite_element(2, 0.001);
    plain_vector W(mf_refined.nb_dof());

    getfem::interpolation(mf, mf_refined, V, W);
    
//     getfem::mesh_fem mf_grad(mcut, 2) ;
//     plain_vector VGRAD(mf_grad.nb_dof() ) ;
//     getfem::compute_gradient(mls, mf_u(), mf_grad_u, U, GRAD) ;
//     getfem::mesh_fem mf_grad_refined(mcut_refined, 2) ;
//     plain_vector WGRAD(mf_grad_refined.nb_dof()) ;
//     getfem::interpolation(mf_grad, mf_grad_refined, GRAD, WGRAD) ;

      if (VTK_EXPORT){
      cout << "exporting solution to " << datafilename + ".vtk" << "..\n";
      getfem::vtk_export exp(datafilename + ".vtk", false);
      exp.exporting(mf_refined); 
      exp.write_point_data(mf_refined, W, "vertical_displacement");
      cout << "export done, you can view the data file with (for example)\n"
	"mayavi -d " << datafilename  << ".vtk -f "
	"WarpScalar -m BandedSurfaceMap -m Outline\n";

      exact_sol.mf.set_qdim(dim_type(Q));
      assert(exact_sol.mf.nb_dof() == exact_sol.U.size());   
      plain_vector EXACT(mf_refined.nb_dof());
      getfem::interpolation(exact_sol.mf, mf_refined, 
			    exact_sol.U, EXACT);
	
      plain_vector DIFF(EXACT); gmm::add(gmm::scaled(W,-1),DIFF);
      if ((VTK_EXPORT & 2)) {
	cout << "exporting exact solution to VTK\n";
	getfem::vtk_export exp2(datafilename + "_exact.vtk");
	exp2.exporting(mf_refined);
	exp2.write_point_data(mf_refined, EXACT, "reference solution");
	cout << "export done, you can view the data file with (for example)\n"
	"mayavi -d " << datafilename << "_exact.vtk -f "
	"WarpScalar -m BandedSurfaceMap -m Outline\n";
      }
      if ((VTK_EXPORT & 4)) {
	cout << "exporting difference with exact solution to VTK\n";
	getfem::vtk_export exp2(datafilename + "_diff.vtk");
	exp2.exporting(mf_refined);
	exp2.write_point_data(mf_refined, DIFF, "difference solution");
	cout << "export done, you can view the data file with (for example)\n"
	  "mayavi -d " << datafilename  << "_diff.vtk -f "
	"WarpScalar -m BandedSurfaceMap -m Outline\n";
      }
      if ((VTK_EXPORT == 5)) {
	cout << "exporting Von Mises \n";
	getfem::vtk_export exp3(datafilename + "_VM.vtk");
	exp3.exporting(mf_refined);
	plain_vector VM(mf_refined.nb_dof()) ;
	calcul_von_mises(mf_u(), U, mf_refined, VM, D) ;
	exp3.write_point_data(mf_refined, W, "difference solution");
	exp3.write_point_data(mf_refined, VM, "Von Mises");
	cout << "export done, you can view the data file with (for example)\n"
	  "mayavi -d " << datafilename  << "_diff.vtk -f "
	"WarpScalar -m BandedSurfaceMap -m Outline\n";
      }
      }
    
    if (MATLAB_EXPORT) {
      cout << "exporting solution to " << datafilename + ".mf" << " and " << datafilename << ".U\n";
      mf_refined.write_to_file(datafilename + ".mf", true);
      gmm::vecsave(datafilename + ".U", W);
      exact_sol.mf.set_qdim(dim_type(Q));
      assert(exact_sol.mf.nb_dof() == exact_sol.U.size());   // ??
      plain_vector EXACT(mf_refined.nb_dof());
      getfem::interpolation(exact_sol.mf, mf_refined, 
			    exact_sol.U, EXACT);
      gmm::vecsave(datafilename + ".EXACT", EXACT);

      cout << "exporting original mesh to " << datafilename + "_base.mesh\n";
      mesh.write_to_file(datafilename + "_base.mesh");

      cout << "matlab export done, you can view the results with\n";
      cout << "mf=gfMeshFem('load', 'bilaplacian.mf'); U=load('bilaplacian.U')'; "
	"EXACT=load('bilaplacian.EXACT')'; m0=gfMesh('load','bilaplacian_base.mesh');\n";
      cout << "gf_plot(mf, U-EXACT, 'refine',1); hold on; gf_plot_mesh(m0); hold off; colorbar;\n";
    }

    if (DX_EXPORT) {
    cout << "export solution to " << datafilename + ".dx" << "..\n";
    getfem::dx_export exp(datafilename + ".dx",
			   PARAM.int_value("DX_EXPORT")==1);
    exp.exporting(mf_refined); 
    exp.write_point_data(mf_refined, W, "vertical_displacement");
    cout << "export done for open dx \n" ;
    }


    // PRINTING IN 3D 
 

    if (DX_EXPORT > 29){
    mf.clear() ;
    getfem::mesh_fem mf_2d(mcut_refined, 1);
    mf_2d.set_classical_discontinuous_finite_element(3, 0.001);
    
    
    plain_vector V3(mf_2d.nb_dof()), VT(mf_2d.nb_dof()*2), VTHETA(mf_2d.nb_dof()*2); 
    if ( DX_EXPORT == 31){  // exporting the "finite element" solution on lagrange discont.
	// getfem::interpolation( "mesh_fem of exact sol", mf_2d, "VECTOR of exact sol", VT); -> for membrane
	getfem::interpolation(mf_refined, mf_2d, W, V3);
	plain_vector GRAD_U(mf_refined.nb_dof() * 2) ;
	getfem::compute_gradient(mf_refined, mf_refined, W, GRAD_U);
	getfem::interpolation(mf_refined, mf_2d, GRAD_U, VTHETA);	
// 	getfem::interpolation(mf_u(), mf_2d, U, V3);
// 	plain_vector GRAD_U(mf_rhs.nb_dof() * 2) ;
// 	getfem::compute_gradient(mf_u(), mf_rhs, U, GRAD_U);
// 	getfem::interpolation(mf_rhs, mf_2d, GRAD_U, VTHETA);
	}
    else if (DX_EXPORT == 32) { // exporting the "exact" solution
        //exact_sol.mf_ut.set_qdim(2) ;  -> to modifify for taking account  of the membrane displacement
        //exact_sol.mf_2d.set_qdim(2) ;
	//getfem::interpolation(exact_sol.mf_ut,    mf_2d, exact_sol.UT, VT);
	getfem::interpolation(exact_sol.mf,    mf_2d, exact_sol.U, V3);
	getfem::interpolation(exact_sol.mf,    mf_refined, exact_sol.U, W);
	plain_vector GRAD_U(mf_refined.nb_dof() * 2) ; // compute exact gradient on P3 mesh_fem
	getfem::compute_gradient(mf_refined, mf_refined, W, GRAD_U);
	getfem::interpolation(mf_refined, mf_2d, GRAD_U, VTHETA);
    }
    
    getfem::mesh m3d; getfem::extrude(mcut_refined,m3d,1);
    getfem::base_matrix trans(3,3); 
    trans(0,0) = trans(1,1) = 1; trans(2,2) = epsilon;
    m3d.transformation(trans);
    getfem::mesh_fem mf3d(m3d);
    mf3d.set_classical_discontinuous_finite_element(3, 0.001);
    
    
    gmm::resize(V, mf3d.nb_dof()*3);
    GMM_ASSERT1(!mf_2d.is_reduced(), "To be adapted");
    GMM_ASSERT1(!mf3d.is_reduced(), "To be adapted");
    bgeot::kdtree tree; tree.reserve(mf_2d.nb_dof());
    for (unsigned i=0; i < mf_2d.nb_dof(); ++i)
      tree.add_point_with_id(mf_2d.point_of_basic_dof(i),i);
    bgeot::kdtree_tab_type pts;
    for (unsigned i=0; i < mf3d.nb_dof(); ++i) {
      base_node P = mf3d.point_of_basic_dof(i);
      base_node P2d0(2), P2d1(2); 
      scalar_type EPS = 1e-6;
      P2d0[0] = P[0]-EPS; P2d0[1] = P[1]-EPS;
      P2d1[0] = P[0]+EPS; P2d1[1] = P[1]+EPS;
      tree.points_in_box(pts, P2d0, P2d1);
      //cout << "P = " << P << ", P2d0=" << P2d0 << " " << P2d1 << ", pts.size=" << pts.size() << "\n";
      assert(pts.size() == 1);
      size_type j=pts[0].i;
      scalar_type x3 = P[2];
      assert(finite(VTHETA[2*j]));
      //assert(finite(VT[2*j]));
      V[3*i+0] = - x3 * VTHETA[2*j+0]; //V[3*i+0] = VT[2*j+0] + x3 * VTHETA.at(2*j+0);
      V[3*i+1] = - x3 * VTHETA[2*j+1]; //V[3*i+1] = VT[2*j+1] + x3 * VTHETA[2*j+1];
      V[3*i+2] = V3[j];
      assert(finite(V[3*i]));
      assert(finite(V[3*i+1]));
      assert(finite(V[3*i+2]));
    }
    
    cout << "export to " << datafilename + "_3d.dx" << "..\n";
	getfem::dx_export exp(datafilename + "_3d.dx",
			       PARAM.int_value("DX_EXPORT")>30);
    getfem::stored_mesh_slice sl_tetra;
    getfem::mesh mtetra;
    sl_tetra.build(m3d, getfem::slicer_build_mesh(mtetra), 1);
    getfem::mesh_fem mftetra(mtetra,3); 
    mftetra.set_classical_discontinuous_finite_element(3, 0.001);
    
    plain_vector Vtetra(mftetra.nb_dof());
    mf3d.set_qdim(3);
    getfem::interpolation(mf3d, mftetra, V, Vtetra);
    /* evaluating Von Mises*/
    getfem::mesh_fem mf_vm(mtetra,1) ; 
    mf_vm.set_classical_discontinuous_finite_element(3, 0.001);
    plain_vector VM(mf_vm.nb_dof()) ;
    calcul_von_mises(mftetra, Vtetra, mf_vm, VM) ;   // to do: add a mu value in additional last argument
        
    exp.exporting(mftetra);
    exp.write_point_data(mftetra, Vtetra, "plate_displacement");
    size_type deb=1;
    if (deb==1){ 
        exp.exporting(mf_vm);
        exp.write_point_data(mf_vm, VM, "von_mises_stress");
    }
    }
    
} 















