//===========================================================================
//
// Copyright (C) 2006-2009 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 2.1 of the License,  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 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"

size_type is_global_dof_type_bis(getfem::pdof_description dof){
size_type global_dof = 0 ;
   for (dim_type d = 0; d < 4 ; ++d){
       if (dof == getfem::global_dof(d)) {
          global_dof = 1;
	      }
   }
return global_dof ;
}

/******** Exact Solution *******************************/

scalar_type D  = 1.  ;
scalar_type nu = 0.3 ;
scalar_type AAA = 0.1 ;    // mode II
scalar_type BB = AAA * (3. * nu + 5.)/ (3. * (nu - 1.))   ;
scalar_type DD = 0.0 ;   // mode 1
scalar_type CC = DD * (nu + 7.)/ (3. * (nu - 1.))   ;
scalar_type EE = 0.0 ;           // singul 61
scalar_type FF = 0.0 ;           // singul 62
scalar_type GG = 0.0 ;           // singul 63
scalar_type HH = 0.0 ; //3.0     // singul 6 

scalar_type P0 = 0.0 ;
scalar_type P1 = 0.0 ;
scalar_type P2 = 0.0 ;

scalar_type sol_u(const base_node &x){
 scalar_type r = sqrt( x[0] * x[0] + x[1] * x[1] ) ;
 //scalar_type theta = 2. * atan( x[1] / ( x[0] + r ) ) ;
 scalar_type theta = atan2(x[1], x[0]);
 return sqrt(r*r*r)*(AAA*sin(theta/2.0)+BB*sin(3.0/2.0*theta)+CC*cos(3.0/2.0*theta)+DD*cos(theta/2.0)) + EE * x[1] * (10. * x[1] * x[1]* x[1] + 1.) ;

}

scalar_type sol_F(const base_node &)
{return 1.  ;//EE * D *  240. ;//256. * cos(2. * x[1]) ; 
}

void exact_solution_bilap::init(getfem::level_set &ls) {
  std::vector<getfem::pglobal_function> cfun(11) ;
  for (unsigned j=0; j < 4; ++j)
    cfun[j] = bilaplacian_crack_singular(j, ls, nu, 0.) ;
  cfun[4] = bilaplacian_crack_singular(61, ls, nu, 0.) ;
  cfun[5] = bilaplacian_crack_singular(62, ls, nu, 0.) ;
  cfun[6] = bilaplacian_crack_singular(63, ls, nu, 0.) ;  
  cfun[7] = bilaplacian_crack_singular(6, ls, nu, 0.) ;
  cfun[8] = bilaplacian_crack_singular(10, ls, nu, 0.) ;
  cfun[9] = bilaplacian_crack_singular(11, ls, nu, 0.) ;
  cfun[10] = bilaplacian_crack_singular(12, ls, nu, 0.) ;

  mf.set_functions(cfun);
  U.resize(11); assert(mf.nb_dof() == 11);
  U[0] = AAA ;
  U[1] = BB ;
  U[2] = CC ;
  U[3] = DD ;
  U[4] = EE ;
  U[5] = FF ;
  U[6] = GG ;
  U[7] = HH ;
  U[8] = P0 ;
  U[9] = P1 ;
  U[10] = P2 ;

}




scalar_type eval_fem_gradient_with_finite_differences(getfem::pfem pf, 
					       const base_vector &coeff,
					       size_type cv,
					       bgeot::pgeometric_trans pgt, 
					       bgeot::geotrans_inv_convex &gic,
					       const base_matrix &G, 
					       base_node X0, 
					       scalar_type h, unsigned dg) {
  X0[dg] -= h/2;
  base_node X0ref; gic.invert(X0, X0ref);
  getfem::fem_interpolation_context c(pgt, pf, X0ref, G, cv);

  base_vector val0(1);
  pf->interpolation(c, coeff, val0, 1);

  base_node X1(X0), X1ref; X1[dg] += h;
  gic.invert(X1, X1ref);
  c.set_xref(X1ref);

  base_vector val1(1);
  pf->interpolation(c, coeff, val1, 1);

  return (val1[0] - val0[0])/h;
}

scalar_type eval_fem_hessian_with_finite_differences(getfem::pfem pf, 
					      const base_vector &coeff,
					      size_type cv, 
					      bgeot::pgeometric_trans pgt, 
					      bgeot::geotrans_inv_convex &gic,
					      const base_matrix &G, 
					      base_node X0, 
					      scalar_type h, 
					      unsigned dg, unsigned dh) {
  X0[dh] -= h/2;
  scalar_type Gr0 = 
    eval_fem_gradient_with_finite_differences(pf, coeff, cv, pgt, gic, G, X0, h, dg);
  base_node X1(X0);
  X1[dh] += h;
  scalar_type Gr1 = 
    eval_fem_gradient_with_finite_differences(pf, coeff, cv, pgt, gic, G, X1, h, dg);
  return (Gr1 - Gr0)/h;
}

void validate_fem_derivatives(getfem::pfem pf, unsigned cv, 
			      bgeot::pgeometric_trans pgt, const base_matrix &G) {
  unsigned N = unsigned(gmm::mat_nrows(G));
  scalar_type h = 1e-5;

  std::vector<base_node> pts(gmm::mat_ncols(G));
  for (unsigned j=0; j < pts.size(); ++j) {
    pts[j].resize(N); gmm::copy(gmm::mat_col(G, j), pts[j]);
  }
  cout << "validate_fem_derivatives: pf = " << &(*pf) << ", nbdof = "<< pf->nb_dof(cv) << ", cv = " << cv << " (~ at " << gmm::mean_value(pts) << ")\n";
  bgeot::geotrans_inv_convex gic(pts, pgt);

  //cout << "pts = " << pts << "\n";
  
  for (unsigned idof = 0; idof < pf->nb_dof(cv); ++idof) {    
    /* choose a random point in the convex */
    base_node X0(N), X0ref;
    base_node w(pgt->nb_points());
    do {
      for (unsigned i=0; i < w.size(); ++i) w[i] = 0.1 + 0.8*gmm::random(); 
      gmm::scale(w, 1/gmm::vect_norm1(w));
      gmm::mult(G, w, X0);

      //cout << "w = " << w << "\n";
      
      gic.invert(X0, X0ref);
      
      // avoid discontinuity lines in the HCT composite element..
      if (gmm::abs(X0ref[0] + X0ref[1] - 1) > 1e-2 &&
	  gmm::abs(X0ref[0] - X0ref[1]) > 1e-2 && 
	  gmm::abs(X0[0]) > 1e-3 && gmm::abs(X0[1])> 1e-3) break;
    } while (1);
    //cout << "testing X0 = " << X0 << " (X0ref=" << X0ref << ")\n";


    base_vector coeff(pf->nb_dof(cv)); coeff[idof] = 1;
    base_matrix grad(1,N), grad_fd(1,N);
    base_matrix hess(1,N*N), hess_fd(1,N*N);

    getfem::fem_interpolation_context c(pgt, pf, X0ref, G, cv);
    pf->interpolation_grad(c, coeff, grad, 1);
    pf->interpolation_hess(c, coeff, hess, 1);

    for (unsigned dg = 0; dg < N; ++dg) {
      grad_fd[dg] = 
	eval_fem_gradient_with_finite_differences(pf, coeff, cv, pgt, gic, G, X0, h, dg);
      for (unsigned dh = 0; dh < N; ++dh) {
	hess_fd(0,dg*N+dh) = 
	  eval_fem_hessian_with_finite_differences(pf, coeff, cv, pgt, gic, G, X0, h, dg, dh);
      }
    }
    
    scalar_type err_grad = 
      gmm::vect_dist2((base_vector&)grad, (base_vector&)grad_fd);
    scalar_type err_hess = 
      gmm::vect_dist2((base_vector&)hess, (base_vector&)hess_fd);
    
    if (err_grad > 1e-4 ||
	err_hess > 1e-4) {
      cout << "validate_fem_derivatives dof=" << idof << "/" << pf->nb_dof(cv) << " -- X0ref = " << X0ref << "\n";

      if (gmm::vect_dist2((base_vector&)grad, (base_vector&)grad_fd) > 1e-4)
	cout << "grad = " << (base_vector&)grad << "\ngrad_fd = " << (base_vector&)grad_fd << "\n";
      cout << "hess = " << (base_vector&)hess << "\nhess_fd = " << (base_vector&)hess_fd << "\n";
      if (err_grad + err_hess > 1.0) { cout << "---------> COMPLETEMENT FAUX!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"; abort(); }
    }
  }
}


void validate_fem_derivatives(const getfem::mesh_fem &mf) {
  bgeot::base_matrix G;
  for (dal::bv_visitor cv(mf.convex_index()); !cv.finished(); ++cv) {
    //if (mf.nb_dof_of_element(cv) > 12) {
      vectors_to_base_matrix(G, mf.linked_mesh().points_of_convex(cv));
      validate_fem_derivatives(mf.fem_of_element(cv), unsigned(cv), mf.linked_mesh().trans_of_convex(cv), G);
      //}
  }
}






/*                                                          */
/*****  Methods for class bilaplacian_crack_problem  ********/
/*                                                          */





void bilaplacian_crack_problem::init(void) {
  std::string MESH_FILE = PARAM.string_value("MESH_FILE");
  std::string MESH_TYPE = PARAM.string_value("MESH_TYPE","Mesh type ");
  std::string FEM_TYPE  = PARAM.string_value("FEM_TYPE","FEM name");
  std::string INTEGRATION = PARAM.string_value("INTEGRATION",
					       "Name of integration method");
  cout << "MESH_TYPE=" << MESH_TYPE << "\n";
  cout << "FEM_TYPE="  << FEM_TYPE << "\n";
  cout << "INTEGRATION=" << INTEGRATION << "\n";

  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 = 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") ;
  size_type N = 2 ;
  /* First step : build the mesh */
  if (!MESH_FILE.empty()) {
     mesh.read_from_file(MESH_FILE);
     // printing number of elements
     cout << "Number of element of the mesh: " << mesh.convex_index().card() << "\n" ;
     base_small_vector tt(N);
     tt[0] = PARAM.real_value("TRANSLAT_X") ;
     tt[1] = PARAM.real_value("TRANSLAT_Y") ;
     if (sol_ref == 1 || sol_ref == 2){
        tt[0] -= PARAM.real_value("CRACK_SEMI_LENGTH") ;
     }
     cout << "TRANSLAT_X = " << tt[0] << " ; TRANSLAT_Y = " << tt[1] << "\n" ;
     mesh.translation(tt); 
     MESH_TYPE = bgeot::name_of_geometric_trans
       (mesh.trans_of_convex(mesh.convex_index().first_true()));
     bgeot::pgeometric_trans pgt = 
       bgeot::geometric_trans_descriptor(MESH_TYPE);
     cout << "MESH_TYPE=" << MESH_TYPE << "\n";
     N = mesh.dim();
  } 
  else {
     bgeot::pgeometric_trans pgt = 
       bgeot::geometric_trans_descriptor(MESH_TYPE);
     N = pgt->dim();
     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 ;
     }
     getfem::regular_unit_mesh(mesh, nsubdiv, pgt, PARAM.int_value("MESH_NOISED") != 0);

    /* scale the unit mesh to [LX,LY,..] and incline it */
    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 ;
    case 3 : {
       tt[0] = - 0.5 + PARAM.real_value("TRANSLAT_X") ;
       tt[1] = - 0.5 + PARAM.real_value("TRANSLAT_Y") ;
    } break ;
    default : 
       GMM_ASSERT1(false, "SOL_REF parameter is undefined");
      break ;  
    }
    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);
  }
 
  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");
  
  cutoff.fun_num = PARAM.int_value("CUTOFF_FUNC", "cutoff function");
  cutoff.radius = PARAM.real_value("CUTOFF", "Cutoff");
  cutoff.radius1 = PARAM.real_value("CUTOFF1", "Cutoff1");
  cutoff.radius0 = PARAM.real_value("CUTOFF0", "Cutoff0");

 // Setting the integration methods

  getfem::pfem pf_u = getfem::fem_descriptor(FEM_TYPE);
  getfem::pintegration_method ppi = 
    getfem::int_method_descriptor(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(mesh.convex_index(), ppi);
  mls.add_level_set(ls);
  mim.set_simplex_im(sppi, sing_ppi);

  /* Setting the finite element on the mf_u */  
  mf_pre_u.set_finite_element(mesh.convex_index(), pf_u); 
  getfem::pfem pf_partition_of_unity = getfem::fem_descriptor(PARAM.string_value("PARTITION_OF_UNITY_FEM_TYPE")) ;
  mf_partition_of_unity.set_finite_element(mesh.convex_index(), pf_partition_of_unity);

  // set the mesh_fem of the multipliers (for the dirichlet condition)
  std::string dirichlet_fem_name = PARAM.string_value("DIRICHLET_FEM_TYPE");
  if (dirichlet_fem_name.size() == 0)
    mf_mult.set_finite_element(mesh.convex_index(), pf_u);
  else {
    cout << "DIRICHLET_FEM_TYPE="  << dirichlet_fem_name << "\n";
    mf_mult.set_finite_element(mesh.convex_index(), 
			       getfem::fem_descriptor(dirichlet_fem_name));
  }
  std::string dirichlet_der_fem_name
    = PARAM.string_value("DIRICHLET_DER_FEM_TYPE", "");  // for the dirichlet condition on the derivative
  if (dirichlet_der_fem_name.size() == 0)
    mf_mult_d.set_finite_element(mesh.convex_index(), pf_u);
  else {
    cout << "DIRICHLET_DER_FEM_TYPE="  << dirichlet_der_fem_name << "\n";
    mf_mult_d.set_finite_element(mesh.convex_index(), 
			     getfem::fem_descriptor(dirichlet_der_fem_name));
  }

  /* 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 = PARAM.string_value("DATA_FEM_TYPE");
  if (data_fem_name.size() == 0) {
    GMM_ASSERT1(pf_u->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(mesh.convex_index(), pf_u);
  } else {
    mf_rhs.set_finite_element(mesh.convex_index(), 
			      getfem::fem_descriptor(data_fem_name));
  }

  // set the mesh_fem for the multipliers for the case the Integral Matching  
  mf_pre_mortar.set_finite_element(mesh.convex_index(),
             getfem::fem_descriptor(PARAM.string_value("MORTAR_FEM_TYPE")));
  mf_pre_mortar_deriv.set_finite_element(mesh.convex_index(),
             getfem::fem_descriptor(PARAM.string_value("MORTAR_DERIV_FEM_TYPE")));

  /* set boundary conditions: non-homogeneous Dirichlet */
  cout << "Selecting Neumann and Dirichlet boundaries\n";
  getfem::mesh_region border_faces;
  getfem::outer_faces_of_mesh(mesh, border_faces);
  if (sol_ref == 0 && angle_rot == 0.){
     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 ( gmm::abs(un[0]) >= 0.9999 ) { // vertical edges
	   mesh.region(SIMPLE_SUPPORT_BOUNDARY_NUM).add(i.cv(), i.f());
           mesh.region(CLAMPED_BOUNDARY_NUM).add(i.cv(), i.f()); 
	}
	else{ // horizontal edges
	   unsigned id_point_1_of_face, id_point_2_of_face ;
	   scalar_type x1, x2 ;
	   id_point_1_of_face = mesh.structure_of_convex(i.cv())->ind_points_of_face(i.f())[0] ;
	   id_point_2_of_face = mesh.structure_of_convex(i.cv())->ind_points_of_face(i.f())[1] ;
           x1 = mesh.points_of_convex(i.cv())[id_point_1_of_face][1] ;
           x2 = mesh.points_of_convex(i.cv())[id_point_2_of_face][1] ;
           if ( gmm::abs(x1) > 0.4999 && gmm::abs(x2) > 0.4999 ) {// on the boundary => clamped
	     mesh.region(SIMPLE_SUPPORT_BOUNDARY_NUM).add(i.cv(), i.f());
             mesh.region(CLAMPED_BOUNDARY_NUM).add(i.cv(), i.f());
	   }
	   else { // on the crack => free boundary condition
	   mesh.region(MOMENTUM_BOUNDARY_NUM).add(i.cv(), i.f());
	   mesh.region(FORCE_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 == 0 && angle_rot != 0. ){ 
    // does not support the case of a conformal mesh around the crack 
    // (all the boundary, including the crack faces, will be clamped).
     for (getfem::mr_visitor i(border_faces); !i.finished(); ++i) {
	   mesh.region(CLAMPED_BOUNDARY_NUM).add(i.cv(), i.f());
	   mesh.region(SIMPLE_SUPPORT_BOUNDARY_NUM).add(i.cv(), i.f());
	}
     }

  exact_sol.init(ls);
  
  cout << "initialisation de la level-set : \n" ;
  
  // 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){ // to modify if rotation is supported
     ls.values(0)[d] = y ;
     ls.values(1)[d] = x ; //x * x - a * a ;
    }
  }
  //ls.simplify(0.5);
  ls.touch();  
  mls.adapt();
  mim.adapt();
  mfls_u.adapt();
//   mfls_mult.adapt();
//   mfls_mult_d.adapt();
  mfls_mortar.adapt();
  mfls_mortar_deriv.adapt();
  cout << "mfls_u.nb_dof()=" << mfls_u.nb_dof() << "\n";
}

// /* compute the relative error with respect to the exact solution */
// void bilaplacian_crack_problem::compute_error(plain_vector &U) {
//   GMM_ASSERT1(!mf_rhs.is_reduced(), "To be adapted");
//   std::vector<scalar_type> V(mf_rhs.nb_dof());
//   getfem::interpolation(mf_u(), mf_rhs, U, V);
//   for (size_type i = 0; i < mf_rhs.nb_dof(); ++i)
//     V[i] -= sol_u(mf_rhs.point_of_basic_dof(i));
//   cout.precision(16);
//   cout  << "L2 error = " << getfem::asm_L2_norm(mim, mf_rhs, V)  << endl
//         << "H1 error = " << getfem::asm_H1_norm(mim, mf_rhs, V)  << endl
//         << "H2 error = " << getfem::asm_H2_norm(mim, mf_rhs, V)  << endl
//         /*<< "Linfty error = " << gmm::vect_norminf(V)  << endl*/; 
//   cout  << "semi-norme H1 = " << getfem::asm_H1_semi_norm(mim, mf_rhs, V)  << endl 
//         << "semi-norme H2 = " << getfem::asm_H2_semi_norm(mim, mf_rhs, V)  << endl ;
//        
// }

/* compute the error with respect to the exact solution */
void bilaplacian_crack_problem::compute_error(plain_vector &U) {

  if (PARAM.real_value("RADIUS_SPLIT_DOMAIN") == 0){
     plain_vector V(gmm::vect_size(U)) ;
     gmm::clear(V) ;

    cout << "L2 ERROR:"
       << getfem::asm_L2_dist(mim, mf_u(), U,
			      exact_sol.mf, exact_sol.U) << "\n";
    cout << "H1 ERROR:"
         << getfem::asm_H1_dist(mim, mf_u(), U,
  	  		      exact_sol.mf, exact_sol.U) << "\n";
    cout << "H2 ERROR:"
         << getfem::asm_H2_dist(mim, mf_u(), U, 
                              exact_sol.mf, exact_sol.U) << "\n"; 
//     cout << "SEMI H1 ERROR:"
//          << getfem::asm_H1_semi_dist(mim, mf_u(), U, 
//                               exact_sol.mf, exact_sol.U) << "\n"; 
//     cout << "SEMI H2 ERROR:"
//          << getfem::asm_H2_semi_dist(mim, mf_u(), U, 
//                               exact_sol.mf, exact_sol.U) << "\n"; 
    if ( PARAM.int_value("NORM_EXACT") ){
    cout << "L2 exact:"
         << getfem::asm_L2_dist(mim, mf_u(), V,
			      exact_sol.mf, exact_sol.U) << "\n";
    cout << "H1 exact:"
         << getfem::asm_H1_dist(mim, mf_u(), V,
			      exact_sol.mf, exact_sol.U) << "\n";
    cout << "H2 exact:"
         << getfem::asm_H2_dist(mim, mf_u(), V, 
                              exact_sol.mf, exact_sol.U) << "\n";
    } 
  }
  else {
  getfem::mesh_region r_center, r_ext ;
  scalar_type radius_split_domain = PARAM.real_value("RADIUS_SPLIT_DOMAIN") ;
  bool in_area ;
  for (dal::bv_visitor cv(mesh.convex_index()) ; !cv.finished() ; ++cv){
	in_area = true;
	/* For each element, we test all of its nodes. 
	   If all the nodes are inside the enrichment area,
	   then the element is completly inside the area too */ 
	for (unsigned j=0; j < mesh.nb_points_of_convex(cv); ++j) {
	  if (gmm::sqr(mesh.points_of_convex(cv)[j][0] ) + 
	      gmm::sqr(mesh.points_of_convex(cv)[j][1] ) > 
	      gmm::sqr(radius_split_domain)) 
	    in_area = false; break; 
	}
	  if (in_area) r_center.add(cv) ;
	  else r_ext.add(cv) ;
  }
  scalar_type L2_center, H1_center, H2_center;
  cout << "ERROR SPLITTED - RADIUS =  " << radius_split_domain << "\n";
  cout << "Error on the crack tip zone:\n" ;
        L2_center = getfem::asm_L2_dist(mim, mf_u(), U, exact_sol.mf, exact_sol.U, r_center) ;
  cout << "  L2 error:" << L2_center << "\n";
	H1_center = getfem::asm_H1_dist(mim, mf_u(), U, exact_sol.mf, exact_sol.U, r_center) ;
  cout << "  H1 error:" << H1_center << "\n";
	H2_center = getfem::asm_H2_dist(mim, mf_u(), U, exact_sol.mf, exact_sol.U, r_center) ;
  cout << "  H2 error:" << H2_center << "\n";
 	
  cout << "Error on the remaining part of the domain:\n"; 
  scalar_type L2_ext, H1_ext, H2_ext;
        L2_ext = getfem::asm_L2_dist(mim, mf_u(), U, exact_sol.mf, exact_sol.U, r_ext) ;
  cout << "  L2 error:" << L2_ext << "\n";
	H1_ext = getfem::asm_H1_dist(mim, mf_u(), U, exact_sol.mf, exact_sol.U, r_ext) ;
  cout << "  H1 error:" << H1_ext << "\n";
	H2_ext = getfem::asm_H2_dist(mim, mf_u(), U, exact_sol.mf, exact_sol.U, r_ext) ;
  cout << "  H2 error:" << H2_ext << "\n";

  cout << "Error on the hole domain:\n";
  cout << "L2 ERROR:"
       << gmm::sqrt( gmm::sqr(L2_center) + gmm::sqr(L2_ext) ) << "\n";

    cout << "H1 ERROR:"
         << gmm::sqrt( gmm::sqr(H1_center) + gmm::sqr(H1_ext) ) << "\n";
    cout << "H2 ERROR:"
         << gmm::sqrt( gmm::sqr(H2_center) + gmm::sqr(H2_ext) ) << "\n";
  }
}




/**************************************************************************/
/*  Model.                                                                */
/**************************************************************************/

bool bilaplacian_crack_problem::solve(plain_vector &U) {
  
  if (enrichment_option == 2 || enrichment_option == 3){
	cout << "setting singularities \n" ;
	if (PARAM.int_value("SING_BASE_TYPE") == 0){
		std::vector<getfem::pglobal_function> ufunc(4);
		for (size_type i = 0 ; i < ufunc.size() ; ++i) {
			ufunc[i] = bilaplacian_crack_singular(i, ls, nu, 0.);
		}
	mf_sing_u.set_functions(ufunc);
	}
	if (PARAM.int_value("SING_BASE_TYPE") == 1){
		std::vector<getfem::pglobal_function> ufunc(2);
		for (size_type i = 0 ; i < ufunc.size()  ; ++i) {
			ufunc[i] = bilaplacian_crack_singular(i + 4, ls, nu, 0.);
		}
	mf_sing_u.set_functions(ufunc);
	}
  }
  else if (enrichment_option == 4){
	cout << "Setting up the singular functions for the cutoff enrichment\n";
	if (PARAM.int_value("SING_BASE_TYPE") == 0){
		std::vector<getfem::pglobal_function> vfunc(4);
		for (size_type i = 0; i < vfunc.size(); ++i) {
		/* use the singularity */
		getfem::abstract_xy_function *s = 
		new crack_singular_bilaplacian_xy_function(i, ls, nu, 0.);
		/* use the product of the singularity function
			with a cutoff */
		getfem::abstract_xy_function *cc = 
			new getfem::cutoff_xy_function(int(cutoff.fun_num),
						cutoff.radius, 
						cutoff.radius1,
						cutoff.radius0);
		s = new getfem::product_of_xy_functions(*s, *cc);
		vfunc[i] = getfem::global_function_on_level_set(ls, *s);
		}
	mf_sing_u.set_functions(vfunc);
	}
	if (PARAM.int_value("SING_BASE_TYPE") == 1){
		std::vector<getfem::pglobal_function> vfunc(2);
		for (size_type i = 0; i < vfunc.size(); ++i) {
		/* use the singularity */
		getfem::abstract_xy_function *s = 
		new crack_singular_bilaplacian_xy_function(i+4, ls, nu, 0.);
		/* use the product of the singularity function
			with a cutoff */
		getfem::abstract_xy_function *cc = 
			new getfem::cutoff_xy_function(int(cutoff.fun_num),
						cutoff.radius, 
						cutoff.radius1,
						cutoff.radius0);
		s = new getfem::product_of_xy_functions(*s, *cc);
		vfunc[i] = getfem::global_function_on_level_set(ls, *s);
		}
	mf_sing_u.set_functions(vfunc);
	}
  }
  // Setting the enrichment --------------------------------------------/
   
  switch(enrichment_option) {
  case -1: // classical FEM
    {
    mf_u_sum.set_mesh_fems(mf_pre_u);
    }
    break ;
  case 0 :  // No enrichment
    {
    mf_u_sum.set_mesh_fems(mfls_u);
      // an optional treatment : exporting a representation of the mesh
      if (!PARAM.int_value("MIXED_ELEMENTS")){
      getfem::mesh_fem mf_enrich(mesh);
      getfem::pfem pf_mef = getfem::classical_fem(mesh.trans_of_convex(mesh.convex_index().first_true()), 1 );
      mf_enrich.set_finite_element(mesh.convex_index(), pf_mef) ;
      std::vector<scalar_type> UU(mf_enrich.nb_dof()) ;
      std::fill(UU.begin(), UU.end() ,0.) ;
      cout << "exporting mesh to " << "mesh_representation.vtk" << "..\n";
      getfem::vtk_export exp("mesh_representation.vtk", false);
      exp.exporting(mf_enrich); 
      exp.write_point_data(mf_enrich, UU, "mesh");
      cout << "export done, you can view the data file with (for example)\n"
	"mayavi -d mesh_representation.vtk -f "
	"WarpScalar -m BandedSurfaceMap -m Outline\n";}
    }
    break ;
  case 1 : 
    {
      cout << "\npointwise matching\n";
     /* first : selecting the convexes that are completly included in the enrichment area */ 
     for (dal::bv_visitor i(mesh.convex_index()); !i.finished(); ++i) {
       pm_convexes.add(i) ;
	/* For each element, we test all of its nodes. 
	   If all the nodes are inside the enrichment area,
	   then the element is completly inside the area too */ 
       for (unsigned j=0; j < mesh.nb_points_of_convex(i); ++j) {
	 if (gmm::sqr(mesh.points_of_convex(i)[j][0]) + 
	     gmm::sqr(mesh.points_of_convex(i)[j][1]) > 
	     gmm::sqr(enr_area_radius)) 
	   pm_convexes.sup(i); break;
       }
     }
      
      for (dal::bv_visitor cv(mf_sing_u.convex_index()); !cv.finished(); ++cv) {
	if (!pm_convexes.is_in(cv))
	  mf_sing_u.set_finite_element(cv, 0);
      }
      cout << "mf_sing_u: convex_index() = " << mf_sing_u.convex_index().card() << " convexes\n";

      //mf_u_sum.set_mesh_fems(mfls_u_ext, mf_pre_u); //_ext, mf_sing_u);
      mf_u_sum.set_smart_global_dof_linking(true);
      mf_u_sum.set_mesh_fems(mf_pre_u, mf_sing_u);


      cout << "mf_u_sum.nb_dof = " << mf_u_sum.nb_dof() << "\n";
      cout << "mfls_u.convex_index = " << mfls_u.convex_index() << "\nmf_sing_u: " << mf_sing_u.convex_index() << "\n";
      
    } break ;
  case 2 :  // standard XFEM on a fixed zone
    {
      dal::bit_vector enriched_dofs;
      plain_vector X(mf_partition_of_unity.nb_dof());
      plain_vector Y(mf_partition_of_unity.nb_dof());
      getfem::interpolation(ls.get_mesh_fem(), mf_partition_of_unity,
			    ls.values(1), X);    
      getfem::interpolation(ls.get_mesh_fem(), mf_partition_of_unity,
			    ls.values(0), Y);    
      for (size_type j = 0; j < mf_partition_of_unity.nb_dof(); ++j) {
            if (gmm::sqr(X[j]) + gmm::sqr(Y[j]) <= gmm::sqr(enr_area_radius))
	           enriched_dofs.add(j);
          }
      //cout << "enriched_dofs: " << enriched_dofs << "\n";
      if (enriched_dofs.card() < 3)
            GMM_WARNING0("There is " << enriched_dofs.card() <<
		   " enriched dofs for the crack tip");
      mf_u_product.set_enrichment(enriched_dofs);
      mf_u_sum.set_mesh_fems(mf_u_product, mfls_u);
      cout << "enrichment done \n" ;}
      break ;
  case 4 :{
      if(cutoff.fun_num == getfem::cutoff_xy_function::EXPONENTIAL_CUTOFF)
	cout<<"Using exponential Cutoff..."<<endl;
      else
	cout<<"Using Polynomial Cutoff..."<<endl;
//       dal::bit_vector enriched_dofs;
//       enriched_dofs.clear() ;
//       cout << "mf_cutoff.nb_dof() = " << mf_cutoff.nb_dof() << "\n" ;
//       
//       for (size_type j = 0; j < mf_cutoff.nb_dof(); ++j) {
//             enriched_dofs.add(j);
//           }
//       for (dal::bv_visitor j(enriched_dofs) ; !j.finished() ; ++j){
//           cout << j << " ; " ; 
//       }
//       cout << "\n" ;
//       cout << "mf_prod_cutoff.nb_dof() = " << mf_prod_cutoff.nb_dof() << "\n" ;
//       cout << "mf_sing_u.nb_dof() = " << mf_sing_u.nb_dof() << "\n" ;
//       mf_prod_cutoff.set_enrichment(enriched_dofs) ;
//       
//       cout << "mf_prod_cutoff.nb_dof() = " << mf_prod_cutoff.nb_dof() << "\n" ;
      
      //mf_u_sum.set_mesh_fems(mf_prod_cutoff, mfls_u);
      mf_u_sum.set_mesh_fems(mf_sing_u, mfls_u);
    } break;
  case 3 : // Integral matching (mortar)
    {
    cout << "\nIntegral Matching (Mortar)\n" ;    
    dal::bit_vector cvlist_in_area, cvlist_out_area;
    bool in_area = true;
    for (dal::bv_visitor cv(mesh.convex_index()); 
	   !cv.finished(); ++cv) {
	in_area = true;
	/* For each element, we test all of its nodes. 
	   If all the nodes are inside the enrichment area,
	   then the element is completly inside the area too */ 
	for (unsigned j=0; j < mesh.nb_points_of_convex(cv); ++j) {
// 	  if (gmm::sqr(mesh.points_of_convex(cv)[j][0] ) + 
// 	      gmm::sqr(mesh.points_of_convex(cv)[j][1] ) > 
// 	      gmm::sqr(enr_area_radius)) {
          if ( (gmm::abs(mesh.points_of_convex(cv)[j][0] ) > enr_area_radius) || 
               (gmm::abs(mesh.points_of_convex(cv)[j][1] ) > enr_area_radius)) {
	          in_area = false; 
		  break;
	  }
	}

	/* "remove" the global function on convexes outside the enrichment
	   area */
	if (!in_area) {
	  cvlist_out_area.add(cv);
	  mf_sing_u.set_finite_element(cv, 0);
	  mf_u().set_dof_partition(cv, 1);
	} else cvlist_in_area.add(cv);
      }
       
       /* extract the boundary of the enrichment area, from the
	 "inside" point-of-view, and from the "outside"
	 point-of-view */
      getfem::mesh_region r_border, r_enr_out;
      getfem::outer_faces_of_mesh(mesh, r_border);

      getfem::outer_faces_of_mesh(mesh, cvlist_in_area, 
				  mesh.region(MORTAR_BOUNDARY_IN));
      getfem::outer_faces_of_mesh(mesh, cvlist_out_area, 
				  mesh.region(MORTAR_BOUNDARY_OUT));
      for (getfem::mr_visitor v(r_border); !v.finished(); ++v) {
	mesh.region(MORTAR_BOUNDARY_OUT).sup(v.cv(), v.f());
      }
      if (PARAM.int_value("MORTAR_WITHOUT_SINGUL"))
         mf_u_sum.set_mesh_fems(mfls_u);
      else
         mf_u_sum.set_mesh_fems(mf_sing_u, mfls_u);
      
      //cout << "cvlist_in_area: " << cvlist_in_area << "\n";
      cout << "mfls_u.nb_dof: " << mfls_u.nb_dof() << "\n";
      cout << "mf_u_sum.nb_dof: " << mf_u_sum.nb_dof() << "\n";
      //cout << "MORTAR_BOUNDARY_IN: " << mesh.region(MORTAR_BOUNDARY_IN) << "\n";
      //cout << "MORTAR_BOUNDARY_OUT: " << mesh.region(MORTAR_BOUNDARY_OUT) << "\n";
      
      // an optional treatment : creating a representation of the enrichment area     
      getfem::mesh_fem mf_enrich(mesh);
      for (dal::bv_visitor i(mesh.convex_index()) ; !i.finished() ; ++i){
          getfem::pfem pf_mef = getfem::classical_fem(mesh.trans_of_convex(i), 1 );
          mf_enrich.set_finite_element(i, pf_mef) ;
      }
      std::vector<scalar_type> UU(mf_enrich.nb_dof()) ;
      std::fill(UU.begin(), UU.end() ,0.) ;
      cout << "exporting the enrichment zone: \n" ;
      GMM_ASSERT1(!mf_enrich.is_reduced(), "To be adapted");
      for (dal::bv_visitor i(cvlist_in_area) ; !i.finished() ; ++i){ 
	  for (unsigned int j = 0 ;
	       j < mf_enrich.ind_basic_dof_of_element(i).size() ; ++j )  
	  UU[mf_enrich.ind_basic_dof_of_element(i)[j]] = 1. ;         
      }
      
      cout << "exporting enrichment to " << "enrichment_zone.vtk" << "..\n";
      getfem::vtk_export exp("enrichment_zone.vtk", false);
      exp.exporting(mf_enrich); 
      exp.write_point_data(mf_enrich, UU, "enrichment");
      cout << "export done, you can view the data file with (for example)\n"
	"mayavi -d enrichment_zone.vtk -f "
	"WarpScalar -m BandedSurfaceMap -m Outline\n";
	
//       // Another optional treatment :
//       // Searching the elements that are both crossed by the crack
//       // and with one of their faces which constitutes a part of the 
//       // boundary between the enriched zone and the rest of the domain.
//       getfem::mesh_region &boundary = mesh.region(MORTAR_BOUNDARY_IN);
//       unsigned int cpt = 0 ;
//       for (dal::bv_visitor i(cvlist_in_area); !i.finished(); ++i) {
//          if (mls.is_convex_cut(i)){
// 	    // Among the faces of the convex, we search if some are
// 	    // part of the boundary
// 	    cpt = 0 ;
// 	    for (unsigned j=0; j < mesh.structure_of_convex(i) ->nb_faces(); ++j) {
// 	        if (boundary.is_in(i,j))
// 		   cpt += 1;
// 	    }
// 	    if (cpt) {
//                cout << "\n The convex number " << i << " is crossed by the crack :\n" ;
// 	       cout << "  it has : " << cpt << " face(s) among the boundary.\n \n " ;
// 	    }
// 	 }
//       }

    }  // end of "enrichment_option = 3"
    break ;
  default : 
	GMM_ASSERT1(false, "Enrichment_option parameter is undefined");
	break ;  
	}  // end of switch

  mesh.write_to_file("toto.mesh");

  if (PARAM.int_value("SHOW_NAME_OF_DOF")==1) {  // printing the type of each dof
    unsigned Q = mf_u().get_qdim();
    GMM_ASSERT1(!mf_u().is_reduced(), "To be adapted");  
    for (unsigned d=0; d < mf_u().nb_dof(); d += Q) {
      printf("dof %4d @ %+6.2f:%+6.2f: ", d, 
             mf_u().point_of_basic_dof(d)[0], mf_u().point_of_basic_dof(d)[1]);

      const getfem::mesh::ind_cv_ct cvs = mf_u().convex_to_basic_dof(d);
      for (size_type i=0; i < cvs.size(); ++i) {
        unsigned cv = cvs[i];
        //if (pm_cvlist.is_in(cv)) flag1 = true; else flag2 = true;

        getfem::pfem pf = mf_u().fem_of_element(cv);
        unsigned ld = unsigned(-1);
        for (unsigned dd = 0; dd < mf_u().nb_basic_dof_of_element(cv); dd += Q) {
          if (mf_u().ind_basic_dof_of_element(cv)[dd] == d) {
            ld = dd/Q; break;
          }
        }
        if (ld == unsigned(-1)) {
          cout << "DOF " << d << "NOT FOUND in " << cv << " BUG BUG\n";
        } else {
          printf(" %3d:%.16s", unsigned(cv), name_of_dof_2(pf->dof_types().at(ld)).c_str());
        }
      }
      printf("\n");
    }
  }

  //cout << "validate mf_sing_u():\n"; validate_fem_derivatives(mf_sing_u);

  //cout << "validate mf_u():\n"; validate_fem_derivatives(mf_u());

  cout << "Number of dof for u --> : " << mf_u().nb_dof() << endl;
  scalar_type pressure ;
  pressure = PARAM.real_value("PRESSURE") ;
  // Bilaplacian brick.
  getfem::mdbrick_bilaplacian<> BIL(mim, mf_u());
  BIL.D().set(D);
  if (KL) { BIL.set_to_KL(); BIL.nu().set(nu); }

  // Defining the volumic source term.
  size_type nb_dof_rhs = mf_rhs.nb_dof();
  plain_vector F(nb_dof_rhs);
  gmm::clear(F) ;
  if (sol_ref == 2){
     getfem::interpolation_function(mf_rhs, F, sol_F);
     gmm::scale(F, pressure) ;
     }
  //Volumic source term brick.
  getfem::mdbrick_source_term<> VOL_F(BIL, mf_rhs, F);
  
  // Defining the moment condition (for free edge condition)
  size_type N ; N = mesh.dim() ;
  gmm::resize(F, nb_dof_rhs*N*N);
  gmm::clear(F) ;
  getfem::mdbrick_normal_derivative_source_term<>
     MOMENTUM(VOL_F, mf_rhs, F, MOMENTUM_BOUNDARY_NUM);
  // Defining the imposed force condtion (also for free edge condition)
  plain_vector tensor_H(nb_dof_rhs*N*N);
  gmm::resize(F, nb_dof_rhs*N);
  
  getfem::mdbrick_abstract<> *NEUMANN;
  NEUMANN = new getfem::mdbrick_neumann_KL_term<>
       (MOMENTUM, mf_rhs, tensor_H, F, FORCE_BOUNDARY_NUM);
  
  // Defining the normal derivative Dirichlet condition value.

  /* WRONG !! 

    F.resize(nb_dof_rhs*N);
  getfem::interpolation_function(mf_rhs, F, sol_du, CLAMPED_BOUNDARY_NUM);

  // Normal derivative Dirichlet condition brick. 
  getfem::mdbrick_normal_derivative_Dirichlet<>                   
    NDER_DIRICHLET(BIL, CLAMPED_BOUNDARY_NUM, mf_mult);

  NDER_DIRICHLET.set_constraints_type(dirichlet_version);
  NDER_DIRICHLET.rhs().set(mf_rhs, F);
  */

  //gmm::resize(U, mf_u().nb_dof());  return true;


  // Normal derivative Dirichlet condition brick. 
  getfem::mdbrick_normal_derivative_Dirichlet<>                   
    NDER_DIRICHLET(*NEUMANN, CLAMPED_BOUNDARY_NUM, mf_mult_d);
/*  if (sol_ref == 2) 
    std::fill(exact_sol.U.begin(), exact_sol.U.end(), 0.) ;*/   
    
  NDER_DIRICHLET.set_constraints_type(dirichlet_version);
  NDER_DIRICHLET.R_must_be_derivated(); // hence we give the exact solution , and its gradient will be taken
  NDER_DIRICHLET.rhs().set(exact_sol.mf,exact_sol.U);
  if (dirichlet_version == getfem::PENALIZED_CONSTRAINTS)
    NDER_DIRICHLET.set_penalization_parameter(PARAM.real_value("EPS_DIRICHLET_PENAL")) ;
  

//   // Defining the normal derivative Dirichlet condition value.
//   gmm::resize(F, nb_dof_rhs*2);
//   gmm::clear(F);
//   getfem::interpolation_function(mf_rhs, F, sol_du, CLAMPED_BOUNDARY_NUM);
// 
//   // Normal derivative Dirichlet condition brick
//   getfem::mdbrick_normal_derivative_Dirichlet<> 
// 	NDER_DIRICHLET(VOL_F, CLAMPED_BOUNDARY_NUM, mf_mult);
// 
//     
//   NDER_DIRICHLET.set_constraints_type(dirichlet_version);
//   NDER_DIRICHLET.rhs().set(mf_rhs, F);

  // Defining the Dirichlet condition value.
  gmm::resize(F, nb_dof_rhs);
  
  // Dirichlet condition brick.

  getfem::mdbrick_Dirichlet<>
    DIRICHLET_0(NDER_DIRICHLET, SIMPLE_SUPPORT_BOUNDARY_NUM, mf_mult); //mfls_mult
  DIRICHLET_0.rhs().set(exact_sol.mf,exact_sol.U);
  DIRICHLET_0.set_constraints_type(getfem::constraints_type(dirichlet_version));  
  if (dirichlet_version == getfem::PENALIZED_CONSTRAINTS)
    DIRICHLET_0.set_penalization_parameter(PARAM.real_value("EPS_DIRICHLET_PENAL")) ;  
  
  getfem::mdbrick_Dirichlet<>
    DIRICHLET_2(NDER_DIRICHLET, SIMPLE_SUPPORT_BOUNDARY_NUM, mf_mult); 
  //DIRICHLET_2.rhs().set(exact_sol.mf,exact_sol.U);  
  DIRICHLET_2.set_constraints_type(getfem::constraints_type(dirichlet_version));  
  if (dirichlet_version == getfem::PENALIZED_CONSTRAINTS)
    DIRICHLET_2.set_penalization_parameter(PARAM.real_value("EPS_DIRICHLET_PENAL")) ;  
  
  getfem::mdbrick_abstract<> *DIRICHLET = &DIRICHLET_0 ;  
  if (sol_ref == 2)
     DIRICHLET = &DIRICHLET_2 ;   

  
  //getfem::interpolation_function(mf_rhs, F, sol_u, SIMPLE_SUPPORT_BOUNDARY_NUM);
  //DIRICHLET.rhs().set(mf_rhs, F) ;  -> wrong (near the crack).

  getfem::mdbrick_abstract<> *final_model = DIRICHLET ;
  
  sparse_matrix H(1, mf_u().nb_dof());
  if (enrichment_option == 3 ) {
     /* add a constraint brick for the mortar junction between
       the enriched area and the rest of the mesh */

    getfem::mdbrick_constraint<> &mortar = 
      *(new getfem::mdbrick_constraint<>(*DIRICHLET, 0));
    mortar.set_constraints_type(getfem::constraints_type(mortar_version));
    if (mortar_version == getfem::PENALIZED_CONSTRAINTS)
      mortar.set_penalization_parameter(PARAM.real_value("EPS_MORTAR_PENAL")) ;

    // calcul des matrices de contraintes
    plain_vector R(1) ;
//     sparse_matrix H(1, mf_u().nb_dof());
    (*this).set_matrix_mortar(H) ;

    /* because of the discontinuous partition of mf_u(), some levelset 
       enriched functions do not contribute any more to the
       mass-matrix (the ones which are null on one side of the
       levelset, when split in two by the mortar partition, may create
       a "null" dof whose base function is all zero.. */
    sparse_matrix M2(mf_u().nb_dof(), mf_u().nb_dof());
    getfem::asm_mass_matrix(M2, mim, mf_u(), mf_u());
    //gmm::HarwellBoeing_IO::write("M2.hb", M2);
    cout << "PARAM.real_value(\"SEUIL\") : " << PARAM.real_value("SEUIL") << "\n" ;
    for (size_type d = 0; d < mf_u().nb_dof(); ++d) {
      //       if (M2(d,d) < 1e-7) cout << "  weak mf_u() dof " << d << " @ " << 
      // 	  mf_u().point_of_dof(d) << " M2(d,d) = " << M2(d,d) << "\n";
      if (M2(d,d) < PARAM.real_value("SEUIL")) {
	cout << "removed : " << mf_u().point_of_basic_dof(d) << "\n";	
	unsigned n = gmm::mat_nrows(H);
	gmm::resize(H, n+1, gmm::mat_ncols(H));
	H(n, d) = 1;
      }
    }  
    gmm::resize(R, gmm::mat_nrows(H)); 
    mortar.set_constraints(H,R);
    final_model = &mortar;
    gmm::Harwell_Boeing_save("H.hb", H);

  }

  if ( PARAM.real_value("SEUIL_FINAL")!=0 ) { 
  // suppression of nodes with a very small term on the stiffness matrix diag
    getfem::mdbrick_constraint<> &extra = *(new getfem::mdbrick_constraint<>(*DIRICHLET, 0));
    extra.set_constraints_type(getfem::constraints_type(dirichlet_version));  
    if (dirichlet_version == getfem::PENALIZED_CONSTRAINTS)
      extra.set_penalization_parameter(PARAM.real_value("EPS_DIRICHLET_PENAL"));

    sparse_matrix M2(mf_u().nb_dof(), mf_u().nb_dof());
    sparse_matrix H1(0, mf_u().nb_dof());
    //getfem::asm_mass_matrix(M2, mim, mf_u(), mf_u());
    base_vector RR(mf_rhs.nb_dof(), 1.0);
    getfem::asm_stiffness_matrix_for_bilaplacian(M2, mim, mf_u(), 
                                                 mf_rhs, RR);
						 
    //cout << "stiffness_matrix_for_bilaplacian : " << M2 << "\n" ;
    cout << "termes diagonaux, de la matrice de rigidite, inferieurs a 1e-10 : " ;
    for (size_type d = 0; d < mf_u().nb_dof(); ++d) {
        if (M2(d,d) < 1e-10) cout << M2(d,d) << " ; " ;
    }  
    cout << "\n" ;
    cout << "SEUIL_FINAL = " << PARAM.real_value("SEUIL_FINAL") << "\n" ;
    for (size_type d = 0; d < mf_u().nb_dof(); ++d) {
      if (M2(d,d) < PARAM.real_value("SEUIL_FINAL")) {
	cout << "OULALA " << d << " @ " << mf_u().point_of_basic_dof(d) << " : " << M2(d,d) << "\n";	
        unsigned n = gmm::mat_nrows(H);
	gmm::resize(H1, n+1, gmm::mat_ncols(H));
	H1(n, d) = 1;
      }
    }
    base_vector R(gmm::mat_nrows(H1)); 
    extra.set_constraints(H1,R);
    final_model = &extra;
    gmm::Harwell_Boeing_save("M2.hb", M2);
  }

  
  cout << "Total number of variables : " << final_model->nb_dof() << endl;
  getfem::standard_model_state MS(*final_model);
  gmm::iteration iter(residual, 1, 40000);


  // getfem::useful_types<getfem::standard_model_state>::plsolver_type p;
  // p.reset(new getfem::linear_solver_cg_preconditioned_ildlt<sparse_matrix,plain_vector>);

  getfem::standard_solve(MS, *final_model, iter /* , p*/);
  
  // Solution extraction
  gmm::resize(U, mf_u().nb_dof());
  gmm::copy(BIL.get_solution(MS), U);

  /****************************/
  if (PARAM.int_value("FIC_ORTHO") ) {

	sparse_matrix A = MS.reduced_tangent_matrix() ;
	plain_vector b = MS.residual() ;
	gmm::scale(b, -1.) ;
	plain_vector X(b) ;
	scalar_type condest ;
	SuperLU_solve(A, X, b, condest, 1) ;
	cout << "cond super LU = " << 1./condest << "\n" ;
	cout << "X = " << gmm::sub_vector(X, gmm::sub_interval(0, 10)) << "\n" ;
	cout << "U = " << gmm::sub_vector(U, gmm::sub_interval(0, 10)) << "\n" ;
	
	
	unsigned q = mf_u().get_qdim();
	
	base_small_vector tab_fic(4);
	std::vector<size_type> ind_sing(2) ;
	unsigned cpt = 0;
	if (PARAM.int_value("ENRICHMENT_OPTION") == 3){
	// affichage des coeffs devant les singularites, avec le raccord integral
	  GMM_ASSERT1(!mf_u().is_reduced(), "To be adapted");
	  
	  for (unsigned d=0; d < mf_u().nb_dof(); d += q) {
	    unsigned cv = mf_u().first_convex_of_basic_dof(d) ;
	    getfem::pfem pf = mf_u().fem_of_element(cv);
	    unsigned ld = unsigned(-1);
	    for (unsigned dd = 0; dd < mf_u().nb_basic_dof_of_element(cv); dd += q) {
	      if (mf_u().ind_basic_dof_of_element(cv)[dd] == d) {
		ld = dd/q; break;
	      }
	    }   
	    if (ld == unsigned(-1)) {
	      cout << "DOF " << d << "NOT FOUND in " << cv << " BUG BUG\n";
	    } 
	    else {
	      if ( is_global_dof_type_bis(pf->dof_types().at(ld)) ){
		cout << "coeff:" << U[d] << "\n" ;
		cout << "dof index:" << d << "\n" ;
		tab_fic[cpt] = U[d] ;
		ind_sing[cpt] = d ;
		cpt +=1 ;
	      }
	    }
	  }
	}
	
	
	plain_vector b1(gmm::mat_nrows(A)), b2(b1), X1(b1), X2(b1) ;
	
	scalar_type as1, as2, as1s2, bs1, bs2 ;
	as1 = A(ind_sing[0], ind_sing[0])  ;
	scalar_type max = 0. ;
	size_type imax = 0 ;
	for (unsigned i = 0 ;  i < gmm::mat_nrows(A) ; i++){
	if (gmm::abs(A(ind_sing[0],i)) > max && i!= ind_sing[0] && i!= ind_sing[1]){
		max = gmm::abs(A(ind_sing[0],i)) ;
		imax = i ;
	}
	}
	cout << "imax = " << imax << endl ;
	cout << "max = " << max << endl ;
	if (imax < mf_u().nb_dof())
	cout << "position de imax = [" << mf_u().point_of_basic_dof(imax)[0] << " ; " << mf_u().point_of_basic_dof(imax)[1] << "\n" ;
	as2 = A(ind_sing[1], ind_sing[1])  ;
	as1s2 = A(ind_sing[0], ind_sing[1])  ;
	bs1 = b[ind_sing[0]] ;
	bs2 = b[ind_sing[1]] ;
	gmm::copy(gmm::mat_col(A, ind_sing[0]), b1) ;
	gmm::copy(gmm::mat_col(A, ind_sing[1]), b2) ;
	//cout << "gmm::mat_col(A, ind_sing[0]) = " << b1 << "\n" ;
	//cout << "gmm::mat_col(A, ind_sing[1]) = " << b2 << "\n" ;
	
	for (int i=0 ; i < 2 ; i++){
	for (unsigned j=0 ; j < gmm::mat_nrows(A) ; j++){
		A(ind_sing[i],j) = 0. ;
		A(j,ind_sing[i]) = 0. ;
	}
	A(ind_sing[i], ind_sing[i]) = 1.  ;
	b[ind_sing[i]] = 0. ;
	b1[ind_sing[i]] = 0. ;
	b2[ind_sing[i]] = 0. ;
	}
	
	
	
	SuperLU_solve(A, X1, b1, condest, 1) ;
	cout << "solving for s1 OK, cond = " << 1./condest << "\n" ;
	SuperLU_solve(A, X2, b2, condest, 1) ;
	cout << "solving for s2 OK, cond = " << 1./condest << "\n" ;
	cout << "X1[ind_sing[0]] = " << X1[ind_sing[0]] << "\n" ;
	cout << "X1 = " << gmm::sub_vector(X1, gmm::sub_interval(0, 10)) << "\n" ;
	scalar_type max1 = 0., max2 = 0. ;
	size_type imax1 = 0 , imax2 = 0 ;
	for (unsigned i = 0 ;  i < X1.size() ; i++){
	if (gmm::abs(X1[i]) > max1){
		max1 = gmm::abs(X1[i]) ;
		imax1 = i ;
	}
	if (gmm::abs(X2[i]) > max2){
		max2 = gmm::abs(X2[i]) ;
		imax2 = i ;
	}
	}
	cout << "imax1 = " << imax1 << endl ;
	cout << "max1 = " << max1 << endl ;
	if (imax1 < mf_u().nb_dof())
	cout << "position de imax1 = [" << mf_u().point_of_basic_dof(imax1)[0] << " ; " << mf_u().point_of_basic_dof(imax1)[1] << "\n" ;
	cout << "imax2 = " << imax2 << endl ;
	cout << "max2 = " << max2 << endl ;
	if (imax2 < mf_u().nb_dof())
	cout << "position de imax2 = [" << mf_u().point_of_basic_dof(imax2)[0] << " ; " << mf_u().point_of_basic_dof(imax2)[1] << "\n" ;
	//cout << "X1 = " << gmm::sub_vector(X1, gmm::sub_interval(0, 100)) << "\n" ;
	//cout << "X2 = " << gmm::sub_vector(X2, gmm::sub_interval(0, 100)) << "\n" ;
	base_matrix M(2,2) ;
	plain_vector AX1(gmm::mat_nrows(A)), AX2(AX1) ;
	gmm::mult(A, X1, AX1) ;
	gmm::mult(A, X2, AX2) ;
	M(0,0) = as1 - 2. * gmm::vect_sp(b1, X1) + gmm::vect_sp(X1, AX1) ;
	M(1,1) = as2 - 2. * gmm::vect_sp(b2, X2) + gmm::vect_sp(X2, AX2) ;
	M(0,1) = as1s2 - gmm::vect_sp(b1, X2) - gmm::vect_sp(b2, X1) + gmm::vect_sp(X1, AX2) ;
	M(1,0) = M(0,1) ;
	plain_vector Z(2), FIC_ORTHO(2) ;
	Z[0] = bs1 - gmm::vect_sp(X1, b) ;
	Z[1] = bs2 - gmm::vect_sp(X2, b) ;
	gmm::lu_solve(M, FIC_ORTHO, Z) ;
	
	cout << "[as1 as2 as1s2] = " << as1 << " ; " << as2 << " ; " << as1s2 << "\n" ;
	cout << "[bs1 bs2] = " << bs1 << " ; " << bs2 << "\n" ;
	cout << "M = " << M << "\n" ;
	cout << "Z = " << Z << "\n" ; 
	
	cout << "FIC1 ORTHO = " << FIC_ORTHO[0] << "\n" ;
	cout << "FIC2 ORTHO = " << FIC_ORTHO[1] << "\n" ;
	cout << "-----------------------\n" ;
  }
  /************************************************/
  return true;
}

template<typename VEC1, typename VEC2>
void asm_H2_semi_dist_map(const getfem::mesh_im &mim, 
                          const getfem::mesh_fem &mf1, const VEC1 &U1,
                          const getfem::mesh_fem &mf2, const VEC2 &U2,
                          const getfem::mesh_fem &mf_P0, VEC1 &V,
                          getfem::mesh_region rg = getfem::mesh_region::all_convexes()) {
  mim.linked_mesh().intersect_with_mpi_region(rg);
  getfem::generic_assembly assem;    
  assem.set("u1=data$1(#1); u2=data$2(#2); "
            "V(#3)+=u1(i).u1(j).comp(Hess(#1).Hess(#1).Base(#3))(i,d,e,j,d,e,:)"
            "+ u2(i).u2(j).comp(Hess(#2).Hess(#2).Base(#3))(i,d,e,j,d,e,:)"
            "- 2*u1(i).u2(j).comp(Hess(#1).Hess(#2).Base(#3))(i,d,e,j,d,e,:)");
  
  assem.push_mi(mim);
  assem.push_mf(mf1);
  assem.push_mf(mf2);
  assem.push_mf(mf_P0);
  assem.push_data(U1);
  assem.push_data(U2);
  assem.push_vec(V);
  assem.assembly(rg);
}
  
void bilaplacian_crack_problem::compute_H2_error_field(const plain_vector &U) {

    getfem::mesh_fem mf_P0(mesh);
    mf_P0.set_finite_element(mesh.convex_index(), getfem::classical_fem(mesh.trans_of_convex(0), 0));
    plain_vector V(mf_P0.nb_dof());
    asm_H2_semi_dist_map(mim, mf_u(), U, exact_sol.mf, exact_sol.U, mf_P0, V);
    cout << "exporting H2 error map\n";
    getfem::vtk_export exp2(datafilename + "_H2.vtk");
    exp2.exporting(mf_P0);
    exp2.write_point_data(mf_P0, V, "H2 error map");
    
    mf_P0.write_to_file(datafilename + "_H2.meshfem", true);
    gmm::vecsave(datafilename + "_H2.V", V);
}

