diff --git a/extra_packages/asr-tools/package.cmake b/extra_packages/asr-tools/package.cmake
index 2169b5bdc..95bee1bf1 100644
--- a/extra_packages/asr-tools/package.cmake
+++ b/extra_packages/asr-tools/package.cmake
@@ -1,33 +1,36 @@
 #===============================================================================
 # @file   package.cmake
 #
 # @author Nicolas Richart <nicolas.richart@epfl.ch>
 #
 # @brief  package description for asr stuff
 #
 # @section LICENSE
 #
 # Copyright (©) 2010-2012, 2014 EPFL (Ecole Polytechnique Fédérale de Lausanne)
 # Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
 #
 #===============================================================================
 
 package_declare(asr_tools
   DESCRIPTION "ASR stuffs materials, model FE2, toolboxes"
   DEPENDS extra_materials)
 
 package_declare_sources(asr_tools
   asr_tools.cc
   asr_tools.hh
   material_FE2/material_FE2.hh
   material_FE2/material_FE2.cc
   material_FE2/material_FE2_inline_impl.hh
   solid_mechanics_model_RVE.hh
   solid_mechanics_model_RVE.cc
   mesh_utils/nodes_flag_updater.hh
   mesh_utils/nodes_flag_updater.cc
   mesh_utils/nodes_flag_updater_inline_impl.hh
+  mesh_utils/nodes_eff_stress_updater.hh
+  mesh_utils/nodes_eff_stress_updater.cc
+  mesh_utils/nodes_eff_stress_updater_inline_impl.hh
   mesh_utils/crack_numbers_updater.hh
   mesh_utils/crack_numbers_updater.cc
   mesh_utils/crack_numbers_updater_inline_impl.hh
 )
diff --git a/extra_packages/asr-tools/src/asr_tools.cc b/extra_packages/asr-tools/src/asr_tools.cc
index e3299b60d..3e012d346 100644
--- a/extra_packages/asr-tools/src/asr_tools.cc
+++ b/extra_packages/asr-tools/src/asr_tools.cc
@@ -1,3913 +1,5455 @@
 /**
  * @file   ASR_tools.cc
  * @author Aurelia Cuba Ramos <aurelia.cubaramos@epfl.ch>
  * @author Emil Gallyamov <emil.gallyamov@epfl.ch>
  *
  * @brief  implementation tools for the analysis of ASR samples
  *
  * @section LICENSE
  *
  * Copyright (©) 2010-2011 EPFL (Ecole Polytechnique Fédérale de Lausanne)
  * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
  *
  * Akantu 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.
  *
  * Akantu 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 Akantu. If not, see <http://www.gnu.org/licenses/>.
  **/
 
 #include <boost/config.hpp>
 #include <boost/graph/adjacency_list.hpp>
 #include <boost/graph/connected_components.hpp>
+#include <boost/graph/dijkstra_shortest_paths.hpp>
+#include <boost/graph/graph_traits.hpp>
+#include <boost/graph/graph_utility.hpp>
+#include <boost/property_map/property_map.hpp>
 #include <fstream>
 /* -------------------------------------------------------------------------- */
 #include "aka_voigthelper.hh"
 #include "asr_tools.hh"
 #include "communicator.hh"
 #include "crack_numbers_updater.hh"
 #include "material_FE2.hh"
+#include "material_cohesive_linear_sequential.hh"
 #include "material_damage_iterative_orthotropic.hh"
 #include "material_iterative_stiffness_reduction.hh"
 #include "mesh_utils.hh"
 #include "node_synchronizer.hh"
+#include "nodes_eff_stress_updater.hh"
 #include "nodes_flag_updater.hh"
 #include "non_linear_solver.hh"
 #include "solid_mechanics_model.hh"
 #include "solid_mechanics_model_RVE.hh"
 #include "solid_mechanics_model_cohesive.hh"
 #include <cmath>
 #include <mesh_events.hh>
 
 /* -------------------------------------------------------------------------- */
 
 namespace akantu {
 /* -------------------------------------------------------------------------- */
 ASRTools::ASRTools(SolidMechanicsModel & model)
     : model(model), volume(0.), nb_dumps(0), cohesive_insertion(false),
       doubled_facets_ready(false), doubled_nodes_ready(false),
       disp_stored(0, model.getSpatialDimension()),
       ext_force_stored(0, model.getSpatialDimension()),
       boun_stored(0, model.getSpatialDimension()),
       tensile_homogenization(false), modified_pos(false),
-      partition_border_nodes(false), ASR_nodes(false) {
+      partition_border_nodes(false), nodes_eff_stress(0), ASR_nodes(false) {
 
   // register event handler for asr tools
   auto & mesh = model.getMesh();
   // auto const dim = model.getSpatialDimension();
   mesh.registerEventHandler(*this, _ehp_lowest);
   if (mesh.hasMeshFacets()) {
     mesh.getMeshFacets().registerEventHandler(*this, _ehp_lowest);
   }
 
   /// find four corner nodes of the RVE
   findCornerNodes();
 
   /// initialize the modified_pos array with the initial number of nodes
   auto nb_nodes = mesh.getNbNodes();
   modified_pos.resize(nb_nodes);
   modified_pos.set(false);
   partition_border_nodes.resize(nb_nodes);
   partition_border_nodes.set(false);
+  nodes_eff_stress.resize(nb_nodes);
+  nodes_eff_stress.set(0);
   ASR_nodes.resize(nb_nodes);
   ASR_nodes.set(false);
 }
 /* -------------------------------------------------------------------------- */
 void ASRTools::computePhaseVolumes() {
   /// compute volume of each phase and save it into a map
   for (auto && mat : model.getMaterials()) {
     this->phase_volumes[mat.getName()] = computePhaseVolume(mat.getName());
     auto it = this->phase_volumes.find(mat.getName());
     if (it == this->phase_volumes.end()) {
       this->phase_volumes.erase(mat.getName());
     }
   }
 }
 /* -------------------------------------------------------------------------- */
 void ASRTools::computeModelVolume() {
   auto const dim = model.getSpatialDimension();
   auto & mesh = model.getMesh();
   auto & fem = model.getFEEngine("SolidMechanicsFEEngine");
   GhostType gt = _not_ghost;
   this->volume = 0;
   for (auto element_type : mesh.elementTypes(dim, gt, _ek_regular)) {
     Array<Real> Volume(mesh.getNbElement(element_type) *
                            fem.getNbIntegrationPoints(element_type),
                        1, 1.);
     this->volume += fem.integrate(Volume, element_type);
   }
 
   /// do not communicate if model is multi-scale
   if (not aka::is_of_type<SolidMechanicsModelRVE>(model)) {
     auto && comm = akantu::Communicator::getWorldCommunicator();
     comm.allReduce(this->volume, SynchronizerOperation::_sum);
   }
 }
 /* ------------------------------------------------------------------------- */
 void ASRTools::applyFreeExpansionBC() {
   /// boundary conditions
   const auto & mesh = model.getMesh();
   const Vector<Real> & lowerBounds = mesh.getLowerBounds();
   const Vector<Real> & upperBounds = mesh.getUpperBounds();
   const UInt dim = mesh.getSpatialDimension();
   const Array<Real> & pos = mesh.getNodes();
   Array<Real> & disp = model.getDisplacement();
   Array<bool> & boun = model.getBlockedDOFs();
 
   /// accessing bounds
   Real bottom = lowerBounds(1);
   Real left = lowerBounds(0);
   Real top = upperBounds(1);
   Real eps = std::abs((top - bottom) * 1e-6);
 
   switch (dim) {
   case 2: {
     for (UInt i = 0; i < mesh.getNbNodes(); ++i) {
       if (std::abs(pos(i, 1) - bottom) < eps) {
         boun(i, 1) = true;
         disp(i, 1) = 0.0;
 
         if (std::abs(pos(i, 0) - left) < eps) {
           boun(i, 0) = true;
           disp(i, 0) = 0.0;
         }
       }
     }
     break;
   }
   case 3: {
     /// accessing bounds
     Real back = lowerBounds(2);
     for (UInt i = 0; i < mesh.getNbNodes(); ++i) {
       if (std::abs(pos(i, 1) - bottom) < eps) {
         boun(i, 1) = true;
         disp(i, 1) = 0.0;
 
         if ((std::abs(pos(i, 0) - left) < eps) &&
             (std::abs(pos(i, 2) - back) < eps)) {
           boun(i, 0) = true;
           boun(i, 2) = true;
           disp(i, 0) = 0.0;
           disp(i, 2) = 0.0;
         }
       }
     }
     break;
   }
   }
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::applyLoadedBC(const Vector<Real> & traction,
                              const ID & element_group, bool multi_axial) {
 
   /// boundary conditions
   const auto & mesh = model.getMesh();
   const UInt dim = mesh.getSpatialDimension();
   const Vector<Real> & lowerBounds = mesh.getLowerBounds();
   const Vector<Real> & upperBounds = mesh.getUpperBounds();
   Real bottom = lowerBounds(1);
   Real left = lowerBounds(0);
   Real top = upperBounds(1);
 
   Real eps = std::abs((top - bottom) * 1e-6);
   const Array<Real> & pos = mesh.getNodes();
   Array<Real> & disp = model.getDisplacement();
   Array<bool> & boun = model.getBlockedDOFs();
   // disp.clear();
   // boun.clear();
 
   switch (dim) {
   case 2: {
     for (UInt i = 0; i < mesh.getNbNodes(); ++i) {
       if (std::abs(pos(i, 1) - bottom) < eps) {
         boun(i, 1) = true;
         disp(i, 1) = 0.0;
       }
       if ((std::abs(pos(i, 1) - bottom) < eps) &&
           (std::abs(pos(i, 0) - left) < eps)) {
         boun(i, 0) = true;
         disp(i, 0) = 0.0;
       }
       if (multi_axial && (std::abs(pos(i, 0) - left) < eps)) {
         boun(i, 0) = true;
         disp(i, 0) = 0.0;
       }
     }
     break;
   }
   case 3: {
     Real back = lowerBounds(2);
     for (UInt i = 0; i < mesh.getNbNodes(); ++i) {
       if ((std::abs(pos(i, 1) - bottom) < eps) &&
           (std::abs(pos(i, 0) - left) < eps) &&
           (std::abs(pos(i, 2) - back) < eps)) {
         boun(i, 0) = true;
         boun(i, 1) = true;
         boun(i, 2) = true;
         disp(i, 0) = 0.0;
         disp(i, 1) = 0.0;
         disp(i, 2) = 0.0;
       }
       if (std::abs(pos(i, 1) - bottom) < eps) {
         boun(i, 1) = true;
         disp(i, 1) = 0.0;
       }
       if (multi_axial && (std::abs(pos(i, 2) - back) < eps)) {
         boun(i, 2) = true;
         disp(i, 2) = 0.0;
       }
       if (multi_axial && (std::abs(pos(i, 0) - left) < eps)) {
         boun(i, 0) = true;
         disp(i, 0) = 0.0;
       }
     }
   }
   }
   // try {
   model.applyBC(BC::Neumann::FromTraction(traction), element_group);
   // } catch (...) {
   // }
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::fillNodeGroup(NodeGroup & node_group, bool multi_axial) {
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   const Vector<Real> & upperBounds = mesh.getUpperBounds();
   const Vector<Real> & lowerBounds = mesh.getLowerBounds();
   Real top = upperBounds(1);
   Real right = upperBounds(0);
   Real bottom = lowerBounds(1);
   Real front;
   if (dim == 3) {
     front = upperBounds(2);
   }
   Real eps = std::abs((top - bottom) * 1e-6);
   const Array<Real> & pos = mesh.getNodes();
   if (multi_axial) {
     /// fill the NodeGroup with the nodes on the left, bottom and back
     /// surface
     for (UInt i = 0; i < mesh.getNbNodes(); ++i) {
       if (std::abs(pos(i, 0) - right) < eps) {
         node_group.add(i);
       }
       if (std::abs(pos(i, 1) - top) < eps) {
         node_group.add(i);
       }
       if (dim == 3 && std::abs(pos(i, 2) - front) < eps) {
         node_group.add(i);
       }
     }
   }
   /// fill the NodeGroup with the nodes on the bottom surface
   else {
     for (UInt i = 0; i < mesh.getNbNodes(); ++i) {
       if (std::abs(pos(i, 1) - top) < eps) {
         node_group.add(i);
       }
     }
   }
 }
 
 /* -------------------------------------------------------------------------- */
 Real ASRTools::computeAverageDisplacement(SpatialDirection direction) {
 
   Real av_displ = 0;
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   const Vector<Real> & upperBounds = mesh.getUpperBounds();
   const Vector<Real> & lowerBounds = mesh.getLowerBounds();
   Real right = upperBounds(0);
   Real left = lowerBounds(0);
   Real top = upperBounds(1);
   Real front;
   if (dim == 3) {
     front = upperBounds(2);
   }
   Real eps = std::abs((right - left) * 1e-6);
   const Array<Real> & pos = mesh.getNodes();
   const Array<Real> & disp = model.getDisplacement();
   UInt nb_nodes = 0;
 
   if (direction == _x) {
     for (UInt i = 0; i < mesh.getNbNodes(); ++i) {
       if (std::abs(pos(i, 0) - right) < eps && mesh.isLocalOrMasterNode(i)) {
         av_displ += disp(i, 0);
         ++nb_nodes;
       }
     }
   }
 
   else if (direction == _y) {
     for (UInt i = 0; i < mesh.getNbNodes(); ++i) {
       if (std::abs(pos(i, 1) - top) < eps && mesh.isLocalOrMasterNode(i)) {
         av_displ += disp(i, 1);
         ++nb_nodes;
       }
     }
   } else if ((direction == _z) && (model.getSpatialDimension() == 3)) {
     AKANTU_DEBUG_ASSERT(model.getSpatialDimension() == 3,
                         "no z-direction in 2D problem");
     for (UInt i = 0; i < mesh.getNbNodes(); ++i) {
       if (std::abs(pos(i, 2) - front) < eps && mesh.isLocalOrMasterNode(i)) {
         av_displ += disp(i, 2);
         ++nb_nodes;
       }
     }
   } else {
     AKANTU_EXCEPTION("The parameter for the testing direction is wrong!!!");
   }
 
   /// do not communicate if model is multi-scale
-  if (not aka::is_of_type<SolidMechanicsModelRVE>(model)) {
+  if (!aka::is_of_type<SolidMechanicsModelRVE>(model)) {
     auto && comm = akantu::Communicator::getWorldCommunicator();
     comm.allReduce(av_displ, SynchronizerOperation::_sum);
     comm.allReduce(nb_nodes, SynchronizerOperation::_sum);
   }
 
   return av_displ / nb_nodes;
 }
 
 /* --------------------------------------------------------------------------
  */
 Real ASRTools::computeVolumetricExpansion(SpatialDirection direction) {
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   UInt tensor_component = 0;
   if (dim == 2) {
     if (direction == _x) {
       tensor_component = 0;
     } else if (direction == _y) {
       tensor_component = 3;
     } else {
       AKANTU_EXCEPTION("The parameter for the testing direction is wrong!!!");
     }
   }
 
   else if (dim == 3) {
     if (direction == _x) {
       tensor_component = 0;
     } else if (direction == _y) {
       tensor_component = 4;
     } else if (direction == _z) {
       tensor_component = 8;
     } else {
       AKANTU_EXCEPTION("The parameter for the testing direction is wrong!!!");
     }
   } else {
     AKANTU_EXCEPTION("This example does not work for 1D!!!");
   }
 
   Real gradu_tot = 0;
   Real tot_volume = 0;
   GhostType gt = _not_ghost;
 
   for (auto element_type : mesh.elementTypes(dim, gt, _ek_regular)) {
     const FEEngine & fe_engine = model.getFEEngine();
     for (UInt m = 0; m < model.getNbMaterials(); ++m) {
       const ElementTypeMapUInt & element_filter_map =
           model.getMaterial(m).getElementFilter();
       if (!element_filter_map.exists(element_type, gt)) {
         continue;
       }
       const Array<UInt> & elem_filter =
           model.getMaterial(m).getElementFilter(element_type);
       if (elem_filter.empty()) {
         continue;
       }
       // const Array<Real> & gradu_vec =
       // model.getMaterial(m).getGradU(element_type);
       Array<Real> gradu_vec(elem_filter.size() *
                                 fe_engine.getNbIntegrationPoints(element_type),
                             dim * dim, 0.);
 
       fe_engine.gradientOnIntegrationPoints(model.getDisplacement(), gradu_vec,
                                             dim, element_type, gt, elem_filter);
 
       Array<Real> int_gradu_vec(elem_filter.size(), dim * dim, "int_of_gradu");
 
       fe_engine.integrate(gradu_vec, int_gradu_vec, dim * dim, element_type,
                           _not_ghost, elem_filter);
 
       for (UInt k = 0; k < elem_filter.size(); ++k) {
         gradu_tot += int_gradu_vec(k, tensor_component);
       }
     }
 
     Array<Real> Volume(mesh.getNbElement(element_type) *
                            fe_engine.getNbIntegrationPoints(element_type),
                        1, 1.);
     Real int_volume = fe_engine.integrate(Volume, element_type);
     tot_volume += int_volume;
   }
 
   /// do not communicate if model is multi-scale
   if (not aka::is_of_type<SolidMechanicsModelRVE>(model)) {
     auto && comm = akantu::Communicator::getWorldCommunicator();
     comm.allReduce(gradu_tot, SynchronizerOperation::_sum);
     comm.allReduce(tot_volume, SynchronizerOperation::_sum);
   }
 
   return gradu_tot / tot_volume;
 }
 
 /* --------------------------------------------------------------------------
  */
 Real ASRTools::computeDamagedVolume(const ID & mat_name) {
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   Real total_damage = 0;
   GhostType gt = _not_ghost;
   const Material & mat = model.getMaterial(mat_name);
 
   for (auto element_type : mesh.elementTypes(dim, gt, _ek_not_defined)) {
     const FEEngine & fe_engine = model.getFEEngine();
     const ElementTypeMapUInt & element_filter_map = mat.getElementFilter();
     if (!element_filter_map.exists(element_type, gt)) {
       continue;
     }
     const Array<UInt> & elem_filter = mat.getElementFilter(element_type);
     if (elem_filter.empty()) {
       continue;
     }
     const Array<Real> & damage = mat.getInternal<Real>("damage")(element_type);
 
     total_damage += fe_engine.integrate(damage, element_type, gt, elem_filter);
   }
 
   /// do not communicate if model is multi-scale
   if (not aka::is_of_type<SolidMechanicsModelRVE>(model)) {
     auto && comm = akantu::Communicator::getWorldCommunicator();
     comm.allReduce(total_damage, SynchronizerOperation::_sum);
   }
 
   return total_damage;
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::computeStiffnessReduction(std::ofstream & file_output, Real time,
                                          bool tension) {
 
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   AKANTU_DEBUG_ASSERT(dim != 1, "Example does not work for 1D");
 
   /// save nodal values before test
   storeNodalFields();
 
   auto && comm = akantu::Communicator::getWorldCommunicator();
   auto prank = comm.whoAmI();
 
   if (dim == 2) {
     Real int_residual_x = 0;
     Real int_residual_y = 0;
     // try {
     int_residual_x = performLoadingTest(_x, tension);
     int_residual_y = performLoadingTest(_y, tension);
     // } catch (...) {
     // }
 
     if (prank == 0) {
       file_output << time << "," << int_residual_x << "," << int_residual_y
                   << std::endl;
     }
   } else {
     Real int_residual_x = performLoadingTest(_x, tension);
     Real int_residual_y = performLoadingTest(_y, tension);
     Real int_residual_z = performLoadingTest(_z, tension);
     if (prank == 0) {
       file_output << time << "," << int_residual_x << "," << int_residual_y
                   << "," << int_residual_z << std::endl;
     }
   }
 
   /// return the nodal values
   restoreNodalFields();
 }
 
 /* -------------------------------------------------------------------------- */
 void ASRTools::storeNodalFields() {
   auto & disp = this->model.getDisplacement();
   auto & boun = this->model.getBlockedDOFs();
   auto & ext_force = this->model.getExternalForce();
   this->disp_stored.copy(disp);
   this->boun_stored.copy(boun);
   this->ext_force_stored.copy(ext_force);
 }
 
 /* -------------------------------------------------------------------------- */
 void ASRTools::restoreNodalFields() {
   auto & disp = this->model.getDisplacement();
   auto & boun = this->model.getBlockedDOFs();
   auto & ext_force = this->model.getExternalForce();
   disp.copy(this->disp_stored);
   boun.copy(this->boun_stored);
   ext_force.copy(this->ext_force_stored);
   /// update grad_u
   // model.assembleInternalForces();
 }
 
 /* ---------------------------------------------------------------------- */
 void ASRTools::resetNodalFields() {
   auto & disp = this->model.getDisplacement();
   auto & boun = this->model.getBlockedDOFs();
   auto & ext_force = this->model.getExternalForce();
   disp.clear();
   boun.clear();
   ext_force.clear();
 }
 
 /* -------------------------------------------------------------------------- */
 void ASRTools::restoreInternalFields() {
   for (UInt m = 0; m < model.getNbMaterials(); ++m) {
     Material & mat = model.getMaterial(m);
     mat.restorePreviousState();
   }
 }
 /* -------------------------------------------------------------------------- */
 void ASRTools::resetInternalFields() {
   for (UInt m = 0; m < model.getNbMaterials(); ++m) {
     Material & mat = model.getMaterial(m);
     mat.resetInternalsWithHistory();
   }
 }
 /* -------------------------------------------------------------------------- */
 void ASRTools::storeDamageField() {
   for (UInt m = 0; m < model.getNbMaterials(); ++m) {
     Material & mat = model.getMaterial(m);
     if (mat.isInternal<Real>("damage_stored", _ek_regular) &&
         mat.isInternal<UInt>("reduction_step_stored", _ek_regular)) {
       for (const auto & el_type : mat.getElementFilter().elementTypes(
                _all_dimensions, _not_ghost, _ek_not_defined)) {
         auto & red_stored =
             mat.getInternal<UInt>("reduction_step_stored")(el_type, _not_ghost);
         auto & red_current =
             mat.getInternal<UInt>("reduction_step")(el_type, _not_ghost);
         red_stored.copy(red_current);
 
         auto & dam_stored =
             mat.getInternal<Real>("damage_stored")(el_type, _not_ghost);
         auto & dam_current =
             mat.getInternal<Real>("damage")(el_type, _not_ghost);
         dam_stored.copy(dam_current);
       }
     }
   }
 }
 /* -------------------------------------------------------------------------- */
 void ASRTools::restoreDamageField() {
   for (UInt m = 0; m < model.getNbMaterials(); ++m) {
     Material & mat = model.getMaterial(m);
     if (mat.isInternal<Real>("damage_stored", _ek_regular) &&
         mat.isInternal<UInt>("reduction_step_stored", _ek_regular)) {
       for (const auto & el_type : mat.getElementFilter().elementTypes(
                _all_dimensions, _not_ghost, _ek_not_defined)) {
         auto & red_stored =
             mat.getInternal<UInt>("reduction_step_stored")(el_type, _not_ghost);
         auto & red_current =
             mat.getInternal<UInt>("reduction_step")(el_type, _not_ghost);
         red_current.copy(red_stored);
 
         auto & dam_stored =
             mat.getInternal<Real>("damage_stored")(el_type, _not_ghost);
         auto & dam_current =
             mat.getInternal<Real>("damage")(el_type, _not_ghost);
         dam_current.copy(dam_stored);
       }
     }
   }
 }
 
 /* -------------------------------------------------------------------------- */
 Real ASRTools::performLoadingTest(SpatialDirection direction, bool tension) {
   UInt dir;
 
   if (direction == _x) {
     dir = 0;
   }
   if (direction == _y) {
     dir = 1;
   }
   if (direction == _z) {
     AKANTU_DEBUG_ASSERT(model.getSpatialDimension() == 3,
                         "Error in problem dimension!!!");
     dir = 2;
   }
 
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
 
   /// indicate to material that its in the loading test
   UInt nb_materials = model.getNbMaterials();
   for (UInt m = 0; m < nb_materials; ++m) {
     Material & mat = model.getMaterial(m);
     if (aka::is_of_type<MaterialDamageIterativeOrthotropic<2>>(mat)) {
       mat.setParam("loading_test", true);
     }
   }
 
   /// boundary conditions
   const Vector<Real> & lowerBounds = mesh.getLowerBounds();
   const Vector<Real> & upperBounds = mesh.getUpperBounds();
   Real bottom = lowerBounds(1);
   Real top = upperBounds(1);
   Real left = lowerBounds(0);
   Real back;
 
   if (dim == 3) {
     back = lowerBounds(2);
   }
 
   Real eps = std::abs((top - bottom) * 1e-6);
   Real imposed_displacement = std::abs(lowerBounds(dir) - upperBounds(dir));
   imposed_displacement *= 0.001;
   const auto & pos = mesh.getNodes();
   auto & disp = model.getDisplacement();
   auto & boun = model.getBlockedDOFs();
   auto & ext_force = model.getExternalForce();
   UInt nb_nodes = mesh.getNbNodes();
 
   disp.clear();
   boun.clear();
   ext_force.clear();
 
   if (dim == 3) {
     for (UInt i = 0; i < nb_nodes; ++i) {
 
       /// fix one corner node to avoid sliding
       if ((std::abs(pos(i, 1) - bottom) < eps) &&
           (std::abs(pos(i, 0) - left) < eps) &&
           (std::abs(pos(i, 2) - back) < eps)) {
         boun(i, 0) = true;
         boun(i, 1) = true;
         boun(i, 2) = true;
         disp(i, 0) = 0.0;
         disp(i, 1) = 0.0;
         disp(i, 2) = 0.0;
       }
 
       if ((std::abs(pos(i, dir) - lowerBounds(dir)) < eps)) {
         boun(i, dir) = true;
         disp(i, dir) = 0.0;
       }
       if ((std::abs(pos(i, dir) - upperBounds(dir)) < eps)) {
         boun(i, dir) = true;
         disp(i, dir) = (2 * tension - 1) * imposed_displacement;
       }
     }
   } else {
     for (UInt i = 0; i < nb_nodes; ++i) {
 
       /// fix one corner node to avoid sliding
       if ((std::abs(pos(i, 1) - bottom) < eps) &&
           (std::abs(pos(i, 0) - left) < eps)) {
         boun(i, 0) = true;
         boun(i, 1) = true;
         disp(i, 0) = 0.0;
         disp(i, 1) = 0.0;
       }
 
       if ((std::abs(pos(i, dir) - lowerBounds(dir)) < eps)) {
         boun(i, dir) = true;
         disp(i, dir) = 0.0;
       }
       if ((std::abs(pos(i, dir) - upperBounds(dir)) < eps)) {
         boun(i, dir) = true;
         disp(i, dir) = (2 * tension - 1) * imposed_displacement;
       }
     }
   }
 
   try {
     model.solveStep();
   } catch (debug::Exception & e) {
     auto & solver = model.getNonLinearSolver("static");
     int nb_iter = solver.get("nb_iterations");
     std::cout << "Loading test did not converge in " << nb_iter
               << " iterations." << std::endl;
     throw e;
   }
 
   /// compute the force (residual in this case) along the edge of the
   /// imposed displacement
   Real int_residual = 0.;
   const Array<Real> & residual = model.getInternalForce();
 
   for (UInt n = 0; n < nb_nodes; ++n) {
     if (std::abs(pos(n, dir) - upperBounds(dir)) < eps &&
         mesh.isLocalOrMasterNode(n)) {
       int_residual += -residual(n, dir);
     }
   }
 
   /// indicate to material that its out of the loading test
   for (UInt m = 0; m < nb_materials; ++m) {
     Material & mat = model.getMaterial(m);
     if (aka::is_of_type<MaterialDamageIterativeOrthotropic<2>>(mat)) {
       mat.setParam("loading_test", false);
     }
   }
 
   /// restore historical internal fields
   restoreInternalFields();
 
   /// do not communicate if model is multi-scale
   if (not aka::is_of_type<SolidMechanicsModelRVE>(model)) {
     auto && comm = akantu::Communicator::getWorldCommunicator();
     comm.allReduce(int_residual, SynchronizerOperation::_sum);
   }
 
   return int_residual;
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::computeAverageProperties(std::ofstream & file_output) {
 
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   AKANTU_DEBUG_ASSERT(dim != 1, "Example does not work for 1D");
 
   Real av_strain_x = computeVolumetricExpansion(_x);
   Real av_strain_y = computeVolumetricExpansion(_y);
   Real av_displ_x = computeAverageDisplacement(_x);
   Real av_displ_y = computeAverageDisplacement(_y);
   Real damage_agg, damage_paste, crack_agg, crack_paste;
   computeDamageRatioPerMaterial(damage_agg, "aggregate");
   computeDamageRatioPerMaterial(damage_paste, "paste");
   computeCrackVolumePerMaterial(crack_agg, "aggregate");
   computeCrackVolumePerMaterial(crack_paste, "paste");
 
   auto && comm = akantu::Communicator::getWorldCommunicator();
   auto prank = comm.whoAmI();
 
   if (dim == 2) {
 
     if (prank == 0)
       file_output << av_strain_x << "," << av_strain_y << "," << av_displ_x
                   << "," << av_displ_y << "," << damage_agg << ","
                   << damage_paste << "," << crack_agg << "," << crack_paste
                   << std::endl;
   }
 
   else {
     Real av_displ_z = computeAverageDisplacement(_z);
     Real av_strain_z = computeVolumetricExpansion(_z);
 
     if (prank == 0)
       file_output << av_strain_x << "," << av_strain_y << "," << av_strain_z
                   << "," << av_displ_x << "," << av_displ_y << "," << av_displ_z
                   << "," << damage_agg << "," << damage_paste << ","
                   << crack_agg << "," << crack_paste << std::endl;
   }
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::computeAverageProperties(std::ofstream & file_output,
                                         Real time) {
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
 
   AKANTU_DEBUG_ASSERT(dim != 1, "Example does not work for 1D");
 
   Real av_strain_x = computeVolumetricExpansion(_x);
   Real av_strain_y = computeVolumetricExpansion(_y);
   Real av_displ_x = computeAverageDisplacement(_x);
   Real av_displ_y = computeAverageDisplacement(_y);
   Real damage_agg, damage_paste, crack_agg, crack_paste;
   computeDamageRatioPerMaterial(damage_agg, "aggregate");
   computeDamageRatioPerMaterial(damage_paste, "paste");
   computeCrackVolumePerMaterial(crack_agg, "aggregate");
   computeCrackVolumePerMaterial(crack_paste, "paste");
 
   auto && comm = akantu::Communicator::getWorldCommunicator();
   auto prank = comm.whoAmI();
 
   if (dim == 2) {
 
     if (prank == 0)
       file_output << time << "," << av_strain_x << "," << av_strain_y << ","
                   << av_displ_x << "," << av_displ_y << "," << damage_agg << ","
                   << damage_paste << "," << crack_agg << "," << crack_paste
                   << std::endl;
   }
 
   else {
     Real av_displ_z = computeAverageDisplacement(_z);
     Real av_strain_z = computeVolumetricExpansion(_z);
 
     if (prank == 0)
       file_output << time << "," << av_strain_x << "," << av_strain_y << ","
                   << av_strain_z << "," << av_displ_x << "," << av_displ_y
                   << "," << av_displ_z << "," << damage_agg << ","
                   << damage_paste << "," << crack_agg << "," << crack_paste
                   << std::endl;
   }
 }
 /* --------------------------------------------------------------------------
  */
 void ASRTools::computeAveragePropertiesCohesiveModel(
     std::ofstream & file_output, Real time) {
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
 
   AKANTU_DEBUG_ASSERT(dim != 1, "Example does not work for 1D");
 
   Real av_strain_x = computeVolumetricExpansion(_x);
   Real av_strain_y = computeVolumetricExpansion(_y);
   Real av_displ_x = computeAverageDisplacement(_x);
   Real av_displ_y = computeAverageDisplacement(_y);
 
   auto && comm = akantu::Communicator::getWorldCommunicator();
   auto prank = comm.whoAmI();
 
   if (dim == 2) {
 
     if (prank == 0)
       file_output << time << "," << av_strain_x << "," << av_strain_y << ","
                   << av_displ_x << "," << av_displ_y << "," << std::endl;
   }
 
   else {
     Real av_displ_z = computeAverageDisplacement(_z);
     Real av_strain_z = computeVolumetricExpansion(_z);
 
     if (prank == 0)
       file_output << time << "," << av_strain_x << "," << av_strain_y << ","
                   << av_strain_z << "," << av_displ_x << "," << av_displ_y
                   << "," << av_displ_z << std::endl;
   }
 }
 /* --------------------------------------------------------------------------
  */
 void ASRTools::computeAveragePropertiesFe2Material(std::ofstream & file_output,
                                                    Real time) {
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
 
   AKANTU_DEBUG_ASSERT(dim != 1, "Example does not work for 1D");
 
   Real av_strain_x = computeVolumetricExpansion(_x);
   Real av_strain_y = computeVolumetricExpansion(_y);
   Real av_displ_x = computeAverageDisplacement(_x);
   Real av_displ_y = computeAverageDisplacement(_y);
   Real crack_agg = averageScalarField("crack_volume_ratio_agg");
   Real crack_paste = averageScalarField("crack_volume_ratio_paste");
 
   auto && comm = akantu::Communicator::getWorldCommunicator();
   auto prank = comm.whoAmI();
 
   if (dim == 2) {
 
     if (prank == 0)
       file_output << time << "," << av_strain_x << "," << av_strain_y << ","
                   << av_displ_x << "," << av_displ_y << "," << crack_agg << ","
                   << crack_paste << std::endl;
   }
 
   else {
     Real av_displ_z = computeAverageDisplacement(_z);
     Real av_strain_z = computeVolumetricExpansion(_z);
 
     if (prank == 0)
       file_output << time << "," << av_strain_x << "," << av_strain_y << ","
                   << av_strain_z << "," << av_displ_x << "," << av_displ_y
                   << "," << av_displ_z << "," << crack_agg << "," << crack_paste
                   << std::endl;
   }
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::saveState(UInt current_step) {
 
   /// variables for parallel execution
   auto && comm = akantu::Communicator::getWorldCommunicator();
   auto prank = comm.whoAmI();
   GhostType gt = _not_ghost;
 
   /// save the reduction step on each quad
   UInt nb_materials = model.getNbMaterials();
 
   /// open the output file
   std::stringstream file_stream;
   file_stream << "./restart/reduction_steps_file_" << prank << "_"
               << current_step << ".txt";
   std::string reduction_steps_file = file_stream.str();
   std::ofstream file_output;
   file_output.open(reduction_steps_file.c_str());
 
   if (!file_output.is_open())
     AKANTU_EXCEPTION("Could not create the file " + reduction_steps_file +
                      ", does its folder exist?");
 
   for (UInt m = 0; m < nb_materials; ++m) {
     const Material & mat = model.getMaterial(m);
     if (mat.getName() == "gel")
       continue;
     /// get the reduction steps internal field
     const InternalField<UInt> & reduction_steps =
         mat.getInternal<UInt>("damage_step");
     /// loop over all types in that material
     for (auto element_type : reduction_steps.elementTypes(gt)) {
       const Array<UInt> & elem_filter = mat.getElementFilter(element_type, gt);
       if (!elem_filter.size())
         continue;
       const Array<UInt> & reduction_step_array =
           reduction_steps(element_type, gt);
       for (UInt i = 0; i < reduction_step_array.size(); ++i) {
         file_output << reduction_step_array(i) << std::endl;
 
         if (file_output.fail())
           AKANTU_EXCEPTION("Error in writing data to file " +
                            reduction_steps_file);
       }
     }
   }
 
   /// close the file
   file_output.close();
   comm.barrier();
   /// write the number of the last successfully saved step in a file
   if (prank == 0) {
     std::string current_step_file = "./restart/current_step.txt";
     std::ofstream file_output;
     file_output.open(current_step_file.c_str());
     file_output << current_step << std::endl;
     if (file_output.fail())
       AKANTU_EXCEPTION("Error in writing data to file " + current_step_file);
     file_output.close();
   }
 }
 
 /* --------------------------------------------------------------------------
  */
 bool ASRTools::loadState(UInt & current_step) {
   current_step = 0;
   bool restart = false;
 
   /// variables for parallel execution
   auto && comm = akantu::Communicator::getWorldCommunicator();
   auto prank = comm.whoAmI();
   GhostType gt = _not_ghost;
 
   /// proc 0 has to save the current step
   if (prank == 0) {
     std::string line;
     std::string current_step_file = "./restart/current_step.txt";
     std::ifstream file_input;
     file_input.open(current_step_file.c_str());
     if (file_input.is_open()) {
       std::getline(file_input, line);
       std::stringstream sstr(line);
       sstr >> current_step;
       file_input.close();
     }
   }
 
   comm.broadcast(current_step);
 
   if (!current_step)
     return restart;
 
   if (prank == 0)
     std::cout << "....Restarting simulation" << std::endl;
   restart = true;
 
   /// save the reduction step on each quad
   UInt nb_materials = model.getNbMaterials();
 
   /// open the output file
   std::stringstream file_stream;
   file_stream << "./restart/reduction_steps_file_" << prank << "_"
               << current_step << ".txt";
   std::string reduction_steps_file = file_stream.str();
   std::ifstream file_input;
   file_input.open(reduction_steps_file.c_str());
 
   if (!file_input.is_open())
     AKANTU_EXCEPTION("Could not open file " + reduction_steps_file);
 
   for (UInt m = 0; m < nb_materials; ++m) {
     const Material & mat = model.getMaterial(m);
     if (mat.getName() == "gel")
       continue;
     /// get the material parameters
     Real E = mat.getParam("E");
     Real max_damage = mat.getParam("max_damage");
     Real max_reductions = mat.getParam("max_reductions");
 
     /// get the internal field that need to be set
     InternalField<UInt> & reduction_steps =
         const_cast<InternalField<UInt> &>(mat.getInternal<UInt>("damage_step"));
     InternalField<Real> & Sc =
         const_cast<InternalField<Real> &>(mat.getInternal<Real>("Sc"));
 
     InternalField<Real> & damage =
         const_cast<InternalField<Real> &>(mat.getInternal<Real>("damage"));
     /// loop over all types in that material
     Real reduction_constant = mat.getParam("reduction_constant");
     for (auto element_type : reduction_steps.elementTypes(gt)) {
       const Array<UInt> & elem_filter = mat.getElementFilter(element_type, gt);
       if (!elem_filter.size())
         continue;
       Array<UInt> & reduction_step_array = reduction_steps(element_type, gt);
       Array<Real> & Sc_array = Sc(element_type, gt);
       Array<Real> & damage_array = damage(element_type, gt);
       const Array<Real> & D_tangent =
           mat.getInternal<Real>("tangent")(element_type, gt);
       const Array<Real> & eps_u =
           mat.getInternal<Real>("ultimate_strain")(element_type, gt);
       std::string line;
       for (UInt i = 0; i < reduction_step_array.size(); ++i) {
 
         std::getline(file_input, line);
 
         if (file_input.fail())
           AKANTU_EXCEPTION("Could not read data from file " +
                            reduction_steps_file);
 
         std::stringstream sstr(line);
         sstr >> reduction_step_array(i);
         if (reduction_step_array(i) == 0)
           continue;
         if (reduction_step_array(i) == max_reductions) {
           damage_array(i) = max_damage;
           Real previous_damage =
               1. -
               (1. / std::pow(reduction_constant, reduction_step_array(i) - 1));
 
           Sc_array(i) = eps_u(i) * (1. - previous_damage) * E * D_tangent(i) /
                         ((1. - previous_damage) * E + D_tangent(i));
         } else {
           damage_array(i) =
               1. - (1. / std::pow(reduction_constant, reduction_step_array(i)));
           Sc_array(i) = eps_u(i) * (1. - damage_array(i)) * E * D_tangent(i) /
                         ((1. - damage_array(i)) * E + D_tangent(i));
         }
       }
     }
   }
   /// close the file
   file_input.close();
   return restart;
 }
 
 /* ------------------------------------------------------------------ */
 Real ASRTools::computePhaseVolume(const ID & mat_name) {
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   Real total_volume = 0;
   GhostType gt = _not_ghost;
   const Material & mat = model.getMaterial(mat_name);
 
   for (auto element_type : mesh.elementTypes(dim, gt, _ek_regular)) {
     const FEEngine & fe_engine = model.getFEEngine();
     const ElementTypeMapUInt & element_filter_map = mat.getElementFilter();
     if (!element_filter_map.exists(element_type, gt))
       continue;
     const Array<UInt> & elem_filter = mat.getElementFilter(element_type);
     if (!elem_filter.size())
       continue;
     Array<Real> volume(elem_filter.size() *
                            fe_engine.getNbIntegrationPoints(element_type),
                        1, 1.);
 
     total_volume += fe_engine.integrate(volume, element_type, gt, elem_filter);
   }
 
   /// do not communicate if model is multi-scale
   if (not aka::is_of_type<SolidMechanicsModelRVE>(model)) {
     auto && comm = akantu::Communicator::getWorldCommunicator();
     comm.allReduce(total_volume, SynchronizerOperation::_sum);
   }
 
   return total_volume;
 }
 /* --------------------------------------------------------------------------
  */
 void ASRTools::applyEigenGradUinCracks(
     const Matrix<Real> prescribed_eigen_grad_u, const ID & material_name) {
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   GhostType gt = _not_ghost;
   Material & mat = model.getMaterial(material_name);
   const Real max_damage = mat.getParam("max_damage");
   const ElementTypeMapUInt & element_filter = mat.getElementFilter();
   for (auto element_type :
        element_filter.elementTypes(dim, gt, _ek_not_defined)) {
 
     if (!element_filter(element_type, gt).size())
       continue;
     const Array<Real> & damage =
         mat.getInternal<Real>("damage")(element_type, gt);
     const Real * dam_it = damage.storage();
     Array<Real> & eigen_gradu =
         mat.getInternal<Real>("eigen_grad_u")(element_type, gt);
     Array<Real>::matrix_iterator eigen_it = eigen_gradu.begin(dim, dim);
     Array<Real>::matrix_iterator eigen_end = eigen_gradu.end(dim, dim);
     for (; eigen_it != eigen_end; ++eigen_it, ++dam_it) {
       if (Math::are_float_equal(max_damage, *dam_it)) {
         Matrix<Real> & current_eigengradu = *eigen_it;
         current_eigengradu = prescribed_eigen_grad_u;
       }
     }
   }
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::applyEigenGradUinCracks(
     const Matrix<Real> prescribed_eigen_grad_u,
     const ElementTypeMapUInt & critical_elements, bool to_add) {
   GhostType gt = _not_ghost;
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   for (auto element_type : mesh.elementTypes(dim, gt, _ek_not_defined)) {
     const Array<UInt> & critical_elements_vec =
         critical_elements(element_type, gt);
     const Array<UInt> & material_index_vec =
         model.getMaterialByElement(element_type, gt);
     const Array<UInt> & material_local_numbering_vec =
         model.getMaterialLocalNumbering(element_type, gt);
 
     for (UInt e = 0; e < critical_elements_vec.size(); ++e) {
       UInt element = critical_elements_vec(e);
       Material & material = model.getMaterial(material_index_vec(element));
       Array<Real> & eigen_gradu =
           material.getInternal<Real>("eigen_grad_u")(element_type, gt);
       UInt material_local_num = material_local_numbering_vec(element);
       Array<Real>::matrix_iterator eigen_it = eigen_gradu.begin(dim, dim);
       eigen_it += material_local_num;
       Matrix<Real> & current_eigengradu = *eigen_it;
       if (to_add)
         current_eigengradu += prescribed_eigen_grad_u;
       else
         current_eigengradu = prescribed_eigen_grad_u;
     }
   }
 }
 
 // /*
 // --------------------------------------------------------------------------
 // */ void ASRTools<dim>::fillCracks(ElementTypeMapReal & saved_damage) {
 //   const UInt dim = model.getSpatialDimension();
 //   const Material & mat_gel = model.getMaterial("gel");
 //   const Real E_gel = mat_gel.getParam("E");
 //   Real E_homogenized = 0.;
 //   GhostType gt = _not_ghost;
 //   const auto & mesh = model.getMesh();
 //   for (UInt m = 0; m < model.getNbMaterials(); ++m) {
 //     Material & mat = model.getMaterial(m);
 //     if (mat.getName() == "gel")
 //       continue;
 //     const Real E = mat.getParam("E");
 //     InternalField<Real> & damage = mat.getInternal<Real>("damage");
 //     for (auto element_type : mesh.elementTypes(dim, gt, _ek_regular)) {
 //       const Array<UInt> & elem_filter =
 //       mat.getElementFilter(element_type, gt); if (!elem_filter.size())
 //         continue;
 //       Array<Real> & damage_vec = damage(element_type, gt);
 //       Array<Real> & saved_damage_vec = saved_damage(element_type, gt);
 //       for (UInt i = 0; i < damage_vec.size(); ++i) {
 //         saved_damage_vec(elem_filter(i)) = damage_vec(i);
 //         E_homogenized = (E_gel - E) * damage_vec(i) + E;
 //         damage_vec(i) = 1. - (E_homogenized / E);
 //       }
 //     }
 //   }
 // }
 
 // /*
 // --------------------------------------------------------------------------
 // */ void ASRTools<dim>::drainCracks(const ElementTypeMapReal &
 // saved_damage) {
 //   // model.dump();
 //   const UInt dim = model.getSpatialDimension();
 //   const auto & mesh = model.getMesh();
 //   GhostType gt = _not_ghost;
 //   for (UInt m = 0; m < model.getNbMaterials(); ++m) {
 //     Material & mat = model.getMaterial(m);
 //     if (mat.getName() == "gel")
 //       continue;
 //     else {
 //       InternalField<Real> & damage = mat.getInternal<Real>("damage");
 //       for (auto element_type : mesh.elementTypes(dim, gt,
 //       _ek_not_defined)) {
 //         const Array<UInt> & elem_filter =
 //             mat.getElementFilter(element_type, gt);
 //         if (!elem_filter.size())
 //           continue;
 //         Array<Real> & damage_vec = damage(element_type, gt);
 //         const Array<Real> & saved_damage_vec =
 //         saved_damage(element_type, gt); for (UInt i = 0; i <
 //         damage_vec.size(); ++i) {
 //           damage_vec(i) = saved_damage_vec(elem_filter(i));
 //         }
 //       }
 //     }
 //   }
 //   // model.dump();
 // }
 
 /* --------------------------------------------------------------------------
  */
 Real ASRTools::computeSmallestElementSize() {
 
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
 
   //  constexpr auto dim = model.getSpatialDimension();
   /// compute smallest element size
   const Array<Real> & pos = mesh.getNodes();
   Real el_h_min = std::numeric_limits<Real>::max();
   GhostType gt = _not_ghost;
   for (auto element_type : mesh.elementTypes(dim, gt)) {
 
     UInt nb_nodes_per_element = mesh.getNbNodesPerElement(element_type);
     UInt nb_element = mesh.getNbElement(element_type);
 
     Array<Real> X(0, nb_nodes_per_element * dim);
     model.getFEEngine().extractNodalToElementField(mesh, pos, X, element_type,
                                                    _not_ghost);
 
     Array<Real>::matrix_iterator X_el = X.begin(dim, nb_nodes_per_element);
 
     for (UInt el = 0; el < nb_element; ++el, ++X_el) {
       Real el_h = model.getFEEngine().getElementInradius(*X_el, element_type);
       if (el_h < el_h_min)
         el_h_min = el_h;
     }
   }
 
   if (not aka::is_of_type<SolidMechanicsModelRVE>(model)) {
     auto && comm = akantu::Communicator::getWorldCommunicator();
     comm.allReduce(el_h_min, SynchronizerOperation::_min);
   }
 
   return el_h_min;
 }
 
 // /*
 // --------------------------------------------------------------------------
 // */
 // /// apply homogeneous temperature on the whole specimen
 
 // void ASRTools<dim>::applyTemperatureFieldToSolidmechanicsModel(
 //     const Real & temperature) {
 
 //   UInt dim = model.getMesh().getSpatialDimension();
 
 //   for (UInt m = 0; m < model.getNbMaterials(); ++m) {
 //     Material & mat = model.getMaterial(m);
 
 //     for (auto el_type : mat.getElementFilter().elementTypes(dim)) {
 
 //       const auto & filter = mat.getElementFilter()(el_type);
 //       if (filter.size() == 0)
 //         continue;
 
 //       auto & delta_T = mat.getArray<Real>("delta_T", el_type);
 //       auto dt = delta_T.begin();
 //       auto dt_end = delta_T.end();
 
 //       for (; dt != dt_end; ++dt) {
 //         *dt = temperature;
 //       }
 //     }
 //   }
 // }
 /* --------------------------------------------------------------------------
  */
 void ASRTools::computeASRStrainLarive(
     const Real & delta_time_day, const Real & T, Real & ASRStrain,
     const Real & eps_inf, const Real & time_ch_ref, const Real & time_lat_ref,
     const Real & U_C, const Real & U_L, const Real & T_ref) {
   AKANTU_DEBUG_IN();
 
   Real time_ch, time_lat, lambda, ksi, exp_ref;
   ksi = ASRStrain / eps_inf;
   if (T == 0) {
     ksi += 0;
   } else {
     time_ch = time_ch_ref * std::exp(U_C * (1. / T - 1. / T_ref));
     time_lat = time_lat_ref * std::exp(U_L * (1. / T - 1. / T_ref));
     exp_ref = std::exp(-time_lat / time_ch);
     lambda = (1 + exp_ref) / (ksi + exp_ref);
     ksi += delta_time_day / time_ch * (1 - ksi) / lambda;
   }
 
   ASRStrain = ksi * eps_inf;
 
   AKANTU_DEBUG_OUT();
 }
 
 /* --------------------------------------------------------------------------
  */
 Real ASRTools::computeDeltaGelStrainThermal(const Real delta_time_day,
                                             const Real k,
                                             const Real activ_energy,
                                             const Real R, const Real T,
                                             Real & amount_reactive_particles,
                                             const Real saturation_const) {
   /// compute increase in gel strain value for interval of time delta_time
   /// as temperatures are stored in C, conversion to K is done
 
   Real delta_strain = amount_reactive_particles * k *
                       std::exp(-activ_energy / (R * T)) * delta_time_day;
 
   amount_reactive_particles -=
       std::exp(-activ_energy / (R * T)) * delta_time_day / saturation_const;
 
   if (amount_reactive_particles < 0.)
     amount_reactive_particles = 0.;
   return delta_strain;
 }
 
 /* -----------------------------------------------------------------------*/
 Real ASRTools::computeDeltaGelStrainLinear(const Real delta_time,
                                            const Real k) {
   /// compute increase in gel strain value for dt simply by deps = k *
   /// delta_time
 
   Real delta_strain = k * delta_time;
 
   return delta_strain;
 }
 
 /* ---------------------------------------------------------------------- */
 void ASRTools::applyBoundaryConditionsRve(
     const Matrix<Real> & displacement_gradient) {
   AKANTU_DEBUG_IN();
 
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   // /// transform into Green strain to exclude rotations
   // auto F = displacement_gradient + Matrix<Real>::eye(dim);
   // auto E = 0.5 * (F.transpose() * F - Matrix<Real>::eye(dim));
   /// get the position of the nodes
   const Array<Real> & pos = mesh.getNodes();
   /// storage for the coordinates of a given node and the displacement
   /// that will be applied
   Vector<Real> x(dim);
   Vector<Real> appl_disp(dim);
 
   const auto & lower_bounds = mesh.getLowerBounds();
   for (auto node : this->corner_nodes) {
     x(0) = pos(node, 0);
     x(1) = pos(node, 1);
     x -= lower_bounds;
     appl_disp.mul<false>(displacement_gradient, x);
     (model.getBlockedDOFs())(node, 0) = true;
     (model.getDisplacement())(node, 0) = appl_disp(0);
     (model.getBlockedDOFs())(node, 1) = true;
     (model.getDisplacement())(node, 1) = appl_disp(1);
   }
   AKANTU_DEBUG_OUT();
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::applyHomogeneousTemperature(const Real & temperature) {
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
 
   for (UInt m = 0; m < model.getNbMaterials(); ++m) {
     Material & mat = model.getMaterial(m);
 
     for (auto el_type : mat.getElementFilter().elementTypes(dim)) {
 
       const auto & filter = mat.getElementFilter()(el_type);
       if (filter.size() == 0)
         continue;
 
       auto & deltas_T = mat.getArray<Real>("delta_T", el_type);
 
       for (auto && delta_T : deltas_T) {
         delta_T = temperature;
       }
     }
   }
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::removeTemperature() {
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
 
   for (UInt m = 0; m < model.getNbMaterials(); ++m) {
     Material & mat = model.getMaterial(m);
 
     for (auto el_type : mat.getElementFilter().elementTypes(dim)) {
 
       const auto & filter = mat.getElementFilter()(el_type);
       if (filter.size() == 0)
         continue;
 
       auto & deltas_T = mat.getArray<Real>("delta_T", el_type);
 
       for (auto && delta_T : deltas_T) {
         delta_T = 0.;
       }
     }
   }
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::findCornerNodes() {
   AKANTU_DEBUG_IN();
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
 
   // find corner nodes
   const auto & position = mesh.getNodes();
   const auto & lower_bounds = mesh.getLowerBounds();
   const auto & upper_bounds = mesh.getUpperBounds();
 
   switch (dim) {
   case 2: {
     corner_nodes.resize(4);
     corner_nodes.set(UInt(-1));
     for (auto && data : enumerate(make_view(position, dim))) {
       auto node = std::get<0>(data);
       const auto & X = std::get<1>(data);
 
       auto distance = X.distance(lower_bounds);
       // node 1 - left bottom corner
       if (Math::are_float_equal(distance, 0)) {
         corner_nodes(0) = node;
       }
       // node 2 - right bottom corner
       else if (Math::are_float_equal(X(_x), upper_bounds(_x)) &&
                Math::are_float_equal(X(_y), lower_bounds(_y))) {
         corner_nodes(1) = node;
       }
       // node 3 - right top
       else if (Math::are_float_equal(X(_x), upper_bounds(_x)) &&
                Math::are_float_equal(X(_y), upper_bounds(_y))) {
         corner_nodes(2) = node;
       }
       // node 4 - left top
       else if (Math::are_float_equal(X(_x), lower_bounds(_x)) &&
                Math::are_float_equal(X(_y), upper_bounds(_y))) {
         corner_nodes(3) = node;
       }
     }
     break;
   }
   case 3: {
     corner_nodes.resize(8);
     corner_nodes.set(UInt(-1));
     for (auto && data : enumerate(make_view(position, dim))) {
       auto node = std::get<0>(data);
       const auto & X = std::get<1>(data);
 
       auto distance = X.distance(lower_bounds);
       // node 1
       if (Math::are_float_equal(distance, 0)) {
         corner_nodes(0) = node;
       }
       // node 2
       else if (Math::are_float_equal(X(_x), upper_bounds(_x)) &&
                Math::are_float_equal(X(_y), lower_bounds(_y)) &&
                Math::are_float_equal(X(_z), lower_bounds(_z))) {
         corner_nodes(1) = node;
       }
       // node 3
       else if (Math::are_float_equal(X(_x), upper_bounds(_x)) &&
                Math::are_float_equal(X(_y), upper_bounds(_y)) &&
                Math::are_float_equal(X(_z), lower_bounds(_z))) {
         corner_nodes(2) = node;
       }
       // node 4
       else if (Math::are_float_equal(X(_x), lower_bounds(_x)) &&
                Math::are_float_equal(X(_y), upper_bounds(_y)) &&
                Math::are_float_equal(X(_z), lower_bounds(_z))) {
         corner_nodes(3) = node;
       }
       // node 5
       if (Math::are_float_equal(X(_x), lower_bounds(_x)) &&
           Math::are_float_equal(X(_y), lower_bounds(_y)) &&
           Math::are_float_equal(X(_z), upper_bounds(_z))) {
         corner_nodes(4) = node;
       }
       // node 6
       else if (Math::are_float_equal(X(_x), upper_bounds(_x)) &&
                Math::are_float_equal(X(_y), lower_bounds(_y)) &&
                Math::are_float_equal(X(_z), upper_bounds(_z))) {
         corner_nodes(5) = node;
       }
       // node 7
       else if (Math::are_float_equal(X(_x), upper_bounds(_x)) &&
                Math::are_float_equal(X(_y), upper_bounds(_y)) &&
                Math::are_float_equal(X(_z), upper_bounds(_z))) {
         corner_nodes(6) = node;
       }
       // node 8
       else if (Math::are_float_equal(X(_x), lower_bounds(_x)) &&
                Math::are_float_equal(X(_y), upper_bounds(_y)) &&
                Math::are_float_equal(X(_z), upper_bounds(_z))) {
         corner_nodes(7) = node;
       }
     }
     break;
   }
   }
 
   //  AKANTU_DEBUG_ASSERT(dim == 2, "This is 2D only!");
 
   // for (UInt i = 0; i < corner_nodes.size(); ++i) {
   //   if (corner_nodes(i) == UInt(-1))
   //     AKANTU_ERROR("The corner node " << i + 1 << " wasn't found");
   // }
   AKANTU_DEBUG_OUT();
 }
 /* --------------------------------------------------------------------------
  */
 Real ASRTools::averageScalarField(const ID & field_name) {
   AKANTU_DEBUG_IN();
   auto & fem = model.getFEEngine("SolidMechanicsFEEngine");
   Real average = 0;
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
 
   GhostType gt = _not_ghost;
   for (auto element_type : mesh.elementTypes(dim, gt, _ek_not_defined)) {
     for (UInt m = 0; m < model.getNbMaterials(); ++m) {
       const auto & elem_filter =
           model.getMaterial(m).getElementFilter(element_type);
       if (!elem_filter.size())
         continue;
       const auto & scalar_field =
           model.getMaterial(m).getInternal<Real>(field_name)(element_type);
       Array<Real> int_scalar_vec(elem_filter.size(), 1, "int_of_scalar");
 
       fem.integrate(scalar_field, int_scalar_vec, 1, element_type, _not_ghost,
                     elem_filter);
 
       for (UInt k = 0; k < elem_filter.size(); ++k)
         average += int_scalar_vec(k);
     }
   }
 
   /// compute total model volume
   if (!this->volume)
     computeModelVolume();
 
   return average / this->volume;
 
   AKANTU_DEBUG_OUT();
 }
 
 /* --------------------------------------------------------------------------
  */
 Real ASRTools::averageTensorField(UInt row_index, UInt col_index,
                                   const ID & field_type) {
   AKANTU_DEBUG_IN();
   auto & fem = model.getFEEngine("SolidMechanicsFEEngine");
   Real average = 0;
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
 
   GhostType gt = _not_ghost;
   for (auto element_type : mesh.elementTypes(dim, gt, _ek_not_defined)) {
     if (field_type == "stress") {
       for (UInt m = 0; m < model.getNbMaterials(); ++m) {
         const auto & stress_vec = model.getMaterial(m).getStress(element_type);
         const auto & elem_filter =
             model.getMaterial(m).getElementFilter(element_type);
         Array<Real> int_stress_vec(elem_filter.size(), dim * dim,
                                    "int_of_stress");
 
         fem.integrate(stress_vec, int_stress_vec, dim * dim, element_type,
                       _not_ghost, elem_filter);
 
         for (UInt k = 0; k < elem_filter.size(); ++k)
           average +=
               int_stress_vec(k, row_index * dim + col_index); // 3 is the value
                                                               // for the yy (in
                                                               // 3D, the value
                                                               // is 4)
       }
     } else if (field_type == "strain") {
       for (UInt m = 0; m < model.getNbMaterials(); ++m) {
         const auto & gradu_vec = model.getMaterial(m).getGradU(element_type);
         const auto & elem_filter =
             model.getMaterial(m).getElementFilter(element_type);
         Array<Real> int_gradu_vec(elem_filter.size(), dim * dim,
                                   "int_of_gradu");
 
         fem.integrate(gradu_vec, int_gradu_vec, dim * dim, element_type,
                       _not_ghost, elem_filter);
 
         for (UInt k = 0; k < elem_filter.size(); ++k)
           /// averaging is done only for normal components, so stress and
           /// strain are equal
           average += 0.5 * (int_gradu_vec(k, row_index * dim + col_index) +
                             int_gradu_vec(k, col_index * dim + row_index));
       }
     } else if (field_type == "eigen_grad_u") {
       for (UInt m = 0; m < model.getNbMaterials(); ++m) {
         const auto & eigen_gradu_vec = model.getMaterial(m).getInternal<Real>(
             "eigen_grad_u")(element_type);
         const auto & elem_filter =
             model.getMaterial(m).getElementFilter(element_type);
         Array<Real> int_eigen_gradu_vec(elem_filter.size(), dim * dim,
                                         "int_of_gradu");
 
         fem.integrate(eigen_gradu_vec, int_eigen_gradu_vec, dim * dim,
                       element_type, _not_ghost, elem_filter);
 
         for (UInt k = 0; k < elem_filter.size(); ++k)
           /// averaging is done only for normal components, so stress and
           /// strain are equal
           average += int_eigen_gradu_vec(k, row_index * dim + col_index);
       }
     } else {
       AKANTU_ERROR("Averaging not implemented for this field!!!");
     }
   }
 
   /// compute total model volume
   if (!this->volume)
     computeModelVolume();
 
   return average / this->volume;
 
   AKANTU_DEBUG_OUT();
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::homogenizeStressField(Matrix<Real> & stress) {
   AKANTU_DEBUG_IN();
   stress(0, 0) = averageTensorField(0, 0, "stress");
   stress(1, 1) = averageTensorField(1, 1, "stress");
   stress(0, 1) = averageTensorField(0, 1, "stress");
   stress(1, 0) = averageTensorField(1, 0, "stress");
   AKANTU_DEBUG_OUT();
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::setStiffHomogenDir(Matrix<Real> & stress) {
   AKANTU_DEBUG_IN();
   auto dim = stress.rows();
   Vector<Real> eigenvalues(dim);
   stress.eig(eigenvalues);
   Real hydrostatic_stress = 0;
   UInt denominator = 0;
   for (UInt i = 0; i < dim; ++i, ++denominator)
     hydrostatic_stress += eigenvalues(i);
   hydrostatic_stress /= denominator;
   AKANTU_DEBUG_OUT();
   this->tensile_homogenization = (hydrostatic_stress > 0);
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::homogenizeStiffness(Matrix<Real> & C_macro, bool tensile_test) {
   AKANTU_DEBUG_IN();
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
 
   AKANTU_DEBUG_ASSERT(dim == 2, "Is only implemented for 2D!!!");
 
   /// apply three independent loading states to determine C
   /// 1. eps_el = (0.001;0;0) 2. eps_el = (0,0.001,0) 3. eps_el = (0,0,0.001)
 
   /// store and clear the eigenstrain
   ///(to exclude stresses due to internal pressure)
   Array<Real> stored_eig(0, dim * dim, 0);
   storeASREigenStrain(stored_eig);
   clearASREigenStrain();
 
   /// save nodal values before tests
   storeNodalFields();
 
   /// storage for results of 3 different loading states
   UInt voigt_size = 1;
   switch (dim) {
   case 2: {
     voigt_size = VoigtHelper<2>::size;
     break;
   }
   case 3: {
     voigt_size = VoigtHelper<3>::size;
     break;
   }
   }
   Matrix<Real> stresses(voigt_size, voigt_size, 0.);
   Matrix<Real> strains(voigt_size, voigt_size, 0.);
   Matrix<Real> H(dim, dim, 0.);
 
   /// save the damage state before filling up cracks
   // ElementTypeMapReal saved_damage("saved_damage");
   // saved_damage.initialize(getFEEngine(), _nb_component = 1,
   // _default_value = 0); this->fillCracks(saved_damage);
 
   /// indicate to material that its in the loading test
   UInt nb_materials = model.getNbMaterials();
   for (UInt m = 0; m < nb_materials; ++m) {
     Material & mat = model.getMaterial(m);
     if (aka::is_of_type<MaterialDamageIterativeOrthotropic<2>>(mat)) {
       mat.setParam("loading_test", true);
     }
   }
 
   /// virtual test 1:
   H(0, 0) = 0.001 * (2 * tensile_test - 1);
   performVirtualTesting(H, stresses, strains, 0);
 
   /// virtual test 2:
   H.zero();
   H(1, 1) = 0.001 * (2 * tensile_test - 1);
   performVirtualTesting(H, stresses, strains, 1);
 
   /// virtual test 3:
   H.zero();
   H(0, 1) = 0.001;
   H(1, 0) = 0.001;
   performVirtualTesting(H, stresses, strains, 2);
 
   /// indicate to material that its out of the loading test
   for (UInt m = 0; m < nb_materials; ++m) {
     Material & mat = model.getMaterial(m);
     if (aka::is_of_type<MaterialDamageIterativeOrthotropic<2>>(mat)) {
       mat.setParam("loading_test", false);
     }
   }
 
   /// drain cracks
   // this->drainCracks(saved_damage);
 
   /// compute effective stiffness
   Matrix<Real> eps_inverse(voigt_size, voigt_size);
   eps_inverse.inverse(strains);
 
   /// Make C matrix symmetric
   Matrix<Real> C_direct(voigt_size, voigt_size);
   C_direct.mul<false, false>(stresses, eps_inverse);
   for (UInt i = 0; i != voigt_size; ++i) {
     for (UInt j = 0; j != voigt_size; ++j) {
       C_macro(i, j) = 0.5 * (C_direct(i, j) + C_direct(j, i));
       C_macro(j, i) = C_macro(i, j);
     }
   }
 
   /// return the nodal values and the ASR eigenstrain
   restoreNodalFields();
   restoreASREigenStrain(stored_eig);
 
   AKANTU_DEBUG_OUT();
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::performVirtualTesting(const Matrix<Real> & H,
                                      Matrix<Real> & eff_stresses,
                                      Matrix<Real> & eff_strains,
                                      const UInt test_no) {
   AKANTU_DEBUG_IN();
   auto & disp = model.getDisplacement();
   auto & boun = model.getBlockedDOFs();
   auto & ext_force = model.getExternalForce();
   disp.clear();
   boun.clear();
   ext_force.clear();
 
   applyBoundaryConditionsRve(H);
 
   model.solveStep();
 
   /// get average stress and strain
   eff_stresses(0, test_no) = averageTensorField(0, 0, "stress");
   eff_strains(0, test_no) = averageTensorField(0, 0, "strain");
   eff_stresses(1, test_no) = averageTensorField(1, 1, "stress");
   eff_strains(1, test_no) = averageTensorField(1, 1, "strain");
   eff_stresses(2, test_no) = averageTensorField(1, 0, "stress");
   eff_strains(2, test_no) = 2. * averageTensorField(1, 0, "strain");
 
   /// restore historical internal fields
   restoreInternalFields();
 
   AKANTU_DEBUG_OUT();
 }
 
 /* --------------------------------------------------- */
 void ASRTools::homogenizeEigenGradU(Matrix<Real> & eigen_gradu_macro) {
   AKANTU_DEBUG_IN();
   eigen_gradu_macro(0, 0) = averageTensorField(0, 0, "eigen_grad_u");
   eigen_gradu_macro(1, 1) = averageTensorField(1, 1, "eigen_grad_u");
   eigen_gradu_macro(0, 1) = averageTensorField(0, 1, "eigen_grad_u");
   eigen_gradu_macro(1, 0) = averageTensorField(1, 0, "eigen_grad_u");
   AKANTU_DEBUG_OUT();
 }
 
 /* -------------------------------------------------------------------------
  */
 void ASRTools::fillCracks(ElementTypeMapReal & saved_damage) {
   const auto & mat_gel = model.getMaterial("gel");
   Real E_gel = mat_gel.get("E");
   Real E_homogenized = 0.;
 
   for (UInt m = 0; m < model.getNbMaterials(); ++m) {
     Material & mat = model.getMaterial(m);
     if (mat.getName() == "gel" || mat.getName() == "FE2_mat")
       continue;
 
     Real E = mat.get("E");
     auto & damage = mat.getInternal<Real>("damage");
 
     GhostType gt = _not_ghost;
     for (auto && type : damage.elementTypes(gt)) {
       const auto & elem_filter = mat.getElementFilter(type);
       auto nb_integration_point =
           model.getFEEngine().getNbIntegrationPoints(type);
 
       auto sav_dam_it =
           make_view(saved_damage(type), nb_integration_point).begin();
       for (auto && data :
            zip(elem_filter, make_view(damage(type), nb_integration_point))) {
         auto el = std::get<0>(data);
         auto & dam = std::get<1>(data);
         Vector<Real> sav_dam = sav_dam_it[el];
 
         sav_dam = dam;
 
         for (auto q : arange(dam.size())) {
           E_homogenized = (E_gel - E) * dam(q) + E;
           dam(q) = 1. - (E_homogenized / E);
         }
       }
     }
   }
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::drainCracks(const ElementTypeMapReal & saved_damage) {
   for (UInt m = 0; m < model.getNbMaterials(); ++m) {
     Material & mat = model.getMaterial(m);
     if (mat.getName() == "gel" || mat.getName() == "FE2_mat")
       continue;
     auto & damage = mat.getInternal<Real>("damage");
 
     GhostType gt = _not_ghost;
     for (auto && type : damage.elementTypes(gt)) {
       const auto & elem_filter = mat.getElementFilter(type);
       auto nb_integration_point =
           model.getFEEngine().getNbIntegrationPoints(type);
 
       auto sav_dam_it =
           make_view(saved_damage(type), nb_integration_point).begin();
       for (auto && data :
            zip(elem_filter, make_view(damage(type), nb_integration_point))) {
         auto el = std::get<0>(data);
         auto & dam = std::get<1>(data);
         Vector<Real> sav_dam = sav_dam_it[el];
 
         dam = sav_dam;
       }
     }
   }
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::computeDamageRatio(Real & damage_ratio) {
   damage_ratio = 0.;
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   for (UInt m = 0; m < model.getNbMaterials(); ++m) {
     Material & mat = model.getMaterial(m);
     if (mat.getName() == "gel" || mat.getName() == "FE2_mat")
       continue;
     GhostType gt = _not_ghost;
     const ElementTypeMapArray<UInt> & filter_map = mat.getElementFilter();
 
     // Loop over the boundary element types
     for (auto & element_type : filter_map.elementTypes(dim, gt)) {
       const Array<UInt> & filter = filter_map(element_type);
       if (!filter_map.exists(element_type, gt))
         continue;
       if (filter.size() == 0)
         continue;
 
       const FEEngine & fe_engine = model.getFEEngine();
       auto & damage_array = mat.getInternal<Real>("damage")(element_type);
 
       damage_ratio +=
           fe_engine.integrate(damage_array, element_type, gt, filter);
     }
   }
 
   /// do not communicate if model is multi-scale
   if (not aka::is_of_type<SolidMechanicsModelRVE>(model)) {
     auto && comm = akantu::Communicator::getWorldCommunicator();
     comm.allReduce(damage_ratio, SynchronizerOperation::_sum);
   }
 
   /// compute total model volume
   if (!this->volume)
     computeModelVolume();
 
   damage_ratio /= this->volume;
 }
 /* --------------------------------------------------------------------------
  */
 void ASRTools::computeDamageRatioPerMaterial(Real & damage_ratio,
                                              const ID & material_name) {
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   GhostType gt = _not_ghost;
   Material & mat = model.getMaterial(material_name);
   const ElementTypeMapArray<UInt> & filter_map = mat.getElementFilter();
   const FEEngine & fe_engine = model.getFEEngine();
   damage_ratio = 0.;
 
   // Loop over the boundary element types
   for (auto & element_type : filter_map.elementTypes(dim, gt)) {
     const Array<UInt> & filter = filter_map(element_type);
     if (!filter_map.exists(element_type, gt))
       continue;
     if (filter.size() == 0)
       continue;
 
     auto & damage_array = mat.getInternal<Real>("damage")(element_type);
 
     damage_ratio += fe_engine.integrate(damage_array, element_type, gt, filter);
   }
 
   /// do not communicate if model is multi-scale
   if (not aka::is_of_type<SolidMechanicsModelRVE>(model)) {
     auto && comm = akantu::Communicator::getWorldCommunicator();
     comm.allReduce(damage_ratio, SynchronizerOperation::_sum);
   }
 
   if (not this->phase_volumes.size())
     computePhaseVolumes();
   damage_ratio /= this->phase_volumes[material_name];
 }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::computeCrackVolume(Real & crack_volume_ratio) {
   crack_volume_ratio = 0.;
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   for (UInt m = 0; m < model.getNbMaterials(); ++m) {
     Material & mat = model.getMaterial(m);
     if (mat.getName() == "gel" || mat.getName() == "FE2_mat")
       continue;
     GhostType gt = _not_ghost;
     const ElementTypeMapArray<UInt> & filter_map = mat.getElementFilter();
 
     // Loop over the boundary element types
     for (auto & element_type : filter_map.elementTypes(dim, gt)) {
       const Array<UInt> & filter = filter_map(element_type);
       if (!filter_map.exists(element_type, gt))
         continue;
       if (filter.size() == 0)
         continue;
 
       auto extra_volume_copy =
           mat.getInternal<Real>("extra_volume")(element_type);
       crack_volume_ratio += Math::reduce(extra_volume_copy);
     }
   }
 
   /// do not communicate if model is multi-scale
   if (not aka::is_of_type<SolidMechanicsModelRVE>(model)) {
     auto && comm = akantu::Communicator::getWorldCommunicator();
     comm.allReduce(crack_volume_ratio, SynchronizerOperation::_sum);
   }
 
   /// compute total model volume
   if (!this->volume)
     computeModelVolume();
 
   crack_volume_ratio /= this->volume;
 }
 /* --------------------------------------------------------------------------
  */
 void ASRTools::computeCrackVolumePerMaterial(Real & crack_volume,
                                              const ID & material_name) {
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   GhostType gt = _not_ghost;
   Material & mat = model.getMaterial(material_name);
   const ElementTypeMapArray<UInt> & filter_map = mat.getElementFilter();
   crack_volume = 0.;
 
   // Loop over the boundary element types
   for (auto & element_type : filter_map.elementTypes(dim, gt)) {
     const Array<UInt> & filter = filter_map(element_type);
     if (!filter_map.exists(element_type, gt))
       continue;
     if (filter.size() == 0)
       continue;
 
     auto extra_volume_copy =
         mat.getInternal<Real>("extra_volume")(element_type);
 
     crack_volume += Math::reduce(extra_volume_copy);
   }
 
   /// do not communicate if model is multi-scale
   if (not aka::is_of_type<SolidMechanicsModelRVE>(model)) {
     auto && comm = akantu::Communicator::getWorldCommunicator();
     comm.allReduce(crack_volume, SynchronizerOperation::_sum);
   }
 
   if (not this->phase_volumes.size())
     computePhaseVolumes();
   crack_volume /= this->phase_volumes[material_name];
 }
 
 /* ------------------------------------------------------------------------ */
 void ASRTools::dumpRve() { model.dump(); }
 
 /* ------------------------------------------------------------------------ */
 // void ASRTools::applyBodyForce(const Real gravity = 9.81) {
 //   auto spatial_dimension = model.getSpatialDimension();
 //   model.assembleMassLumped();
 //   auto & mass = model.getMass();
 //   auto & force = model.getExternalForce();
 //   Vector<Real> gravity(spatial_dimension);
 //   gravity(1) = -gravity;
 
 //   for (auto && data : zip(make_view(mass, spatial_dimension),
 //                           make_view(force, spatial_dimension))) {
 
 //     const auto & mass_vec = (std::get<0>(data));
 //     AKANTU_DEBUG_ASSERT(mass_vec.norm(), "Mass vector components are zero");
 //     auto & force_vec = (std::get<1>(data));
 
 //     force_vec += gravity * mass_vec;
 //   }
 // }
 
 /* ------------------------------------------------------------------- */
 void ASRTools::insertASRCohesivesRandomly(const UInt & nb_insertions,
                                           std::string facet_mat_name,
                                           Real gap_ratio) {
   AKANTU_DEBUG_IN();
 
   // fill in the partition_border_nodes array
   communicateFlagsOnNodes();
 
   /// pick central facets and neighbors
   pickFacetsRandomly(nb_insertions, facet_mat_name);
 
   insertOppositeFacetsAndCohesives();
 
   assignCrackNumbers();
 
   insertGap(gap_ratio);
 
   preventCohesiveInsertionInNeighbors();
 
   AKANTU_DEBUG_OUT();
 }
 /* ------------------------------------------------------------------- */
 void ASRTools::insertASRCohesivesRandomly3D(const UInt & nb_insertions,
                                             std::string facet_mat_name,
                                             Real /*gap_ratio*/) {
   AKANTU_DEBUG_IN();
 
   // fill in the partition_border_nodes array
   communicateFlagsOnNodes();
 
   /// pick central facets and neighbors
   pickFacetsRandomly(nb_insertions, facet_mat_name);
 
   insertOppositeFacetsAndCohesives();
 
   assignCrackNumbers();
 
   // insertGap3D(gap_ratio);
 
   preventCohesiveInsertionInNeighbors();
 
   AKANTU_DEBUG_OUT();
 }
 
 /* ------------------------------------------------------------------- */
 void ASRTools::insertASRCohesiveLoops3D(const UInt & nb_insertions,
                                         std::string facet_mat_name,
                                         Real /*gap_ratio*/) {
   AKANTU_DEBUG_IN();
 
   // fill in the partition_border_nodes array
   communicateFlagsOnNodes();
 
   /// pick central facets and neighbors
   auto inserted = closedFacetsLoopAroundPoint(nb_insertions, facet_mat_name);
 
   auto && comm = akantu::Communicator::getWorldCommunicator();
   comm.allReduce(inserted, SynchronizerOperation::_sum);
   AKANTU_DEBUG_ASSERT(inserted, "No ASR sites were inserted across the domain");
 
   insertOppositeFacetsAndCohesives();
 
   assignCrackNumbers();
 
   communicateCrackNumbers();
 
   // insertGap3D(gap_ratio);
 
   // preventCohesiveInsertionInNeighbors();
 
   AKANTU_DEBUG_OUT();
 }
 
 /* -------------------------------------------------------------------------
  */
 void ASRTools::insertASRCohesivesByCoords(const Matrix<Real> & positions,
                                           Real gap_ratio) {
   AKANTU_DEBUG_IN();
 
   // fill in the border_nodes array
   communicateFlagsOnNodes();
 
   /// pick the facets to duplicate
   pickFacetsByCoord(positions);
 
   insertOppositeFacetsAndCohesives();
 
   assignCrackNumbers();
 
   insertGap(gap_ratio);
 
   preventCohesiveInsertionInNeighbors();
 
   AKANTU_DEBUG_OUT();
 }
 /* -------------------------------------------------------------------------
  */
 void ASRTools::communicateFlagsOnNodes() {
   auto & mesh = model.getMesh();
   auto & synch = mesh.getElementSynchronizer();
   NodesFlagUpdater nodes_flag_updater(mesh, synch,
                                       this->partition_border_nodes);
   nodes_flag_updater.fillPreventInsertion();
 }
-/* -------------------------------------------------------------------------
- */
+/* ------------------------------------------------------------------- */
+void ASRTools::communicateEffStressesOnNodes() {
+  auto & mesh = model.getMesh();
+  auto & synch = mesh.getElementSynchronizer();
+  NodesEffStressUpdater nodes_eff_stress_updater(mesh, synch,
+                                                 this->nodes_eff_stress);
+  nodes_eff_stress_updater.updateMaxEffStressAtNodes();
+}
+/* ------------------------------------------------------------------- */
 void ASRTools::communicateCrackNumbers() {
   AKANTU_DEBUG_IN();
 
   if (model.getMesh().getCommunicator().getNbProc() == 1)
     return;
 
   auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
   const auto & coh_synch = coh_model.getCohesiveSynchronizer();
   CrackNumbersUpdater crack_numbers_updater(model, coh_synch);
   crack_numbers_updater.communicateCrackNumbers();
   AKANTU_DEBUG_OUT();
 }
 /* ----------------------------------------------------------------------- */
 void ASRTools::pickFacetsByCoord(const Matrix<Real> & positions) {
   auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
   auto & inserter = coh_model.getElementInserter();
   auto & mesh = model.getMesh();
   auto & mesh_facets = inserter.getMeshFacets();
   auto dim = mesh.getSpatialDimension();
   const GhostType gt = _not_ghost;
   auto type = *mesh.elementTypes(dim, gt, _ek_regular).begin();
   auto facet_type = Mesh::getFacetType(type);
   auto & doubled_facets =
       mesh_facets.createElementGroup("doubled_facets", dim - 1);
   auto & doubled_nodes = mesh.createNodeGroup("doubled_nodes");
   auto & facet_conn = mesh_facets.getConnectivity(facet_type, gt);
   auto & check_facets = inserter.getCheckFacets(facet_type, gt);
   const UInt nb_nodes_facet = facet_conn.getNbComponent();
   const auto facet_conn_it = make_view(facet_conn, nb_nodes_facet).begin();
   auto && comm = akantu::Communicator::getWorldCommunicator();
   auto prank = comm.whoAmI();
   Vector<Real> bary_facet(dim);
 
   for (const auto & position : positions) {
     Real min_dist = std::numeric_limits<Real>::max();
     Element cent_facet;
     cent_facet.ghost_type = gt;
     for_each_element(
         mesh_facets,
         [&](auto && facet) {
           if (check_facets(facet.element)) {
             mesh_facets.getBarycenter(facet, bary_facet);
             auto dist = std::abs(bary_facet.distance(position));
             if (dist < min_dist) {
               min_dist = dist;
               cent_facet = facet;
             }
           }
         },
         _spatial_dimension = dim - 1, _ghost_type = _not_ghost);
 
     /// communicate between processors for the element closest to the position
     auto local_min_dist = min_dist;
     auto && comm = akantu::Communicator::getWorldCommunicator();
     comm.allReduce(min_dist, SynchronizerOperation::_min);
 
     /// let other processor insert the cohesive at this position
     if (local_min_dist != min_dist)
       continue;
 
     /// eliminate possibility of inserting on a partition border
     bool border_facets{false};
     Vector<UInt> facet_nodes = facet_conn_it[cent_facet.element];
     for (auto node : facet_nodes) {
       if (this->partition_border_nodes(node))
         border_facets = true;
     }
     if (border_facets) {
       std::cout << "Facet at the position " << position(0) << "," << position(1)
                 << " is close to the partition border. Skipping this position"
                 << std::endl;
       continue;
     }
 
     /// eliminate possibility of intersecting existing ASR element
     bool intersection{false};
     for (auto node : facet_nodes) {
       if (this->ASR_nodes(node))
         intersection = true;
     }
     if (intersection) {
       std::cout
           << "Facet at the position " << position(0) << "," << position(1)
           << " is intersecting another ASR element. Skipping this position"
           << std::endl;
       continue;
     } else {
       auto success_with_neighbors = pickFacetNeighbors(cent_facet);
 
       if (success_with_neighbors) {
         doubled_facets.add(cent_facet);
         /// add all facet nodes to the group
         for (auto node : arange(nb_nodes_facet)) {
           doubled_nodes.add(facet_conn(cent_facet.element, node));
           this->ASR_nodes(facet_conn(cent_facet.element, node)) = true;
         }
         std::cout << "Proc " << prank << " placed 1 ASR site" << std::endl;
         continue;
       } else
         continue;
     }
   }
 }
 /* ------------------------------------------------------------------- */
 void ASRTools::pickFacetsRandomly(UInt nb_insertions,
                                   std::string facet_mat_name) {
   auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
   auto & inserter = coh_model.getElementInserter();
   auto & mesh = model.getMesh();
   auto & mesh_facets = inserter.getMeshFacets();
   auto dim = mesh.getSpatialDimension();
   const GhostType gt = _not_ghost;
   auto type = *mesh.elementTypes(dim, gt, _ek_regular).begin();
   auto facet_type = Mesh::getFacetType(type);
   auto & doubled_facets =
       mesh_facets.createElementGroup("doubled_facets", dim - 1);
   auto & doubled_nodes = mesh.createNodeGroup("doubled_nodes");
   auto & matrix_elements =
       mesh_facets.createElementGroup("matrix_facets", dim - 1);
   auto facet_material_id = model.getMaterialIndex(facet_mat_name);
   auto & facet_conn = mesh_facets.getConnectivity(facet_type, gt);
   const UInt nb_nodes_facet = facet_conn.getNbComponent();
   const auto facet_conn_it = make_view(facet_conn, nb_nodes_facet).begin();
   auto & check_facets = inserter.getCheckFacets(facet_type, gt);
   auto && comm = akantu::Communicator::getWorldCommunicator();
   auto prank = comm.whoAmI();
 
   if (not nb_insertions)
     return;
 
   Vector<Real> bary_facet(dim);
   for_each_element(
       mesh_facets,
       [&](auto && facet) {
         if (check_facets(facet.element)) {
           mesh_facets.getBarycenter(facet, bary_facet);
           auto & facet_material = coh_model.getFacetMaterial(
               facet.type, facet.ghost_type)(facet.element);
           if (facet_material == facet_material_id)
             matrix_elements.add(facet);
         }
       },
       _spatial_dimension = dim - 1, _ghost_type = _not_ghost);
 
   UInt nb_element = matrix_elements.getElements(facet_type).size();
   std::mt19937 random_generator(0);
   std::uniform_int_distribution<> dis(0, nb_element - 1);
 
   if (not nb_element) {
     std::cout << "Proc " << prank << " couldn't place " << nb_insertions
               << " ASR sites" << std::endl;
     return;
   }
 
   UInt already_inserted = 0;
   while (already_inserted < nb_insertions) {
     auto id = dis(random_generator);
 
     Element cent_facet;
     cent_facet.type = facet_type;
     cent_facet.element = matrix_elements.getElements(facet_type)(id);
     cent_facet.ghost_type = gt;
 
     /// eliminate possibility of inserting on a partition border or intersecting
     bool border_facets{false};
     Vector<UInt> facet_nodes = facet_conn_it[cent_facet.element];
     for (auto node : facet_nodes) {
       if (this->partition_border_nodes(node) or this->ASR_nodes(node))
         border_facets = true;
     }
     if (border_facets)
       continue;
     else {
       auto success_with_neighbors = pickFacetNeighbors(cent_facet);
 
       if (success_with_neighbors) {
         already_inserted++;
         doubled_facets.add(cent_facet);
         /// add all facet nodes to the group
         for (auto node : arange(nb_nodes_facet)) {
           doubled_nodes.add(facet_conn(cent_facet.element, node));
           this->ASR_nodes(facet_conn(cent_facet.element, node)) = true;
         }
         std::cout << "Proc " << prank << " placed 1 ASR site" << std::endl;
         continue;
       } else
         continue;
     }
   }
 }
 /* ------------------------------------------------------------------- */
 UInt ASRTools::closedFacetsLoopAroundPoint(UInt nb_insertions,
                                            std::string mat_name) {
   auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
   auto & inserter = coh_model.getElementInserter();
   auto & mesh = model.getMesh();
   auto & mesh_facets = inserter.getMeshFacets();
   auto dim = mesh.getSpatialDimension();
   const GhostType gt = _not_ghost;
   auto element_type = *mesh.elementTypes(dim, gt, _ek_regular).begin();
   auto facet_type = Mesh::getFacetType(element_type);
   auto & doubled_facets =
       mesh_facets.createElementGroup("doubled_facets", dim - 1);
   auto & doubled_nodes = mesh.createNodeGroup("doubled_nodes");
   auto material_id = model.getMaterialIndex(mat_name);
   auto & facet_conn = mesh_facets.getConnectivity(facet_type, gt);
   const UInt nb_nodes_facet = facet_conn.getNbComponent();
   const auto facet_conn_it = make_view(facet_conn, nb_nodes_facet).begin();
+  auto & node_conn = mesh_facets.getConnectivity(_point_1, gt);
   auto && comm = akantu::Communicator::getWorldCommunicator();
   auto prank = comm.whoAmI();
 
+  const Real max_dot{0.7};
+
   if (not nb_insertions)
     return 0;
 
   CSR<Element> nodes_to_facets;
   MeshUtils::buildNode2Elements(mesh_facets, nodes_to_facets, dim - 1);
 
   std::set<UInt> matrix_nodes;
 
   for_each_element(
       mesh_facets,
-      [&](auto && node) {
+      [&](auto && point_el) {
+        // get the node number
+        auto node = node_conn(point_el.element);
         // discard ghost nodes
-        // if (not mesh.isLocalOrMasterNode(node.element))
-        if (mesh.isPureGhostNode(node.element))
+        if (mesh.isPureGhostNode(node))
           goto nextnode;
-        for (auto & facet : nodes_to_facets.getRow(node.element)) {
+        for (auto & facet : nodes_to_facets.getRow(node)) {
           auto & facet_material = coh_model.getFacetMaterial(
               facet.type, facet.ghost_type)(facet.element);
           if (facet_material != material_id) {
             goto nextnode;
           }
         }
-        matrix_nodes.emplace(node.element);
+        matrix_nodes.emplace(node);
       nextnode:;
       },
       _spatial_dimension = 0, _ghost_type = _not_ghost);
 
   UInt nb_nodes = matrix_nodes.size();
   std::mt19937 random_generator(0);
   std::uniform_int_distribution<> dis(0, nb_nodes - 1);
 
   if (not nb_nodes) {
     std::cout << "Proc " << prank << " couldn't place " << nb_insertions
               << " ASR sites" << std::endl;
     return 0;
   }
 
   UInt already_inserted = 0;
   std::set<UInt> left_nodes = matrix_nodes;
   while (already_inserted < nb_insertions) {
 
     if (not left_nodes.size()) {
       std::cout << "Proc " << prank << " inserted " << already_inserted
                 << " ASR sites out of " << nb_insertions << std::endl;
       return already_inserted;
     }
     auto id = dis(random_generator);
     auto it = matrix_nodes.begin();
     std::advance(it, id);
 
     UInt cent_node(*it);
     left_nodes.erase(*it);
 
     // not touching other cracks
     if (this->ASR_nodes(cent_node))
       continue;
 
     // поехали
     CSR<Element> segments_to_nodes;
     MeshUtils::buildNode2Elements(mesh_facets, segments_to_nodes, dim - 2);
     auto && segments_to_node = segments_to_nodes.getRow(cent_node);
     Array<Element> segments_list;
     for (auto && segment : segments_to_node) {
       segments_list.push_back(segment);
     }
 
     // pick the first non-ghost segment in the list
     Element starting_segment;
     for (auto segment : segments_list) {
       if (segment.ghost_type == _not_ghost) {
         starting_segment = segment;
         break;
       }
     }
 
     // go to next node if starting segment could not be picked
     if (starting_segment == ElementNull) {
       std::cout
           << "Could not pick the starting segment, switching to the next node"
           << std::endl;
       continue;
     }
 
     auto & facets_to_segment =
         mesh_facets.getElementToSubelement(starting_segment);
     // pick the first good facet in the list
     Element starting_facet;
     for (const auto & facet : facets_to_segment) {
       // check if the facet and its nodes are ok
-      if (isFacetAndNodesGood(facet, material_id)) {
+      if (isFacetAndNodesGood(facet, material_id, true)) {
         starting_facet = facet;
         break;
       }
     }
 
     // go to next node if starting facet could not be picked
     if (starting_facet == ElementNull) {
       std::cout
           << "Could not pick the starting facet, switching to the next node"
           << std::endl;
       continue;
     }
-    // loop through facets to arrive to the starting segment
-    bool loop_closed{false};
-    auto current_facet(starting_facet);
-    auto current_segment(starting_segment);
-    Array<Element> facets_in_loop;
-    Array<Element> segments_in_loop;
-    while (not loop_closed) {
-      facets_in_loop.push_back(current_facet);
-      segments_in_loop.push_back(current_segment);
-      const Vector<Element> current_segments =
-          mesh_facets.getSubelementToElement(current_facet);
-      // identify the border segment
-      Element border_segment(ElementNull);
-      for (auto & segment : current_segments) {
-        if (segment == current_segment)
-          continue;
-        // check if the segment includes the central node
-        auto && nodes_to_segment = mesh_facets.getConnectivity(segment);
-        bool includes_cent_node{false};
-        for (auto & node : nodes_to_segment) {
-          if (node == cent_node) {
-            includes_cent_node = true;
-            break;
-          }
-        }
-        if (not includes_cent_node)
-          continue;
 
-        // check if the segment was previously added
-        if (segments_in_loop.find(segment) != UInt(-1))
-          continue;
+    // call the actual function that build the bridge
+    auto facets_in_loop = findFacetsLoopFromSegment2Segment(
+        starting_facet, starting_segment, starting_segment, cent_node, max_dot,
+        material_id, true);
 
-        border_segment = segment;
-        segments_in_loop.push_back(border_segment);
-        break;
-      }
-      if (border_segment == ElementNull) {
-        std::cout << "Could not close the loop, switching to the next node"
-                  << std::endl;
-        break;
+    // loop broke -> try another point
+    if (not facets_in_loop.size()) {
+      std::cout << "Couldn't close the facet loop, taking the next node"
+                << std::endl;
+      continue;
+    }
+
+    // otherwise champaigne
+    for (auto & facet : facets_in_loop) {
+      doubled_facets.add(facet);
+      /// add all facet nodes to the group
+      for (auto node : arange(nb_nodes_facet)) {
+        doubled_nodes.add(facet_conn(facet.element, node));
+        this->ASR_nodes(facet_conn(facet.element, node)) = true;
       }
+    }
+    // store the asr facets
+    this->ASR_facets_from_mesh_facets.push_back(facets_in_loop);
+    already_inserted++;
+  }
+  std::cout << "Proc " << prank << " placed " << already_inserted << " ASR site"
+            << std::endl;
+  return already_inserted;
+}
+/* ------------------------------------------------------------------- */
+Array<Element> ASRTools::findFacetsLoopFromSegment2Segment(
+    Element directional_facet, Element starting_segment, Element ending_segment,
+    UInt cent_node, Real max_dot, UInt material_id, bool check_asr_nodes) {
 
-      // loop through all the neighboring facets and pick the prettiest
-      Element next_facet(ElementNull);
-      Real max_dot(0.7);
-      // Real max_dot(std ::numeric_limits<Real>::min());
-      Real current_facet_indiam =
-          MeshUtils::getInscribedCircleDiameter(model, current_facet);
-      bool almost_closed{false};
-      auto neighbor_facets = mesh_facets.getElementToSubelement(border_segment);
-      for (auto neighbor_facet : neighbor_facets) {
-        if (neighbor_facet == current_facet)
-          continue;
-        if (not isFacetAndNodesGood(neighbor_facet, material_id))
-          continue;
-        // first check if it has the starting segment -> loop closed
-        bool starting_segment_touched{false};
+  auto & mesh = model.getMesh();
+  auto & mesh_facets = mesh.getMeshFacets();
+  auto dim = mesh.getSpatialDimension();
+  AKANTU_DEBUG_ASSERT(
+      dim == 3, "findFacetsLoopFromSegment2Segment currently supports only 3D");
+  AKANTU_DEBUG_ASSERT(Mesh::getSpatialDimension(directional_facet.type) ==
+                          dim - 1,
+                      "Directional facet provided has wrong spatial dimension");
+
+  if (Mesh::getSpatialDimension(starting_segment.type) != dim - 2 or
+      Mesh::getSpatialDimension(ending_segment.type) != dim - 2) {
+    AKANTU_EXCEPTION("Starting facet provided has wrong spatial dimension");
+  }
+  // get the point el corresponding to the central node
+  CSR<Element> points_to_nodes;
+  MeshUtils::buildNode2Elements(mesh_facets, points_to_nodes, 0);
+  auto && points_to_node = points_to_nodes.getRow(cent_node);
+  auto point_el = *points_to_node.begin();
+
+  AKANTU_DEBUG_ASSERT(point_el.type == _point_1,
+                      "Provided node element type is wrong");
+
+  // check if the provided node is shared by two segments
+  auto & segments_to_node = mesh_facets.getElementToSubelement(point_el);
+  auto ret1 = std::find(segments_to_node.begin(), segments_to_node.end(),
+                        starting_segment);
+  AKANTU_DEBUG_ASSERT(ret1 != segments_to_node.end(),
+                      "Starting segment doesn't contain the central node");
+  auto ret2 = std::find(segments_to_node.begin(), segments_to_node.end(),
+                        ending_segment);
+  AKANTU_DEBUG_ASSERT(ret2 != segments_to_node.end(),
+                      "Ending segment doesn't contain the central node");
+
+  // check if the directional facet contain starting segment
+  auto & facets_to_segment =
+      mesh_facets.getElementToSubelement(starting_segment);
+  auto ret3 = std::find(facets_to_segment.begin(), facets_to_segment.end(),
+                        directional_facet);
+  AKANTU_DEBUG_ASSERT(ret3 != facets_to_segment.end(),
+                      "Starting facet doesn't contain the starting segment");
+
+  // loop through facets to arrive from the starting segment to the endin
+  bool loop_closed{false};
+  auto current_facet(directional_facet);
+  auto border_segment(starting_segment);
+  Array<Element> facets_in_loop;
+  Array<Element> segments_in_loop;
+  while (not loop_closed) {
+
+    // loop through all the neighboring facets and pick the prettiest
+    Element next_facet(ElementNull);
+    Real current_facet_indiam =
+        MeshUtils::getInscribedCircleDiameter(model, current_facet);
+    bool almost_closed{false};
+    auto neighbor_facets = mesh_facets.getElementToSubelement(border_segment);
+    for (auto neighbor_facet : neighbor_facets) {
+      if (neighbor_facet == current_facet)
+        continue;
+      if (not isFacetAndNodesGood(neighbor_facet, material_id, check_asr_nodes))
+        continue;
+      // first check if it has the ending segment -> loop closed
+      bool ending_segment_touched{false};
+      if (facets_in_loop.size()) {
         const Vector<Element> neighbors_segments =
             mesh_facets.getSubelementToElement(neighbor_facet);
-        for (auto neighbors_segment : neighbors_segments) {
-          if (neighbors_segment == starting_segment) {
-            starting_segment_touched = true;
-            almost_closed = true;
-          }
+        auto ret = std::find(neighbors_segments.begin(),
+                             neighbors_segments.end(), ending_segment);
+        if (ret != neighbors_segments.end()) {
+          ending_segment_touched = true;
+          almost_closed = true;
+        }
+      }
+
+      // conditions on the angle between facets
+      Real neighbor_facet_indiam =
+          MeshUtils::getInscribedCircleDiameter(model, neighbor_facet);
+      Real hypotenuse = sqrt(neighbor_facet_indiam * neighbor_facet_indiam +
+                             current_facet_indiam * current_facet_indiam) /
+                        2;
+      auto dist = MeshUtils::distanceBetweenIncentersCorrected(
+          mesh_facets, current_facet, neighbor_facet);
+      if (dist < hypotenuse) {
+        continue;
+      }
+
+      // get abs of dot product between two normals
+      Real dot = MeshUtils::cosSharpAngleBetween2Facets(model, current_facet,
+                                                        neighbor_facet);
+      if (dot > max_dot) {
+        next_facet = neighbor_facet;
+        if (ending_segment_touched) {
+          facets_in_loop.push_back(neighbor_facet);
+          loop_closed = true;
+          break;
+        }
+      }
+    }
+
+    if (next_facet == ElementNull) {
+      facets_in_loop.clear();
+      return facets_in_loop;
+    }
+
+    if (almost_closed & not loop_closed) {
+      facets_in_loop.clear();
+      return facets_in_loop;
+    }
+
+    if (loop_closed) {
+      return facets_in_loop;
+    }
+
+    // otherwise make this facet a current one
+    current_facet = next_facet;
+    facets_in_loop.push_back(current_facet);
+    segments_in_loop.push_back(border_segment);
+
+    // identify the next border segment
+    auto previous_border_segment = border_segment;
+    border_segment = ElementNull;
+    const Vector<Element> current_segments =
+        mesh_facets.getSubelementToElement(current_facet);
+    for (auto & segment : current_segments) {
+      if (segment == previous_border_segment)
+        continue;
+      // check if the segment includes the central node
+      auto && nodes_to_segment = mesh_facets.getConnectivity(segment);
+      bool includes_cent_node{false};
+      for (auto & node : nodes_to_segment) {
+        if (node == cent_node) {
+          includes_cent_node = true;
+          break;
         }
+      }
+      if (not includes_cent_node)
+        continue;
+
+      // check if the segment was previously added
+      if (segments_in_loop.find(segment) != UInt(-1))
+        continue;
+
+      border_segment = segment;
+      break;
+    }
+    if (border_segment == ElementNull) {
+      facets_in_loop.clear();
+      return facets_in_loop;
+    }
+  }
+  return facets_in_loop;
+}
+/* ------------------------------------------------------------------- */
+Array<Element>
+ASRTools::findFacetsLoopByGraphByDist(const Array<Element> & limit_facets,
+                                      const Array<Element> & limit_segments,
+                                      const UInt & cent_node) {
+
+  auto & mesh = model.getMesh();
+  auto & mesh_facets = mesh.getMeshFacets();
+  auto dim = mesh.getSpatialDimension();
+  Array<Element> facets_in_loop;
+  auto && starting_facet = limit_facets(0);
+  auto && ending_facet = limit_facets(1);
+  auto && starting_segment = limit_segments(0);
+  auto && ending_segment = limit_segments(1);
+  Real max_dist = std::numeric_limits<Real>::min();
+
+  // Create a struct to hold properties for each vertex
+  typedef struct vertex_properties {
+    Element segment;
+  } vertex_properties_t;
+
+  // Create a struct to hold properties for each edge
+  typedef struct edge_properties {
+    Element facet;
+    Real weight;
+  } edge_properties_t;
+
+  // Define the type of the graph
+  typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS,
+                                vertex_properties_t, edge_properties_t>
+      graph_t;
+  typedef graph_t::vertex_descriptor vertex_descriptor_t;
+  typedef graph_t::edge_descriptor edge_descriptor_t;
+  typedef boost::graph_traits<graph_t>::edge_iterator edge_iter;
+  typedef boost::graph_traits<graph_t>::vertex_iterator vertex_iter;
+  graph_t g;
+
+  // prepare the list of facets sharing the central node
+  CSR<Element> facets_to_nodes;
+  MeshUtils::buildNode2Elements(mesh_facets, facets_to_nodes, dim - 1);
+  auto && facets_to_node = facets_to_nodes.getRow(cent_node);
+  std::set<Element> facets_added;
+  std::map<Element, vertex_descriptor_t> segment_vertex_map;
+
+  // get the point el corresponding to the central node
+  CSR<Element> points_to_nodes;
+  MeshUtils::buildNode2Elements(mesh_facets, points_to_nodes, 0);
+  auto && points_to_node = points_to_nodes.getRow(cent_node);
+  auto point_el = *points_to_node.begin();
+  // get list of all segments connected to the node
+  auto && segm_to_point = mesh_facets.getElementToSubelement(point_el);
+
+  // add starting and ending segment to the graph
+  vertex_descriptor_t v_start = boost::add_vertex(g);
+  g[v_start].segment = starting_segment;
+  vertex_descriptor_t v_end = boost::add_vertex(g);
+  g[v_end].segment = ending_segment;
+  segment_vertex_map[starting_segment] = v_start;
+  segment_vertex_map[ending_segment] = v_end;
+
+  for (auto && facet : facets_to_node) {
+    // discard undesired facets first
+    if (facet == starting_facet or facet == ending_facet)
+      continue;
+    if (not isFacetAndNodesGood(facet))
+      continue;
+    if (belong2SameElement(starting_facet, facet) or
+        belong2SameElement(ending_facet, facet))
+      continue;
+
+    // identify 2 side facets
+    Array<Element> side_segments;
+    auto && facet_segments = mesh_facets.getSubelementToElement(facet);
+    for (auto & segment : facet_segments) {
+      auto ret = std::find(segm_to_point.begin(), segm_to_point.end(), segment);
+      if (ret != segm_to_point.end()) {
+        side_segments.push_back(segment);
+      }
+    }
+    AKANTU_DEBUG_ASSERT(side_segments.size() == 2,
+                        "Number of side facets is wrong");
+
+    // add corresponding vertex (if not there)and edge into graph
+    Array<vertex_descriptor_t> v(2);
+    for (UInt i = 0; i < 2; i++) {
+      auto it = segment_vertex_map.find(side_segments(i));
+      if (it != segment_vertex_map.end()) {
+        v(i) = it->second;
+      } else {
+        v(i) = boost::add_vertex(g);
+        g[v(i)].segment = side_segments(i);
+        segment_vertex_map[side_segments(i)] = v(i);
+      }
+    }
+
+    // add corresponding edge into the graph
+    auto ret = facets_added.emplace(facet);
+    if (ret.second) {
+      // compute facet's area to use as a weight
+      // auto facet_area = MeshUtils::getFacetArea(model, facet);
+      auto dist = MeshUtils::distanceBetweenIncenters(mesh_facets,
+                                                      starting_facet, facet);
+      dist +=
+          MeshUtils::distanceBetweenIncenters(mesh_facets, ending_facet, facet);
+      if (dist > max_dist)
+        max_dist = dist;
+
+      // add edge
+      std::pair<edge_descriptor_t, bool> e = boost::add_edge(v(0), v(1), g);
+      g[e.first].weight = dist;
+      // g[e.first].weight = facet_area;
+      g[e.first].facet = facet;
+    }
+  }
+
+  // normalize and flip weights
+  edge_iter ei, ei_end;
+  for (std::tie(ei, ei_end) = boost::edges(g); ei != ei_end; ++ei) {
+    auto weight = g[*ei].weight;
+    weight = max_dist - weight;
+    // weight = std::pow(weight, 4);//give less weight for smaller values
+    auto facet_area = MeshUtils::getFacetArea(model, g[*ei].facet);
+    g[*ei].weight = weight + sqrt(facet_area);
+  }
+
+  // Print out some useful information
+  std::cout << "Starting segm " << starting_segment.element << " ending segm "
+            << ending_segment.element << std::endl;
+  std::cout << "Graph:" << std::endl;
+  for (std::tie(ei, ei_end) = boost::edges(g); ei != ei_end; ++ei) {
+    std::cout << g[*ei].facet.element << " ";
+  }
+  std::cout << std::endl;
+  std::cout << "num_verts: " << boost::num_vertices(g) << std::endl;
+  std::cout << "num_edges: " << boost::num_edges(g) << std::endl;
+
+  // BGL Dijkstra's Shortest Paths here...
+  std::vector<Real> distances(boost::num_vertices(g));
+  std::vector<vertex_descriptor_t> predecessors(boost::num_vertices(g));
+
+  boost::dijkstra_shortest_paths(
+      g, v_start,
+      boost::weight_map(boost::get(&edge_properties_t::weight, g))
+          .distance_map(boost::make_iterator_property_map(
+              distances.begin(), boost::get(boost::vertex_index, g)))
+          .predecessor_map(boost::make_iterator_property_map(
+              predecessors.begin(), boost::get(boost::vertex_index, g))));
+  std::cout << "distances and parents:" << std::endl;
+  vertex_iter vi, vend;
+  for (boost::tie(vi, vend) = boost::vertices(g); vi != vend; ++vi) {
+    std::cout << "distance(" << g[*vi].segment.element
+              << ") = " << distances[*vi] << ", ";
+    std::cout << "parent = " << g[predecessors[*vi]].segment.element
+              << std::endl;
+  }
+
+  // Extract the shortest path from v1 to v3.
+  typedef std::vector<edge_descriptor_t> path_t;
+  path_t path;
+
+  vertex_descriptor_t v = v_end;
+  for (vertex_descriptor_t u = predecessors[v]; u != v;
+       v = u, u = predecessors[v]) {
+    std::pair<edge_descriptor_t, bool> edge_pair = boost::edge(u, v, g);
+    path.push_back(edge_pair.first);
+  }
+
+  std::cout << std::endl;
+  std::cout << "Shortest Path from segment " << starting_segment.element
+            << " to " << ending_segment.element << ":" << std::endl;
+  for (path_t::reverse_iterator riter = path.rbegin(); riter != path.rend();
+       ++riter) {
+    vertex_descriptor_t u_tmp = boost::source(*riter, g);
+    vertex_descriptor_t v_tmp = boost::target(*riter, g);
+    edge_descriptor_t e_tmp = boost::edge(u_tmp, v_tmp, g).first;
+
+    std::cout << "  " << g[u_tmp].segment.element << " -> "
+              << g[v_tmp].segment.element << " through "
+              << g[e_tmp].facet.element << "    (area: " << g[e_tmp].weight
+              << ")" << std::endl;
+    facets_in_loop.push_back(g[e_tmp].facet);
+  }
+  return facets_in_loop;
+}
+/* ------------------------------------------------------------------- */
+Array<Element> ASRTools::findFacetsLoopByGraphByArea(
+    const Array<Element> & limit_facets, const Array<Element> & limit_segments,
+    const Array<Element> & preinserted_facets, const UInt & cent_node) {
 
+  auto & mesh = model.getMesh();
+  auto & mesh_facets = mesh.getMeshFacets();
+  auto dim = mesh.getSpatialDimension();
+  Array<Element> facets_in_loop;
+
+  // find two best neighbors
+  Array<Element> best_neighbors(2, 1, ElementNull);
+  Array<Element> neighbors_borders(2, 1, ElementNull);
+  for (UInt i = 0; i < 2; i++) {
+    Element best_neighbor{ElementNull};
+    // if the facet is already provided - use it
+    if (preinserted_facets(i) != ElementNull) {
+      best_neighbor = preinserted_facets(i);
+    } else {
+      // if not - search for it
+      auto limit_facet_indiam =
+          MeshUtils::getInscribedCircleDiameter(model, limit_facets(i));
+      auto max_dot = std::numeric_limits<Real>::min();
+      auto && neighbor_facets =
+          mesh_facets.getElementToSubelement(limit_segments(i));
+      for (auto && neighbor_facet : neighbor_facets) {
+        if (neighbor_facet == limit_facets(0) or
+            neighbor_facet == limit_facets(1))
+          continue;
+        if (not isFacetAndNodesGood(neighbor_facet))
+          continue;
         // conditions on the angle between facets
-        auto dist = MeshUtils::distanceBetweenBarycentersCorrected(
-            mesh_facets, current_facet, neighbor_facet);
-        if (dist < 0.9 * current_facet_indiam) {
+        Real neighbor_facet_indiam =
+            MeshUtils::getInscribedCircleDiameter(model, neighbor_facet);
+        Real hypotenuse = sqrt(neighbor_facet_indiam * neighbor_facet_indiam +
+                               limit_facet_indiam * limit_facet_indiam) /
+                          2;
+        auto dist = MeshUtils::distanceBetweenIncentersCorrected(
+            mesh_facets, limit_facets(i), neighbor_facet);
+        if (dist < hypotenuse)
           continue;
-        }
 
+        // find neighbor with the biggest thing
         // get abs of dot product between two normals
-        Real dot = MeshUtils::cosSharpAngleBetween2Facets(model, current_facet,
-                                                          neighbor_facet);
+        Real dot = MeshUtils::cosSharpAngleBetween2Facets(
+            model, limit_facets(i), neighbor_facet);
         if (dot > max_dot) {
-          next_facet = neighbor_facet;
-          if (starting_segment_touched) {
-            facets_in_loop.push_back(neighbor_facet);
-            loop_closed = true;
-            break;
-          }
+          max_dot = dot;
+          best_neighbor = neighbor_facet;
         }
       }
+    }
+    if (best_neighbor == ElementNull) {
+      return facets_in_loop;
+    } else {
+      best_neighbors(i) = best_neighbor;
+    }
 
-      if (next_facet == ElementNull) {
-        std::cout << "Could not close the loop, switching to the next node"
-                  << std::endl;
-        break;
+    // find new limiting segments
+    auto && neighbor_segments =
+        mesh_facets.getSubelementToElement(best_neighbor);
+    for (auto & segment : neighbor_segments) {
+      if (segment == limit_segments(i))
+        continue;
+
+      // check if the segment includes the central node
+      auto && nodes_to_segment = mesh_facets.getConnectivity(segment);
+      bool includes_cent_node{false};
+      for (auto & node : nodes_to_segment) {
+        if (node == cent_node) {
+          includes_cent_node = true;
+          break;
+        }
       }
+      if (not includes_cent_node)
+        continue;
 
-      if (almost_closed & not loop_closed) {
-        std::cout << "Could not close the loop, switching to the next node"
-                  << std::endl;
+      neighbors_borders(i) = segment;
+      break;
+    }
+  }
+  // check if borders are not the same segment
+  if (neighbors_borders(0) == neighbors_borders(1)) {
+    for (auto && best_neighbor : best_neighbors) {
+      facets_in_loop.push_back(best_neighbor);
+    }
+  }
+
+  // Create a struct to hold properties for each vertex
+  typedef struct vertex_properties {
+    Element segment;
+  } vertex_properties_t;
+
+  // Create a struct to hold properties for each edge
+  typedef struct edge_properties {
+    Element facet;
+    Real weight;
+  } edge_properties_t;
+
+  // Define the type of the graph
+  typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS,
+                                vertex_properties_t, edge_properties_t>
+      graph_t;
+  typedef graph_t::vertex_descriptor vertex_descriptor_t;
+  typedef graph_t::edge_descriptor edge_descriptor_t;
+  // typedef boost::graph_traits<graph_t>::edge_iterator edge_iter;
+  // typedef boost::graph_traits<graph_t>::vertex_iterator vertex_iter;
+  graph_t g;
+
+  // prepare the list of facets sharing the central node
+  CSR<Element> facets_to_nodes;
+  MeshUtils::buildNode2Elements(mesh_facets, facets_to_nodes, dim - 1);
+  auto && facets_to_node = facets_to_nodes.getRow(cent_node);
+  std::set<Element> facets_added;
+  std::map<Element, vertex_descriptor_t> segment_vertex_map;
+
+  // get the point el corresponding to the central node
+  CSR<Element> points_to_nodes;
+  MeshUtils::buildNode2Elements(mesh_facets, points_to_nodes, 0);
+  auto && points_to_node = points_to_nodes.getRow(cent_node);
+  auto point_el = *points_to_node.begin();
+  // get list of all segments connected to the node
+  auto && segm_to_point = mesh_facets.getElementToSubelement(point_el);
+
+  // add starting and ending segment to the graph
+  vertex_descriptor_t v_start = boost::add_vertex(g);
+  g[v_start].segment = neighbors_borders(0);
+  vertex_descriptor_t v_end = boost::add_vertex(g);
+  g[v_end].segment = neighbors_borders(1);
+  segment_vertex_map[neighbors_borders(0)] = v_start;
+  segment_vertex_map[neighbors_borders(1)] = v_end;
+
+  // compute inscribed circles diameters
+  Array<Real> best_neighbors_indiam;
+  for (auto && best_neighbor : best_neighbors) {
+    auto neighbor_facet_indiam =
+        MeshUtils::getInscribedCircleDiameter(model, best_neighbor);
+    best_neighbors_indiam.push_back(neighbor_facet_indiam);
+  }
+
+  for (auto && facet : facets_to_node) {
+    // discard undesired facets first
+    if (facet == best_neighbors(0) or facet == best_neighbors(1) or
+        facet == limit_facets(0) or facet == limit_facets(1))
+      continue;
+    if (not isFacetAndNodesGood(facet))
+      continue;
+    if (belong2SameElement(best_neighbors(0), facet) or
+        belong2SameElement(best_neighbors(1), facet) or
+        belong2SameElement(limit_facets(0), facet) or
+        belong2SameElement(limit_facets(1), facet))
+      continue;
+
+    // discard short distances to starting and ending facets
+    bool short_dist{false};
+    auto facet_indiam = MeshUtils::getInscribedCircleDiameter(model, facet);
+    for (UInt i = 0; i < 2; i++) {
+      auto hypotenuse =
+          sqrt(best_neighbors_indiam(i) * best_neighbors_indiam(i) +
+               facet_indiam * facet_indiam) /
+          2;
+      auto dist = MeshUtils::distanceBetweenIncenters(mesh_facets,
+                                                      best_neighbors(i), facet);
+
+      if (dist <= hypotenuse) {
+        short_dist = true;
         break;
       }
-      if (loop_closed)
-        break;
+    }
+    if (short_dist)
+      continue;
 
-      // otherwise make this facet a current one
-      current_facet = next_facet;
-      current_segment = border_segment;
+    // identify 2 side facets
+    Array<Element> side_segments;
+    auto && facet_segments = mesh_facets.getSubelementToElement(facet);
+    for (auto & segment : facet_segments) {
+      auto ret = std::find(segm_to_point.begin(), segm_to_point.end(), segment);
+      if (ret != segm_to_point.end()) {
+        side_segments.push_back(segment);
+      }
     }
-    // loop broke -> try another point
-    if (not loop_closed)
+    AKANTU_DEBUG_ASSERT(side_segments.size() == 2,
+                        "Number of side facets is wrong");
+
+    // add corresponding vertex (if not there)and edge into graph
+    Array<vertex_descriptor_t> v(2);
+    for (UInt i = 0; i < 2; i++) {
+      auto it = segment_vertex_map.find(side_segments(i));
+      if (it != segment_vertex_map.end()) {
+        v(i) = it->second;
+      } else {
+        v(i) = boost::add_vertex(g);
+        g[v(i)].segment = side_segments(i);
+        segment_vertex_map[side_segments(i)] = v(i);
+      }
+    }
+
+    // add corresponding edge into the graph
+    auto ret = facets_added.emplace(facet);
+    if (ret.second) {
+      // compute facet's area to use as a weight
+      auto facet_area = MeshUtils::getFacetArea(model, facet);
+
+      // add edge
+      std::pair<edge_descriptor_t, bool> e = boost::add_edge(v(0), v(1), g);
+      g[e.first].weight = facet_area;
+      g[e.first].facet = facet;
+    }
+  }
+
+  // // Print out some useful information
+  // std::cout << "Starting facet " << best_neighbors(0).element
+  //           << " ending facet " << best_neighbors(0).element << std::endl;
+  // std::cout << "Graph:" << std::endl;
+  // // boost::print_graph(g, boost::get(&vertex_properties_t::segment, g));
+  // edge_iter ei, ei_end;
+  // for (std::tie(ei, ei_end) = boost::edges(g); ei != ei_end; ++ei) {
+  //   std::cout << g[*ei].facet.element << ",";
+  // }
+  // std::cout << std::endl;
+  // for (std::tie(ei, ei_end) = boost::edges(g); ei != ei_end; ++ei) {
+  //   vertex_descriptor_t u = boost::source(*ei, g);
+  //   vertex_descriptor_t v = boost::target(*ei, g);
+  //   std::cout << g[*ei].facet.element << " from " << g[u].segment.element
+  //             << "->" << g[v].segment.element << " area " << g[*ei].weight
+  //             << std::endl;
+  // }
+  // std::cout << "num_verts: " << boost::num_vertices(g) << std::endl;
+  // std::cout << "num_edges: " << boost::num_edges(g) << std::endl;
+  // std::cout << std::endl;
+
+  // BGL Dijkstra's Shortest Paths here...
+  std::vector<Real> distances(boost::num_vertices(g));
+  std::vector<vertex_descriptor_t> predecessors(boost::num_vertices(g));
+
+  boost::dijkstra_shortest_paths(
+      g, v_end,
+      boost::weight_map(boost::get(&edge_properties_t::weight, g))
+          .distance_map(boost::make_iterator_property_map(
+              distances.begin(), boost::get(boost::vertex_index, g)))
+          .predecessor_map(boost::make_iterator_property_map(
+              predecessors.begin(), boost::get(boost::vertex_index, g))));
+  // Extract the shortest path from v1 to v3.
+  typedef std::vector<edge_descriptor_t> path_t;
+  path_t path;
+
+  vertex_descriptor_t v = v_start;
+  for (vertex_descriptor_t u = predecessors[v]; u != v;
+       v = u, u = predecessors[v]) {
+    std::pair<edge_descriptor_t, bool> edge_pair = boost::edge(u, v, g);
+    path.push_back(edge_pair.first);
+  }
+
+  if (path.size()) {
+    // add 2 determenistic facets
+    for (auto && best_neighbor : best_neighbors) {
+      facets_in_loop.push_back(best_neighbor);
+    }
+    // add the shortest path facets
+    for (path_t::reverse_iterator riter = path.rbegin(); riter != path.rend();
+         ++riter) {
+      vertex_descriptor_t u_tmp = boost::source(*riter, g);
+      vertex_descriptor_t v_tmp = boost::target(*riter, g);
+      edge_descriptor_t e_tmp = boost::edge(u_tmp, v_tmp, g).first;
+
+      facets_in_loop.push_back(g[e_tmp].facet);
+    }
+  }
+  return facets_in_loop;
+}
+/* ------------------------------------------------------------------- */
+Array<Element> ASRTools::findStressedFacetLoopAroundNode(
+    const Array<Element> & limit_facets, const Array<Element> & limit_segments,
+    const UInt & cent_node, Real min_dot) {
+
+  // auto && comm = akantu::Communicator::getWorldCommunicator();
+  // auto prank = comm.whoAmI();
+  auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
+  auto & mesh = model.getMesh();
+  auto & mesh_facets = mesh.getMeshFacets();
+  auto dim = mesh.getSpatialDimension();
+  AKANTU_DEBUG_ASSERT(limit_facets(0) != limit_facets(1),
+                      "Limit facets are equal");
+  AKANTU_DEBUG_ASSERT(limit_segments(0) != limit_segments(1),
+                      "Limit segments are equal");
+  AKANTU_DEBUG_ASSERT(
+      dim == 3, "findFacetsLoopFromSegment2Segment currently supports only 3D");
+  // get the point el corresponding to the central node
+  CSR<Element> points_to_nodes;
+  MeshUtils::buildNode2Elements(mesh_facets, points_to_nodes, 0);
+  auto && points_to_node = points_to_nodes.getRow(cent_node);
+  auto point_el = *points_to_node.begin();
+  auto & segments_to_point = mesh_facets.getElementToSubelement(point_el);
+
+  AKANTU_DEBUG_ASSERT(point_el.type == _point_1,
+                      "Provided node element type is wrong");
+
+  for (UInt i = 0; i < 2; i++) {
+    // check the dimensions
+    AKANTU_DEBUG_ASSERT(
+        Mesh::getSpatialDimension(limit_facets(i).type) == dim - 1,
+        "Facet" << limit_facets(i) << " has wrong spatial dimension");
+    AKANTU_DEBUG_ASSERT(
+        Mesh::getSpatialDimension(limit_segments(i).type) == dim - 2,
+        "Segment" << limit_segments(i) << " has wrong spatial dimension");
+
+    // check if the provided node is shared by two segments
+    auto ret = std::find(segments_to_point.begin(), segments_to_point.end(),
+                         limit_segments(i));
+    AKANTU_DEBUG_ASSERT(ret != segments_to_point.end(),
+                        "Segment " << limit_segments(i)
+                                   << " doesn't contain the central node");
+
+    // check if limit facets contain limit segments
+    auto & facets_to_segment =
+        mesh_facets.getElementToSubelement(limit_segments(i));
+    auto ret1 = std::find(facets_to_segment.begin(), facets_to_segment.end(),
+                          limit_facets(i));
+    AKANTU_DEBUG_ASSERT(ret1 != facets_to_segment.end(),
+                        "Facet " << limit_facets(i)
+                                 << " doesn't contain the segment "
+                                 << limit_segments(i));
+  }
+  Array<Element> facets_in_loop;
+
+  // Create a struct to hold properties for each vertex
+  typedef struct vertex_properties {
+    Element segment;
+  } vertex_properties_t;
+
+  // Create a struct to hold properties for each edge
+  typedef struct edge_properties {
+    Element facet;
+    Real weight;
+  } edge_properties_t;
+
+  // Define the type of the graph
+  typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS,
+                                vertex_properties_t, edge_properties_t>
+      graph_t;
+  typedef typename graph_t::vertex_descriptor vertex_descriptor_t;
+  typedef typename graph_t::edge_descriptor edge_descriptor_t;
+  graph_t g;
+
+  // prepare the list of facets sharing the central node
+  CSR<Element> facets_to_nodes;
+  MeshUtils::buildNode2Elements(mesh_facets, facets_to_nodes, dim - 1);
+  auto && facets_to_node = facets_to_nodes.getRow(cent_node);
+  std::set<Element> facets_added;
+  std::map<Element, vertex_descriptor_t> segment_vertex_map;
+
+  // add starting and ending segment to the graph
+  vertex_descriptor_t v_start = boost::add_vertex(g);
+  g[v_start].segment = limit_segments(0);
+  vertex_descriptor_t v_end = boost::add_vertex(g);
+  g[v_end].segment = limit_segments(1);
+  segment_vertex_map[limit_segments(0)] = v_start;
+  segment_vertex_map[limit_segments(1)] = v_end;
+
+  for (auto && facet : facets_to_node) {
+    // discard undesired facets first
+    if (facet == limit_facets(0) or facet == limit_facets(1))
+      continue;
+    if (not isFacetAndNodesGood(facet))
+      continue;
+    if (belong2SameElement(limit_facets(0), facet) or
+        belong2SameElement(limit_facets(1), facet))
       continue;
 
-    // otherwise champaigne
-    for (auto & facet : facets_in_loop) {
-      doubled_facets.add(facet);
-      /// add all facet nodes to the group
-      for (auto node : arange(nb_nodes_facet)) {
-        doubled_nodes.add(facet_conn(facet.element, node));
-        this->ASR_nodes(facet_conn(facet.element, node)) = true;
+    // check the effective stress on this facet
+    auto & facet_material_index =
+        coh_model.getFacetMaterial(facet.type, facet.ghost_type)(facet.element);
+    auto & mat = model.getMaterial(facet_material_index);
+    auto * mat_coh = dynamic_cast<MaterialCohesive *>(&mat);
+    if (mat_coh == nullptr)
+      continue;
+    auto && mat_facet_filter =
+        mat_coh->getFacetFilter(facet.type, facet.ghost_type);
+    UInt facet_local_num = mat_facet_filter.find(facet.element);
+    AKANTU_DEBUG_ASSERT(facet_local_num != UInt(-1),
+                        "Local facet number was not identified");
+    auto & eff_stress_array = mat.getInternal<Real>("effective_stresses")(
+        facet.type, facet.ghost_type);
+    auto eff_stress = eff_stress_array(facet_local_num);
+    if (eff_stress == 0)
+      eff_stress = 1e-3;
+    // if (eff_stress < 1)
+    //   continue;
+
+    // identify 2 sides of a facets
+    Array<Element> side_segments;
+    auto && facet_segments = mesh_facets.getSubelementToElement(facet);
+    for (auto & segment : facet_segments) {
+      auto ret = std::find(segments_to_point.begin(), segments_to_point.end(),
+                           segment);
+      if (ret != segments_to_point.end()) {
+        side_segments.push_back(segment);
       }
     }
-    // store the asr facets
-    this->ASR_facets_from_mesh_facets.push_back(facets_in_loop);
-    already_inserted++;
+    AKANTU_DEBUG_ASSERT(side_segments.size() == 2,
+                        "Number of side facets is wrong");
+
+    // add corresponding vertex (if not there)and edge into graph
+    Array<vertex_descriptor_t> v(2);
+    for (UInt i = 0; i < 2; i++) {
+      auto it = segment_vertex_map.find(side_segments(i));
+      if (it != segment_vertex_map.end()) {
+        v(i) = it->second;
+      } else {
+        v(i) = boost::add_vertex(g);
+        g[v(i)].segment = side_segments(i);
+        segment_vertex_map[side_segments(i)] = v(i);
+      }
+    }
+
+    // add corresponding edge into the graph
+    auto ret = facets_added.emplace(facet);
+    if (ret.second) {
+      // compute facet's area to use as a weight
+      // auto facet_area = MeshUtils::getFacetArea(model, facet);
+
+      // add edge
+      std::pair<edge_descriptor_t, bool> e = boost::add_edge(v(0), v(1), g);
+      // g[e.first].weight = facet_area;
+      g[e.first].weight = 1 / eff_stress;
+      g[e.first].facet = facet;
+    }
   }
-  std::cout << "Proc " << prank << " placed " << already_inserted << " ASR site"
-            << std::endl;
-  return already_inserted;
+
+  // BGL Dijkstra's Shortest Paths here...
+  std::vector<Real> distances(boost::num_vertices(g));
+  std::vector<vertex_descriptor_t> predecessors(boost::num_vertices(g));
+
+  boost::dijkstra_shortest_paths(
+      g, v_start,
+      boost::weight_map(boost::get(&edge_properties_t::weight, g))
+          .distance_map(boost::make_iterator_property_map(
+              distances.begin(), boost::get(boost::vertex_index, g)))
+          .predecessor_map(boost::make_iterator_property_map(
+              predecessors.begin(), boost::get(boost::vertex_index, g))));
+
+  // Extract the shortest path from v_end to v_start (reversed order)
+  typedef std::vector<edge_descriptor_t> path_t;
+  path_t path;
+
+  vertex_descriptor_t v = v_end;
+  for (vertex_descriptor_t u = predecessors[v]; u != v;
+       v = u, u = predecessors[v]) {
+    std::pair<edge_descriptor_t, bool> edge_pair = boost::edge(u, v, g);
+    path.push_back(edge_pair.first);
+  }
+
+  // compute average effective stress over the loop and discard if <1
+  Array<Element> facet_loop_tmp;
+  for (auto edge : path) {
+    facet_loop_tmp.push_back(g[edge].facet);
+  }
+  auto av_eff_stress = averageEffectiveStressInMultipleFacets(facet_loop_tmp);
+  if (av_eff_stress < 1)
+    return facets_in_loop;
+
+  // check path for smoothness
+  bool recompute_path{false};
+  do {
+    recompute_path = false;
+    if (path.size()) {
+      for (UInt i = 0; i <= path.size(); i++) {
+        // evaluate two neighboring facets
+        Element facet1, facet2;
+        if (i == 0) {
+          facet1 = limit_facets(1);
+          facet2 = g[path[i]].facet;
+        } else if (i == path.size()) {
+          facet1 = g[path[i - 1]].facet;
+          facet2 = limit_facets(0);
+        } else {
+          facet1 = g[path[i - 1]].facet;
+          facet2 = g[path[i]].facet;
+        }
+        // discard short distances
+        auto facet1_indiam =
+            MeshUtils::getInscribedCircleDiameter(model, facet1);
+        auto facet2_indiam =
+            MeshUtils::getInscribedCircleDiameter(model, facet2);
+        auto hypotenuse = sqrt(facet1_indiam * facet1_indiam +
+                               facet2_indiam * facet2_indiam) /
+                          2;
+        auto dist = MeshUtils::distanceBetweenIncentersCorrected(
+            mesh_facets, facet1, facet2);
+        // get abs of dot product between two normals
+        Real dot =
+            MeshUtils::cosSharpAngleBetween2Facets(model, facet1, facet2);
+
+        if (dist < hypotenuse or dot < min_dot) {
+          if (i < path.size()) {
+            boost::remove_edge(path[i], g);
+          } else {
+            boost::remove_edge(path[i - 1], g);
+          }
+          recompute_path = true;
+          break;
+        }
+      }
+      // find new shortest path without problematic edge
+      if (recompute_path) {
+        boost::dijkstra_shortest_paths(
+            g, v_start,
+            boost::weight_map(boost::get(&edge_properties_t::weight, g))
+                .distance_map(boost::make_iterator_property_map(
+                    distances.begin(), boost::get(boost::vertex_index, g)))
+                .predecessor_map(boost::make_iterator_property_map(
+                    predecessors.begin(), boost::get(boost::vertex_index, g))));
+        path.clear();
+        v = v_end;
+        for (vertex_descriptor_t u = predecessors[v]; u != v;
+             v = u, u = predecessors[v]) {
+          std::pair<edge_descriptor_t, bool> edge_pair = boost::edge(u, v, g);
+          path.push_back(edge_pair.first);
+        }
+      }
+    }
+  } while (recompute_path);
+
+  // add the shortest path facets
+  for (typename path_t::reverse_iterator riter = path.rbegin();
+       riter != path.rend(); ++riter) {
+    vertex_descriptor_t u_tmp = boost::source(*riter, g);
+    vertex_descriptor_t v_tmp = boost::target(*riter, g);
+    edge_descriptor_t e_tmp = boost::edge(u_tmp, v_tmp, g).first;
+
+    facets_in_loop.push_back(g[e_tmp].facet);
+  }
+  return facets_in_loop;
+}
+/* ------------------------------------------------------------------- */
+Array<Element>
+ASRTools::findSingleFacetLoop(const Array<Element> & limit_facets,
+                              const Array<Element> & limit_segments,
+                              const UInt & cent_node) {
+
+  auto & mesh = model.getMesh();
+  auto & mesh_facets = mesh.getMeshFacets();
+  auto dim = mesh.getSpatialDimension();
+
+  AKANTU_DEBUG_ASSERT(limit_facets(0) != limit_facets(1),
+                      "Limit facets are equal");
+  AKANTU_DEBUG_ASSERT(limit_segments(0) != limit_segments(1),
+                      "Limit segments are equal");
+  AKANTU_DEBUG_ASSERT(
+      dim == 3, "findFacetsLoopFromSegment2Segment currently supports only 3D");
+  // get the point el corresponding to the central node
+  CSR<Element> points_to_nodes;
+  MeshUtils::buildNode2Elements(mesh_facets, points_to_nodes, 0);
+  auto && points_to_node = points_to_nodes.getRow(cent_node);
+  auto point_el = *points_to_node.begin();
+  auto & segments_to_node = mesh_facets.getElementToSubelement(point_el);
+
+  AKANTU_DEBUG_ASSERT(point_el.type == _point_1,
+                      "Provided node element type is wrong");
+
+  for (UInt i = 0; i < 2; i++) {
+    // check the dimensions
+    AKANTU_DEBUG_ASSERT(
+        Mesh::getSpatialDimension(limit_facets(i).type) == dim - 1,
+        "Facet" << limit_facets(i) << " has wrong spatial dimension");
+    AKANTU_DEBUG_ASSERT(
+        Mesh::getSpatialDimension(limit_segments(i).type) == dim - 2,
+        "Segment" << limit_segments(i) << " has wrong spatial dimension");
+
+    // check if the provided node is shared by two segments
+    auto ret = std::find(segments_to_node.begin(), segments_to_node.end(),
+                         limit_segments(i));
+    AKANTU_DEBUG_ASSERT(ret != segments_to_node.end(),
+                        "Segment " << limit_segments(i)
+                                   << " doesn't contain the central node");
+
+    // check if limit facets contain limit segments
+    auto & facets_to_segment =
+        mesh_facets.getElementToSubelement(limit_segments(i));
+    auto ret1 = std::find(facets_to_segment.begin(), facets_to_segment.end(),
+                          limit_facets(i));
+    AKANTU_DEBUG_ASSERT(ret1 != facets_to_segment.end(),
+                        "Facet " << limit_facets(i)
+                                 << " doesn't contain the segment "
+                                 << limit_segments(i));
+  }
+
+  Array<Element> facets_in_loop;
+
+  // check if two segments are not connected by a single facet
+  auto st_segment_neighbors =
+      mesh_facets.getElementToSubelement(limit_segments(0));
+  auto end_segment_neighbors =
+      mesh_facets.getElementToSubelement(limit_segments(1));
+  std::sort(st_segment_neighbors.begin(), st_segment_neighbors.end());
+  std::sort(end_segment_neighbors.begin(), end_segment_neighbors.end());
+
+  Array<Element> shared_facets(st_segment_neighbors.size());
+  // An iterator to the end of the constructed range.
+  auto it = std::set_intersection(
+      st_segment_neighbors.begin(), st_segment_neighbors.end(),
+      end_segment_neighbors.begin(), end_segment_neighbors.end(),
+      shared_facets.begin());
+  shared_facets.resize(it - shared_facets.begin());
+
+  // if there is a single facet connecting two segments test it
+  if (shared_facets.size() == 1) {
+    bool bad_facet{false};
+    if (not isFacetAndNodesGood(shared_facets(0)))
+      bad_facet = true;
+    // check angle with starting and ending -> not to be sharp
+    bool sharp_angle{false};
+    auto facet_indiam =
+        MeshUtils::getInscribedCircleDiameter(model, shared_facets(0));
+    for (auto && limit_facet : limit_facets) {
+      auto limit_facet_indiam =
+          MeshUtils::getInscribedCircleDiameter(model, limit_facet);
+      auto hypotenuse = sqrt(limit_facet_indiam * limit_facet_indiam +
+                             facet_indiam * facet_indiam) /
+                        2;
+      auto dist = MeshUtils::distanceBetweenIncentersCorrected(
+          mesh_facets, limit_facet, shared_facets(0));
+
+      if (dist <= hypotenuse) {
+        sharp_angle = true;
+        break;
+      }
+    }
+    if (not sharp_angle and not bad_facet) {
+      facets_in_loop.push_back(shared_facets(0));
+    }
+  }
+  return facets_in_loop;
 }
 /* ------------------------------------------------------------------- */
-bool ASRTools::isFacetAndNodesGood(const Element & facet, UInt material_id) {
+bool ASRTools::isFacetAndNodesGood(const Element & facet, UInt material_id,
+                                   bool check_asr_nodes) {
 
   auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
   auto & inserter = coh_model.getElementInserter();
   auto facet_type = facet.type;
   auto gt = facet.ghost_type;
   auto & mesh_facets = inserter.getMeshFacets();
   Vector<UInt> facet_nodes = mesh_facets.getConnectivity(facet);
   auto & check_facets = inserter.getCheckFacets(facet_type, gt);
 
-  // check if the facet is ghost
+  // check if the facet is not ghost
   if (gt == _ghost)
     return false;
   // check if the facet is allowed to be inserted
   if (check_facets(facet.element) == false)
     return false;
-  // check if the facet's nodes are not on the partition or other ASR zones
   for (auto & facet_node : facet_nodes) {
-    if (model.getMesh().isPureGhostNode(
-            facet_node) /*model.getMesh().isLocalOrMasterNode(facet_node)*/
-        or this->ASR_nodes(facet_node))
+    // check if the facet's nodes are not pure ghost nodes
+    if (model.getMesh().isPureGhostNode(facet_node))
       return false;
-  }
-  // check if the facet material is good
-  const auto & facet_material =
-      coh_model.getFacetMaterial(facet_type, gt)(facet.element);
-  if (facet_material != material_id)
-    return false;
-
-  return true;
-}
-/* ------------------------------------------------------------------- */
-bool ASRTools::isNodeWithinMaterial(Element & node, UInt material_id) {
-
-  auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
-  auto & inserter = coh_model.getElementInserter();
-  auto & mesh_facets = inserter.getMeshFacets();
-  auto dim = coh_model.getSpatialDimension();
 
-  CSR<Element> nodes_to_facets;
-  MeshUtils::buildNode2Elements(mesh_facets, nodes_to_facets, dim - 1);
+    // check if the facet's nodes are asr nodes
+    if (check_asr_nodes and this->ASR_nodes(facet_node))
+      return false;
+  }
 
-  for (auto & facet : nodes_to_facets.getRow(node.element)) {
-    auto & facet_material =
-        coh_model.getFacetMaterial(facet.type, facet.ghost_type)(facet.element);
+  // check if the facet material is good
+  if (material_id != UInt(-1)) {
+    const auto & facet_material =
+        coh_model.getFacetMaterial(facet_type, gt)(facet.element);
     if (facet_material != material_id)
       return false;
   }
 
   return true;
 }
-
 /* ------------------------------------------------------------------- */
-bool ASRTools::pickFacetNeighborsOld(Element & cent_facet) {
-  auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
-  auto & inserter = coh_model.getElementInserter();
+bool ASRTools::belong2SameElement(const Element & facet1,
+                                  const Element & facet2) {
+
+  auto & mesh = model.getMesh();
+  auto & mesh_facets = mesh.getMeshFacets();
+
+  Array<Element> f1_neighbors, f2_neighbors;
+  auto facet1_neighbors = mesh_facets.getElementToSubelement(facet1);
+  for (auto && neighbor : facet1_neighbors) {
+    if (mesh.getKind(neighbor.type) == _ek_regular)
+      f1_neighbors.push_back(neighbor);
+    if (mesh.getKind(neighbor.type) == _ek_cohesive) {
+      auto && coh_neighbors = mesh_facets.getSubelementToElement(neighbor);
+      for (auto && coh_neighbor : coh_neighbors) {
+        if (coh_neighbor == facet1)
+          continue;
+        auto coh_facet_neighbors =
+            mesh_facets.getElementToSubelement(coh_neighbor);
+        for (auto && coh_facet_neighbor : coh_facet_neighbors) {
+          if (mesh.getKind(coh_facet_neighbor.type) == _ek_regular)
+            f1_neighbors.push_back(coh_facet_neighbor);
+        }
+      }
+    }
+  }
+
+  auto facet2_neighbors = mesh_facets.getElementToSubelement(facet2);
+  for (auto && neighbor : facet2_neighbors) {
+    if (mesh.getKind(neighbor.type) == _ek_regular)
+      f2_neighbors.push_back(neighbor);
+    if (mesh.getKind(neighbor.type) == _ek_cohesive) {
+      auto && coh_neighbors = mesh_facets.getSubelementToElement(neighbor);
+      for (auto && coh_neighbor : coh_neighbors) {
+        if (coh_neighbor == facet2)
+          continue;
+        auto coh_facet_neighbors =
+            mesh_facets.getElementToSubelement(coh_neighbor);
+        for (auto && coh_facet_neighbor : coh_facet_neighbors) {
+          if (mesh.getKind(coh_facet_neighbor.type) == _ek_regular)
+            f2_neighbors.push_back(coh_facet_neighbor);
+        }
+      }
+    }
+  }
+  std::sort(f1_neighbors.begin(), f1_neighbors.end());
+  std::sort(f2_neighbors.begin(), f2_neighbors.end());
+
+  Array<Element> shared_neighbors(f1_neighbors.size());
+  // An iterator to the end of the constructed range.
+  auto it = std::set_intersection(f1_neighbors.begin(), f1_neighbors.end(),
+                                  f2_neighbors.begin(), f2_neighbors.end(),
+                                  shared_neighbors.begin());
+  shared_neighbors.resize(it - shared_neighbors.begin());
+
+  if (shared_neighbors.size() == 0)
+    return false;
+
+  // check kind of shared neighbor
+  auto shared_neighbor = shared_neighbors(0);
+  if (mesh.getKind(shared_neighbor.type) != _ek_regular)
+    return false;
+
+  return true;
+}
+/* ------------------------------------------------------------------- */
+bool ASRTools::isNodeWithinMaterial(Element & node, UInt material_id) {
+
+  auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
+  auto & inserter = coh_model.getElementInserter();
+  auto & mesh_facets = inserter.getMeshFacets();
+  auto dim = coh_model.getSpatialDimension();
+
+  CSR<Element> nodes_to_facets;
+  MeshUtils::buildNode2Elements(mesh_facets, nodes_to_facets, dim - 1);
+
+  for (auto & facet : nodes_to_facets.getRow(node.element)) {
+    auto & facet_material =
+        coh_model.getFacetMaterial(facet.type, facet.ghost_type)(facet.element);
+    if (facet_material != material_id)
+      return false;
+  }
+
+  return true;
+}
+
+/* ------------------------------------------------------------------- */
+bool ASRTools::pickFacetNeighborsOld(Element & cent_facet) {
+  auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
+  auto & inserter = coh_model.getElementInserter();
   auto & mesh = model.getMesh();
   auto & mesh_facets = inserter.getMeshFacets();
   auto dim = mesh.getSpatialDimension();
   const auto & pos = mesh.getNodes();
   const auto pos_it = make_view(pos, dim).begin();
   auto & doubled_facets = mesh_facets.getElementGroup("doubled_facets");
   auto & facet_conn =
       mesh_facets.getConnectivity(cent_facet.type, cent_facet.ghost_type);
   auto & cent_facet_material = coh_model.getFacetMaterial(
       cent_facet.type, cent_facet.ghost_type)(cent_facet.element);
   CSR<Element> nodes_to_segments;
   MeshUtils::buildNode2Elements(mesh_facets, nodes_to_segments, dim - 1);
   Array<Element> two_neighbors(2);
   two_neighbors.set(ElementNull);
 
   for (auto node : arange(2)) {
     /// vector of the central facet
     Vector<Real> cent_facet_dir(pos_it[facet_conn(cent_facet.element, !node)],
                                 true);
     cent_facet_dir -=
         Vector<Real>(pos_it[facet_conn(cent_facet.element, node)]);
     cent_facet_dir /= cent_facet_dir.norm();
     // dot product less than -0.5 discards any < 120 deg
     Real min_dot = -0.5;
     // Real min_dot = std::numeric_limits<Real>::max();
     Vector<Real> neighbor_facet_dir(dim);
     for (auto & elem :
          nodes_to_segments.getRow(facet_conn(cent_facet.element, node))) {
       if (elem.element == cent_facet.element)
         continue;
       if (elem.type != cent_facet.type)
         continue;
       if (elem.ghost_type != cent_facet.ghost_type)
         continue;
       if (not inserter.getCheckFacets(elem.type, elem.ghost_type)(elem.element))
         continue;
       // discard neighbors from different materials
       auto & candidate_facet_material =
           coh_model.getFacetMaterial(elem.type, elem.ghost_type)(elem.element);
       if (candidate_facet_material != cent_facet_material)
         continue;
 
       /// decide which node of the neighbor is the second
       UInt first_node{facet_conn(cent_facet.element, node)};
       UInt second_node(-1);
       if (facet_conn(elem.element, 0) == first_node) {
         second_node = facet_conn(elem.element, 1);
       } else if (facet_conn(elem.element, 1) == first_node) {
         second_node = facet_conn(elem.element, 0);
       } else
         AKANTU_EXCEPTION(
             "Neighboring facet"
             << elem << " with nodes " << facet_conn(elem.element, 0) << " and "
             << facet_conn(elem.element, 1)
             << " doesn't have node in common with the central facet "
             << cent_facet << " with nodes " << facet_conn(cent_facet.element, 0)
             << " and " << facet_conn(cent_facet.element, 1));
 
       /// discard facets intersecting other ASR elements
       if (this->ASR_nodes(second_node))
         continue;
 
       neighbor_facet_dir = pos_it[second_node];
       neighbor_facet_dir -= Vector<Real>(pos_it[first_node]);
 
       neighbor_facet_dir /= neighbor_facet_dir.norm();
 
       Real dot = cent_facet_dir.dot(neighbor_facet_dir);
       if (dot < min_dot) {
         min_dot = dot;
         two_neighbors(node) = elem;
       }
     }
   }
 
   // insert neighbors only if two of them were identified
   if (two_neighbors.find(ElementNull) == UInt(-1)) {
     for (auto & neighbor : two_neighbors) {
       doubled_facets.add(neighbor);
       for (UInt node : arange(2)) {
         this->ASR_nodes(facet_conn(neighbor.element, node)) = true;
       }
     }
     return true;
   } else
     return false;
 }
 /* ----------------------------------------------------------------------- */
 bool ASRTools::pickFacetNeighbors(Element & cent_facet) {
   auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
   auto & inserter = coh_model.getElementInserter();
   auto & mesh = model.getMesh();
   auto & mesh_facets = inserter.getMeshFacets();
   auto dim = mesh.getSpatialDimension();
   auto facet_type = cent_facet.type;
   auto facet_gt = cent_facet.ghost_type;
   auto facet_nb = cent_facet.element;
   auto & doubled_facets = mesh_facets.getElementGroup("doubled_facets");
   auto & doubled_nodes = mesh.getNodeGroup("doubled_nodes");
   auto & facet_conn = mesh_facets.getConnectivity(facet_type, facet_gt);
   auto & facet_material_index =
       coh_model.getFacetMaterial(facet_type, facet_gt)(facet_nb);
 
   // get list of the subel connected to the facet (nodes in 2D, segments
   // in 3D)
   const Vector<Element> & subelements_to_element =
       mesh_facets.getSubelementToElement(cent_facet);
 
   Array<Element> neighbors(subelements_to_element.size());
   neighbors.set(ElementNull);
   // max weight parameter computed for each subelement iteration
   // here this parameter = dot product between two normals
   // minimum 60 deg between normals -> 120 between dips
   Real weight_parameter_threshold(0.5);
   std::vector<Real> max_weight_parameters(subelements_to_element.size(),
                                           weight_parameter_threshold);
 
   // identify a facet's neighbor through each subelement
   for (UInt i : arange(subelements_to_element.size())) {
     auto & subel = subelements_to_element(i);
     auto & connected_elements = mesh_facets.getElementToSubelement(
         subel.type, subel.ghost_type)(subel.element);
 
     for (auto & connected_element : connected_elements) {
       // check all possible unsatisfactory conditions
       if (connected_element.type != facet_type)
         continue;
       if (connected_element.element == facet_nb)
         continue;
       auto & candidate_facet_material_index = coh_model.getFacetMaterial(
           connected_element.type,
           connected_element.ghost_type)(connected_element.element);
       if (candidate_facet_material_index != facet_material_index)
         continue;
       if (not inserter.getCheckFacets(connected_element.type,
                                       connected_element.ghost_type)(
               connected_element.element))
         continue;
       // discard facets intersecting other ASR elements
       bool ASR_node{false};
       for (UInt j : arange(facet_conn.getNbComponent())) {
         auto node = facet_conn(connected_element.element, j);
         if (this->ASR_nodes(node))
           ASR_node = true;
       }
       if (ASR_node)
         continue;
 
       // get inscribed diameter
       Real facet_indiam =
           MeshUtils::getInscribedCircleDiameter(model, cent_facet);
 
       // get distance between two barycenters
-      auto dist = MeshUtils::distanceBetweenBarycentersCorrected(
+      auto dist = MeshUtils::distanceBetweenIncentersCorrected(
           mesh_facets, cent_facet, connected_element);
 
       // ad-hoc rule on barycenters spacing
       // it should discard all elements under sharp angle
       if (dist < facet_indiam)
         continue;
 
       // get abs of dot product between two normals
       Real dot = MeshUtils::cosSharpAngleBetween2Facets(model, cent_facet,
                                                         connected_element);
 
       auto weight_parameter = dot;
       if (weight_parameter > max_weight_parameters[i]) {
         max_weight_parameters[i] = weight_parameter;
         neighbors(i) = connected_element;
       }
     }
   }
 
   // different insertion procedures for 2D and 3D cases
   switch (dim) {
   case 2: {
     if (neighbors.find(ElementNull) == UInt(-1)) {
       for (auto & neighbor : neighbors) {
         doubled_facets.add(neighbor);
         for (UInt node : arange(2)) {
           this->ASR_nodes(facet_conn(neighbor.element, node)) = true;
         }
       }
       return true;
     } else
       return false;
   }
   case 3: {
     auto max_el_pos = std::max_element(max_weight_parameters.begin(),
                                        max_weight_parameters.end());
     if (*max_el_pos > weight_parameter_threshold) {
       auto max_param_index =
           std::distance(max_weight_parameters.begin(), max_el_pos);
       auto & neighbor = neighbors(max_param_index);
       doubled_facets.add(neighbor);
       for (UInt node : arange(facet_conn.getNbComponent())) {
         doubled_nodes.add(facet_conn(neighbor.element, node));
         this->ASR_nodes(facet_conn(neighbor.element, node)) = true;
       }
       return true;
     } else
       return false;
   }
   default:
     AKANTU_EXCEPTION("Provided dimension is not supported");
   }
 }
 /* ------------------------------------------------------------------ */
 void ASRTools::preventCohesiveInsertionInNeighbors() {
   auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
   auto & inserter = coh_model.getElementInserter();
   auto & mesh = model.getMesh();
   auto & mesh_facets = inserter.getMeshFacets();
   auto dim = mesh.getSpatialDimension();
   const GhostType gt = _not_ghost;
   auto el_type = *mesh.elementTypes(dim, gt, _ek_regular).begin();
   auto facet_type = Mesh::getFacetType(el_type);
   // auto subfacet_type = Mesh::getFacetType(facet_type);
 
   // auto & facet_conn = mesh.getConnectivity(facet_type, gt);
   // auto facet_conn_it = facet_conn.begin(facet_conn.getNbComponent());
   auto & subfacets_to_facets =
       mesh_facets.getSubelementToElement(facet_type, gt);
   auto nb_subfacet = subfacets_to_facets.getNbComponent();
   auto subf_to_fac_it = subfacets_to_facets.begin(nb_subfacet);
   // auto & doubled_nodes = mesh.getNodeGroup("doubled_nodes");
 
   // CSR<Element> nodes_to_elements;
   // MeshUtils::buildNode2Elements(mesh_facets, nodes_to_elements, dim - 1);
   // for (auto node : doubled_nodes.getNodes()) {
   //   for (auto & elem : nodes_to_elements.getRow(node)) {
   //     if (elem.type != facet_type)
   //       continue;
   //     inserter.getCheckFacets(elem.type, gt)(elem.element) = false;
   //   }
   // }
 
   auto & el_group = mesh_facets.getElementGroup("doubled_facets");
   Array<UInt> element_ids = el_group.getElements(facet_type);
   for (UInt i : arange(element_ids.size() / 2)) {
     auto facet1 = element_ids(i);
     auto facet2 = element_ids(i + element_ids.size() / 2);
     std::vector<Element> subfacets1(nb_subfacet);
     std::vector<Element> subfacets2(nb_subfacet);
     for (UInt i : arange(nb_subfacet)) {
       subfacets1[i] = subf_to_fac_it[facet1](i);
       subfacets2[i] = subf_to_fac_it[facet2](i);
     }
     std::sort(subfacets1.begin(), subfacets1.end());
     std::sort(subfacets2.begin(), subfacets2.end());
     std::vector<Element> dif_subfacets(2 * nb_subfacet);
     std::vector<Element>::iterator it;
     // identify subfacets not shared by two facets
     it = std::set_difference(subfacets1.begin(), subfacets1.end(),
                              subfacets2.begin(), subfacets2.end(),
                              dif_subfacets.begin());
     dif_subfacets.resize(it - dif_subfacets.begin());
 
     // prevent insertion of cohesives in the facets including these subf
     for (auto & subfacet : dif_subfacets) {
       auto neighbor_facets = mesh_facets.getElementToSubelement(subfacet);
       for (auto & neighbor_facet : neighbor_facets) {
         inserter.getCheckFacets(facet_type, gt)(neighbor_facet.element) = false;
       }
     }
   }
 }
 
 /* ------------------------------------------------------------------- */
 void ASRTools::insertOppositeFacetsAndCohesives() {
   auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
   auto & inserter = coh_model.getElementInserter();
   auto & insertion = inserter.getInsertionFacetsByElement();
   auto & mesh = model.getMesh();
   auto & mesh_facets = inserter.getMeshFacets();
   auto dim = mesh.getSpatialDimension();
   const GhostType gt = _not_ghost;
   const auto & pos = mesh.getNodes();
   const auto pos_it = make_view(pos, dim).begin();
   auto & doubled_facets = mesh_facets.getElementGroup("doubled_facets");
 
   /// sort facet numbers in doubled_facets_group to comply with new_elements
   /// event passed by the inserter
   doubled_facets.optimize();
 
   /// instruct the inserter which facets to duplicate
   for (auto & type : doubled_facets.elementTypes(dim - 1)) {
     const auto element_ids = doubled_facets.getElements(type, gt);
     /// iterate over facets to duplicate
     for (auto && el : element_ids) {
       inserter.getCheckFacets(type, gt)(el) = false;
       Element new_facet{type, el, gt};
       insertion(new_facet) = true;
     }
   }
 
   /// duplicate facets and insert coh els
   inserter.insertElements(false);
 
   /// add facets connectivity to the mesh and the element group
   NewElementsEvent new_facets_event;
   for (auto & type : doubled_facets.elementTypes(dim - 1)) {
     const auto element_ids = doubled_facets.getElements(type, gt);
 
     if (not mesh.getConnectivities().exists(type, gt))
       mesh.addConnectivityType(type, gt);
     auto & facet_conn = mesh.getConnectivity(type, gt);
     auto & mesh_facet_conn = mesh_facets.getConnectivity(type, gt);
     const UInt nb_nodes_facet = facet_conn.getNbComponent();
     const auto mesh_facet_conn_it =
         make_view(mesh_facet_conn, nb_nodes_facet).begin();
 
     /// iterate over duplicated facets
     for (auto && el : element_ids) {
       Vector<UInt> facet_nodes = mesh_facet_conn_it[el];
       facet_conn.push_back(facet_nodes);
       Element new_facet{type, facet_conn.size() - 1, gt};
       new_facets_event.getList().push_back(new_facet);
     }
   }
 
   MeshUtils::fillElementToSubElementsData(mesh);
   mesh.sendEvent(new_facets_event);
 
   // fill up ASR facets vector
   this->ASR_facets_from_mesh.resize(this->ASR_facets_from_mesh_facets.size());
   for (UInt i = 0; i != this->ASR_facets_from_mesh_facets.size(); i++) {
     auto single_site_facets = this->ASR_facets_from_mesh_facets(i);
     for (auto & facet : single_site_facets) {
       Element cohesive{ElementNull};
       auto & connected_els = mesh_facets.getElementToSubelement(facet);
       for (auto & connected_el : connected_els) {
         if (mesh.getKind(connected_el.type) == _ek_cohesive) {
           cohesive = connected_el;
           break;
         }
       }
       AKANTU_DEBUG_ASSERT(cohesive != ElementNull,
                           "Connected cohesive element not identified");
       auto && connected_subels = mesh.getSubelementToElement(cohesive);
       for (auto & connected_subel : connected_subels) {
         if (connected_subel.type != facet.type)
           continue;
         this->ASR_facets_from_mesh(i).emplace(connected_subel);
       }
     }
   }
 
   // create an element group with nodes to apply Dirichlet
   model.getMesh().createElementGroupFromNodeGroup("doubled_nodes",
                                                   "doubled_nodes", dim - 1);
 
   // update FEEngineBoundary with new elements
   model.getFEEngineBoundary().initShapeFunctions(_not_ghost);
   model.getFEEngineBoundary().initShapeFunctions(_ghost);
   model.getFEEngineBoundary().computeNormalsOnIntegrationPoints(_not_ghost);
   model.getFEEngineBoundary().computeNormalsOnIntegrationPoints(_ghost);
 }
 /* ----------------------------------------------------------------------- */
 void ASRTools::assignCrackNumbers() {
   auto & mesh = model.getMesh();
   auto & mesh_facets = mesh.getMeshFacets();
   auto dim = mesh.getSpatialDimension();
   const GhostType gt = _not_ghost;
 
   for (auto && type_facet : mesh_facets.elementTypes(dim - 1)) {
     ElementType type_cohesive = FEEngine::getCohesiveElementType(type_facet);
     auto & crack_numbers =
         mesh.getDataPointer<UInt>("crack_numbers", type_cohesive);
     auto & coh_conn = mesh.getConnectivity(type_cohesive, gt);
     auto nb_coh_nodes = coh_conn.getNbComponent();
     auto coh_conn_it = coh_conn.begin(coh_conn.getNbComponent());
 
     // initialize a graph
     typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS>
         Graph;
     Graph graph;
 
     // build the graph based on the connectivity of cohesive elements
     for (UInt i = 0; i < coh_conn.size(); i++) {
       for (UInt j = i + 1; j < coh_conn.size(); j++) {
         auto nodes_1 = coh_conn_it[i];
         auto nodes_2 = coh_conn_it[j];
         std::vector<UInt> nod_1(nb_coh_nodes);
         std::vector<UInt> nod_2(nb_coh_nodes);
         for (UInt k : arange(nb_coh_nodes)) {
           nod_1[k] = nodes_1(k);
           nod_2[k] = nodes_2(k);
         }
 
         std::sort(nod_1.begin(), nod_1.end());
         std::sort(nod_2.begin(), nod_2.end());
         std::vector<UInt> common_nodes(nb_coh_nodes);
         std::vector<UInt>::iterator it;
 
         // An iterator to the end of the constructed range.
         it = std::set_intersection(nod_1.begin(), nod_1.end(), nod_2.begin(),
                                    nod_2.end(), common_nodes.begin());
         common_nodes.resize(it - common_nodes.begin());
 
         // switch (dim) {
         // case 2: {
         //   // 1 common node between 2 segments
         //   if (common_nodes.size() == 1) {
         //     boost::add_edge(i, j, graph);
         //   }
         //   break;
         // }
         // case 3: {
         // 2 or 3 common nodes between 2 triangles
         if (common_nodes.size() > 1) {
           boost::add_edge(i, j, graph);
         }
         //   break;
         // }
         // }
       }
     }
 
     /// connectivity and number of components of a graph
     std::vector<int> component(boost::num_vertices(graph));
     int num = boost::connected_components(graph, &component[0]);
 
     /// shift the crack numbers by the number of cracks on processors with the
     /// lower rank
     auto && comm = akantu::Communicator::getWorldCommunicator();
     comm.exclusiveScan(num);
     for (auto & component_nb : component) {
       component_nb += num;
     }
 
     /// assign a corresponding crack flag
     for (UInt coh_el : arange(component.size())) {
       crack_numbers(coh_el) = component[coh_el];
     }
   }
 }
 /* ------------------------------------------------------------------- */
 void ASRTools::insertGap(const Real gap_ratio) {
   AKANTU_DEBUG_IN();
   if (gap_ratio == 0)
     return;
 
   auto & mesh = model.getMesh();
   MeshAccessor mesh_accessor(mesh);
   auto & pos2modify = mesh_accessor.getNodes();
 
   auto dim = mesh.getSpatialDimension();
   const GhostType gt = _not_ghost;
   auto & el_group = mesh.getElementGroup("doubled_nodes");
   auto & pos = mesh.getNodes();
   auto pos_it = make_view(pos, dim).begin();
   auto pos2modify_it = make_view(pos2modify, dim).begin();
 
   for (auto & type : el_group.elementTypes(dim - 1)) {
     auto & facet_conn = mesh.getConnectivity(type, gt);
     const UInt nb_nodes_facet = facet_conn.getNbComponent();
     auto facet_nodes_it = make_view(facet_conn, nb_nodes_facet).begin();
     const auto element_ids = el_group.getElements(type, gt);
     auto && fe_engine = model.getFEEngineBoundary();
     auto nb_qpoints_per_facet = fe_engine.getNbIntegrationPoints(type, gt);
     const auto & normals_on_quad =
         fe_engine.getNormalsOnIntegrationPoints(type, gt);
     auto normals_it = make_view(normals_on_quad, dim).begin();
     for (UInt i : arange(element_ids.size())) {
       auto el_id = element_ids(i);
       Vector<Real> normal = normals_it[el_id * nb_qpoints_per_facet];
       if (i < element_ids.size() / 2)
         normal *= -1;
       /// compute segment length
       auto facet_nodes = facet_nodes_it[el_id];
       UInt A, B;
       A = facet_nodes(0);
       B = facet_nodes(1);
       Vector<Real> AB = Vector<Real>(pos_it[B]) - Vector<Real>(pos_it[A]);
       Real correction = AB.norm() * gap_ratio / 2;
       Vector<Real> half_opening_vector = normal * correction;
       for (auto j : arange(nb_nodes_facet)) {
         Vector<Real> node_pos(pos2modify_it[facet_nodes(j)]);
         node_pos += half_opening_vector;
         this->modified_pos(facet_nodes(j)) = true;
       }
     }
   }
 
   /// update FEEngine & FEEngineBoundary with new elements
   model.getFEEngine().initShapeFunctions(_not_ghost);
   model.getFEEngine().initShapeFunctions(_ghost);
   model.getFEEngineBoundary().initShapeFunctions(_not_ghost);
   model.getFEEngineBoundary().initShapeFunctions(_ghost);
   model.getFEEngineBoundary().computeNormalsOnIntegrationPoints(_not_ghost);
   model.getFEEngineBoundary().computeNormalsOnIntegrationPoints(_ghost);
 
   AKANTU_DEBUG_OUT();
 }
 /* ------------------------------------------------------------------- */
 void ASRTools::insertGap3D(const Real gap_ratio) {
   AKANTU_DEBUG_IN();
   if (gap_ratio == 0)
     return;
 
   auto & mesh = model.getMesh();
   auto & mesh_facets = mesh.getMeshFacets();
   MeshAccessor mesh_accessor(mesh);
   auto & pos2modify = mesh_accessor.getNodes();
 
   auto dim = mesh.getSpatialDimension();
   const GhostType gt = _not_ghost;
   auto & el_group = mesh_facets.getElementGroup("doubled_facets");
   auto & pos = mesh.getNodes();
   auto pos_it = make_view(pos, dim).begin();
   auto pos2modify_it = make_view(pos2modify, dim).begin();
 
   for (auto & type : el_group.elementTypes(dim - 1)) {
     if ((dim == 3) and (type != _triangle_6))
       AKANTU_EXCEPTION("The only facet type supported in 3D is _triangle_6");
     const Array<UInt> element_ids = el_group.getElements(type, gt);
     auto && fe_engine = model.getFEEngineBoundary();
     auto nb_qpoints_per_facet = fe_engine.getNbIntegrationPoints(type, gt);
 
     // identify pairs of facets
     Array<UInt> facets_considered;
     for (UInt i : arange(element_ids.size())) {
       auto el_id = element_ids(i);
       Element element{type, el_id, gt};
 
       // if element was already considered -> skip
       if (facets_considered.find(el_id) != UInt(-1))
         continue;
 
       Array<UInt> element_ids_half(element_ids.size() / 2);
       UInt starting_pos;
       if (i < element_ids.size() / 2) {
         starting_pos = 0;
       } else {
         starting_pos = element_ids.size() / 2;
       }
       for (auto j : arange(element_ids_half.size())) {
         element_ids_half(j) = element_ids(j + starting_pos);
       }
 
       // find a neighbor
       const Vector<Element> & subelements_to_element =
           mesh_facets.getSubelementToElement(element);
       // identify a facet's neighbor through each subelement
       Element neighbor(ElementNull);
       Element border(ElementNull);
       for (UInt i : arange(subelements_to_element.size())) {
         auto & subel = subelements_to_element(i);
         auto & connected_elements = mesh_facets.getElementToSubelement(subel);
         for (auto & connected_element : connected_elements) {
           // check all possible unsatisfactory conditions
           if (connected_element.type != type)
             continue;
           if (connected_element.element == el_id)
             continue;
           // search for this neighbor in the doubled_nodes el group
           UInt neighbor_pos_in_el_ids =
               element_ids_half.find(connected_element.element);
           if (neighbor_pos_in_el_ids == UInt(-1))
             continue;
 
           // if found -> subelement is the border
           neighbor = connected_element;
           border = subel;
           facets_considered.push_back(el_id);
           facets_considered.push_back(connected_element.element);
           break;
         }
         if (neighbor != ElementNull)
           break;
       }
       AKANTU_DEBUG_ASSERT(neighbor != ElementNull,
                           "Neighbor for the facet was not identified for the "
                           "purpose of the gap insertion");
 
       // average the normal in between two neighbors
       const auto & normals_on_quad =
           fe_engine.getNormalsOnIntegrationPoints(type, gt);
       auto normals_it = make_view(normals_on_quad, dim).begin();
       Vector<Real> normal = normals_it[el_id * nb_qpoints_per_facet];
       normal +=
           Vector<Real>(normals_it[neighbor.element * nb_qpoints_per_facet]);
       normal /= normal.norm();
       // and correct it for the direction
       if (i < element_ids.size() / 2) {
         normal *= -1;
       }
 
       // compute segment length
       AKANTU_DEBUG_ASSERT(
           border.type == _segment_3,
           "The only supported segment type in 3D is _segment_3");
       auto & segment_conn =
           mesh_facets.getConnectivity(border.type, border.ghost_type);
       const UInt nb_nodes_segment = segment_conn.getNbComponent();
       auto segment_nodes_it = make_view(segment_conn, nb_nodes_segment).begin();
       auto segment_nodes = segment_nodes_it[border.element];
       // 2 end nodes and the middle node
       UInt A, B, middle_node;
       A = segment_nodes(0);
       B = segment_nodes(1);
       middle_node = segment_nodes(2);
       Vector<Real> AB = Vector<Real>(pos_it[B]) - Vector<Real>(pos_it[A]);
       Real correction = AB.norm() * gap_ratio / 2;
       Vector<Real> half_opening_vector = normal * correction;
       Vector<Real> node_pos(pos2modify_it[middle_node]);
       node_pos += half_opening_vector;
       this->modified_pos(middle_node) = true;
     }
   }
 
   /// update FEEngine & FEEngineBoundary with new elements
   model.getFEEngine().initShapeFunctions(_not_ghost);
   model.getFEEngine().initShapeFunctions(_ghost);
   model.getFEEngineBoundary().initShapeFunctions(_not_ghost);
   model.getFEEngineBoundary().initShapeFunctions(_ghost);
   model.getFEEngineBoundary().computeNormalsOnIntegrationPoints(_not_ghost);
   model.getFEEngineBoundary().computeNormalsOnIntegrationPoints(_ghost);
 
   AKANTU_DEBUG_OUT();
 }
 /* ----------------------------------------------------------------------- */
 void ASRTools::onElementsAdded(const Array<Element> & elements,
                                const NewElementsEvent &) {
 
   // function is activated only when expanding cohesive elements is on
   if (not this->cohesive_insertion)
     return;
 
   if (this->doubled_facets_ready) {
     return;
   }
 
   auto & mesh = model.getMesh();
   auto & mesh_facets = mesh.getMeshFacets();
   auto & doubled_facets = mesh_facets.getElementGroup("doubled_facets");
 
   for (auto elements_range : akantu::MeshElementsByTypes(elements)) {
     auto type = elements_range.getType();
     auto ghost_type = elements_range.getGhostType();
 
     if (mesh.getKind(type) != _ek_regular)
       continue;
 
     if (ghost_type != _not_ghost)
       continue;
 
     /// add new facets into the doubled_facets group
     auto & element_ids = elements_range.getElements();
     for (auto && el : element_ids) {
       Element new_facet{type, el, ghost_type};
       doubled_facets.add(new_facet);
     }
     this->doubled_facets_ready = true;
   }
 }
 /* --------------------------------------------------------------------------
  */
 void ASRTools::onNodesAdded(const Array<UInt> & new_nodes,
                             const NewNodesEvent &) {
   AKANTU_DEBUG_IN();
   if (new_nodes.size() == 0)
     return;
   // increase the internal arrays by the number of new_nodes
   UInt new_nb_nodes = this->modified_pos.size() + new_nodes.size();
   this->modified_pos.resize(new_nb_nodes);
   this->partition_border_nodes.resize(new_nb_nodes);
+  this->nodes_eff_stress.resize(new_nb_nodes);
   this->ASR_nodes.resize(new_nb_nodes);
 
   // function is activated only when expanding cohesive elements is on
   if (not this->cohesive_insertion)
     return;
   if (this->doubled_nodes_ready)
     return;
   auto & mesh = model.getMesh();
   auto & node_group = mesh.getNodeGroup("doubled_nodes");
   auto & central_nodes = node_group.getNodes();
   auto pos_it = make_view(mesh.getNodes(), mesh.getSpatialDimension()).begin();
 
   for (auto & new_node : new_nodes) {
     const Vector<Real> & new_node_coord = pos_it[new_node];
     for (auto & central_node : central_nodes) {
       const Vector<Real> & central_node_coord = pos_it[central_node];
       if (new_node_coord == central_node_coord)
         node_group.add(new_node);
     }
   }
   this->doubled_nodes_ready = true;
   AKANTU_DEBUG_OUT();
 }
 
 /* ------------------------------------------------------------------------ */
 void ASRTools::updateElementGroup(const std::string group_name) {
   AKANTU_DEBUG_IN();
   auto & mesh = model.getMesh();
   AKANTU_DEBUG_ASSERT(mesh.elementGroupExists(group_name),
                       "Element group is not registered in the mesh");
   auto dim = mesh.getSpatialDimension();
   const GhostType gt = _not_ghost;
   auto && group = mesh.getElementGroup(group_name);
   auto && pos = mesh.getNodes();
   const auto pos_it = make_view(pos, dim).begin();
 
   for (auto & type : group.elementTypes(dim - 1)) {
     auto & facet_conn = mesh.getConnectivity(type, gt);
     const UInt nb_nodes_facet = facet_conn.getNbComponent();
     const auto facet_nodes_it = make_view(facet_conn, nb_nodes_facet).begin();
     AKANTU_DEBUG_ASSERT(
         type == _segment_2,
         "Currently update group works only for el type _segment_2");
     const auto element_ids = group.getElements(type, gt);
     for (auto && el_id : element_ids) {
       const auto connected_els = mesh.getElementToSubelement(type, gt)(el_id);
       for (auto && connected_el : connected_els) {
 
         auto type_solid = connected_el.type;
         auto & solid_conn = mesh.getConnectivity(type_solid, gt);
         const UInt nb_nodes_solid_el = solid_conn.getNbComponent();
         const auto solid_nodes_it =
             make_view(solid_conn, nb_nodes_solid_el).begin();
         Vector<UInt> facet_nodes = facet_nodes_it[el_id];
         Vector<UInt> solid_nodes = solid_nodes_it[connected_el.element];
 
         // /// to which of connected elements facet belongs
         // Array<UInt> solid_nodes(nb_nodes_solid_el);
         // for (UInt node : arange(nb_nodes_solid_el)) {
         //   solid_nodes(node) = solid_nodes_it[connected_el.element](node);
         // }
         // /// check for the central facet node (id doesn't change nb)
         // auto id = solid_nodes.find(facet_nodes(2));
         // if (id == UInt(-1))
         //   continue;
 
         /// check only the corner nodes of facets - central will not change
         for (auto f : arange(2)) {
           auto facet_node = facet_nodes(f);
           const Vector<Real> & facet_node_coords = pos_it[facet_node];
           for (auto s : arange(nb_nodes_solid_el)) {
             auto solid_node = solid_nodes(s);
             const Vector<Real> & solid_node_coords = pos_it[solid_node];
             if (solid_node_coords == facet_node_coords) {
               if (solid_node != facet_node) {
                 // group.removeNode(facet_node);
                 facet_conn(el_id, f) = solid_node;
                 // group.addNode(solid_node, true);
               }
               break;
             }
           }
         }
       }
     }
   }
 
   group.optimize();
   AKANTU_DEBUG_OUT();
 }
 
 /* -------------------------------------------------------------------------*/
 // void ASRTools::applyDeltaU(Real delta_u) {
 //   // get element group with nodes to apply Dirichlet
 //   auto & crack_facets = model.getMesh().getElementGroup("doubled_nodes");
 
 //   DeltaU delta_u_bc(model, delta_u, getNodePairs());
 //   model.applyBC(delta_u_bc, crack_facets);
 // }
 
 /* --------------------------------------------------------------------------
  */
 void ASRTools::applyGelStrain(const Matrix<Real> & prestrain) {
   AKANTU_DEBUG_IN();
   auto & mesh = model.getMesh();
   auto dim = mesh.getSpatialDimension();
   AKANTU_DEBUG_ASSERT(dim == 2, "This is 2D only!");
 
   /// apply the new eigenstrain
   for (auto element_type :
        mesh.elementTypes(dim, _not_ghost, _ek_not_defined)) {
     Array<Real> & prestrain_vect =
         const_cast<Array<Real> &>(model.getMaterial("gel").getInternal<Real>(
             "eigen_grad_u")(element_type));
     auto prestrain_it = prestrain_vect.begin(dim, dim);
     auto prestrain_end = prestrain_vect.end(dim, dim);
 
     for (; prestrain_it != prestrain_end; ++prestrain_it)
       (*prestrain_it) = prestrain;
   }
   AKANTU_DEBUG_OUT();
 }
 /* --------------------------------------------------------------------------
  */
 void ASRTools::clearASREigenStrain() {
   AKANTU_DEBUG_IN();
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   Matrix<Real> zero_eigenstrain(dim, dim, 0.);
   GhostType gt = _not_ghost;
   for (auto element_type : mesh.elementTypes(dim, gt, _ek_not_defined)) {
     auto & prestrain_vect =
         const_cast<Array<Real> &>(model.getMaterial("gel").getInternal<Real>(
             "eigen_grad_u")(element_type));
     auto prestrain_it = prestrain_vect.begin(dim, dim);
     auto prestrain_end = prestrain_vect.end(dim, dim);
 
     for (; prestrain_it != prestrain_end; ++prestrain_it)
       (*prestrain_it) = zero_eigenstrain;
   }
   AKANTU_DEBUG_OUT();
 }
 /* --------------------------------------------------------------------------
  */
 void ASRTools::storeASREigenStrain(Array<Real> & stored_eig) {
   AKANTU_DEBUG_IN();
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   GhostType gt = _not_ghost;
   UInt nb_quads = 0;
   for (auto element_type : mesh.elementTypes(dim, gt, _ek_not_defined)) {
     const UInt nb_gp = model.getFEEngine().getNbIntegrationPoints(element_type);
     nb_quads +=
         model.getMaterial("gel").getElementFilter(element_type).size() * nb_gp;
   }
   stored_eig.resize(nb_quads);
   auto stored_eig_it = stored_eig.begin(dim, dim);
   for (auto element_type : mesh.elementTypes(dim, gt, _ek_not_defined)) {
     auto & prestrain_vect =
         const_cast<Array<Real> &>(model.getMaterial("gel").getInternal<Real>(
             "eigen_grad_u")(element_type));
     auto prestrain_it = prestrain_vect.begin(dim, dim);
     auto prestrain_end = prestrain_vect.end(dim, dim);
 
     for (; prestrain_it != prestrain_end; ++prestrain_it, ++stored_eig_it)
       (*stored_eig_it) = (*prestrain_it);
   }
   AKANTU_DEBUG_OUT();
 }
 /* ------------------------------------------------------------------ */
 void ASRTools::restoreASREigenStrain(Array<Real> & stored_eig) {
   AKANTU_DEBUG_IN();
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   GhostType gt = _not_ghost;
   auto stored_eig_it = stored_eig.begin(dim, dim);
   for (auto element_type : mesh.elementTypes(dim, gt, _ek_not_defined)) {
     auto & prestrain_vect =
         const_cast<Array<Real> &>(model.getMaterial("gel").getInternal<Real>(
             "eigen_grad_u")(element_type));
     auto prestrain_it = prestrain_vect.begin(dim, dim);
     auto prestrain_end = prestrain_vect.end(dim, dim);
 
     for (; prestrain_it != prestrain_end; ++prestrain_it, ++stored_eig_it)
       (*prestrain_it) = (*stored_eig_it);
   }
   AKANTU_DEBUG_OUT();
 }
 
-// /* ------------------------------------------------------------------- */
-// template <UInt dim> UInt ASRTools::insertCohesiveElementsSelectively() {
-//   AKANTU_DEBUG_IN();
-
-//   auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
-//   CohesiveElementInserter & inserter = coh_model.getElementInserter();
-//   auto && comm = akantu::Communicator::getWorldCommunicator();
-//   auto prank = comm.whoAmI();
-
-//   if (not coh_model.getIsExtrinsic()) {
-//     AKANTU_EXCEPTION(
-//         "This function can only be used for extrinsic cohesive elements");
-//   }
-
-//   coh_model.interpolateStress();
-
-//   const Mesh & mesh_facets = coh_model.getMeshFacets();
-//   // MeshUtils::fillElementToSubElementsData(mesh_facets);
-//   UInt nb_new_elements(0);
-
-//   // identify the most stressed finite element
-//   for (auto && type_facet : mesh_facets.elementTypes(dim - 1)) {
-
-//     Real max_stress = std::numeric_limits<Real>::min();
-//     std::string max_stress_mat_name;
-//     int max_stress_prank(-1);
-//     UInt max_stress_facet(-1);
-//     UInt crack_nb(-1);
-//     std::tuple<UInt, Real, UInt> max_stress_data(max_stress_facet,
-//     max_stress,
-//                                                  crack_nb);
-
-//     for (auto && mat : model.getMaterials()) {
-
-//       auto * mat_coh =
-//           dynamic_cast<MaterialCohesiveLinearSequential<dim> *>(&mat);
-
-//       if (mat_coh == nullptr)
-//         continue;
-
-//       // check which material has the non ghost facet with the highest stress
-//       // which complies with the topological criteria
-//       auto max_stress_data = mat_coh->findCriticalFacetNonLocal(type_facet);
-//       auto max_stress_mat = std::get<1>(max_stress_data);
-//       auto max_stress_facet_mat = std::get<0>(max_stress_data);
-//       auto crack_nb_mat = std::get<2>(max_stress_data);
-
-//       // communicate between processors the highest stress
-//       Real local_max_stress = max_stress_mat;
-//       comm.allReduce(max_stress_mat, SynchronizerOperation::_max);
-
-//       // write down the maximum stress, material_ID and proc #
-//       if (max_stress_mat > max_stress) {
-//         max_stress = max_stress_mat;
-//         max_stress_mat_name = mat.getName();
-//         max_stress_prank = -1;
-//         if (local_max_stress == max_stress_mat) {
-//           max_stress_facet = max_stress_facet_mat;
-//           crack_nb = crack_nb_mat;
-//           max_stress_prank = prank;
-//         }
-//       }
-//     }
-
-//     // if max stress is low or another proc has the most stressed el ->skip
-//     if ((max_stress < 1) or (max_stress_prank != prank))
-//       continue;
-
-//     // otherwise insert cohesive element in this specific material
-//     auto & mat = coh_model.getMaterial(max_stress_mat_name);
-//     auto * mat_coh =
-//         dynamic_cast<MaterialCohesiveLinearSequential<dim> *>(&mat);
-//     std::map<UInt, UInt> facet_nb_crack_nb;
-//     facet_nb_crack_nb[max_stress_facet] = crack_nb;
-//     mat_coh->insertCohesiveElements(facet_nb_crack_nb, type_facet, false);
-//   }
-
-//   // insert the most stressed cohesive element
-//   nb_new_elements = inserter.insertElements();
-
-//   // fill in the holes in all materials
-//   bool new_holes_filled{true};
-//   while (new_holes_filled) {
-//     new_holes_filled = false;
-//     for (auto && type_facet : mesh_facets.elementTypes(dim - 1)) {
-//       for (auto && mat : model.getMaterials()) {
-//         auto * mat_coh =
-//             dynamic_cast<MaterialCohesiveLinearSequential<dim> *>(&mat);
-//         if (mat_coh == nullptr)
-//           continue;
-//         if (mat_coh->fillInHoles(type_facet, false)) {
-//           new_holes_filled = true;
-//         }
-//         comm.allReduce(new_holes_filled, SynchronizerOperation::_lor);
-//       }
-//     }
-//     nb_new_elements += inserter.insertElements();
-//   }
-
-//   AKANTU_DEBUG_OUT();
-//   return nb_new_elements;
-// }
-
-// template UInt ASRTools::insertCohesiveElementsSelectively<2>();
-// template UInt ASRTools::insertCohesiveElementsSelectively<3>();
-
 /* ------------------------------------------------------------------- */
 template <UInt dim> UInt ASRTools::insertCohesiveElementsOnContour() {
   AKANTU_DEBUG_IN();
 
   auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
   CohesiveElementInserter & inserter = coh_model.getElementInserter();
 
   if (not coh_model.getIsExtrinsic()) {
     AKANTU_EXCEPTION(
         "This function can only be used for extrinsic cohesive elements");
   }
 
   coh_model.interpolateStress();
 
   const Mesh & mesh_facets = coh_model.getMeshFacets();
   UInt nb_new_elements(0);
   auto type_facet = *mesh_facets.elementTypes(dim - 1).begin();
 
   // find crack topology within specific material and insert cohesives
   for (auto && mat : model.getMaterials()) {
     auto * mat_coh =
         dynamic_cast<MaterialCohesiveLinearSequential<dim> *>(&mat);
 
     if (mat_coh == nullptr)
       continue;
 
     auto data = mat_coh->determineCrackSurface();
     auto & contour_subfacets_coh_el = std::get<0>(data);
     auto & surface_subfacets_crack_nb = std::get<1>(data);
     auto & surface_nodes = std::get<2>(data);
     auto & contour_nodes = std::get<3>(data);
 
     if (not contour_subfacets_coh_el.size()) {
       continue;
     }
     std::map<UInt, UInt> facet_nbs_crack_nbs =
         mat_coh->findCriticalFacetsOnContour(contour_subfacets_coh_el,
                                              surface_subfacets_crack_nb,
                                              contour_nodes, surface_nodes);
     mat_coh->insertCohesiveElements(facet_nbs_crack_nbs, type_facet, false);
   }
 
   // insert the most stressed cohesive element
   nb_new_elements = inserter.insertElements();
 
   // communicate crack numbers
   communicateCrackNumbers();
 
   // // loop until no more holes are present
   // bool new_holes_filled{true};
   // while (new_holes_filled) {
   //   new_holes_filled = false;
   // fill in holes
   for (auto && mat : model.getMaterials()) {
     auto * mat_coh =
         dynamic_cast<MaterialCohesiveLinearSequential<dim> *>(&mat);
 
     if (mat_coh == nullptr)
       continue;
 
     auto data = mat_coh->determineCrackSurface();
     auto & contour_subfacets_coh_el = std::get<0>(data);
     auto & surface_subfacets_crack_nb = std::get<1>(data);
     auto & surface_nodes = std::get<2>(data);
     // auto & contour_nodes = std::get<3>(data);
 
     if (not contour_subfacets_coh_el.size()) {
       continue;
     }
     std::map<UInt, UInt> facet_nbs_crack_nbs = mat_coh->findHolesOnContour(
         contour_subfacets_coh_el, surface_subfacets_crack_nb, surface_nodes);
     // new_holes_filled = facet_nbs_crack_nbs.size();
     // comm.allReduce(new_holes_filled, SynchronizerOperation::_lor);
 
     mat_coh->insertCohesiveElements(facet_nbs_crack_nbs, type_facet, false);
   }
   auto nb_new_holes = inserter.insertElements();
   nb_new_elements += nb_new_holes;
 
   // communicate crack numbers
   communicateCrackNumbers();
 
   // }
   AKANTU_DEBUG_OUT();
   return nb_new_elements;
 }
 
 template UInt ASRTools::insertCohesiveElementsOnContour<2>();
 template UInt ASRTools::insertCohesiveElementsOnContour<3>();
 
+/* ------------------------------------------------------------------- */
+template <UInt dim>
+UInt ASRTools::insertCohesiveElementsOnContourByNodes(
+    Real av_stress_threshold) {
+  AKANTU_DEBUG_IN();
+
+  auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
+  CohesiveElementInserter & inserter = coh_model.getElementInserter();
+
+  if (not coh_model.getIsExtrinsic()) {
+    AKANTU_EXCEPTION(
+        "This function can only be used for extrinsic cohesive elements");
+  }
+
+  coh_model.interpolateStress();
+
+  const Mesh & mesh_facets = coh_model.getMeshFacets();
+  UInt nb_new_elements(0);
+  auto type_facet = *mesh_facets.elementTypes(dim - 1).begin();
+
+  // find global crack topology
+  auto data = determineCrackSurface();
+  auto & contour_subfacets_coh_el = std::get<0>(data);
+  auto & surface_subfacets_crack_nb = std::get<1>(data);
+  auto & surface_nodes = std::get<2>(data);
+  auto & contour_nodes_subfacets = std::get<3>(data);
+
+  // compute effective stress in all cohesive material linear sequent.
+  for (auto && mat : model.getMaterials()) {
+    auto * mat_coh =
+        dynamic_cast<MaterialCohesiveLinearSequential<dim> *>(&mat);
+
+    if (mat_coh == nullptr)
+      continue;
+
+    mat_coh->computeEffectiveStresses();
+  }
+
+  // identify facets on contour where cohesives should be inserted
+  std::map<UInt, std::map<UInt, UInt>> mat_index_facet_nbs_crack_nbs =
+      findCriticalFacetsOnContourByNodes<dim>(
+          contour_subfacets_coh_el, surface_subfacets_crack_nb,
+          contour_nodes_subfacets, surface_nodes, av_stress_threshold);
+
+  // insert cohesives depending on a material
+  for (auto & pair : mat_index_facet_nbs_crack_nbs) {
+    auto mat_index = pair.first;
+    auto & facet_nbs_crack_nbs = pair.second;
+    auto & mat = model.getMaterial(mat_index);
+    auto * mat_coh =
+        dynamic_cast<MaterialCohesiveLinearSequential<dim> *>(&mat);
+
+    if (mat_coh == nullptr)
+      continue;
+
+    mat_coh->insertCohesiveElements(facet_nbs_crack_nbs, type_facet, false);
+  }
+
+  // insert the most stressed cohesive element
+  nb_new_elements = inserter.insertElements();
+
+  // communicate crack numbers
+  communicateCrackNumbers();
+
+  AKANTU_DEBUG_OUT();
+  return nb_new_elements;
+}
+
+template UInt
+ASRTools::insertCohesiveElementsOnContourByNodes<2>(Real av_stress_threshold);
+template UInt
+ASRTools::insertCohesiveElementsOnContourByNodes<3>(Real av_stress_threshold);
+
+/* ------------------------------------------------------------------- */
+template <UInt dim>
+UInt ASRTools::insertLoopsOfStressedCohesives(Real min_dot) {
+  AKANTU_DEBUG_IN();
+
+  auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
+  CohesiveElementInserter & inserter = coh_model.getElementInserter();
+
+  if (not coh_model.getIsExtrinsic()) {
+    AKANTU_EXCEPTION(
+        "This function can only be used for extrinsic cohesive elements");
+  }
+
+  coh_model.interpolateStress();
+
+  // compute effective stress in all cohesive material linear sequent.
+  for (auto && mat : model.getMaterials()) {
+    auto * mat_coh =
+        dynamic_cast<MaterialCohesiveLinearSequential<dim> *>(&mat);
+
+    if (mat_coh == nullptr)
+      continue;
+
+    mat_coh->computeEffectiveStresses();
+  }
+
+  const Mesh & mesh_facets = coh_model.getMeshFacets();
+  UInt nb_new_elements(0);
+  auto type_facet = *mesh_facets.elementTypes(dim - 1).begin();
+
+  // find global crack topology
+  auto data = determineCrackSurface();
+  auto & contour_subfacets_coh_el = std::get<0>(data);
+  auto & surface_subfacets_crack_nb = std::get<1>(data);
+  auto & surface_nodes = std::get<2>(data);
+  auto & contour_nodes_subfacets = std::get<3>(data);
+
+  // identify facets on contour where cohesives should be inserted
+  std::map<UInt, std::map<UInt, UInt>> mat_index_facet_nbs_crack_nbs =
+      findStressedFacetsLoops(contour_subfacets_coh_el,
+                              surface_subfacets_crack_nb,
+                              contour_nodes_subfacets, surface_nodes, min_dot);
+
+  // insert cohesives depending on a material
+  for (auto & pair : mat_index_facet_nbs_crack_nbs) {
+    auto mat_index = pair.first;
+    auto & facet_nbs_crack_nbs = pair.second;
+    auto & mat = model.getMaterial(mat_index);
+    auto * mat_coh =
+        dynamic_cast<MaterialCohesiveLinearSequential<dim> *>(&mat);
+
+    if (mat_coh == nullptr)
+      continue;
+
+    mat_coh->insertCohesiveElements(facet_nbs_crack_nbs, type_facet, false);
+  }
+
+  // insert the most stressed cohesive element
+  nb_new_elements = inserter.insertElements();
+
+  // communicate crack numbers
+  communicateCrackNumbers();
+
+  AKANTU_DEBUG_OUT();
+  return nb_new_elements;
+}
+
+template UInt ASRTools::insertLoopsOfStressedCohesives<2>(Real min_dot);
+template UInt ASRTools::insertLoopsOfStressedCohesives<3>(Real min_dot);
+/* --------------------------------------------------------------- */
+template <UInt dim>
+std::map<UInt, std::map<UInt, UInt>>
+ASRTools::findCriticalFacetsOnContourByNodes(
+    const std::map<Element, Element> & contour_subfacets_coh_el,
+    const std::map<Element, UInt> & /*surface_subfacets_crack_nb*/,
+    const std::map<UInt, std::set<Element>> & contour_nodes_subfacets,
+    const std::set<UInt> & /*surface_nodes*/, Real av_stress_threshold) {
+  AKANTU_DEBUG_IN();
+
+  const Mesh & mesh = model.getMesh();
+  const Mesh & mesh_facets = mesh.getMeshFacets();
+
+  std::map<UInt, std::map<UInt, UInt>> mat_index_facet_nbs_crack_nbs;
+  std::map<Element, Element> contour_to_single_facet_map;
+
+  // TODO : make iteration on multiple facet types
+  auto type_facet = *mesh_facets.elementTypes(dim - 1).begin();
+  auto nb_facets = mesh.getNbElement(type_facet);
+  // skip if no facets of this type are present
+  if (not nb_facets) {
+    return mat_index_facet_nbs_crack_nbs;
+  }
+
+  // create a copy to remove nodes where single facets were inserted
+  auto contour_nodes_subfacets_copy = contour_nodes_subfacets;
+  // first loop for single elements
+  for (auto && pair : contour_nodes_subfacets) {
+    auto cent_node = pair.first;
+    auto subfacets = pair.second;
+    if (subfacets.size() != 2) {
+      std::cout << "Number of contour segments connected to the node "
+                << cent_node << " is not equal to 2. Skipping" << std::endl;
+      continue;
+    }
+    auto it = subfacets.begin();
+    auto starting_segment(*it);
+    std::advance(it, 1);
+    auto ending_segment(*it);
+    auto starting_coh_el = contour_subfacets_coh_el.at(starting_segment);
+    auto ending_coh_el = contour_subfacets_coh_el.at(ending_segment);
+    auto & crack_numbers = mesh.getData<UInt>(
+        "crack_numbers", starting_coh_el.type, starting_coh_el.ghost_type);
+    auto crack_nb = crack_numbers(starting_coh_el.element);
+
+    auto starting_facet =
+        mesh_facets.getSubelementToElement(starting_coh_el)(0);
+    auto ending_facet = mesh_facets.getSubelementToElement(ending_coh_el)(0);
+
+    // if an outstanding triangle element - skip
+    if (starting_facet == ending_facet) {
+      contour_nodes_subfacets_copy.erase(cent_node);
+      continue;
+    }
+
+    Array<Element> limit_facets, limit_segments;
+    limit_facets.push_back(starting_facet);
+    limit_facets.push_back(ending_facet);
+    limit_segments.push_back(starting_segment);
+    limit_segments.push_back(ending_segment);
+
+    auto single_facet_loop =
+        findSingleFacetLoop(limit_facets, limit_segments, cent_node);
+
+    if (not single_facet_loop.size())
+      continue;
+
+    // evaluate average effective stress over facets of the loop
+    auto av_eff_stress =
+        averageEffectiveStressInMultipleFacets(single_facet_loop);
+
+    // if average effective stress exceeds threshold->decompose per mat
+    if (av_eff_stress >= av_stress_threshold) {
+
+      decomposeFacetLoopPerMaterial(single_facet_loop, crack_nb,
+                                    mat_index_facet_nbs_crack_nbs);
+
+      // update the map
+      for (auto && limit_segment : limit_segments) {
+        contour_to_single_facet_map[limit_segment] = single_facet_loop(0);
+      }
+      // remove node from the looping map
+      contour_nodes_subfacets_copy.erase(cent_node);
+    }
+  }
+
+  // second loop for long facet loops on a reduced map
+  for (auto && pair : contour_nodes_subfacets_copy) {
+    auto cent_node = pair.first;
+    auto subfacets = pair.second;
+    if (subfacets.size() != 2) {
+      std::cout << "Number of contour segments connected to the node "
+                << cent_node << " is not equal to 2. Skipping" << std::endl;
+      continue;
+    }
+    auto it = subfacets.begin();
+    auto starting_segment(*it);
+    std::advance(it, 1);
+    auto ending_segment(*it);
+    auto starting_coh_el = contour_subfacets_coh_el.at(starting_segment);
+    auto ending_coh_el = contour_subfacets_coh_el.at(ending_segment);
+    auto & crack_numbers = mesh.getData<UInt>(
+        "crack_numbers", starting_coh_el.type, starting_coh_el.ghost_type);
+    auto crack_nb = crack_numbers(starting_coh_el.element);
+
+    auto starting_facet =
+        mesh_facets.getSubelementToElement(starting_coh_el)(0);
+    auto ending_facet = mesh_facets.getSubelementToElement(ending_coh_el)(0);
+
+    Array<Element> limit_facets, limit_segments;
+    limit_facets.push_back(starting_facet);
+    limit_facets.push_back(ending_facet);
+    limit_segments.push_back(starting_segment);
+    limit_segments.push_back(ending_segment);
+    Array<Element> preinserted_facets(2, 1, ElementNull);
+    for (UInt i = 0; i < 2; i++) {
+      auto it = contour_to_single_facet_map.find(limit_segments(i));
+      if (it != contour_to_single_facet_map.end()) {
+        preinserted_facets(i) = it->second;
+      }
+    }
+
+    auto facet_loop = findFacetsLoopByGraphByArea(
+        limit_facets, limit_segments, preinserted_facets, cent_node);
+
+    if (not facet_loop.size())
+      continue;
+
+    // evaluate average effective stress over facets of the loop
+    auto av_eff_stress = averageEffectiveStressInMultipleFacets(facet_loop);
+
+    // if average effective stress exceeds threshold->decompose per mat
+    if (av_eff_stress >= av_stress_threshold) {
+
+      decomposeFacetLoopPerMaterial(facet_loop, crack_nb,
+                                    mat_index_facet_nbs_crack_nbs);
+    }
+  }
+  return mat_index_facet_nbs_crack_nbs;
+}
+
+template std::map<UInt, std::map<UInt, UInt>>
+ASRTools::findCriticalFacetsOnContourByNodes<2>(
+    const std::map<Element, Element> & contour_subfacets_coh_el,
+    const std::map<Element, UInt> & surface_subfacets_crack_nb,
+    const std::map<UInt, std::set<Element>> & contour_nodes_subfacets,
+    const std::set<UInt> & surface_nodes, Real av_stress_threshold);
+
+template std::map<UInt, std::map<UInt, UInt>>
+ASRTools::findCriticalFacetsOnContourByNodes<3>(
+    const std::map<Element, Element> & contour_subfacets_coh_el,
+    const std::map<Element, UInt> & surface_subfacets_crack_nb,
+    const std::map<UInt, std::set<Element>> & contour_nodes_subfacets,
+    const std::set<UInt> & surface_nodes, Real av_stress_threshold);
+
+/* --------------------------------------------------------------- */
+std::map<UInt, std::map<UInt, UInt>> ASRTools::findStressedFacetsLoops(
+    const std::map<Element, Element> & contour_subfacets_coh_el,
+    const std::map<Element, UInt> & /*surface_subfacets_crack_nb*/,
+    const std::map<UInt, std::set<Element>> & contour_nodes_subfacets,
+    const std::set<UInt> & /*surface_nodes*/, Real min_dot) {
+  AKANTU_DEBUG_IN();
+
+  const Mesh & mesh = model.getMesh();
+  const Mesh & mesh_facets = mesh.getMeshFacets();
+  auto dim = mesh.getSpatialDimension();
+  std::map<UInt, std::map<UInt, UInt>> mat_index_facet_nbs_crack_nbs;
+  std::map<UInt, std::pair<UInt, Array<Element>>> node_to_crack_nb_facet_loop;
+  // auto && comm = akantu::Communicator::getWorldCommunicator();
+  // auto prank = comm.whoAmI();
+
+  // clear the nodes_eff_stress array
+  this->nodes_eff_stress.zero();
+
+  // TODO : make iteration on multiple facet types
+  auto type_facet = *mesh_facets.elementTypes(dim - 1).begin();
+  auto nb_facets = mesh.getNbElement(type_facet);
+  // skip if no facets of this type are present
+  if (not nb_facets) {
+    return mat_index_facet_nbs_crack_nbs;
+  }
+  // skip if contour is empty
+  if (not contour_nodes_subfacets.size()) {
+    return mat_index_facet_nbs_crack_nbs;
+  }
+
+  for (auto && pair : contour_nodes_subfacets) {
+    auto cent_node = pair.first;
+    auto subfacets = pair.second;
+    if (subfacets.size() != 2) {
+      // std::cout << "Number of contour segments connected to the node "
+      //           << cent_node << " is not equal to 2. Skipping" << std::endl;
+      continue;
+    }
+    auto it = subfacets.begin();
+    auto starting_segment(*it);
+    std::advance(it, 1);
+    auto ending_segment(*it);
+    auto starting_coh_el = contour_subfacets_coh_el.at(starting_segment);
+    auto ending_coh_el = contour_subfacets_coh_el.at(ending_segment);
+    auto & crack_numbers = mesh.getData<UInt>(
+        "crack_numbers", starting_coh_el.type, starting_coh_el.ghost_type);
+    auto crack_nb = crack_numbers(starting_coh_el.element);
+
+    auto starting_facet =
+        mesh_facets.getSubelementToElement(starting_coh_el)(0);
+    auto ending_facet = mesh_facets.getSubelementToElement(ending_coh_el)(0);
+
+    Array<Element> limit_facets, limit_segments;
+    limit_facets.push_back(starting_facet);
+    limit_facets.push_back(ending_facet);
+    limit_segments.push_back(starting_segment);
+    limit_segments.push_back(ending_segment);
+
+    auto facet_loop = findStressedFacetLoopAroundNode(
+        limit_facets, limit_segments, cent_node, min_dot);
+    // store the facet loop in the map
+    node_to_crack_nb_facet_loop[cent_node] =
+        std::make_pair(crack_nb, facet_loop);
+
+    // evaluate average effective stress over facets of the loop
+    auto av_eff_stress = averageEffectiveStressInMultipleFacets(facet_loop);
+
+    // assign stress value to the node
+    this->nodes_eff_stress(cent_node) = av_eff_stress;
+  }
+
+  // communicate effective stresses by erasing the lowest values
+  communicateEffStressesOnNodes();
+
+  for (auto && pair : node_to_crack_nb_facet_loop) {
+    auto && cent_node = pair.first;
+    auto && crack_nb = pair.second.first;
+    auto && facet_loop = pair.second.second;
+
+    if (this->nodes_eff_stress(cent_node) > 0) {
+      decomposeFacetLoopPerMaterial(facet_loop, crack_nb,
+                                    mat_index_facet_nbs_crack_nbs);
+    }
+  }
+  return mat_index_facet_nbs_crack_nbs;
+}
+/* --------------------------------------------------------------- */
+Real ASRTools::averageEffectiveStressInMultipleFacets(
+    Array<Element> & facet_loop) {
+
+  if (not facet_loop.size())
+    return 0;
+
+  auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
+  const FEEngine & fe_engine = model.getFEEngine("FacetsFEEngine");
+
+  Array<Real> eff_stress_array;
+  ElementType type_facet;
+  GhostType gt;
+  Array<UInt> facet_nbs;
+
+  for (auto & facet : facet_loop) {
+    type_facet = facet.type;
+    gt = facet.ghost_type;
+    facet_nbs.push_back(facet.element);
+    auto & facet_material_index =
+        coh_model.getFacetMaterial(type_facet, gt)(facet.element);
+    auto & mat = model.getMaterial(facet_material_index);
+    auto * mat_coh = dynamic_cast<MaterialCohesive *>(&mat);
+
+    if (mat_coh == nullptr)
+      return 0;
+
+    auto && mat_facet_filter = mat_coh->getFacetFilter(type_facet, gt);
+    UInt facet_local_num = mat_facet_filter.find(facet.element);
+    AKANTU_DEBUG_ASSERT(facet_local_num != UInt(-1),
+                        "Local facet number was not identified");
+
+    auto & eff_stress = mat.getInternal<Real>("effective_stresses")(
+        type_facet, gt)(facet_local_num);
+    for (__attribute__((unused)) UInt quad :
+         arange(fe_engine.getNbIntegrationPoints(type_facet)))
+      eff_stress_array.push_back(eff_stress);
+  }
+
+  Real int_eff_stress =
+      fe_engine.integrate(eff_stress_array, type_facet, gt, facet_nbs);
+  Array<Real> dummy_array(
+      facet_loop.size() * fe_engine.getNbIntegrationPoints(type_facet), 1, 1.);
+  Real loop_area = fe_engine.integrate(dummy_array, type_facet, gt, facet_nbs);
+  return int_eff_stress / loop_area;
+}
+
+/* --------------------------------------------------------------- */
+void ASRTools::decomposeFacetLoopPerMaterial(
+    const Array<Element> & facet_loop, UInt crack_nb,
+    std::map<UInt, std::map<UInt, UInt>> & mat_index_facet_nbs_crack_nbs) {
+  auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
+
+  for (auto & facet : facet_loop) {
+    ElementType type_facet = facet.type;
+    GhostType gt = facet.ghost_type;
+    auto & facet_material_index =
+        coh_model.getFacetMaterial(type_facet, gt)(facet.element);
+    mat_index_facet_nbs_crack_nbs[facet_material_index][facet.element] =
+        crack_nb;
+  }
+}
 /* --------------------------------------------------------------- */
 void ASRTools::applyEigenOpening(Real eigen_strain) {
   auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
 
   if (not coh_model.getIsExtrinsic()) {
     AKANTU_EXCEPTION(
         "This function can only be used for extrinsic cohesive elements");
   }
 
   const Mesh & mesh_facets = coh_model.getMeshFacets();
   const UInt dim = model.getSpatialDimension();
 
   for (auto && mat : model.getMaterials()) {
     auto * mat_coh = dynamic_cast<MaterialCohesive *>(&mat);
 
     if (mat_coh == nullptr)
       continue;
 
     for (auto gt : ghost_types) {
       for (auto && type_facet : mesh_facets.elementTypes(dim - 1)) {
         ElementType type_cohesive =
             FEEngine::getCohesiveElementType(type_facet);
         UInt nb_quad_cohesive = model.getFEEngine("CohesiveFEEngine")
                                     .getNbIntegrationPoints(type_cohesive);
         if (not model.getMesh().getNbElement(type_cohesive, gt))
           continue;
 
         const Array<UInt> & elem_filter =
             mat_coh->getElementFilter(type_cohesive, gt);
 
         if (not elem_filter.size()) {
           continue;
         }
 
         // access openings of cohesive elements
         auto & eig_opening = mat_coh->getEigenOpening(type_cohesive, gt);
         auto & normals = mat_coh->getNormals(type_cohesive, gt);
         auto eig_op_it = eig_opening.begin(dim);
         auto normals_it = normals.begin(dim);
 
         // apply eigen strain as opening
         for (auto el_order : arange(elem_filter.size())) {
 
           Element cohesive{type_cohesive, elem_filter(el_order), gt};
           const Vector<Element> & facets_to_cohesive =
               mesh_facets.getSubelementToElement(cohesive);
           Real facet_indiam = MeshUtils::getInscribedCircleDiameter(
               model, facets_to_cohesive(0));
           auto eigen_opening = eigen_strain * facet_indiam;
 
           Vector<Real> normal(normals_it[el_order * nb_quad_cohesive]);
           for (UInt i : arange(nb_quad_cohesive)) {
             auto eig_op = eig_op_it[el_order * nb_quad_cohesive + i];
 
             eig_op = normal * eigen_opening;
           }
         }
       }
     }
   }
 }
 
 /* --------------------------------------------------------------- */
 void ASRTools::applyEigenOpeningGelVolumeBased(Real gel_volume_ratio) {
   auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
-
-  if (not coh_model.getIsExtrinsic()) {
-    AKANTU_EXCEPTION(
-        "This function can only be used for extrinsic cohesive elements");
-  }
-
   const Mesh & mesh_facets = coh_model.getMeshFacets();
   const UInt dim = model.getSpatialDimension();
   // compute total asr gel volume
   if (!this->volume)
     computeModelVolume();
   Real gel_volume = this->volume * gel_volume_ratio;
 
   // compute total area of existing cracks
   auto data_agg = computeCrackData("agg-agg");
   auto data_agg_mor = computeCrackData("agg-mor");
   auto data_mor = computeCrackData("mor-mor");
   auto area_agg = std::get<0>(data_agg);
   auto area_agg_mor = std::get<0>(data_agg_mor);
   auto area_mor = std::get<0>(data_mor);
   Real total_area = area_agg + area_agg_mor + area_mor;
 
   // compute necessary opening to cover fit gel volume by crack area
   Real average_opening = gel_volume / total_area;
 
   for (auto && mat : model.getMaterials()) {
     auto * mat_coh = dynamic_cast<MaterialCohesive *>(&mat);
 
     if (mat_coh == nullptr)
       continue;
 
     for (auto gt : ghost_types) {
       for (auto && type_facet : mesh_facets.elementTypes(dim - 1)) {
         ElementType type_cohesive =
             FEEngine::getCohesiveElementType(type_facet);
         UInt nb_quad_cohesive = model.getFEEngine("CohesiveFEEngine")
                                     .getNbIntegrationPoints(type_cohesive);
         if (not model.getMesh().getNbElement(type_cohesive, gt))
           continue;
 
         const Array<UInt> & elem_filter =
             mat_coh->getElementFilter(type_cohesive, gt);
 
         if (not elem_filter.size()) {
           continue;
         }
 
         // access openings of cohesive elements
         auto & eig_opening = mat_coh->getEigenOpening(type_cohesive, gt);
         auto & normals = mat_coh->getNormals(type_cohesive, gt);
         auto eig_op_it = eig_opening.begin(dim);
         auto normals_it = normals.begin(dim);
 
         // apply average opening directly as eigen
         for (auto el_order : arange(elem_filter.size())) {
           Vector<Real> normal(normals_it[el_order * nb_quad_cohesive]);
           for (UInt i : arange(nb_quad_cohesive)) {
             auto eig_op = eig_op_it[el_order * nb_quad_cohesive + i];
             eig_op = normal * average_opening;
           }
         }
       }
     }
   }
 }
 /* --------------------------------------------------------------- */
+void ASRTools::applyEigenOpeningToInitialCrack(
+    Real gel_volume_ratio,
+    const Array<std::set<Element>> ASR_facets_from_mesh) {
+  auto & coh_model = dynamic_cast<SolidMechanicsModelCohesive &>(model);
+  const FEEngine & fe_engine = model.getFEEngine("CohesiveFEEngine");
+  const UInt dim = model.getSpatialDimension();
+  const Mesh & mesh = model.getMesh();
+  const Mesh & mesh_facets = coh_model.getMeshFacets();
+  auto type_facet = *mesh_facets.elementTypes(dim - 1).begin();
+  ElementType type_cohesive = FEEngine::getCohesiveElementType(type_facet);
+  UInt nb_quad_cohesive = model.getFEEngine("CohesiveFEEngine")
+                              .getNbIntegrationPoints(type_cohesive);
+  const Array<UInt> & material_index_vec =
+      model.getMaterialByElement(type_cohesive);
+  const Array<UInt> & material_local_numbering_vec =
+      model.getMaterialLocalNumbering(type_cohesive);
+
+  // compute total asr gel volume
+  if (!this->volume)
+    computeModelVolume();
+  Real gel_volume = this->volume * gel_volume_ratio;
+  Real gel_volume_per_crack = gel_volume / ASR_facets_from_mesh.size();
+
+  // iterate through each crack
+  for (auto & single_site_facets : ASR_facets_from_mesh) {
+    // compute area of current ASR site
+    // form cohesive element filter
+    std::set<Element> site_cohesives;
+    for (auto & ASR_facet : single_site_facets) {
+      // find connected cohesive
+      auto & connected_els = mesh.getElementToSubelement(ASR_facet);
+      for (auto & connected_el : connected_els) {
+        if (connected_el.type == type_cohesive) {
+          site_cohesives.emplace(connected_el);
+          break;
+        }
+      }
+    }
+
+    // integrate 1 over identified element filter
+    Real site_area{0};
+    for (auto & coh_el : site_cohesives) {
+      Array<UInt> single_el_array;
+      single_el_array.push_back(coh_el.element);
+
+      Array<Real> area(fe_engine.getNbIntegrationPoints(type_cohesive), 1, 1.);
+      site_area += fe_engine.integrate(area, coh_el.type, coh_el.ghost_type,
+                                       single_el_array);
+    }
+
+    // compute necessary opening to cover fit gel volume by crack area
+    Real average_opening = gel_volume_per_crack / site_area;
+
+    // iterate through each cohesive and apply eigen opening
+    for (auto && coh_el : site_cohesives) {
+      Material & mat = model.getMaterial(material_index_vec(coh_el.element));
+      auto * mat_coh = dynamic_cast<MaterialCohesive *>(&mat);
+      if (mat_coh == nullptr)
+        continue;
+      UInt coh_el_local_num = material_local_numbering_vec(coh_el.element);
+
+      // access openings of cohesive elements
+      auto & eig_opening =
+          mat_coh->getEigenOpening(coh_el.type, coh_el.ghost_type);
+      auto & normals = mat_coh->getNormals(coh_el.type, coh_el.ghost_type);
+      auto eig_op_it = eig_opening.begin(dim);
+      auto normals_it = normals.begin(dim);
+
+      Vector<Real> normal(normals_it[coh_el_local_num * nb_quad_cohesive]);
+      for (UInt i : arange(nb_quad_cohesive)) {
+        Vector<Real> eig_op(eig_op_it[coh_el_local_num * nb_quad_cohesive + i]);
+        eig_op = normal * average_opening;
+      }
+    }
+  }
+}
+/* --------------------------------------------------------------- */
 void ASRTools::outputCrackData(std::ofstream & file_output, Real time) {
 
   auto data_agg = computeCrackData("agg-agg");
   auto data_agg_mor = computeCrackData("agg-mor");
   auto data_mor = computeCrackData("mor-mor");
   auto area_agg = std::get<0>(data_agg);
   auto vol_agg = std::get<1>(data_agg);
+  auto asr_vol_agg = std::get<2>(data_agg);
   auto area_agg_mor = std::get<0>(data_agg_mor);
   auto vol_agg_mor = std::get<1>(data_agg_mor);
+  auto asr_vol_agg_mor = std::get<2>(data_agg_mor);
   auto area_mor = std::get<0>(data_mor);
   auto vol_mor = std::get<1>(data_mor);
+  auto asr_vol_mor = std::get<2>(data_mor);
   Real total_area = area_agg + area_agg_mor + area_mor;
   Real total_volume = vol_agg + vol_agg_mor + vol_mor;
+  Real asr_volume = asr_vol_agg + asr_vol_agg_mor + asr_vol_mor;
 
   auto && comm = akantu::Communicator::getWorldCommunicator();
   auto prank = comm.whoAmI();
 
   if (prank == 0)
-    file_output << time << "," << area_agg << "," << area_agg_mor << ","
-                << area_mor << "," << vol_agg << "," << vol_agg_mor << ","
-                << vol_mor << "," << total_area << "," << total_volume
-                << std::endl;
+    file_output << time << "," << asr_volume << "," << area_agg << ","
+                << area_agg_mor << "," << area_mor << "," << vol_agg << ","
+                << vol_agg_mor << "," << vol_mor << "," << total_area << ","
+                << total_volume << std::endl;
 }
 
 /* --------------------------------------------------------------- */
-std::tuple<Real, Real> ASRTools::computeCrackData(const ID & material_name) {
+std::tuple<Real, Real, Real>
+ASRTools::computeCrackData(const ID & material_name) {
   const auto & mesh = model.getMesh();
   const auto dim = mesh.getSpatialDimension();
   GhostType gt = _not_ghost;
   Material & mat = model.getMaterial(material_name);
-  const ElementTypeMapArray<UInt> & filter_map = mat.getElementFilter();
+  auto * mat_coh = dynamic_cast<MaterialCohesive *>(&mat);
+
+  AKANTU_DEBUG_ASSERT(mat_coh != nullptr,
+                      "Crack data can not be computed on material with id" +
+                          material_name);
+
+  const ElementTypeMapArray<UInt> & filter_map = mat_coh->getElementFilter();
   const FEEngine & fe_engine = model.getFEEngine("CohesiveFEEngine");
   Real crack_volume{0};
   Real crack_area{0};
+  Real ASR_volume{0};
 
-  // Loop over the boundary element types
+  // Loop over the cohesive element types
   for (auto & element_type : filter_map.elementTypes(dim, gt, _ek_cohesive)) {
     const Array<UInt> & filter = filter_map(element_type);
     if (!filter_map.exists(element_type, gt))
       continue;
     if (filter.size() == 0)
       continue;
 
-    auto & opening_norm_array =
-        mat.getInternal<Real>("normal_opening_norm")(element_type);
+    /// compute normal norm of real opening = opening + eigen opening
+    auto opening_it = mat_coh->getOpening(element_type, gt).begin(dim);
+    auto eigen_opening = mat_coh->getEigenOpening(element_type, gt);
+    auto eigen_opening_it = eigen_opening.begin(dim);
+    Array<Real> normal_eigen_opening_norm(
+        filter.size() * fe_engine.getNbIntegrationPoints(element_type), 1);
+    auto normal_eigen_opening_norm_it = normal_eigen_opening_norm.begin();
+    Array<Real> real_normal_opening_norm(
+        filter.size() * fe_engine.getNbIntegrationPoints(element_type), 1);
+    auto real_normal_opening_norm_it = real_normal_opening_norm.begin();
+    auto normal_it = mat_coh->getNormals(element_type, gt).begin(dim);
+    auto normal_end = mat_coh->getNormals(element_type, gt).end(dim);
+    /// loop on each quadrature point
+    for (; normal_it != normal_end; ++opening_it, ++eigen_opening_it,
+                                    ++normal_it, ++real_normal_opening_norm_it,
+                                    ++normal_eigen_opening_norm_it) {
+      auto real_opening = *opening_it + *eigen_opening_it;
+      *real_normal_opening_norm_it = real_opening.dot(*normal_it);
+      Vector<Real> eig_opening(*eigen_opening_it);
+      *normal_eigen_opening_norm_it = eig_opening.dot(*normal_it);
+    }
+
+    ASR_volume += fe_engine.integrate(normal_eigen_opening_norm, element_type,
+                                      gt, filter);
 
     crack_volume +=
-        fe_engine.integrate(opening_norm_array, element_type, gt, filter);
+        fe_engine.integrate(real_normal_opening_norm, element_type, gt, filter);
 
     Array<Real> area(
         filter.size() * fe_engine.getNbIntegrationPoints(element_type), 1, 1.);
     crack_area += fe_engine.integrate(area, element_type, gt, filter);
   }
 
   /// do not communicate if model is multi-scale
   auto && comm = akantu::Communicator::getWorldCommunicator();
+  comm.allReduce(ASR_volume, SynchronizerOperation::_sum);
   comm.allReduce(crack_volume, SynchronizerOperation::_sum);
   comm.allReduce(crack_area, SynchronizerOperation::_sum);
 
-  return std::make_tuple(crack_area, crack_volume);
+  return std::make_tuple(crack_area, crack_volume, ASR_volume);
 }
+/* ----------------------------------------------------------------- */
+std::tuple<std::map<Element, Element>, std::map<Element, UInt>, std::set<UInt>,
+           std::map<UInt, std::set<Element>>>
+ASRTools::determineCrackSurface() {
+  AKANTU_DEBUG_IN();
+
+  Mesh & mesh = model.getMesh();
+  const Mesh & mesh_facets = mesh.getMeshFacets();
+  const UInt dim = mesh.getSpatialDimension();
+  std::map<Element, Element> contour_subfacets_coh_el;
+  std::map<Element, UInt> surface_subfacets_crack_nb;
+  std::set<UInt> surface_nodes;
+  std::map<UInt, std::set<Element>> contour_nodes_subfacets;
+  std::set<Element> visited_subfacets;
+
+  // // first loop on facets (doesn't include duplicated ones)
+  // for (auto & type_facet :
+  //      mesh.elementTypes(dim - 1, _not_ghost, _ek_regular)) {
+  //   auto nb_facets = mesh.getNbElement(type_facet);
+  //   std::cout << "Nb facets = " << nb_facets << std::endl;
+  //   if (not nb_facets) {
+  //     continue;
+  //   }
 
+  //   for (auto facet_nb : arange(nb_facets)) {
+  //     Element facet{type_facet, facet_nb, _not_ghost};
+  //     searchCrackSurfaceInSingleFacet(
+  //         facet, contour_subfacets_coh_el, surface_subfacets_crack_nb,
+  //         surface_nodes, contour_nodes_subfacets, visited_subfacets);
+  //   }
+  // }
+
+  // second loop on cohesives (includes duplicated facets)
+  for (auto gt : ghost_types) {
+    for (auto & type_cohesive : mesh.elementTypes(dim, gt, _ek_cohesive)) {
+      auto nb_cohesives = mesh.getNbElement(type_cohesive, gt);
+      if (not nb_cohesives) {
+        continue;
+      }
+
+      for (auto cohesive_nb : arange(nb_cohesives)) {
+        Element cohesive{type_cohesive, cohesive_nb, gt};
+        auto && facets_to_cohesive =
+            mesh_facets.getSubelementToElement(cohesive);
+        for (auto coh_facet : facets_to_cohesive) {
+          searchCrackSurfaceInSingleFacet(
+              coh_facet, contour_subfacets_coh_el, surface_subfacets_crack_nb,
+              surface_nodes, contour_nodes_subfacets, visited_subfacets);
+        }
+      }
+    }
+  }
+
+  // remove external nodes from the surface nodes list
+  for (auto & pair : contour_nodes_subfacets) {
+    surface_nodes.erase(pair.first);
+  }
+
+  return std::make_tuple(contour_subfacets_coh_el, surface_subfacets_crack_nb,
+                         surface_nodes, contour_nodes_subfacets);
+}
+/* ----------------------------------------------------------------- */
+void ASRTools::searchCrackSurfaceInSingleFacet(
+    Element & facet, std::map<Element, Element> & contour_subfacets_coh_el,
+    std::map<Element, UInt> & surface_subfacets_crack_nb,
+    std::set<UInt> & surface_nodes,
+    std::map<UInt, std::set<Element>> & contour_nodes_subfacets,
+    std::set<Element> & visited_subfacets) {
+
+  Mesh & mesh = model.getMesh();
+  const Mesh & mesh_facets = mesh.getMeshFacets();
+
+  // iterate through subfacets of a facet searching for cohesives
+  auto && subfacets_to_facet = mesh_facets.getSubelementToElement(facet);
+  for (auto & subfacet : subfacets_to_facet) {
+
+    // discard subfacets on the border of ghost area
+    if (subfacet == ElementNull)
+      continue;
+
+    // discard pure ghost subfacets
+    bool pure_ghost_subf{false};
+    auto && subfacet_conn = mesh_facets.getConnectivity(subfacet);
+    for (auto & subfacet_node : subfacet_conn) {
+      if (mesh.isPureGhostNode(subfacet_node))
+        pure_ghost_subf = true;
+    }
+    if (pure_ghost_subf)
+      continue;
+
+    auto ret = visited_subfacets.emplace(subfacet);
+    if (not ret.second)
+      continue;
+
+    std::set<Element> connected_cohesives;
+    auto && facets_to_subfacet = mesh_facets.getElementToSubelement(subfacet);
+    for (auto & neighbor_facet : facets_to_subfacet) {
+      auto & connected_to_facet =
+          mesh_facets.getElementToSubelement(neighbor_facet);
+      for (auto & connected_el : connected_to_facet) {
+        if (connected_el.type == _not_defined)
+          goto nextfacet;
+        if (mesh.getKind(connected_el.type) == _ek_cohesive) {
+          connected_cohesives.emplace(connected_el);
+        }
+      }
+    nextfacet:;
+    }
+    if (connected_cohesives.size() == 1) {
+      contour_subfacets_coh_el[subfacet] = *connected_cohesives.begin();
+      Vector<UInt> contour_subfacet_nodes =
+          mesh_facets.getConnectivity(subfacet);
+      for (auto & contour_subfacet_node : contour_subfacet_nodes) {
+        contour_nodes_subfacets[contour_subfacet_node].emplace(subfacet);
+      }
+    } else if (connected_cohesives.size() > 1) {
+      auto coh_element = *connected_cohesives.begin();
+      UInt crack_nb =
+          mesh.getData<UInt>("crack_numbers", coh_element.type,
+                             coh_element.ghost_type)(coh_element.element);
+      surface_subfacets_crack_nb[subfacet] = crack_nb;
+      // add subfacet nodes to the set
+      for (auto & subfacet_node : mesh_facets.getConnectivity(subfacet))
+        surface_nodes.emplace(subfacet_node);
+    }
+  }
+}
 } // namespace akantu
diff --git a/extra_packages/asr-tools/src/asr_tools.hh b/extra_packages/asr-tools/src/asr_tools.hh
index 4ae6ef077..15326f14c 100644
--- a/extra_packages/asr-tools/src/asr_tools.hh
+++ b/extra_packages/asr-tools/src/asr_tools.hh
@@ -1,1129 +1,1220 @@
 /**
  * @file   asr_tools.hh
  * @author Aurelia Cuba Ramos <aurelia.cubaramos@epfl.ch>
  * @date   Tue Jan 16 10:26:53 2014
  *
  * @brief  tools for the analysis of ASR samples
  *
  * @section LICENSE
  *
  * Copyright (©) 2010-2011 EPFL (Ecole Polytechnique Fédérale de Lausanne)
  * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
  *
  * Akantu 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.
  *
  * Akantu 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 Akantu. If not, see <http://www.gnu.org/licenses/>.
  *
  */
 /* -------------------------------------------------------------------------- */
 #include <aka_array.hh>
 #include <aka_iterators.hh>
 #include <boundary_condition_functor.hh>
 #include <map>
 #include <mesh.hh>
 #include <mesh_accessor.hh>
 #include <mesh_events.hh>
 #include <unordered_set>
 /* -------------------------------------------------------------------------- */
 #include "material_cohesive_linear_sequential.hh"
 #include "solid_mechanics_model.hh"
 #ifdef AKANTU_COHESIVE_ELEMENT
 #include "solid_mechanics_model_cohesive.hh"
 #endif
 #ifdef AKANTU_FLUID_DIFFUSION
 #include "fluid_diffusion_model.hh"
 #endif
 /* -------------------------------------------------------------------------- */
 
 #ifndef __AKANTU_ASR_TOOLS_HH__
 #define __AKANTU_ASR_TOOLS_HH__
 
 namespace akantu {
 class NodeGroup;
 class SolidMechanicsModel;
 } // namespace akantu
 
 namespace akantu {
 
 class ASRTools : public MeshEventHandler {
 public:
   ASRTools(SolidMechanicsModel & model);
 
   virtual ~ASRTools() = default;
 
   /// This function is used in case of uni-axial boundary conditions:
   /// It detects the nodes, on which a traction needs to be applied and stores
   /// them in a node group
   void fillNodeGroup(NodeGroup & node_group, bool multi_axial = false);
 
   /// compute volumes of each phase
   void computePhaseVolumes();
 
   /// compute volume of the model passed to ASRTools (RVE if FE2_mat)
   void computeModelVolume();
 
   /// Apply free expansion boundary conditions
   void applyFreeExpansionBC();
 
   /// Apply loaded boundary conditions
   void applyLoadedBC(const Vector<Real> & traction, const ID & element_group,
                      bool multi_axial);
 
   /// This function computes the average displacement along one side of the
   /// sample,
   /// caused by the expansion of gel pockets
   Real computeAverageDisplacement(SpatialDirection direction);
 
   /// This function computes a compoment of average volumetric strain tensor,
   /// i.e. eps_macro_xx or eps_macro_yy or eps_macro_zz
   Real computeVolumetricExpansion(SpatialDirection direction);
 
   /// This function computes the amount of damage in one material phase
   Real computeDamagedVolume(const ID & mat_name);
 
   /// This function is used to compute the average stiffness by performing a
   /// virtual loading test
   Real performLoadingTest(SpatialDirection direction, bool tension);
 
   /// perform tension tests and integrate the internal force on the upper
   /// surface
   void computeStiffnessReduction(std::ofstream & file_output, Real time,
                                  bool tension = true);
 
   /// just the average properties, NO tension test
   void computeAverageProperties(std::ofstream & file_output);
 
   /// Same as the last one but writes a csv with first column having time in
   /// days
   void computeAverageProperties(std::ofstream & file_output, Real time);
 
   /// computes and writes only displacements and strains
   void computeAveragePropertiesCohesiveModel(std::ofstream & file_output,
                                              Real time);
 
   /// Averages strains & displacements + damage ratios in FE2 material
   void computeAveragePropertiesFe2Material(std::ofstream & file_output,
                                            Real time);
 
   /// Save the state of the simulation for subsequent restart
   void saveState(UInt current_step);
 
   /// Load to previous state of the simulation for restart
   bool loadState(UInt & current_step);
 
   /// compute the volume occupied by a given material
   Real computePhaseVolume(const ID & mat_name);
 
   /// apply eigenstrain in cracks, that are filled with gel
   void applyEigenGradUinCracks(const Matrix<Real> prescribed_eigen_grad_u,
                                const ID & material_name);
 
   /// apply eigenstrain in the cracks, that advanced in the last step
   void applyEigenGradUinCracks(const Matrix<Real> prescribed_eigen_grad_u,
                                const ElementTypeMapUInt & critical_elements,
                                bool to_add = false);
 
   /// fill fully cracked elements with gel
   void fillCracks(ElementTypeMapReal & saved_damage);
 
   /// drain the gel in fully cracked elements
   void drainCracks(const ElementTypeMapReal & saved_damage);
 
   Real computeSmallestElementSize();
 
   // /// apply homogeneous temperature on Solid Mechanics Model
   // void applyTemperatureFieldToSolidmechanicsModel(const Real & temperature);
 
   /// compute ASR strain by a sigmoidal rule (Larive, 1998)
   void computeASRStrainLarive(const Real & delta_time_day, const Real & T,
                               Real & ASRStrain, const Real & eps_inf,
                               const Real & time_ch_ref,
                               const Real & time_lat_ref, const Real & U_C,
                               const Real & U_L, const Real & T_ref);
 
   /// compute increase in gel strain within 1 timestep
   Real computeDeltaGelStrainThermal(const Real delta_time_day, const Real k,
                                     const Real activ_energy, const Real R,
                                     const Real T,
                                     Real & amount_reactive_particles,
                                     const Real saturation_const);
 
   /// compute linear increase in gel strain
   Real computeDeltaGelStrainLinear(const Real delta_time, const Real k);
 
   /// insert multiple blocks of cohesive elements
   void insertASRCohesivesRandomly(const UInt & nb_coh_elem,
                                   std::string matrix_mat_name, Real gap_ratio);
   /// insert multiple blocks of cohesive elements
   void insertASRCohesivesRandomly3D(const UInt & nb_coh_elem,
                                     std::string matrix_mat_name,
                                     Real gap_ratio);
 
   /// ASR insertion for 1st order 3D elements
   void insertASRCohesiveLoops3D(const UInt & nb_insertions,
                                 std::string facet_mat_name, Real gap_ratio);
 
   /// insert block of cohesive elements based on the coord of the central
   void insertASRCohesivesByCoords(const Matrix<Real> & positions,
                                   Real gap_ratio = 0);
 
   /// communicates crack numbers from not ghosts to ghosts cohesive elements
   void communicateCrackNumbers();
 
 protected:
   /// put flags on all nodes who have a ghost counterpart
   void communicateFlagsOnNodes();
 
+  /// on master or slave nodes with potential facet loops around
+  /// put 0 eff stress for the smallest value
+  void communicateEffStressesOnNodes();
+
   /// pick facets by passed coordinates
   void pickFacetsByCoord(const Matrix<Real> & positions);
 
   /// pick facets randomly within specified material
   void pickFacetsRandomly(UInt nb_insertions, std::string facet_mat_name);
 
   /// build closed facets loop around random point of specified material and
   /// returns number of successfully inserted ASR sites
   UInt closedFacetsLoopAroundPoint(UInt nb_insertions, std::string mat_name);
 
+  /// builds a facet loop around a point from starting to ending segm-s
+  Array<Element> findFacetsLoopFromSegment2Segment(
+      Element starting_facet, Element starting_segment, Element ending_segment,
+      UInt cent_node, Real max_dot, UInt material_id, bool check_asr_facets);
+
+  /// builds a facet loop around a point from starting to ending segment using
+  /// the Dijkstra shortest path algorithm in boost library
+  /// uses sum of distances to 2 incenters as weight
+  Array<Element>
+  findFacetsLoopByGraphByDist(const Array<Element> & limit_facets,
+                              const Array<Element> & limit_segments,
+                              const UInt & cent_node);
+
+  /// use area as a weight
+  Array<Element>
+  findFacetsLoopByGraphByArea(const Array<Element> & limit_facets,
+                              const Array<Element> & limit_segments,
+                              const Array<Element> & preinserted_facets,
+                              const UInt & cent_node);
+
+  /// include only facets that have eff_stress >= 1
+  Array<Element>
+  findStressedFacetLoopAroundNode(const Array<Element> & limit_facets,
+                                  const Array<Element> & limit_segments,
+                                  const UInt & cent_node, Real min_dot);
+
+  /// insert single facets before searching for the long loops
+  Array<Element> findSingleFacetLoop(const Array<Element> & limit_facets,
+                                     const Array<Element> & limit_segments,
+                                     const UInt & cent_node);
+
   /// check if the cohesive can be inserted and nodes are not on the partition
   /// border and ASR nodes
-  bool isFacetAndNodesGood(const Element & facet, UInt material_id);
+  bool isFacetAndNodesGood(const Element & facet, UInt material_id = UInt(-1),
+                           bool check_asr_facets = false);
+
+  /// check if 2 facets are bounding a common solid element
+  bool belong2SameElement(const Element & facet1, const Element & facet2);
 
   /// check if the node is surrounded by the material
   bool isNodeWithinMaterial(Element & node, UInt material_id);
 
   /// pick two neighbors of a central facet in 2D: returns true if success
   bool pickFacetNeighborsOld(Element & cent_facet);
 
   /// version working for both 2d and 3d
   bool pickFacetNeighbors(Element & cent_facet);
 
   /// optimise doubled facets group, insert facets, and cohesive elements,
   /// update connectivities
   void insertOppositeFacetsAndCohesives();
 
   /// no cohesive elements in-between neighboring solid elements
   void preventCohesiveInsertionInNeighbors();
 
   /// assign crack tags to the clusters
   void assignCrackNumbers();
 
   /// change coordinates of central crack nodes to create an artificial gap
   void insertGap(const Real gap_ratio);
 
   /// same in 3D
   void insertGap3D(const Real gap_ratio);
 
   /// on elements added for asr-tools
   void onElementsAdded(const Array<Element> & elements,
                        const NewElementsEvent & element_event);
 
   void onNodesAdded(const Array<UInt> & new_nodes,
                     const NewNodesEvent & nodes_event);
 
 public:
   /// update the element group in case connectivity changed after cohesive
   /// elements insertion
   void updateElementGroup(const std::string group_name);
 
   /// works only for the MaterialCohesiveLinearSequential
   template <UInt dim> UInt insertCohesiveElementsSelectively();
 
   /// insert multiple cohesives on contour
   template <UInt dim> UInt insertCohesiveElementsOnContour();
 
+  /// insert multiple cohesives on contour by looping on contour nodes
+  template <UInt dim>
+  UInt insertCohesiveElementsOnContourByNodes(Real av_stress_threshold);
+
+  /// insert cohesives on closed loops of stressed facets
+  template <UInt dim> UInt insertLoopsOfStressedCohesives(Real min_dot);
+
+  /// find critical facets by looping on contour nodes
+  template <UInt dim>
+  std::map<UInt, std::map<UInt, UInt>> findCriticalFacetsOnContourByNodes(
+      const std::map<Element, Element> & contour_subfacets_coh_el,
+      const std::map<Element, UInt> & surface_subfacets_crack_nb,
+      const std::map<UInt, std::set<Element>> & contour_nodes_subfacets,
+      const std::set<UInt> & surface_nodes, Real av_stress_threshold);
+
+  /// find critical facets by looping on contour nodes
+  std::map<UInt, std::map<UInt, UInt>> findStressedFacetsLoops(
+      const std::map<Element, Element> & contour_subfacets_coh_el,
+      const std::map<Element, UInt> & surface_subfacets_crack_nb,
+      const std::map<UInt, std::set<Element>> & contour_nodes_subfacets,
+      const std::set<UInt> & surface_nodes, Real min_dot);
+
+  /// average integrated eff stress over the area of the loop
+  Real averageEffectiveStressInMultipleFacets(Array<Element> & facet_loop);
+
+  void decomposeFacetLoopPerMaterial(
+      const Array<Element> & facet_loop, UInt crack_nb,
+      std::map<UInt, std::map<UInt, UInt>> & mat_index_facet_nbs_crack_nbs);
+
   /// apply eigen opening at all cohesives (including ghosts)
   void applyEigenOpening(Real eigen_strain);
 
   /// distribute total ASR gel volume along existing crack surface
   void applyEigenOpeningGelVolumeBased(Real gel_volume_ratio);
 
+  /// distribute total ASR gel volume along initial crack surface
+  void applyEigenOpeningToInitialCrack(
+      Real gel_volume_ratio,
+      const Array<std::set<Element>> ASR_facets_from_mesh);
+
   /// outputs crack area, volume into a file
   void outputCrackData(std::ofstream & file_output, Real time);
 
-  /// computes crack area and volume per material
-  std::tuple<Real, Real> computeCrackData(const ID & material_name);
+  /// computes crack area and volume per material and ASR volume
+  std::tuple<Real, Real, Real> computeCrackData(const ID & material_name);
+
+  /// compute crack contour segments, surface segments, contour nodes and
+  /// surface nodes
+  std::tuple<std::map<Element, Element>, std::map<Element, UInt>,
+             std::set<UInt>, std::map<UInt, std::set<Element>>>
+  determineCrackSurface();
+
+  /// search for crack contour,etc. in a single facet
+  void searchCrackSurfaceInSingleFacet(
+      Element & facet, std::map<Element, Element> & contour_subfacets_coh_el,
+      std::map<Element, UInt> & surface_subfacets_crack_nb,
+      std::set<UInt> & surface_nodes,
+      std::map<UInt, std::set<Element>> & contour_nodes_subfacets,
+      std::set<Element> & visited_subfacets);
 
   // /// apply self-weight force
   // void applyBodyForce();
 
   /// apply delta u on nodes
   void applyDeltaU(Real delta_u);
 
   /// apply eigenstrain on gel material
   void applyGelStrain(const Matrix<Real> & prestrain);
   /* ------------------------------------------------------------------------
    */
   /// RVE part
 
   /// apply boundary contions based on macroscopic deformation gradient
   virtual void
   applyBoundaryConditionsRve(const Matrix<Real> & displacement_gradient);
 
   /// apply homogeneous temperature field from the macroscale level to the
   /// RVEs
   virtual void applyHomogeneousTemperature(const Real & temperature);
 
   /// remove temperature from RVE on the end of ASR advancement
   virtual void removeTemperature();
 
   /// averages scalar field over the WHOLE!!! volume of a model
   Real averageScalarField(const ID & field_name);
 
   /// compute average stress or strain in the model
   Real averageTensorField(UInt row_index, UInt col_index,
                           const ID & field_type);
 
   /// compute effective stiffness of the RVE (in tension by default)
   void homogenizeStiffness(Matrix<Real> & C_macro, bool tensile_test = true);
 
   /// compute average eigenstrain
   void homogenizeEigenGradU(Matrix<Real> & eigen_gradu_macro);
 
   /// compute damage to the RVE area ratio
   void computeDamageRatio(Real & damage);
 
   /// compute damage to material area ratio
   void computeDamageRatioPerMaterial(Real & damage_ratio,
                                      const ID & material_name);
 
   /// compute ratio between total crack and RVE volumes
   void computeCrackVolume(Real & crack_volume_ratio);
 
   /// compute crack volume to material volume ratio
   void computeCrackVolumePerMaterial(Real & crack_volume,
                                      const ID & material_name);
 
   /// dump the RVE
   void dumpRve();
 
   /// compute average stress in the RVE
   void homogenizeStressField(Matrix<Real> & stress);
 
   /// compute hydrostatic part of the stress and assign homogen direction
   void setStiffHomogenDir(Matrix<Real> & stress);
 
   /// storing nodal fields before tests
   void storeNodalFields();
 
   /// restoring nodal fields before tests
   void restoreNodalFields();
 
   /// resetting nodal fields
   void resetNodalFields();
 
   /// restoring internal fields after tests
   void restoreInternalFields();
 
   /// resetting internal fields with history
   void resetInternalFields();
 
   /// store damage in materials who have damage
   void storeDamageField();
 
   /// assign stored values to the current ones
   void restoreDamageField();
 
   /// find the corner nodes
   void findCornerNodes();
 
   /// perform virtual testing
   void performVirtualTesting(const Matrix<Real> & H,
                              Matrix<Real> & eff_stresses,
                              Matrix<Real> & eff_strains, const UInt test_no);
 
   /// clear the eigenstrain (to exclude stresses due to internal pressure)
   void clearASREigenStrain();
 
   /// store elemental eigenstrain in an array
   void storeASREigenStrain(Array<Real> & stored_eig);
 
   /// restore eigenstrain in ASR sites from previously stored values
   void restoreASREigenStrain(Array<Real> & stored_eig);
   /* ------------------------------------------------------------------------
    */
 public:
   // Accessors
   bool isTensileHomogen() { return this->tensile_homogenization; };
 
   /// phase volumes
   Real getPhaseVolume(const std::string & material_name) {
     if (not this->phase_volumes.size())
       computePhaseVolumes();
 
     return this->phase_volumes.find(material_name)->second;
   };
 
   /// set the value of the insertion flag
   AKANTU_SET_MACRO(CohesiveInsertion, cohesive_insertion, bool);
 
+  AKANTU_GET_MACRO(NodesEffStress, nodes_eff_stress, const Array<Real> &);
   /// get the corner nodes
   AKANTU_GET_MACRO(CornerNodes, corner_nodes, const Array<UInt> &);
 
   AKANTU_GET_MACRO(ASRFacetsFromMeshFacets, ASR_facets_from_mesh_facets,
                    const Array<Array<Element>> &);
   AKANTU_GET_MACRO(ASRFacetsFromMesh, ASR_facets_from_mesh,
                    const Array<std::set<Element>> &);
 
   /* --------------------------------------------------------------------- */
   /* Members */
   /* --------------------------------------------------------------------- */
 
 private:
   SolidMechanicsModel & model;
 
   // /// 2D hardcoded - no 3D support currently
   // using voigt_h = VoigtHelper<2>;
 
 protected:
   /// volume of the RVE
   Real volume;
 
   /// corner nodes 1, 2, 3, 4 (see Leonardo's thesis, page 98)
   Array<UInt> corner_nodes;
 
   /// bottom nodes
   std::unordered_set<UInt> bottom_nodes;
 
   /// left nodes
   std::unordered_set<UInt> left_nodes;
 
   /// dump counter
   UInt nb_dumps;
 
   /// flag to activate ASR expansion through cohesive elements
   bool cohesive_insertion;
 
   /// booleans for applying delta u
   bool doubled_facets_ready;
   bool doubled_nodes_ready;
 
   // arrays to store nodal values during virtual tests
   Array<Real> disp_stored;
   Array<Real> ext_force_stored;
   Array<bool> boun_stored;
 
   /// if stiffness homogenization will be done in tension
   bool tensile_homogenization{false};
 
   /// phase volumes
   std::map<std::string, Real> phase_volumes;
 
   /// array to store flags on nodes position modification
   Array<bool> modified_pos;
 
   /// array to store flags on nodes that are synchronized between processors
   Array<bool> partition_border_nodes;
 
+  /// average eff stress on facet loops around a node
+  Array<Real> nodes_eff_stress;
+
   /// array to store flags on nodes where ASR elements are inserted
   Array<bool> ASR_nodes;
 
   /// Vector to store the initially inserted facets per asr site
   Array<Array<Element>> ASR_facets_from_mesh_facets;
   Array<std::set<Element>> ASR_facets_from_mesh;
 };
 
 /* ------------------------------------------------------------------ */
 /* ASR material selector */
 /* ------------------------------------------------------------------ */
 class GelMaterialSelector : public MeshDataMaterialSelector<std::string> {
 public:
   GelMaterialSelector(SolidMechanicsModel & model, std::string gel_material,
                       const UInt nb_gel_pockets,
                       std::string aggregate_material = "aggregate",
                       bool gel_pairs = false)
       : MeshDataMaterialSelector<std::string>("physical_names", model),
         model(model), gel_material(gel_material),
         nb_gel_pockets(nb_gel_pockets), nb_placed_gel_pockets(0),
         aggregate_material(aggregate_material), gel_pairs(gel_pairs) {}
 
   void initGelPocket() {
     aggregate_material_id = model.getMaterialIndex(aggregate_material);
 
     Mesh & mesh = this->model.getMesh();
     UInt dim = model.getSpatialDimension();
     //    Element el{_triangle_3, 0, _not_ghost};
     for (auto el_type : model.getMaterial(aggregate_material)
                             .getElementFilter()
                             .elementTypes(dim)) {
 
       const auto & filter =
           model.getMaterial(aggregate_material).getElementFilter()(el_type);
       if (!filter.size() == 0)
         AKANTU_EXCEPTION("Check the element type for aggregate material");
 
       Element el{el_type, 0, _not_ghost};
       UInt nb_element = mesh.getNbElement(el.type, el.ghost_type);
       Array<Real> barycenter(nb_element, dim);
 
       for (auto && data : enumerate(make_view(barycenter, dim))) {
         el.element = std::get<0>(data);
         auto & bary = std::get<1>(data);
         mesh.getBarycenter(el, bary);
       }
 
       /// generate the gel pockets
       srand(0.);
       Vector<Real> center(dim);
       std::set<int> checked_baries;
       while (nb_placed_gel_pockets != nb_gel_pockets) {
         /// get a random bary center
         UInt bary_id = rand() % nb_element;
         if (checked_baries.find(bary_id) != checked_baries.end())
           continue;
         checked_baries.insert(bary_id);
         el.element = bary_id;
         if (MeshDataMaterialSelector<std::string>::operator()(el) ==
             aggregate_material_id) {
           gel_pockets.push_back(el);
           nb_placed_gel_pockets += 1;
         }
       }
     }
     is_gel_initialized = true;
     std::cout << nb_placed_gel_pockets << " gelpockets placed" << std::endl;
   }
 
   void initGelPocketPairs() {
     aggregate_material_id = model.getMaterialIndex(aggregate_material);
 
     Mesh & mesh = this->model.getMesh();
     auto & mesh_facets = mesh.getMeshFacets();
     UInt dim = model.getSpatialDimension();
     //    Element el{_triangle_3, 0, _not_ghost};
     for (auto el_type : model.getMaterial(aggregate_material)
                             .getElementFilter()
                             .elementTypes(dim)) {
 
       const auto & filter =
           model.getMaterial(aggregate_material).getElementFilter()(el_type);
       if (!filter.size() == 0)
         AKANTU_EXCEPTION("Check the element type for aggregate material");
 
       Element el{el_type, 0, _not_ghost};
       UInt nb_element = mesh.getNbElement(el.type, el.ghost_type);
       Array<Real> barycenter(nb_element, dim);
 
       for (auto && data : enumerate(make_view(barycenter, dim))) {
         el.element = std::get<0>(data);
         auto & bary = std::get<1>(data);
         mesh.getBarycenter(el, bary);
       }
 
       /// generate the gel pockets
       srand(0.);
       Vector<Real> center(dim);
       std::set<int> checked_baries;
       while (nb_placed_gel_pockets != nb_gel_pockets) {
         /// get a random bary center
         UInt bary_id = rand() % nb_element;
         if (checked_baries.find(bary_id) != checked_baries.end())
           continue;
         checked_baries.insert(bary_id);
         el.element = bary_id;
         if (MeshDataMaterialSelector<std::string>::operator()(el) ==
             aggregate_material_id) {
           auto & sub_to_element =
               mesh_facets.getSubelementToElement(el.type, el.ghost_type);
           auto sub_to_el_it =
               sub_to_element.begin(sub_to_element.getNbComponent());
           const Vector<Element> & subelements_to_element =
               sub_to_el_it[el.element];
           bool successful_placement{false};
           for (auto & subelement : subelements_to_element) {
             auto && connected_elements = mesh_facets.getElementToSubelement(
                 subelement.type, subelement.ghost_type)(subelement.element);
             for (auto & connected_element : connected_elements) {
               if (connected_element.element == el.element)
                 continue;
               if (MeshDataMaterialSelector<std::string>::operator()(
                       connected_element) == aggregate_material_id) {
                 gel_pockets.push_back(el);
                 gel_pockets.push_back(connected_element);
                 nb_placed_gel_pockets += 1;
                 successful_placement = true;
                 break;
               }
             }
             if (successful_placement)
               break;
           }
         }
       }
     }
     is_gel_initialized = true;
     std::cout << nb_placed_gel_pockets << " ASR-pocket pairs placed"
               << std::endl;
   }
 
   UInt operator()(const Element & elem) {
 
     if (not is_gel_initialized) {
       if (this->gel_pairs) {
         initGelPocketPairs();
       } else {
         initGelPocket();
       }
     }
 
     UInt temp_index = MeshDataMaterialSelector<std::string>::operator()(elem);
     if (temp_index != aggregate_material_id)
       return temp_index;
     auto iit = gel_pockets.begin();
     auto eit = gel_pockets.end();
     if (std::find(iit, eit, elem) != eit) {
       return model.getMaterialIndex(gel_material);
     }
     return temp_index;
   }
 
 protected:
   SolidMechanicsModel & model;
   std::string gel_material;
   std::vector<Element> gel_pockets;
   UInt nb_gel_pockets;
   UInt nb_placed_gel_pockets;
   std::string aggregate_material{"aggregate"};
   UInt aggregate_material_id{1};
   bool is_gel_initialized{false};
   bool gel_pairs{false};
 };
 
 /* --------------------------------------------------------------------------
  */
 /* --------------------------------------------------------------------------
  */
 using MaterialCohesiveRules = std::map<std::pair<ID, ID>, ID>;
 
 class GelMaterialCohesiveRulesSelector : public MaterialSelector {
 public:
   GelMaterialCohesiveRulesSelector(SolidMechanicsModelCohesive & model,
                                    const MaterialCohesiveRules & rules,
                                    std::string gel_material,
                                    const UInt nb_gel_pockets,
                                    std::string aggregate_material = "aggregate",
                                    bool gel_pairs = false)
       : model(model), mesh_data_id("physical_names"), mesh(model.getMesh()),
         mesh_facets(model.getMeshFacets()), dim(model.getSpatialDimension()),
         rules(rules), gel_selector(model, gel_material, nb_gel_pockets,
                                    aggregate_material, gel_pairs),
         default_cohesive(model) {}
 
   UInt operator()(const Element & element) {
     if (mesh_facets.getSpatialDimension(element.type) == (dim - 1)) {
       const std::vector<Element> & element_to_subelement =
           mesh_facets.getElementToSubelement(element.type, element.ghost_type)(
               element.element);
       // Array<bool> & facets_check = model.getFacetsCheck();
 
       const Element & el1 = element_to_subelement[0];
       const Element & el2 = element_to_subelement[1];
 
       ID id1 = model.getMaterial(gel_selector(el1)).getName();
       ID id2 = id1;
       if (el2 != ElementNull) {
         id2 = model.getMaterial(gel_selector(el2)).getName();
       }
 
       auto rit = rules.find(std::make_pair(id1, id2));
       if (rit == rules.end()) {
         rit = rules.find(std::make_pair(id2, id1));
       }
 
       if (rit != rules.end()) {
         return model.getMaterialIndex(rit->second);
       }
     }
 
     if (Mesh::getKind(element.type) == _ek_cohesive) {
       return default_cohesive(element);
     }
 
     return gel_selector(element);
   }
 
 private:
   SolidMechanicsModelCohesive & model;
   ID mesh_data_id;
   const Mesh & mesh;
   const Mesh & mesh_facets;
   UInt dim;
   MaterialCohesiveRules rules;
 
   GelMaterialSelector gel_selector;
   DefaultMaterialCohesiveSelector default_cohesive;
 };
 
 /* ------------------------------------------------------------------------ */
 /* Boundary conditions functors */
 /* -------------------------------------------------------------------------*/
 
 class Pressure : public BC::Neumann::NeumannFunctor {
 public:
   Pressure(SolidMechanicsModel & model, const Array<Real> & pressure_on_qpoint,
            const Array<Real> & quad_coords)
       : model(model), pressure_on_qpoint(pressure_on_qpoint),
         quad_coords(quad_coords) {}
 
   inline void operator()(const IntegrationPoint & quad_point,
                          Vector<Real> & dual, const Vector<Real> & coord,
                          const Vector<Real> & normal) const {
 
     // get element types
     auto && mesh = model.getMesh();
     const UInt dim = mesh.getSpatialDimension();
     const GhostType gt = akantu::_not_ghost;
     const UInt facet_nb = quad_point.element;
     const ElementKind cohesive_kind = akantu::_ek_cohesive;
     const ElementType type_facet = quad_point.type;
     const ElementType type_coh = FEEngine::getCohesiveElementType(type_facet);
     auto && cohesive_conn = mesh.getConnectivity(type_coh, gt);
     const UInt nb_nodes_coh_elem = cohesive_conn.getNbComponent();
     auto && facet_conn = mesh.getConnectivity(type_facet, gt);
     const UInt nb_nodes_facet = facet_conn.getNbComponent();
     auto && fem_boundary = model.getFEEngineBoundary();
     UInt nb_quad_points = fem_boundary.getNbIntegrationPoints(type_facet, gt);
     auto facet_nodes_it = make_view(facet_conn, nb_nodes_facet).begin();
 
     AKANTU_DEBUG_ASSERT(nb_nodes_coh_elem == 2 * nb_nodes_facet,
                         "Different number of nodes belonging to one cohesive "
                         "element face and facet");
     // const akantu::Mesh &mesh_facets = cohesive_mesh.getMeshFacets();
     const Array<std::vector<Element>> & elem_to_subelem =
         mesh.getElementToSubelement(type_facet, gt);
     const auto & adjacent_elems = elem_to_subelem(facet_nb);
     auto normal_corrected = normal;
 
     // loop over all adjacent elements
     UInt coh_elem_nb;
     if (not adjacent_elems.empty()) {
       for (UInt f : arange(adjacent_elems.size())) {
         if (adjacent_elems[f].kind() != cohesive_kind)
           continue;
         coh_elem_nb = adjacent_elems[f].element;
         Array<UInt> upper_nodes(nb_nodes_coh_elem / 2);
         Array<UInt> lower_nodes(nb_nodes_coh_elem / 2);
         for (UInt node : arange(nb_nodes_coh_elem / 2)) {
           upper_nodes(node) = cohesive_conn(coh_elem_nb, node);
           lower_nodes(node) =
               cohesive_conn(coh_elem_nb, node + nb_nodes_coh_elem / 2);
         }
         bool upper_face = true;
         bool lower_face = true;
         Vector<UInt> facet_nodes = facet_nodes_it[facet_nb];
         for (UInt s : arange(nb_nodes_facet)) {
           auto idu = upper_nodes.find(facet_nodes(s));
           auto idl = lower_nodes.find(facet_nodes(s));
           if (idu == UInt(-1))
             upper_face = false;
           else if (idl == UInt(-1))
             lower_face = false;
         }
         if (upper_face && not lower_face)
           normal_corrected *= -1;
         else if (not upper_face && lower_face)
           normal_corrected *= 1;
         else
           AKANTU_EXCEPTION("Error in defining side of the cohesive element");
         break;
       }
     }
 
     auto flow_qpoint_it = make_view(quad_coords, dim).begin();
     bool node_found;
     for (auto qp : arange(nb_quad_points)) {
       const Vector<Real> flow_quad_coord =
           flow_qpoint_it[coh_elem_nb * nb_quad_points + qp];
       if (flow_quad_coord != coord)
         continue;
       Real P = pressure_on_qpoint(coh_elem_nb * nb_quad_points + qp);
       // if (P < 0)
       //   P = 0.;
       dual = P * normal_corrected;
       node_found = true;
     }
     if (not node_found)
       AKANTU_EXCEPTION("Quad point is not found in the flow mesh");
   }
 
 protected:
   SolidMechanicsModel & model;
   const Array<Real> & pressure_on_qpoint;
   const Array<Real> & quad_coords;
 };
 
 /* ------------------------------------------------------------------------ */
 class PressureSimple : public BC::Neumann::NeumannFunctor {
 public:
   PressureSimple(SolidMechanicsModel & model, const Real pressure,
                  const std::string group_name)
       : model(model), pressure(pressure), group_name(group_name) {}
 
   inline void operator()(const IntegrationPoint & quad_point,
                          Vector<Real> & dual, const Vector<Real> & /*coord*/,
                          const Vector<Real> & normal) const {
 
     // get element types
     auto && mesh = model.getMesh();
     AKANTU_DEBUG_ASSERT(mesh.elementGroupExists(group_name),
                         "Element group is not registered in the mesh");
     const GhostType gt = akantu::_not_ghost;
     const UInt facet_nb = quad_point.element;
     const ElementType type_facet = quad_point.type;
     auto && facet_conn = mesh.getConnectivity(type_facet, gt);
     const UInt nb_nodes_facet = facet_conn.getNbComponent();
     auto facet_nodes_it = make_view(facet_conn, nb_nodes_facet).begin();
     auto & group = mesh.getElementGroup(group_name);
     Array<UInt> element_ids = group.getElements(type_facet);
     AKANTU_DEBUG_ASSERT(element_ids.size(),
                         "Provided group doesn't contain this element type");
     auto id = element_ids.find(facet_nb);
     AKANTU_DEBUG_ASSERT(id != UInt(-1),
                         "Quad point doesn't belong to this element group");
 
     auto normal_corrected = normal;
 
     if (id < element_ids.size() / 2)
       normal_corrected *= -1;
     else if (id >= element_ids.size())
       AKANTU_EXCEPTION("Error in defining side of the cohesive element");
     else
       normal_corrected *= 1;
 
     dual = pressure * normal_corrected;
   }
 
 protected:
   SolidMechanicsModel & model;
   const Real pressure;
   const std::string group_name;
 };
 
 /* ------------------------------------------------------------------- */
 class PressureVolumeDependent : public BC::Neumann::NeumannFunctor {
 public:
   PressureVolumeDependent(SolidMechanicsModel & model,
                           const Real ASR_volume_ratio,
                           const std::string group_name,
                           const Real compressibility)
       : model(model), ASR_volume_ratio(ASR_volume_ratio),
         group_name(group_name), compressibility(compressibility) {}
 
   inline void operator()(const IntegrationPoint & quad_point,
                          Vector<Real> & dual, const Vector<Real> & /*coord*/,
                          const Vector<Real> & /*normal*/) const {
 
     // get element types
     auto && mesh = model.getMesh();
     AKANTU_DEBUG_ASSERT(mesh.elementGroupExists(group_name),
                         "Element group is not registered in the mesh");
     auto dim = mesh.getSpatialDimension();
     const GhostType gt = akantu::_not_ghost;
     const UInt facet_nb = quad_point.element;
     const ElementType type_facet = quad_point.type;
     auto && facet_conn = mesh.getConnectivity(type_facet, gt);
     const UInt nb_nodes_facet = facet_conn.getNbComponent();
     auto facet_nodes_it = make_view(facet_conn, nb_nodes_facet).begin();
     auto & group = mesh.getElementGroup(group_name);
     Array<UInt> element_ids = group.getElements(type_facet);
     auto && pos = mesh.getNodes();
     const auto pos_it = make_view(pos, dim).begin();
     auto && disp = model.getDisplacement();
     const auto disp_it = make_view(disp, dim).begin();
     auto && fem_boundary = model.getFEEngineBoundary();
     UInt nb_quad_points = fem_boundary.getNbIntegrationPoints(type_facet, gt);
 
     AKANTU_DEBUG_ASSERT(element_ids.size(),
                         "Provided group doesn't contain this element type");
     auto id = element_ids.find(facet_nb);
     AKANTU_DEBUG_ASSERT(id != UInt(-1),
                         "Quad point doesn't belong to this element group");
 
     // get normal to the current positions
     const auto & current_pos = model.getCurrentPosition();
     Array<Real> quad_normals(0, dim);
     fem_boundary.computeNormalsOnIntegrationPoints(current_pos, quad_normals,
                                                    type_facet, gt);
     auto normals_it = quad_normals.begin(dim);
     Vector<Real> normal_corrected(
         normals_it[quad_point.element * nb_quad_points + quad_point.num_point]);
 
     // auto normal_corrected = normal;
     UInt opposite_facet_nb(-1);
     if (id < element_ids.size() / 2) {
       normal_corrected *= -1;
       opposite_facet_nb = element_ids(id + element_ids.size() / 2);
     } else if (id >= element_ids.size())
       AKANTU_EXCEPTION("Error in defining side of the cohesive element");
     else {
       normal_corrected *= 1;
       opposite_facet_nb = element_ids(id - element_ids.size() / 2);
     }
 
     /// compute current area of a gap
     Vector<UInt> first_facet_nodes = facet_nodes_it[facet_nb];
     Vector<UInt> second_facet_nodes = facet_nodes_it[opposite_facet_nb];
     /* corners of a quadrangle consequently
             A---M---B
             \      |
             D--N--C */
     UInt A, B, C, D;
     A = first_facet_nodes(0);
     B = first_facet_nodes(1);
     C = second_facet_nodes(0);
     D = second_facet_nodes(1);
 
     /// quadrangle's area through diagonals
     Vector<Real> AC, BD;
     Vector<Real> A_pos = Vector<Real>(pos_it[A]) + Vector<Real>(disp_it[A]);
     Vector<Real> B_pos = Vector<Real>(pos_it[B]) + Vector<Real>(disp_it[B]);
     Vector<Real> C_pos = Vector<Real>(pos_it[C]) + Vector<Real>(disp_it[C]);
     Vector<Real> D_pos = Vector<Real>(pos_it[D]) + Vector<Real>(disp_it[D]);
     Vector<Real> M_pos = (A_pos + B_pos) * 0.5;
     Vector<Real> N_pos = (C_pos + D_pos) * 0.5;
     Vector<Real> MN = M_pos - N_pos;
     Vector<Real> AB = A_pos - B_pos;
     Vector<Real> AB_0 = Vector<Real>(pos_it[A]) - Vector<Real>(pos_it[B]);
 
     // ASR volume computed as AB * thickness (AB * ratio)
     Real ASR_volume = AB_0.norm() * AB_0.norm() * ASR_volume_ratio;
     Real current_volume = AB.norm() * MN.norm();
     current_volume =
         Math::are_float_equal(current_volume, 0.) ? 0 : current_volume;
     Real volume_change = current_volume - ASR_volume;
     Real pressure_change{0};
     if (volume_change < 0) {
       pressure_change = -volume_change / ASR_volume / this->compressibility;
     }
     dual = pressure_change * normal_corrected;
   }
 
 protected:
   SolidMechanicsModel & model;
   const Real ASR_volume_ratio;
   const std::string group_name;
   const Real compressibility;
 };
 /* ------------------------------------------------------------------- */
 class PressureVolumeDependent3D : public BC::Neumann::NeumannFunctor {
 public:
   PressureVolumeDependent3D(SolidMechanicsModelCohesive & model,
                             const Real ASR_volume_ratio,
                             const Real compressibility,
                             const Array<std::set<Element>> ASR_facets_from_mesh)
       : model(model), ASR_volume_ratio(ASR_volume_ratio),
         compressibility(compressibility),
         ASR_facets_from_mesh(ASR_facets_from_mesh) {}
 
   inline void operator()(const IntegrationPoint & quad_point,
                          Vector<Real> & dual, const Vector<Real> & /*coord*/,
                          const Vector<Real> & /*normal*/) const {
 
     // get element types
     auto && mesh = model.getMesh();
     const FEEngine & fe_engine = model.getFEEngine("CohesiveFEEngine");
     auto dim = mesh.getSpatialDimension();
     Element facet{quad_point.type, quad_point.element, quad_point.ghost_type};
     ElementType type_cohesive = FEEngine::getCohesiveElementType(facet.type);
     auto nb_quad_coh = fe_engine.getNbIntegrationPoints(type_cohesive);
     auto && facet_conn = mesh.getConnectivity(facet.type, facet.ghost_type);
     const UInt nb_nodes_facet = facet_conn.getNbComponent();
     auto facet_nodes_it = make_view(facet_conn, nb_nodes_facet).begin();
     auto && pos = mesh.getNodes();
     const auto pos_it = make_view(pos, dim).begin();
     auto && disp = model.getDisplacement();
     const auto disp_it = make_view(disp, dim).begin();
     auto && fem_boundary = model.getFEEngineBoundary();
     UInt nb_quad_points =
         fem_boundary.getNbIntegrationPoints(facet.type, facet.ghost_type);
 
     // get normal to the current positions
     const auto & current_pos = model.getCurrentPosition();
     Array<Real> quad_normals(0, dim);
     fem_boundary.computeNormalsOnIntegrationPoints(
         current_pos, quad_normals, facet.type, facet.ghost_type);
     auto normals_it = quad_normals.begin(dim);
     Vector<Real> normal_corrected(
         normals_it[quad_point.element * nb_quad_points + quad_point.num_point]);
 
     // search for this facet in the ASR facets array
     UInt ASR_site_nb(-1);
     UInt id_in_array(-1);
     for (auto && data : enumerate(this->ASR_facets_from_mesh)) {
       auto & one_site_facets = std::get<1>(data);
       auto id = one_site_facets.find(facet);
       if (id != one_site_facets.end()) {
         ASR_site_nb = std::get<0>(data);
         id_in_array = std::distance(one_site_facets.begin(), id);
         break;
       }
     }
     AKANTU_DEBUG_ASSERT(ASR_site_nb != UInt(-1),
                         "Quad point doesn't belong to the ASR facets");
 
     // correct normal depending on the facet side with respect to coh
     if (id_in_array < this->ASR_facets_from_mesh(ASR_site_nb).size() / 2) {
       normal_corrected *= -1;
     }
 
     // compute volume (area * normal_opening) of current ASR site
     // form cohesive element filter from the 1st half of facet filter
     std::set<Element> site_cohesives;
     for (auto & ASR_facet : this->ASR_facets_from_mesh(ASR_site_nb)) {
       if (ASR_facet.type == facet.type and
           ASR_facet.ghost_type == facet.ghost_type) {
         // find connected cohesive
         auto & connected_els = mesh.getElementToSubelement(ASR_facet);
         for (auto & connected_el : connected_els) {
           if (connected_el.type == type_cohesive) {
             site_cohesives.emplace(connected_el);
             break;
           }
         }
       }
     }
     // integrate normal opening over identified element filter
     Real site_volume{0};
     Real site_area{0};
     const Array<UInt> & material_index_vec =
         model.getMaterialByElement(type_cohesive, facet.ghost_type);
     const Array<UInt> & material_local_numbering_vec =
         model.getMaterialLocalNumbering(type_cohesive, facet.ghost_type);
     for (auto & coh_el : site_cohesives) {
       Material & material =
           model.getMaterial(material_index_vec(coh_el.element));
       UInt material_local_num = material_local_numbering_vec(coh_el.element);
       Array<UInt> single_el_array;
       single_el_array.push_back(coh_el.element);
       auto & opening_norm_array = material.getInternal<Real>(
           "normal_opening_norm")(coh_el.type, coh_el.ghost_type);
       Array<Real> opening_norm_el;
       for (UInt i = 0; i != nb_quad_coh; i++) {
         auto & opening_per_quad =
             opening_norm_array(material_local_num * nb_quad_coh + i);
         opening_norm_el.push_back(opening_per_quad);
       }
 
       site_volume += fe_engine.integrate(opening_norm_el, coh_el.type,
                                          coh_el.ghost_type, single_el_array);
       Array<Real> area(fe_engine.getNbIntegrationPoints(type_cohesive), 1, 1.);
       site_area += fe_engine.integrate(area, coh_el.type, coh_el.ghost_type,
                                        single_el_array);
     }
 
     Real ASR_volume = site_area * ASR_volume_ratio;
     Real volume_change = site_volume - ASR_volume;
 
     Real pressure_change{0};
     if (volume_change < 0) {
       pressure_change = -volume_change / ASR_volume / this->compressibility;
     }
     std::cout << "ASR volume = " << site_area << " x " << ASR_volume_ratio
               << " = " << ASR_volume << " site volume " << site_volume
               << " volume change " << volume_change << " pressure delta "
               << pressure_change << std::endl;
     dual = pressure_change * normal_corrected;
   }
 
 protected:
   SolidMechanicsModelCohesive & model;
   const Real ASR_volume_ratio;
   const Real compressibility;
   const Array<std::set<Element>> ASR_facets_from_mesh;
 };
 
 /* ------------------------------------------------------------------------ */
 class DeltaU : public BC::Dirichlet::DirichletFunctor {
 public:
   DeltaU(const SolidMechanicsModel & model, const Real delta_u,
          const Array<std::tuple<UInt, UInt>> & node_pairs)
       : model(model), delta_u(delta_u), node_pairs(node_pairs),
         displacement(model.getDisplacement()) {}
 
   inline void operator()(UInt node, Vector<bool> & flags, Vector<Real> & primal,
                          const Vector<Real> &) const {
 
     // get element types
     auto && mesh = model.getMesh();
     const UInt dim = mesh.getSpatialDimension();
     auto && mesh_facets = mesh.getMeshFacets();
     auto disp_it = make_view(displacement, dim).begin();
     CSR<Element> nodes_to_elements;
     MeshUtils::buildNode2Elements(mesh_facets, nodes_to_elements, dim - 1);
 
     // get actual distance between two nodes
     Vector<Real> node_disp(disp_it[node]);
     Vector<Real> other_node_disp(dim);
     bool upper_face = false;
     bool lower_face = false;
     for (auto && pair : this->node_pairs) {
       if (node == std::get<0>(pair)) {
         other_node_disp = disp_it[std::get<1>(pair)];
         upper_face = true;
         break;
       } else if (node == std::get<1>(pair)) {
         other_node_disp = disp_it[std::get<0>(pair)];
         lower_face = true;
         break;
       }
     }
     AKANTU_DEBUG_ASSERT(upper_face == true or lower_face == true,
                         "Error in identifying the node in tuple");
     Real sign = -upper_face + lower_face;
 
     // compute normal at node (averaged between two surfaces)
     Vector<Real> normal(dim);
     for (auto & elem : nodes_to_elements.getRow(node)) {
       if (mesh.getKind(elem.type) != _ek_regular)
         continue;
       if (elem.ghost_type != _not_ghost)
         continue;
       auto & doubled_facets_array = mesh_facets.getData<bool>(
           "doubled_facets", elem.type, elem.ghost_type);
       if (doubled_facets_array(elem.element) != true)
         continue;
 
       auto && fe_engine_facet = model.getFEEngine("FacetsFEEngine");
       auto nb_qpoints_per_facet =
           fe_engine_facet.getNbIntegrationPoints(elem.type, elem.ghost_type);
       const auto & normals_on_quad =
           fe_engine_facet.getNormalsOnIntegrationPoints(elem.type,
                                                         elem.ghost_type);
       auto normals_it = make_view(normals_on_quad, dim).begin();
       normal +=
           sign * Vector<Real>(normals_it[elem.element * nb_qpoints_per_facet]);
     }
     normal /= normal.norm();
 
     // get distance between two nodes in normal direction
     Real node_disp_norm = node_disp.dot(normal);
     Real other_node_disp_norm = other_node_disp.dot(-1. * normal);
     Real dist = node_disp_norm + other_node_disp_norm;
     Real prop_factor = dist == 0. ? 0.5 : node_disp_norm / dist;
 
     // get correction displacement
     Real correction = delta_u - dist;
 
     // apply absolute value of displacement
     primal += normal * correction * prop_factor;
     flags.set(false);
   }
 
 protected:
   const SolidMechanicsModel & model;
   const Real delta_u;
   const Array<std::tuple<UInt, UInt>> node_pairs;
   Array<Real> displacement;
 };
 
 } // namespace akantu
 
 #endif /* __AKANTU_ASR_TOOLS_HH__ */
diff --git a/extra_packages/asr-tools/src/mesh_utils/nodes_flag_updater.cc b/extra_packages/asr-tools/src/mesh_utils/nodes_eff_stress_updater.cc
similarity index 71%
copy from extra_packages/asr-tools/src/mesh_utils/nodes_flag_updater.cc
copy to extra_packages/asr-tools/src/mesh_utils/nodes_eff_stress_updater.cc
index 51f986dc1..b70f80807 100644
--- a/extra_packages/asr-tools/src/mesh_utils/nodes_flag_updater.cc
+++ b/extra_packages/asr-tools/src/mesh_utils/nodes_eff_stress_updater.cc
@@ -1,19 +1,20 @@
-/* -------------------------------------------------------------------------- */
-#include "nodes_flag_updater.hh"
+/* ------------------------------------------------------------------ */
+#include "nodes_eff_stress_updater.hh"
 #include "element_synchronizer.hh"
 #include "mesh_accessor.hh"
 #include "mesh_utils.hh"
-/* -------------------------------------------------------------------------- */
+/* ------------------------------------------------------------------- */
 #include <numeric>
-/* -------------------------------------------------------------------------- */
+/* ------------------------------------------------------------------- */
 
 namespace akantu {
 
-void NodesFlagUpdater::fillPreventInsertion() {
+void NodesEffStressUpdater::updateMaxEffStressAtNodes() {
   if (mesh.getCommunicator().getNbProc() == 1)
     return;
+  this->synchronizer.synchronizeOnce(*this, SynchronizationTag::_border_nodes);
   this->synchronizer.slaveReductionOnce(*this,
                                         SynchronizationTag::_border_nodes);
 }
 
 } // namespace akantu
diff --git a/extra_packages/asr-tools/src/mesh_utils/nodes_flag_updater.hh b/extra_packages/asr-tools/src/mesh_utils/nodes_eff_stress_updater.hh
similarity index 62%
copy from extra_packages/asr-tools/src/mesh_utils/nodes_flag_updater.hh
copy to extra_packages/asr-tools/src/mesh_utils/nodes_eff_stress_updater.hh
index 3316fe331..57c3e405e 100644
--- a/extra_packages/asr-tools/src/mesh_utils/nodes_flag_updater.hh
+++ b/extra_packages/asr-tools/src/mesh_utils/nodes_eff_stress_updater.hh
@@ -1,66 +1,62 @@
 /* -------------------------------------------------------------------------- */
-#ifndef __AKANTU_NODES_FLAG_UPDATER_HH__
-#define __AKANTU_NODES_FLAG_UPDATER_HH__
+#ifndef __AKANTU_NODES_EFF_STRESS_UPDATER_HH__
+#define __AKANTU_NODES_EFF_STRESS_UPDATER_HH__
 
 /* -------------------------------------------------------------------------- */
 #include "data_accessor.hh"
 #include "mesh.hh"
 
 /* -------------------------------------------------------------------------- */
 namespace akantu {
 class ElementSynchronizer;
-} // namespace akantu
+}
 
 namespace akantu {
 
-class NodesFlagUpdater : public DataAccessor<Element> {
+class NodesEffStressUpdater : public DataAccessor<Element> {
 public:
-  NodesFlagUpdater(Mesh & mesh, ElementSynchronizer & synchronizer,
-                   Array<bool> & prevent_insertion)
+  NodesEffStressUpdater(Mesh & mesh, ElementSynchronizer & synchronizer,
+                        Array<Real> & nodes_eff_stress)
       : mesh(mesh), synchronizer(synchronizer),
-        prevent_insertion(prevent_insertion) {
-    AKANTU_DEBUG_ASSERT(
-        mesh.getNbNodes() == prevent_insertion.size(),
-        "Array prevent_insertion does not have the same size as "
-        "the number of nodes in the mesh");
+        nodes_eff_stress(nodes_eff_stress) {
+    AKANTU_DEBUG_ASSERT(mesh.getNbNodes() == nodes_eff_stress.size(),
+                        "Array nodes_eff_stress does not have the same size as "
+                        "the number of nodes in the mesh");
   }
 
-  void fillPreventInsertion();
+  void updateMaxEffStressAtNodes();
 
-  /* ------------------------------------------------------------------------ */
-  /* Data Accessor inherited members                                          */
-  /* ------------------------------------------------------------------------ */
+  /* ----------------------------------------------------------------- */
+  /* Data Accessor inherited members                                   */
+  /* ----------------------------------------------------------------- */
 public:
   inline UInt getNbData(const Array<Element> & elements,
                         const SynchronizationTag & tag) const override;
 
   inline void packData(CommunicationBuffer & buffer,
                        const Array<Element> & elements,
                        const SynchronizationTag & tag) const override;
 
   inline void unpackData(CommunicationBuffer & buffer,
                          const Array<Element> & elements,
                          const SynchronizationTag & tag) override;
 
-  /* ------------------------------------------------------------------------ */
-  /* Members                                                                  */
-  /* ------------------------------------------------------------------------ */
+  /* ----------------------------------------------------------------- */
+  /* Members                                                           */
+  /* ----------------------------------------------------------------- */
 private:
   /// Reference to the mesh to update
   Mesh & mesh;
 
   /// distributed synchronizer to communicate nodes positions
   ElementSynchronizer & synchronizer;
 
-  /// Tells if a reduction is taking place or not
-  bool reduce{false};
-
-  /// tells at which nodes ASR segments shouldn't be inserted
-  Array<bool> & prevent_insertion;
+  /// average effective stress of facets loop around a node
+  Array<Real> & nodes_eff_stress;
 };
 
 } // namespace akantu
 
-#include "nodes_flag_updater_inline_impl.hh"
+#include "nodes_eff_stress_updater_inline_impl.hh"
 
-#endif /* __AKANTU_NODES_FLAG_UPDATER_HH__ */
+#endif /* __AKANTU_NODES_EFF_STRESS_UPDATER_HH__ */
diff --git a/extra_packages/asr-tools/src/mesh_utils/nodes_eff_stress_updater_inline_impl.hh b/extra_packages/asr-tools/src/mesh_utils/nodes_eff_stress_updater_inline_impl.hh
new file mode 100644
index 000000000..92ed6ab81
--- /dev/null
+++ b/extra_packages/asr-tools/src/mesh_utils/nodes_eff_stress_updater_inline_impl.hh
@@ -0,0 +1,82 @@
+/* ------------------------------------------------------------------- */
+#include "communicator.hh"
+#include "mesh.hh"
+#include "mesh_accessor.hh"
+#include "nodes_eff_stress_updater.hh"
+/* ------------------------------------------------------------------- */
+
+#ifndef __AKANTU_NODES_EFF_STRESS_UPDATER_INLINE_IMPL_CC__
+#define __AKANTU_NODES_EFF_STRESS_UPDATER_INLINE_IMPL_CC__
+
+namespace akantu {
+
+/* ------------------------------------------------------------------- */
+inline UInt
+NodesEffStressUpdater::getNbData(const Array<Element> & elements,
+                                 const SynchronizationTag & tag) const {
+  UInt size = 0;
+  if (tag == SynchronizationTag::_border_nodes) {
+    size +=
+        Mesh::getNbNodesPerElementList(elements) * sizeof(Real) + sizeof(int);
+  }
+  return size;
+}
+
+/* ------------------------------------------------------------------- */
+inline void
+NodesEffStressUpdater::packData(CommunicationBuffer & buffer,
+                                const Array<Element> & elements,
+                                const SynchronizationTag & tag) const {
+  if (tag != SynchronizationTag::_border_nodes) {
+    return;
+  }
+
+  int prank = mesh.getCommunicator().whoAmI();
+  buffer << prank;
+
+  for (const auto & element : elements) {
+    /// get element connectivity
+    const Vector<UInt> current_conn =
+        const_cast<const Mesh &>(mesh).getConnectivity(element);
+
+    /// loop on all connectivity nodes
+    for (auto node : current_conn) {
+      buffer << nodes_eff_stress(node);
+    }
+  }
+}
+
+/* ------------------------------------------------------------------ */
+inline void NodesEffStressUpdater::unpackData(CommunicationBuffer & buffer,
+                                              const Array<Element> & elements,
+                                              const SynchronizationTag & tag) {
+  if (tag != SynchronizationTag::_border_nodes) {
+    return;
+  }
+
+  MeshAccessor mesh_accessor(mesh);
+
+  int proc;
+  buffer >> proc;
+
+  for (const auto & element : elements) {
+    /// get element connectivity
+    Vector<UInt> current_conn =
+        const_cast<const Mesh &>(mesh).getConnectivity(element);
+
+    /// loop on all connectivity nodes
+    for (auto node : current_conn) {
+      Real received_eff_stress;
+      buffer >> received_eff_stress;
+      auto master_or_slave =
+          (mesh.isMasterNode(node) or mesh.isSlaveNode(node));
+      if (master_or_slave and (nodes_eff_stress(node) < received_eff_stress)) {
+        nodes_eff_stress(node) = 0;
+      }
+    }
+  }
+}
+
+} // namespace akantu
+
+#endif /* __AKANTU_NODES_EFF_STRESS_UPDATER_INLINE_IMPL_CC__ */
diff --git a/extra_packages/asr-tools/src/mesh_utils/nodes_flag_updater.cc b/extra_packages/asr-tools/src/mesh_utils/nodes_flag_updater.cc
index 51f986dc1..b132705f5 100644
--- a/extra_packages/asr-tools/src/mesh_utils/nodes_flag_updater.cc
+++ b/extra_packages/asr-tools/src/mesh_utils/nodes_flag_updater.cc
@@ -1,19 +1,19 @@
-/* -------------------------------------------------------------------------- */
+/* ------------------------------------------------------------------ */
 #include "nodes_flag_updater.hh"
 #include "element_synchronizer.hh"
 #include "mesh_accessor.hh"
 #include "mesh_utils.hh"
-/* -------------------------------------------------------------------------- */
+/* ------------------------------------------------------------------- */
 #include <numeric>
-/* -------------------------------------------------------------------------- */
+/* ------------------------------------------------------------------- */
 
 namespace akantu {
 
 void NodesFlagUpdater::fillPreventInsertion() {
   if (mesh.getCommunicator().getNbProc() == 1)
     return;
   this->synchronizer.slaveReductionOnce(*this,
                                         SynchronizationTag::_border_nodes);
 }
 
 } // namespace akantu
diff --git a/extra_packages/asr-tools/src/mesh_utils/nodes_flag_updater.hh b/extra_packages/asr-tools/src/mesh_utils/nodes_flag_updater.hh
index 3316fe331..94804ec4c 100644
--- a/extra_packages/asr-tools/src/mesh_utils/nodes_flag_updater.hh
+++ b/extra_packages/asr-tools/src/mesh_utils/nodes_flag_updater.hh
@@ -1,66 +1,66 @@
 /* -------------------------------------------------------------------------- */
 #ifndef __AKANTU_NODES_FLAG_UPDATER_HH__
 #define __AKANTU_NODES_FLAG_UPDATER_HH__
 
 /* -------------------------------------------------------------------------- */
 #include "data_accessor.hh"
 #include "mesh.hh"
 
 /* -------------------------------------------------------------------------- */
 namespace akantu {
 class ElementSynchronizer;
-} // namespace akantu
+}
 
 namespace akantu {
 
 class NodesFlagUpdater : public DataAccessor<Element> {
 public:
   NodesFlagUpdater(Mesh & mesh, ElementSynchronizer & synchronizer,
                    Array<bool> & prevent_insertion)
       : mesh(mesh), synchronizer(synchronizer),
         prevent_insertion(prevent_insertion) {
     AKANTU_DEBUG_ASSERT(
         mesh.getNbNodes() == prevent_insertion.size(),
         "Array prevent_insertion does not have the same size as "
         "the number of nodes in the mesh");
   }
 
   void fillPreventInsertion();
 
   /* ------------------------------------------------------------------------ */
   /* Data Accessor inherited members                                          */
   /* ------------------------------------------------------------------------ */
 public:
   inline UInt getNbData(const Array<Element> & elements,
                         const SynchronizationTag & tag) const override;
 
   inline void packData(CommunicationBuffer & buffer,
                        const Array<Element> & elements,
                        const SynchronizationTag & tag) const override;
 
   inline void unpackData(CommunicationBuffer & buffer,
                          const Array<Element> & elements,
                          const SynchronizationTag & tag) override;
 
   /* ------------------------------------------------------------------------ */
   /* Members                                                                  */
   /* ------------------------------------------------------------------------ */
 private:
   /// Reference to the mesh to update
   Mesh & mesh;
 
   /// distributed synchronizer to communicate nodes positions
   ElementSynchronizer & synchronizer;
 
   /// Tells if a reduction is taking place or not
   bool reduce{false};
 
   /// tells at which nodes ASR segments shouldn't be inserted
   Array<bool> & prevent_insertion;
 };
 
 } // namespace akantu
 
 #include "nodes_flag_updater_inline_impl.hh"
 
 #endif /* __AKANTU_NODES_FLAG_UPDATER_HH__ */
diff --git a/extra_packages/extra-materials/src/material_cohesive/material_cohesive_linear_sequential.cc b/extra_packages/extra-materials/src/material_cohesive/material_cohesive_linear_sequential.cc
index 9a539cebe..b8dcb634c 100644
--- a/extra_packages/extra-materials/src/material_cohesive/material_cohesive_linear_sequential.cc
+++ b/extra_packages/extra-materials/src/material_cohesive/material_cohesive_linear_sequential.cc
@@ -1,1086 +1,1176 @@
 /**
  * @file   material_cohesive_linear.cc
  *
  * @author Mauro Corrado <mauro.corrado@epfl.ch>
  * @author Nicolas Richart <nicolas.richart@epfl.ch>
  * @author Marco Vocialta <marco.vocialta@epfl.ch>
  *
  * @date creation: Wed Feb 22 2012
  * @date last modification: Wed Feb 21 2018
  *
  * @brief  Linear irreversible cohesive law of mixed mode loading with
  * random stress definition for extrinsic type
  *
  * @section LICENSE
  *
  * Copyright (©)  2010-2018 EPFL (Ecole Polytechnique Fédérale de Lausanne)
  * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
  *
  * Akantu 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.
  *
  * Akantu 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 Akantu. If not, see <http://www.gnu.org/licenses/>.
  *
  */
 #include <boost/config.hpp>
 #include <boost/graph/adjacency_list.hpp>
 #include <boost/graph/connected_components.hpp>
 /* ------------------------------------------------------------------ */
 #include "material_cohesive_linear_sequential.hh"
 /* ------------------------------------------------------------------ */
 
 namespace akantu {
 
 /* ------------------------------------------------------------------ */
 template <UInt spatial_dimension>
 MaterialCohesiveLinearSequential<spatial_dimension>::
     MaterialCohesiveLinearSequential(SolidMechanicsModel & model, const ID & id)
     : MaterialCohesiveLinear<spatial_dimension>(model, id),
       scalar_tractions("scalar_tractions", *this),
       normal_tractions("normal_tractions", *this),
       effective_stresses("effective_stresses", *this) {}
 /* ------------------------------------------------------------------ */
 template <UInt spatial_dimension>
 void MaterialCohesiveLinearSequential<spatial_dimension>::initMaterial() {
   AKANTU_DEBUG_IN();
 
   MaterialCohesiveLinear<spatial_dimension>::initMaterial();
 
   scalar_tractions.initialize(1);
   normal_tractions.initialize(spatial_dimension);
   effective_stresses.initialize(1);
   AKANTU_DEBUG_OUT();
 }
 
 /* ------------------------------------------------------------------ */
 template <UInt spatial_dimension>
 void MaterialCohesiveLinearSequential<spatial_dimension>::checkInsertion(
     bool check_only) {
   AKANTU_DEBUG_IN();
 
   const Mesh & mesh_facets = this->model->getMeshFacets();
 
   for (auto && type_facet : mesh_facets.elementTypes(spatial_dimension - 1)) {
 
     // find the facet with the highest tensile stress
     auto max_stress_data = findCriticalFacet(type_facet);
     auto max_stress_facet = std::get<0>(max_stress_data);
     auto max_stress = std::get<1>(max_stress_data);
     auto crack_number = std::get<2>(max_stress_data);
 
     // communicate between processors the highest stress
     Real local_max_stress = max_stress;
     auto && comm = akantu::Communicator::getWorldCommunicator();
     comm.allReduce(max_stress, SynchronizerOperation::_max);
 
     // if the effective max stress is 0 or < 1 ->skip
     if (max_stress < 1)
       continue;
 
     // if the max stress at this proc != global max stress -> skip
     if (local_max_stress != max_stress)
       continue;
 
     // duplicate single facet and insert cohesive element
     std::map<UInt, UInt> facet_nb_crack_nb;
     facet_nb_crack_nb[max_stress_facet] = crack_number;
     insertCohesiveElements(facet_nb_crack_nb, type_facet, check_only);
   }
 
   AKANTU_DEBUG_OUT();
 }
 
 /* ----------------------------------------------------------------- */
 template <UInt spatial_dimension>
 std::tuple<UInt, Real, UInt>
 MaterialCohesiveLinearSequential<spatial_dimension>::findCriticalFacet(
     const ElementType & type_facet) {
   AKANTU_DEBUG_IN();
 
   Mesh & mesh = this->model->getMesh();
   const Mesh & mesh_facets = this->model->getMeshFacets();
   // MeshUtils::fillElementToSubElementsData(mesh_facets);
   const auto & pos = mesh.getNodes();
   const auto pos_it = make_view(pos, spatial_dimension).begin();
   CohesiveElementInserter & inserter = this->model->getElementInserter();
 
   Array<Element> candidate_facets;
   Array<UInt> coh_crack_nbs;
   Array<Real> highest_stresses;
   Array<UInt> coh_neighbors_nb;
   std::set<Element> crack_contour;
   auto output =
       std::make_tuple(UInt(-1), std::numeric_limits<Real>::min(), UInt(-1));
 
   ElementType type_cohesive = FEEngine::getCohesiveElementType(type_facet);
   const auto & facets_check = inserter.getCheckFacets(type_facet);
   auto & f_filter = this->facet_filter(type_facet);
   auto & scal_tractions = scalar_tractions(type_facet);
   auto & norm_tractions = normal_tractions(type_facet);
   const auto & f_stress = this->model->getStressOnFacets(type_facet);
   const auto & sigma_limits = this->sigma_c(type_facet);
   auto & eff_stresses = effective_stresses(type_facet);
 
   UInt nb_quad_facet = this->model->getFEEngine("FacetsFEEngine")
                            .getNbIntegrationPoints(type_facet);
 
 #ifndef AKANTU_NDEBUG
   UInt nb_quad_cohesive = this->model->getFEEngine("CohesiveFEEngine")
                               .getNbIntegrationPoints(type_cohesive);
 
   AKANTU_DEBUG_ASSERT(nb_quad_cohesive == nb_quad_facet,
                       "The cohesive element and the corresponding facet do "
                       "not have the same numbers of integration points");
 #endif
   // skip if no facets of this type are present
   UInt nb_facet = f_filter.size();
   if (nb_facet == 0)
     return output;
 
   Matrix<Real> stress_tmp(spatial_dimension, spatial_dimension);
   UInt sp2 = spatial_dimension * spatial_dimension;
 
   const auto & tangents = this->model->getTangents(type_facet);
   const auto & normals = this->model->getFEEngine("FacetsFEEngine")
                              .getNormalsOnIntegrationPoints(type_facet);
   auto normal_begin = normals.begin(spatial_dimension);
   auto tangent_begin = tangents.begin(tangents.getNbComponent());
   auto facet_stress_begin =
       f_stress.begin(spatial_dimension, spatial_dimension * 2);
 
   // loop over each facet belonging to this material
   for (auto && data :
        zip(f_filter, sigma_limits, eff_stresses,
            make_view(scal_tractions, nb_quad_facet),
            make_view(norm_tractions, spatial_dimension, nb_quad_facet))) {
     auto facet_nb = std::get<0>(data);
     Element facet{type_facet, facet_nb, _not_ghost};
     auto & sigma_limit = std::get<1>(data);
     auto & eff_stress = std::get<2>(data);
     auto & stress_check = std::get<3>(data);
     auto & normal_traction = std::get<4>(data);
 
     // skip facets where check shouldn't be inserted (or already inserted)
     if (!facets_check(facet_nb))
       continue;
 
     // compute the effective norm on each quadrature point of the facet
     for (UInt q : arange(nb_quad_facet)) {
       UInt current_quad = facet_nb * nb_quad_facet + q;
       const Vector<Real> & normal = normal_begin[current_quad];
       const Vector<Real> & tangent = tangent_begin[current_quad];
       const Matrix<Real> & facet_stress_it = facet_stress_begin[current_quad];
 
       // compute average stress on the current quadrature point
       Matrix<Real> stress_1(facet_stress_it.storage(), spatial_dimension,
                             spatial_dimension);
 
       Matrix<Real> stress_2(facet_stress_it.storage() + sp2, spatial_dimension,
                             spatial_dimension);
 
       stress_tmp.copy(stress_1);
       stress_tmp += stress_2;
       stress_tmp /= 2.;
 
       Vector<Real> normal_traction_vec(normal_traction(q));
 
       // compute normal and effective stress
       stress_check(q) = this->computeEffectiveNorm(stress_tmp, normal, tangent,
                                                    normal_traction_vec);
     }
 
     // verify if the effective stress overcomes the threshold
     Real final_stress = stress_check.mean();
 
     // normalize by the limit stress and skip non-stressed facets
     eff_stress = final_stress / sigma_limit;
     if (eff_stress < 1)
       continue;
 
     // check to how many cohesive elements this facet is connected
     const Vector<Element> & sub_to_facet =
         mesh_facets.getSubelementToElement(facet);
     std::vector<std::set<Element>> coh_neighbors(sub_to_facet.size());
 
     for (auto && data : enumerate(sub_to_facet)) {
       auto & subel = std::get<1>(data);
       auto & i = std::get<0>(data);
       auto & connected_elements_dim_min_1 =
           mesh_facets.getElementToSubelement(subel);
       for (auto & connected_element_dim_min_1 : connected_elements_dim_min_1) {
         auto & connected_elements_dim =
             mesh_facets.getElementToSubelement(connected_element_dim_min_1);
         for (auto & connected_element_dim : connected_elements_dim) {
           if (mesh.getKind(connected_element_dim.type) == _ek_cohesive) {
             coh_neighbors[i].emplace(connected_element_dim);
             crack_contour.insert(subel);
           }
         }
       }
     }
 
     // see if a single tip crack is present
     Array<UInt> single_tip_subs;
     for (UInt i : arange(sub_to_facet.size())) {
       if (coh_neighbors[i].size() == 1) {
         single_tip_subs.push_back(i);
         // experimental feature (all neighbors have to be single tips)
       } else if (coh_neighbors[i].size() > 1) {
         goto endloop;
       }
     }
 
     // if no coh els are connected - skip facet
     if (not single_tip_subs.size())
       goto endloop;
 
     // compute distances between barycenters and sharp angles between
     // facets; condition distance and angle
     UInt potential_crack_nb;
     for (auto tip_sub : single_tip_subs) {
       auto coh_el = *coh_neighbors[tip_sub].begin();
       auto & crack_numbers =
           mesh.getData<UInt>("crack_numbers", coh_el.type, coh_el.ghost_type);
       potential_crack_nb = crack_numbers(coh_el.element);
 
       // get inscribed diameter
       Real facet_indiam =
           MeshUtils::getInscribedCircleDiameter(*(this->model), facet);
 
       // get distance between two barycenters
       auto facet_to_coh_el = mesh_facets.getSubelementToElement(coh_el)(0);
-      auto dist = MeshUtils::distanceBetweenBarycentersCorrected(
+      auto dist = MeshUtils::distanceBetweenIncentersCorrected(
           mesh_facets, facet, facet_to_coh_el);
 
       // ad-hoc rule on barycenters spacing
       // it should discard all elements under sharp angle
       if (dist < 0.8 * facet_indiam)
         goto endloop;
 
       // find facets bounding cohesive element
       const Vector<Element> & facets_to_cohesive =
           mesh_facets.getSubelementToElement(coh_el);
       // compute abs(dot) product between 2 normals & discard sharp angle
       Real dot = MeshUtils::cosSharpAngleBetween2Facets(*(this->model), facet,
                                                         facets_to_cohesive(0));
 
       if (dot < 0.7)
         goto endloop;
     }
 
     // add a candidate facet into a pool
     candidate_facets.push_back(facet);
     coh_crack_nbs.push_back(potential_crack_nb);
     highest_stresses.push_back(eff_stress);
     coh_neighbors_nb.push_back(single_tip_subs.size());
 
   endloop:;
   }
   if (not candidate_facets.size())
     return output;
 
   // insert element with the highest stress
   std::map<Real, UInt> stress_map;
   for (UInt i : arange(highest_stresses.size())) {
     stress_map.emplace(highest_stresses(i), i);
   }
   auto critical_facet_pos = stress_map.rbegin()->second;
   output = std::make_tuple(candidate_facets(critical_facet_pos).element,
                            highest_stresses(critical_facet_pos),
                            coh_crack_nbs(critical_facet_pos));
 
   return output;
 }
 
 /* ----------------------------------------------------------------- */
 
 template <UInt spatial_dimension>
 std::map<UInt, UInt> MaterialCohesiveLinearSequential<spatial_dimension>::
     findCriticalFacetsOnContour(
         const std::map<Element, Element> & contour_subfacets_coh_el,
         const std::map<Element, UInt> & surface_subfacets_crack_nb,
         const std::set<UInt> & contour_nodes,
         const std::set<UInt> & surface_nodes) {
   AKANTU_DEBUG_IN();
 
   const Mesh & mesh = this->model->getMesh();
   const Mesh & mesh_facets = this->model->getMeshFacets();
   UInt current_mat_index = this->model->getMaterialIndex(this->getName());
   // map is necessary to keep facet nbs sorted (otherwise insertion is messed
   // up)
   std::map<UInt, UInt> facet_nbs_crack_nbs;
   std::set<UInt> facet_nbs;
   std::set<Element> visited_facets;
 
   // TODO : make iteration on multiple facet types
   auto type_facet = *mesh_facets.elementTypes(spatial_dimension - 1).begin();
   ElementType type_cohesive = FEEngine::getCohesiveElementType(type_facet);
   auto & f_filter = this->facet_filter(type_facet);
   // skip if no facets of this type are present
   if (not f_filter.size()) {
     return facet_nbs_crack_nbs;
   }
 
   UInt nb_quad_facet = this->model->getFEEngine("FacetsFEEngine")
                            .getNbIntegrationPoints(type_facet);
 #ifndef AKANTU_NDEBUG
   UInt nb_quad_cohesive = this->model->getFEEngine("CohesiveFEEngine")
                               .getNbIntegrationPoints(type_cohesive);
 
   AKANTU_DEBUG_ASSERT(nb_quad_cohesive == nb_quad_facet,
                       "The cohesive element and the corresponding facet do "
                       "not have the same numbers of integration points");
 #endif
   CohesiveElementInserter & inserter = this->model->getElementInserter();
   const auto & facets_check = inserter.getCheckFacets(type_facet);
   auto & scal_tractions = scalar_tractions(type_facet);
   auto & norm_tractions = normal_tractions(type_facet);
   const auto & f_stress = this->model->getStressOnFacets(type_facet);
   const auto & sigma_limits = this->sigma_c(type_facet);
   auto & eff_stresses = effective_stresses(type_facet);
   const auto & tangents = this->model->getTangents(type_facet);
   const auto & normals = this->model->getFEEngine("FacetsFEEngine")
                              .getNormalsOnIntegrationPoints(type_facet);
   auto scal_tractions_it =
       scal_tractions.begin_reinterpret(nb_quad_facet, f_filter.size());
   auto norm_tractions_it = norm_tractions.begin_reinterpret(
       spatial_dimension, nb_quad_facet, f_filter.size());
   auto normal_it = normals.begin(spatial_dimension);
   auto tangent_it = tangents.begin(tangents.getNbComponent());
   auto facet_stress_it =
       f_stress.begin(spatial_dimension, spatial_dimension * 2);
 
   Matrix<Real> stress_tmp(spatial_dimension, spatial_dimension);
   UInt sp2 = spatial_dimension * spatial_dimension;
 
   for (auto && pair : contour_subfacets_coh_el) {
     auto & contour_el = pair.first;
     auto & coh_el = pair.second;
 
     Real max_stress = std::numeric_limits<Real>::min();
     Element max_stress_facet{ElementNull};
     auto & facets_to_contour = mesh_facets.getElementToSubelement(contour_el);
     // identify the crack number and cohesive nodes
     UInt crack_nb = mesh.getData<UInt>("crack_numbers", coh_el.type,
                                        coh_el.ghost_type)(coh_el.element);
 
     auto && coh_facets_vec = mesh_facets.getSubelementToElement(coh_el);
     Array<Element> coh_facets;
     for (auto coh_facet : coh_facets_vec) {
       auto && subfacets = mesh_facets.getSubelementToElement(coh_facet);
       auto ret = std::find(subfacets.storage(),
                            subfacets.storage() + subfacets.size(), contour_el);
       if (ret == subfacets.storage() + subfacets.size())
         continue;
       coh_facets.push_back(coh_facet);
     }
 
     Vector<UInt> cohesive_nodes = mesh.getConnectivity(coh_el);
     AKANTU_DEBUG_ASSERT(coh_facets.size(),
                         "Cohesive element on countour not identified");
 
     // proceed to identifying the potential insertion facet
     for (auto & facet : facets_to_contour) {
       // check if the facet was already visited
       auto ret = visited_facets.emplace(facet);
       if (not ret.second)
         continue;
 
       // skip ghost facets
       if (facet.ghost_type == _ghost) {
         continue;
       }
 
       // skip cohesive facets
       if (coh_facets.find(facet) != UInt(-1)) {
         continue;
       }
 
       // check if the facet belongs to this material
       auto & facet_mat_index = this->model->getFacetMaterial(
           facet.type, facet.ghost_type)(facet.element);
       if (facet_mat_index != current_mat_index) {
         continue;
       }
 
       // check on check_facets
       if (not facets_check(facet.element)) {
       nextfacet:;
         continue;
       }
 
       // discard facets intersecting crack surface (unless different cracks)
       auto && subfacet_to_facet = mesh_facets.getSubelementToElement(facet);
       for (auto & subfacet : subfacet_to_facet) {
         auto ret = surface_subfacets_crack_nb.find(subfacet);
         if (ret != surface_subfacets_crack_nb.end()) {
           if (crack_nb == ret->second) {
             goto nextfacet;
           }
         }
       }
 
       // discard facets touching the surface
       Vector<UInt> facet_nodes = mesh_facets.getConnectivity(facet);
       for (auto & facet_node : facet_nodes) {
         if (surface_nodes.find(facet_node) != surface_nodes.end()) {
           goto nextfacet;
         }
       }
 
       // discard facets touching the contour
       UInt nb_subfacets_on_contour(0);
       for (auto & subfacet : subfacet_to_facet) {
         if (contour_subfacets_coh_el.find(subfacet) !=
             contour_subfacets_coh_el.end()) {
           nb_subfacets_on_contour++;
         }
       }
       if (nb_subfacets_on_contour == 1) {
         UInt nb_nodes_on_contour(0);
         Vector<UInt> facet_nodes = mesh_facets.getConnectivity(facet);
         for (auto & facet_node : facet_nodes) {
           if (contour_nodes.find(facet_node) != contour_nodes.end()) {
             nb_nodes_on_contour++;
           }
         }
         if (nb_nodes_on_contour == 3) {
           goto nextfacet;
         }
       }
 
       // discard facets under sharp angle with crack
       Real facet_indiam =
           MeshUtils::getInscribedCircleDiameter(*(this->model), facet);
-      auto dist = MeshUtils::distanceBetweenBarycentersCorrected(
+      auto dist = MeshUtils::distanceBetweenIncentersCorrected(
           mesh_facets, facet, coh_facets[0]);
       // ad-hoc rule on barycenters spacing
       // it should discard all elements under sharp angle
       if (dist < 0.8 * facet_indiam) {
         continue;
       }
 
       // compute abs(dot) product between 2 normals & discard sharp angle
       Real dot = MeshUtils::cosSharpAngleBetween2Facets(*(this->model), facet,
                                                         coh_facets[0]);
       if (dot < 0.8) {
         continue;
       }
 
       // // non-local check on facet normal (all nearby facets)
       // CSR<Element> nodes_to_facets;
       // MeshUtils::buildNode2Elements(mesh, nodes_to_facets,
       //                               spatial_dimension - 1, _ek_regular);
       // Vector<Real> facet_normal = normal_it[facet.element * nb_quad_facet];
       // Vector<Real> final_normal(spatial_dimension);
       // std::set<Element> visited_facets;
       // Vector<Real> ref_vector(spatial_dimension);
       // for (auto node : cohesive_nodes) {
       //   for (auto & near_facet : nodes_to_facets.getRow(node)) {
       //     if (not visited_facets.size()) {
       //       const auto & near_facets_normals =
       //           this->model->getFEEngine("FacetsFEEngine")
       //               .getNormalsOnIntegrationPoints(near_facet.type,
       //                                              near_facet.ghost_type);
       //       auto near_facet_normal_it =
       //           near_facets_normals.begin(spatial_dimension);
 
       //       ref_vector =
       //           near_facet_normal_it[near_facet.element * nb_quad_facet];
       //     }
       //     auto ret = visited_facets.emplace(near_facet);
       //     if (ret.second) {
       //       const auto & near_facets_normals =
       //           this->model->getFEEngine("FacetsFEEngine")
       //               .getNormalsOnIntegrationPoints(near_facet.type,
       //                                              near_facet.ghost_type);
       //       auto near_facet_normal_it =
       //           near_facets_normals.begin(spatial_dimension);
       //       Vector<Real> near_facet_normal =
       //           near_facet_normal_it[near_facet.element * nb_quad_facet];
       //       Real test_dot = ref_vector.dot(near_facet_normal);
       //       if (test_dot < 0)
       //         near_facet_normal *= -1;
 
       //       final_normal += near_facet_normal;
       //     }
       //   }
       // }
       // final_normal /= final_normal.norm();
 
       // // compute abs(dot) product between 2 normals & discard sharp angle
       // Real dot = facet_normal.dot(final_normal);
       // dot = std::abs(dot);
 
       // if (dot < 0.8)
       //   continue;
 
       // check stress
       auto facet_local_nb = f_filter.find(facet.element);
       auto & sigma_limit = sigma_limits(facet_local_nb);
       auto & eff_stress = eff_stresses(facet_local_nb);
       Vector<Real> stress_check(scal_tractions_it[facet_local_nb]);
       Matrix<Real> normal_traction(norm_tractions_it[facet_local_nb]);
 
       // compute the effective norm on each quadrature point of the facet
       for (UInt q : arange(nb_quad_facet)) {
         UInt current_quad = facet.element * nb_quad_facet + q;
         const Vector<Real> & normal = normal_it[current_quad];
         const Vector<Real> & tangent = tangent_it[current_quad];
         const Matrix<Real> & facet_stress = facet_stress_it[current_quad];
 
         // compute average stress on the current quadrature point
         Matrix<Real> stress_1(facet_stress.storage(), spatial_dimension,
                               spatial_dimension);
 
         Matrix<Real> stress_2(facet_stress.storage() + sp2, spatial_dimension,
                               spatial_dimension);
 
         stress_tmp.copy(stress_1);
         stress_tmp += stress_2;
         stress_tmp /= 2.;
 
         Vector<Real> normal_traction_vec(normal_traction(q));
 
         // compute normal and effective stress
         stress_check(q) = this->computeEffectiveNorm(
             stress_tmp, normal, tangent, normal_traction_vec);
       }
 
       // verify if the effective stress overcomes the threshold
       Real final_stress = stress_check.mean();
 
       // normalize by the limit stress and skip non-stressed facets
       eff_stress = final_stress / sigma_limit;
 
       if (eff_stress < 1)
         continue;
 
       // determine the most stressed facet connected to this contour
       if (eff_stress > max_stress) {
         max_stress = eff_stress;
         max_stress_facet = facet;
       }
     }
     if (max_stress_facet != ElementNull) {
       auto ret = facet_nbs.emplace(max_stress_facet.element);
       if (ret.second) {
         facet_nbs_crack_nbs[max_stress_facet.element] = crack_nb;
       }
     }
   }
   return facet_nbs_crack_nbs;
 }
 
+/* ----------------------------------------------------------------- */
+template <UInt spatial_dimension>
+void MaterialCohesiveLinearSequential<
+    spatial_dimension>::computeEffectiveStresses() {
+
+  AKANTU_DEBUG_IN();
+
+  const Mesh & mesh_facets = this->model->getMeshFacets();
+
+  for (auto && type_facet : mesh_facets.elementTypes(spatial_dimension - 1)) {
+
+    ElementType type_cohesive = FEEngine::getCohesiveElementType(type_facet);
+    auto & f_filter = this->facet_filter(type_facet);
+    // skip if no facets of this type are present
+    if (not f_filter.size()) {
+      return;
+    }
+
+    UInt nb_quad_facet = this->model->getFEEngine("FacetsFEEngine")
+                             .getNbIntegrationPoints(type_facet);
+#ifndef AKANTU_NDEBUG
+    UInt nb_quad_cohesive = this->model->getFEEngine("CohesiveFEEngine")
+                                .getNbIntegrationPoints(type_cohesive);
+
+    AKANTU_DEBUG_ASSERT(nb_quad_cohesive == nb_quad_facet,
+                        "The cohesive element and the corresponding facet do "
+                        "not have the same numbers of integration points");
+#endif
+    auto & scal_tractions = scalar_tractions(type_facet);
+    auto & norm_tractions = normal_tractions(type_facet);
+    const auto & f_stress = this->model->getStressOnFacets(type_facet);
+    const auto & sigma_limits = this->sigma_c(type_facet);
+    auto & eff_stresses = effective_stresses(type_facet);
+    const auto & tangents = this->model->getTangents(type_facet);
+    const auto & normals = this->model->getFEEngine("FacetsFEEngine")
+                               .getNormalsOnIntegrationPoints(type_facet);
+    auto scal_tractions_it =
+        scal_tractions.begin_reinterpret(nb_quad_facet, f_filter.size());
+    auto norm_tractions_it = norm_tractions.begin_reinterpret(
+        spatial_dimension, nb_quad_facet, f_filter.size());
+    auto normal_it = normals.begin(spatial_dimension);
+    auto tangent_it = tangents.begin(tangents.getNbComponent());
+    auto facet_stress_it =
+        f_stress.begin(spatial_dimension, spatial_dimension * 2);
+
+    Matrix<Real> stress_tmp(spatial_dimension, spatial_dimension);
+    UInt sp2 = spatial_dimension * spatial_dimension;
+
+    for (auto && data : enumerate(f_filter)) {
+      auto facet_local_nb = std::get<0>(data);
+      auto facet_global_nb = std::get<1>(data);
+      auto & sigma_limit = sigma_limits(facet_local_nb);
+      auto & eff_stress = eff_stresses(facet_local_nb);
+      Vector<Real> stress_check(scal_tractions_it[facet_local_nb]);
+      Matrix<Real> normal_traction(norm_tractions_it[facet_local_nb]);
+
+      // compute the effective norm on each quadrature point of the facet
+      for (UInt q : arange(nb_quad_facet)) {
+        UInt current_quad = facet_global_nb * nb_quad_facet + q;
+        const Vector<Real> & normal = normal_it[current_quad];
+        const Vector<Real> & tangent = tangent_it[current_quad];
+        const Matrix<Real> & facet_stress = facet_stress_it[current_quad];
+
+        // compute average stress on the current quadrature point
+        Matrix<Real> stress_1(facet_stress.storage(), spatial_dimension,
+                              spatial_dimension);
+
+        Matrix<Real> stress_2(facet_stress.storage() + sp2, spatial_dimension,
+                              spatial_dimension);
+
+        stress_tmp.copy(stress_1);
+        stress_tmp += stress_2;
+        stress_tmp /= 2.;
+
+        Vector<Real> normal_traction_vec(normal_traction(q));
+
+        // compute normal and effective stress
+        stress_check(q) = this->computeEffectiveNorm(
+            stress_tmp, normal, tangent, normal_traction_vec);
+      }
+
+      // verify if the effective stress overcomes the threshold
+      Real final_stress = stress_check.mean();
+
+      // normalize by the limit stress and skip non-stressed facets
+      eff_stress = final_stress / sigma_limit;
+    }
+  }
+}
+
 /* ----------------------------------------------------------------- */
 template <UInt spatial_dimension>
 std::map<UInt, UInt>
 MaterialCohesiveLinearSequential<spatial_dimension>::findHolesOnContour(
     std::map<Element, Element> & contour_subfacets_coh_el,
     const std::map<Element, UInt> & /*surface_subfacets_crack_nb*/,
     const std::set<UInt> & /*surface_nodes*/) {
   AKANTU_DEBUG_IN();
   Mesh & mesh = this->model->getMesh();
   const Mesh & mesh_facets = this->model->getMeshFacets();
   CohesiveElementInserter & inserter = this->model->getElementInserter();
   UInt current_mat_index = this->model->getMaterialIndex(this->getName());
 
   std::map<UInt, UInt> facet_nbs_crack_nbs;
   std::map<UInt, std::set<Element>> facet_contour_subfacets;
   std::map<UInt, UInt> facet_crack_nb;
   std::set<Element> facet_pool;
 
   // TODO : make iteration on multiple facet types
   auto type_facet = *mesh_facets.elementTypes(spatial_dimension - 1).begin();
   auto type_cohesive = FEEngine::getCohesiveElementType(type_facet);
   UInt nb_quad_facet = this->model->getFEEngine("FacetsFEEngine")
                            .getNbIntegrationPoints(type_facet);
   const auto & facets_check = inserter.getCheckFacets(type_facet);
   auto & f_filter = this->facet_filter(type_facet);
-  auto & scal_tractions = scalar_tractions(type_facet);
-  auto & norm_tractions = normal_tractions(type_facet);
-  const auto & f_stress = this->model->getStressOnFacets(type_facet);
-  const auto & sigma_limits = this->sigma_c(type_facet);
-  auto & eff_stresses = effective_stresses(type_facet);
-  const auto & tangents = this->model->getTangents(type_facet);
-  const auto & normals = this->model->getFEEngine("FacetsFEEngine")
-                             .getNormalsOnIntegrationPoints(type_facet);
-  auto normal_it = normals.begin(spatial_dimension);
-  auto scal_tractions_it =
-      scal_tractions.begin_reinterpret(nb_quad_facet, f_filter.size());
-  auto norm_tractions_it = norm_tractions.begin_reinterpret(
-      spatial_dimension, nb_quad_facet, f_filter.size());
-  auto tangent_it = tangents.begin(tangents.getNbComponent());
-  auto facet_stress_it =
-      f_stress.begin(spatial_dimension, spatial_dimension * 2);
-
-  Matrix<Real> stress_tmp(spatial_dimension, spatial_dimension);
-  UInt sp2 = spatial_dimension * spatial_dimension;
+  // auto & scal_tractions = scalar_tractions(type_facet);
+  // auto & norm_tractions = normal_tractions(type_facet);
+  // const auto & f_stress = this->model->getStressOnFacets(type_facet);
+  // const auto & sigma_limits = this->sigma_c(type_facet);
+  // auto & eff_stresses = effective_stresses(type_facet);
+  // const auto & tangents = this->model->getTangents(type_facet);
+  // const auto & normals = this->model->getFEEngine("FacetsFEEngine")
+  //                            .getNormalsOnIntegrationPoints(type_facet);
+  // auto normal_it = normals.begin(spatial_dimension);
+  // auto scal_tractions_it =
+  //     scal_tractions.begin_reinterpret(nb_quad_facet, f_filter.size());
+  // auto norm_tractions_it = norm_tractions.begin_reinterpret(
+  //     spatial_dimension, nb_quad_facet, f_filter.size());
+  // auto tangent_it = tangents.begin(tangents.getNbComponent());
+  // auto facet_stress_it =
+  //     f_stress.begin(spatial_dimension, spatial_dimension * 2);
+
+  // Matrix<Real> stress_tmp(spatial_dimension, spatial_dimension);
+  // UInt sp2 = spatial_dimension * spatial_dimension;
 
 #ifndef AKANTU_NDEBUG
   UInt nb_quad_cohesive = this->model->getFEEngine("CohesiveFEEngine")
                               .getNbIntegrationPoints(type_cohesive);
 
   AKANTU_DEBUG_ASSERT(nb_quad_cohesive == nb_quad_facet,
                       "The cohesive element and the corresponding facet do "
                       "not have the same numbers of integration points");
 #endif
   // skip if no facets of this type are present
   UInt nb_facet = f_filter.size();
   if (nb_facet == 0)
     return facet_nbs_crack_nbs;
 
   for (auto && pair : contour_subfacets_coh_el) {
     auto & contour_el = pair.first;
     auto & coh_el = pair.second;
     auto & facets_to_contour = mesh_facets.getElementToSubelement(contour_el);
     // identify the crack number and cohesive nodes
     UInt crack_nb = mesh.getData<UInt>("crack_numbers", coh_el.type,
                                        coh_el.ghost_type)(coh_el.element);
     auto && coh_facets_vec = mesh_facets.getSubelementToElement(coh_el);
     Array<Element> coh_facets;
     for (auto coh_facet : coh_facets_vec) {
       auto && subfacets = mesh_facets.getSubelementToElement(coh_facet);
       auto ret = std::find(subfacets.storage(),
                            subfacets.storage() + subfacets.size(), contour_el);
       if (ret == subfacets.storage() + subfacets.size())
         continue;
       coh_facets.push_back(coh_facet);
     }
     AKANTU_DEBUG_ASSERT(coh_facets.size(),
                         "Cohesive element on countour not identified");
 
     // proceed to identifying the potential insertion facet
     for (auto & facet : facets_to_contour) {
 
       // skip ghost facets
       if (facet.ghost_type == _ghost) {
         continue;
       }
 
       // skip cohesive facets
       if (coh_facets.find(facet) != UInt(-1)) {
         continue;
       }
 
       // check if the facet belongs to this material
       auto & facet_mat_index = this->model->getFacetMaterial(
           facet.type, facet.ghost_type)(facet.element);
       if (facet_mat_index != current_mat_index) {
         continue;
       }
       // check check_facets
       if (not facets_check(facet.element)) {
         continue;
       }
 
       // // discard facets intersecting crack surface
       // auto && subfacet_to_facet = mesh_facets.getSubelementToElement(facet);
       // for (auto & subfacet : subfacet_to_facet) {
       //   if (surface_subfacets.find(subfacet) != surface_subfacets.end()) {
       //     goto nextfacet;
       //   }
       // }
 
       // // discard facets touching the surface
       // Vector<UInt> facet_nodes = mesh_facets.getConnectivity(facet);
       // for (auto & facet_node : facet_nodes) {
       //   if (surface_nodes.find(facet_node) != surface_nodes.end()) {
       //     goto nextfacet;
       //   }
       // }
 
       // discard facets under sharp angle with crack
       Real facet_indiam =
           MeshUtils::getInscribedCircleDiameter(*(this->model), facet);
-      auto dist = MeshUtils::distanceBetweenBarycentersCorrected(
+      auto dist = MeshUtils::distanceBetweenIncentersCorrected(
           mesh_facets, facet, coh_facets[0]);
       // ad-hoc rule on barycenters spacing
       // it should discard all elements under sharp angle
       if (dist < 0.8 * facet_indiam) {
         continue;
       }
 
       // avoid facets under sharp angles with their cohesives
       Real dot = MeshUtils::cosSharpAngleBetween2Facets(*(this->model), facet,
                                                         coh_facets[0]);
       if (dot < 0.8)
         continue;
 
       // // discard not stressed or compressed facets
       // auto facet_local_nb = f_filter.find(facet.element);
       // auto & sigma_limit = sigma_limits(facet_local_nb);
       // auto & eff_stress = eff_stresses(facet_local_nb);
       // Vector<Real> stress_check(scal_tractions_it[facet_local_nb]);
       // Matrix<Real> normal_traction(norm_tractions_it[facet_local_nb]);
 
       // // compute the effective norm on each quadrature point of the facet
       // for (UInt q : arange(nb_quad_facet)) {
       //   UInt current_quad = facet.element * nb_quad_facet + q;
       //   const Vector<Real> & normal = normal_it[current_quad];
       //   const Vector<Real> & tangent = tangent_it[current_quad];
       //   const Matrix<Real> & facet_stress = facet_stress_it[current_quad];
       //   // compute average stress on the current quadrature point
       //   Matrix<Real> stress_1(facet_stress.storage(), spatial_dimension,
       //                         spatial_dimension);
 
       //   Matrix<Real> stress_2(facet_stress.storage() + sp2,
       //   spatial_dimension,
       //                         spatial_dimension);
       //   stress_tmp.copy(stress_1);
       //   stress_tmp += stress_2;
       //   stress_tmp /= 2.;
 
       //   Vector<Real> normal_traction_vec(normal_traction(q));
       //   // compute normal and effective stress
       //   stress_check(q) = this->computeEffectiveNorm(
       //       stress_tmp, normal, tangent, normal_traction_vec);
       // }
 
       // // verify if the effective stress overcomes the threshold
       // Real final_stress = stress_check.mean();
 
       // // normalize by the limit stress and skip non-stressed facets
       // eff_stress = final_stress / sigma_limit;
       // if (eff_stress <= 0)
       //   continue;
 
       // add facet into the map
       facet_contour_subfacets[facet.element].emplace(contour_el);
       facet_crack_nb[facet.element] = crack_nb;
       // add all passed facets into the pool
       facet_pool.emplace(facet);
     }
   }
 
   // get all facets having more than 1 connection to the contour
   // and clean facet_pool from the facets connected to these contours
   for (auto it = facet_contour_subfacets.begin();
        it != facet_contour_subfacets.end(); it++) {
     if (it->second.size() > 1) {
       facet_nbs_crack_nbs[it->first] = facet_crack_nb[it->first];
 
       // delete facets connected to above subfacets from the pool
       Element facet{type_facet, it->first, _not_ghost};
       for (auto & contour_subf : it->second) {
         auto & facets_to_taken_contour =
             mesh_facets.getElementToSubelement(contour_subf);
         for (auto & facet_to_taken_contour : facets_to_taken_contour) {
           if (facet_to_taken_contour == facet)
             continue;
           facet_pool.erase(facet_to_taken_contour);
         }
       }
     }
   }
 
   // determine all interconnected holes
   // initialize a graph
   typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS>
       Graph;
   Graph graph;
   // fill the graph in
   auto first = facet_pool.begin();
   for (auto it1 = first; it1 != facet_pool.end(); it1++) {
     // insert a single edge
     boost::add_edge(std::distance(first, it1), std::distance(first, it1),
                     graph);
     for (auto it2 = std::next(it1); it2 != facet_pool.end(); it2++) {
       auto data = MeshUtils::areFacetsConnected(mesh_facets, *it1, *it2);
       // facets are not connected
       if (not std::get<0>(data))
         continue;
       // connected but through the current crack tip
       for (auto & shared_sub : std::get<1>(data)) {
         if (contour_subfacets_coh_el.find(shared_sub) !=
             contour_subfacets_coh_el.end()) {
           goto endloop;
         }
       }
       // otherwise properly connected;
       boost::add_edge(std::distance(first, it1), std::distance(first, it2),
                       graph);
     endloop:;
     }
   }
   // connectivity and number of components of a graph
   std::vector<int> components(boost::num_vertices(graph));
   boost::connected_components(graph, &components[0]);
   if (not components.size()) {
     return facet_nbs_crack_nbs;
   }
   // compute component sizes
   std::map<int, UInt> components_size;
   for (auto component : components) {
     components_size[component]++;
   }
   // insert all components with size > 1
   for (auto it = first; it != facet_pool.end(); it++) {
     auto component = components[std::distance(first, it)];
     if (components_size[component] <= 1)
       continue;
     // otherwise insert this facet as it is a godly act
     facet_nbs_crack_nbs[it->element] = facet_crack_nb[it->element];
   }
   AKANTU_DEBUG_OUT();
   return facet_nbs_crack_nbs;
 }
 
 /* ----------------------------------------------------------------- */
 template <UInt spatial_dimension>
 std::tuple<std::map<Element, Element>, std::map<Element, UInt>, std::set<UInt>,
            std::set<UInt>>
 MaterialCohesiveLinearSequential<spatial_dimension>::determineCrackSurface() {
   AKANTU_DEBUG_IN();
 
   Mesh & mesh = this->model->getMesh();
   const Mesh & mesh_facets = this->model->getMeshFacets();
   std::map<Element, Element> contour_subfacets_coh_el;
   std::map<Element, UInt> surface_subfacets_crack_nb;
   std::set<UInt> surface_nodes;
   std::set<UInt> contour_nodes;
   std::set<Element> visited_subfacets;
 
   // first loop on facets (doesn't include duplicated ones)
   for (auto & type_facet :
        mesh.elementTypes(spatial_dimension - 1, _not_ghost, _ek_regular)) {
     auto & filter = this->facet_filter(type_facet);
     if (filter.empty()) {
       continue;
     }
 
     for (auto & facet_nb : filter) {
       Element facet{type_facet, facet_nb, _not_ghost};
       auto && subfacets_to_facet = mesh_facets.getSubelementToElement(facet);
       for (auto & subfacet : subfacets_to_facet) {
         auto ret = visited_subfacets.emplace(subfacet);
         if (not ret.second)
           continue;
 
         std::set<Element> connected_cohesives;
         auto && facets_to_subfacet =
             mesh_facets.getElementToSubelement(subfacet);
         for (auto & facet : facets_to_subfacet) {
           auto & connected_to_facet = mesh_facets.getElementToSubelement(facet);
           for (auto & connected_el : connected_to_facet) {
             if (connected_el.type == _not_defined)
               goto nextfacet;
             if (mesh.getKind(connected_el.type) == _ek_cohesive) {
               connected_cohesives.emplace(connected_el);
             }
           }
         nextfacet:;
         }
         if (connected_cohesives.size() == 1) {
           contour_subfacets_coh_el[subfacet] = *connected_cohesives.begin();
           Vector<UInt> contour_subfacet_nodes =
               mesh_facets.getConnectivity(subfacet);
           for (auto & contour_subfacet_node : contour_subfacet_nodes) {
             contour_nodes.emplace(contour_subfacet_node);
           }
         } else if (connected_cohesives.size() > 1) {
           auto coh_element = *connected_cohesives.begin();
           UInt crack_nb =
               mesh.getData<UInt>("crack_numbers", coh_element.type,
                                  coh_element.ghost_type)(coh_element.element);
           surface_subfacets_crack_nb[subfacet] = crack_nb;
           // add subfacet nodes to the set
           for (auto & subfacet_node : mesh_facets.getConnectivity(subfacet))
             surface_nodes.emplace(subfacet_node);
         }
       }
     }
   }
 
   // second loop on cohesives (includes duplicated facets)
   for (auto & type_cohesive :
        mesh.elementTypes(spatial_dimension, _not_ghost, _ek_cohesive)) {
     auto & filter = this->element_filter(type_cohesive, _not_ghost);
     if (filter.empty()) {
       continue;
     }
 
     for (auto & cohesive_nb : filter) {
       Element cohesive{type_cohesive, cohesive_nb, _not_ghost};
       auto && facets_to_cohesive = mesh_facets.getSubelementToElement(cohesive);
       for (auto coh_facet : facets_to_cohesive) {
         auto && subfacets_to_facet =
             mesh_facets.getSubelementToElement(coh_facet);
         for (auto & subfacet : subfacets_to_facet) {
           auto ret = visited_subfacets.emplace(subfacet);
           if (not ret.second)
             continue;
 
           std::set<Element> connected_cohesives;
           auto && facets_to_subfacet =
               mesh_facets.getElementToSubelement(subfacet);
           for (auto & facet : facets_to_subfacet) {
             auto & connected_to_facet =
                 mesh_facets.getElementToSubelement(facet);
             for (auto & connected_el : connected_to_facet) {
               if (connected_el.type == _not_defined)
                 goto nextfacet2;
               if (mesh.getKind(connected_el.type) == _ek_cohesive) {
                 connected_cohesives.emplace(connected_el);
               }
             }
           nextfacet2:;
           }
           if (connected_cohesives.size() == 1) {
             contour_subfacets_coh_el[subfacet] = *connected_cohesives.begin();
             Vector<UInt> contour_subfacet_nodes =
                 mesh_facets.getConnectivity(subfacet);
             for (auto & contour_subfacet_node : contour_subfacet_nodes) {
               contour_nodes.emplace(contour_subfacet_node);
             }
           } else if (connected_cohesives.size() > 1) {
             auto coh_element = *connected_cohesives.begin();
             UInt crack_nb =
                 mesh.getData<UInt>("crack_numbers", coh_element.type,
                                    coh_element.ghost_type)(coh_element.element);
             surface_subfacets_crack_nb[subfacet] = crack_nb;
             // add subfacet nodes to the set
             for (auto & subfacet_node : mesh_facets.getConnectivity(subfacet))
               surface_nodes.emplace(subfacet_node);
           }
         }
       }
     }
   }
 
   // remove external nodes from the surface nodes list
   for (auto & contour_node : contour_nodes) {
     surface_nodes.erase(contour_node);
   }
 
   return std::make_tuple(contour_subfacets_coh_el, surface_subfacets_crack_nb,
                          surface_nodes, contour_nodes);
 }
 /* ----------------------------------------------------------------- */
 template <UInt spatial_dimension>
 void MaterialCohesiveLinearSequential<spatial_dimension>::
     insertCohesiveElements(std::map<UInt, UInt> & facet_nbs_crack_nbs,
                            ElementType type_facet, bool check_only) {
   if (not facet_nbs_crack_nbs.size()) {
     return;
   }
 
   Mesh & mesh = this->model->getMesh();
   CohesiveElementInserter & inserter = this->model->getElementInserter();
   ElementType type_cohesive = FEEngine::getCohesiveElementType(type_facet);
   auto & f_filter = this->facet_filter(type_facet);
   auto & f_insertion = inserter.getInsertionFacets(type_facet);
   auto & scal_tractions = scalar_tractions(type_facet);
   auto & norm_tractions = normal_tractions(type_facet);
   const auto & sigma_limits = this->sigma_c(type_facet);
   auto & sig_c_eff = this->sigma_c_eff(type_cohesive);
   auto & del_c = this->delta_c_eff(type_cohesive);
   auto & del_max = this->delta_max(type_cohesive);
   auto & ins_stress = this->insertion_stress(type_cohesive);
   auto & trac_old = this->tractions.previous(type_cohesive);
   auto & crack_numbers = mesh.getData<UInt>("crack_numbers", type_cohesive);
   const auto & normals = this->model->getFEEngine("FacetsFEEngine")
                              .getNormalsOnIntegrationPoints(type_facet);
   auto normal_begin = normals.begin(spatial_dimension);
 
   UInt nb_quad_facet = this->model->getFEEngine("FacetsFEEngine")
                            .getNbIntegrationPoints(type_facet);
   std::vector<Real> new_sigmas;
   std::vector<Vector<Real>> new_normal_traction;
   std::vector<Real> new_delta_c;
   std::vector<Real> new_delta_max;
   UInt nb_understressed{0};
 
   for (auto && pair : facet_nbs_crack_nbs) {
     auto & facet_nb = pair.first;
     auto & crack_nb = pair.second;
     // get facet's local id
     auto local_id = f_filter.find(facet_nb);
     AKANTU_DEBUG_ASSERT(local_id != UInt(-1),
                         "mismatch between global and local facet numbering"
                             << facet_nb);
 
     // mark the insertion of the cohesive element
     f_insertion(facet_nb) = true;
 
     if (check_only)
       continue;
 
     auto & sigma_limit = sigma_limits(local_id);
     Vector<Real> stress_check =
         make_view(scal_tractions, nb_quad_facet).begin()[local_id];
     Matrix<Real> normal_traction =
         make_view(norm_tractions, spatial_dimension, nb_quad_facet)
             .begin()[local_id];
 
     // store the new cohesive material parameters for each quad point
     for (UInt q = 0; q < nb_quad_facet; ++q) {
       UInt current_quad = facet_nb * nb_quad_facet + q;
       Real new_sigma = stress_check(q);
       Real new_del_max{0};
       Vector<Real> normal_traction_vec(normal_traction(q));
       const Vector<Real> & normal = normal_begin[current_quad];
 
       // workaround to insert understressed cohesives
       if (new_sigma < sigma_limit) {
         new_sigma = sigma_limit;
         normal_traction_vec = normal * sigma_limit;
         new_del_max = 2 * this->G_c / new_sigma / 100;
         nb_understressed++;
       }
 
       if (spatial_dimension != 3)
         normal_traction_vec *= -1.;
 
       new_sigmas.push_back(new_sigma);
       new_normal_traction.push_back(normal_traction_vec);
       new_delta_max.push_back(new_del_max);
 
       Real new_delta;
 
       // set delta_c in function of G_c or a given delta_c value
       if (Math::are_float_equal(this->delta_c, 0.))
         new_delta = 2 * this->G_c / new_sigma;
       else
         new_delta = sigma_limit / new_sigma * this->delta_c;
 
       new_delta_c.push_back(new_delta);
     }
 
     // insert a corresponding crack number
     crack_numbers.push_back(crack_nb);
   }
 
   // update material data for the new elements
   UInt old_nb_quad_points = sig_c_eff.size();
   UInt new_nb_quad_points = new_sigmas.size();
   sig_c_eff.resize(old_nb_quad_points + new_nb_quad_points);
   ins_stress.resize(old_nb_quad_points + new_nb_quad_points);
   trac_old.resize(old_nb_quad_points + new_nb_quad_points);
   del_c.resize(old_nb_quad_points + new_nb_quad_points);
   del_max.resize(old_nb_quad_points + new_nb_quad_points);
 
   for (UInt q = 0; q < new_nb_quad_points; ++q) {
     sig_c_eff(old_nb_quad_points + q) = new_sigmas[q];
     del_c(old_nb_quad_points + q) = new_delta_c[q];
     del_max(old_nb_quad_points + q) = new_delta_max[q];
     for (UInt dim = 0; dim < spatial_dimension; ++dim) {
       ins_stress(old_nb_quad_points + q, dim) = new_normal_traction[q](dim);
       trac_old(old_nb_quad_points + q, dim) = new_normal_traction[q](dim);
     }
   }
   AKANTU_DEBUG_OUT();
 }
 
 INSTANTIATE_MATERIAL(cohesive_linear_sequential,
                      MaterialCohesiveLinearSequential);
 
 } // namespace akantu
diff --git a/extra_packages/extra-materials/src/material_cohesive/material_cohesive_linear_sequential.hh b/extra_packages/extra-materials/src/material_cohesive/material_cohesive_linear_sequential.hh
index 1fbb0b83d..3249a21fe 100644
--- a/extra_packages/extra-materials/src/material_cohesive/material_cohesive_linear_sequential.hh
+++ b/extra_packages/extra-materials/src/material_cohesive/material_cohesive_linear_sequential.hh
@@ -1,127 +1,135 @@
 /**
  * @file   material_cohesive_linear.hh
  *
  * @author Mauro Corrado <mauro.corrado@epfl.ch>
  * @author Nicolas Richart <nicolas.richart@epfl.ch>
  * @author Marco Vocialta <marco.vocialta@epfl.ch>
  *
  * @date creation: Fri Jun 18 2010
  * @date last modification: Wed Feb 21 2018
  *
  * @brief  Linear irreversible cohesive law of mixed mode loading with
  * random stress definition for extrinsic type
  *
  * @section LICENSE
  *
  * Copyright (©)  2010-2018 EPFL (Ecole Polytechnique Fédérale de Lausanne)
  * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
  *
  * Akantu 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.
  *
  * Akantu 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 Akantu. If not, see <http://www.gnu.org/licenses/>.
  *
  */
 
 /* ------------------------------------------------------------------ */
 #include "material_cohesive_linear.hh"
 /* ------------------------------------------------------------------ */
 
 #ifndef __AKANTU_MATERIAL_COHESIVE_LINEAR_SEQUENTIAL_HH__
 #define __AKANTU_MATERIAL_COHESIVE_LINEAR_SEQUENTIAL_HH__
 
 namespace akantu {
 
 /**
  * Cohesive material linear damage for extrinsic case
  *
  * parameters in the material files :
  *   - sigma_c   : critical stress sigma_c  (default: 0)
  *   - beta      : weighting parameter for sliding and normal opening (default:
  * 0)
  *   - G_cI      : fracture energy for mode I (default: 0)
  *   - G_cII     : fracture energy for mode II (default: 0)
  *   - penalty   : stiffness in compression to prevent penetration
  */
 template <UInt spatial_dimension>
 class MaterialCohesiveLinearSequential
     : public MaterialCohesiveLinear<spatial_dimension> {
   /* ---------------------------------------------------------------- */
   /* Constructors/Destructors                                         */
   /* ---------------------------------------------------------------- */
 public:
   MaterialCohesiveLinearSequential(SolidMechanicsModel & model,
                                    const ID & id = "");
 
   /* ---------------------------------------------------------------- */
   /* Methods                                                          */
   /* ---------------------------------------------------------------- */
 public:
   /// initialise material
   void initMaterial() override;
 
   /// check stress for cohesive elements' insertion
   void checkInsertion(bool check_only = false) override;
 
   /// identify a facet with the highest tensile stress complying with
   /// topological criterion
   std::tuple<UInt, Real, UInt>
   findCriticalFacet(const ElementType & type_facet);
 
   /// determine the crack contour_subfacet_coh_el, surface_subfacets_crac_nb,
   /// surface nodes and contour_nodes within this specific material
   std::tuple<std::map<Element, Element>, std::map<Element, UInt>,
              std::set<UInt>, std::set<UInt>>
   determineCrackSurface();
 
   /// searches for the "stressed" facets connected to contour segments
   std::map<UInt, UInt> findCriticalFacetsOnContour(
       const std::map<Element, Element> & contour_subfacets_coh_el,
       const std::map<Element, UInt> & surface_subfacets_crack_nb,
       const std::set<UInt> & contour_nodes,
       const std::set<UInt> & surface_nodes);
 
+  /// update effective stresses, scalar and normal tractions on
+  /// all facets of material
+  void computeEffectiveStresses();
+
   /// insert a row of cohesives according to provided numbers
   void insertCohesiveElements(std::map<UInt, UInt> & facet_nbs_crack_nbs,
                               ElementType facet_type, bool check_only);
 
   /// insert facets having 2 or more cohesive neighbors
   std::map<UInt, UInt>
   findHolesOnContour(std::map<Element, Element> & contour_subfacets_coh_el,
                      const std::map<Element, UInt> & surface_subfacets_crack_nb,
                      const std::set<UInt> & surface_nodes);
 
 protected:
   /* ---------------------------------------------------------------- */
   /* Accessors                                                        */
   /* ---------------------------------------------------------------- */
+public:
+  /// get the effective stresses
+  AKANTU_GET_MACRO_BY_ELEMENT_TYPE_CONST(EffectiveStress, effective_stresses,
+                                         Real);
 
   /* ---------------------------------------------------------------- */
   /* Class Members                                                    */
   /* ---------------------------------------------------------------- */
 protected:
   /// scalar tractions = combination of normal and tangential norms
   FacetInternalField<Real> scalar_tractions;
 
   /// traction acting on a facet plane
   FacetInternalField<Real> normal_tractions;
 
   /// normal stresses normalized by the stress limit
   FacetInternalField<Real> effective_stresses;
 };
 
 /* ------------------------------------------------------------------ */
 /* inline functions                                                   */
 /* ------------------------------------------------------------------ */
 
 } // namespace akantu
 
 #endif /* __AKANTU_MATERIAL_COHESIVE_LINEAR_SEQUENTIAL_HH__ */
diff --git a/src/mesh/mesh.hh b/src/mesh/mesh.hh
index 0e882f7ca..c5427a463 100644
--- a/src/mesh/mesh.hh
+++ b/src/mesh/mesh.hh
@@ -1,697 +1,701 @@
 /**
  * @file   mesh.hh
  *
  * @author Guillaume Anciaux <guillaume.anciaux@epfl.ch>
  * @author Dana Christen <dana.christen@epfl.ch>
  * @author David Simon Kammer <david.kammer@epfl.ch>
  * @author Nicolas Richart <nicolas.richart@epfl.ch>
  * @author Marco Vocialta <marco.vocialta@epfl.ch>
  *
  * @date creation: Fri Jun 18 2010
  * @date last modification: Mon Feb 19 2018
  *
  * @brief  the class representing the meshes
  *
  *
  * Copyright (©)  2010-2018 EPFL (Ecole Polytechnique Fédérale de Lausanne)
  * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
  *
  * Akantu 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.
  *
  * Akantu 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 Akantu. If not, see <http://www.gnu.org/licenses/>.
  *
  */
 
 /* -------------------------------------------------------------------------- */
 #ifndef AKANTU_MESH_HH_
 #define AKANTU_MESH_HH_
 
 /* -------------------------------------------------------------------------- */
 #include "aka_array.hh"
 #include "aka_bbox.hh"
 #include "aka_event_handler_manager.hh"
 #include "aka_memory.hh"
 #include "communicator.hh"
 #include "dumpable.hh"
 #include "element.hh"
 #include "element_class.hh"
 #include "element_type_map.hh"
 #include "group_manager.hh"
 #include "mesh_data.hh"
 #include "mesh_events.hh"
 /* -------------------------------------------------------------------------- */
 #include <functional>
 #include <set>
 #include <unordered_map>
 /* -------------------------------------------------------------------------- */
 
 namespace akantu {
 class ElementSynchronizer;
 class NodeSynchronizer;
 class PeriodicNodeSynchronizer;
 class MeshGlobalDataUpdater;
 } // namespace akantu
 
 namespace akantu {
 
 namespace {
   DECLARE_NAMED_ARGUMENT(communicator);
   DECLARE_NAMED_ARGUMENT(edge_weight_function);
   DECLARE_NAMED_ARGUMENT(vertex_weight_function);
 } // namespace
 
 /* -------------------------------------------------------------------------- */
 /* Mesh                                                                       */
 /* -------------------------------------------------------------------------- */
 
 /**
  * @class  Mesh mesh.hh
  *
  * This class contaisn the coordinates of the nodes in the Mesh.nodes
  * akant::Array, and the connectivity. The connectivity are stored in by element
  * types.
  *
  * In order to loop on all element you have to loop on all types like this :
  * @code{.cpp}
  for(auto & type : mesh.elementTypes()) {
    UInt nb_element  = mesh.getNbElement(type);
    const Array<UInt> & conn = mesh.getConnectivity(type);
    for(UInt e = 0; e < nb_element; ++e) {
      ...
    }
  }
 
  or
 
  for_each_element(mesh, [](Element & element) {
     std::cout << element << std::endl
   });
  @endcode
 */
 class Mesh : protected Memory,
              public EventHandlerManager<MeshEventHandler>,
              public GroupManager,
              public MeshData,
              public Dumpable {
   /* ------------------------------------------------------------------------ */
   /* Constructors/Destructors                                                 */
   /* ------------------------------------------------------------------------ */
 private:
   /// default constructor used for chaining, the last parameter is just to
   /// differentiate constructors
   Mesh(UInt spatial_dimension, const ID & id, const MemoryID & memory_id,
        Communicator & communicator);
 
 public:
   /// constructor that create nodes coordinates array
   Mesh(UInt spatial_dimension, const ID & id = "mesh",
        const MemoryID & memory_id = 0);
 
   /// mesh not distributed and not using the default communicator
   Mesh(UInt spatial_dimension, Communicator & communicator,
        const ID & id = "mesh", const MemoryID & memory_id = 0);
 
   /**
    * constructor that use an existing nodes coordinates
    * array, by getting the vector of coordinates
    */
   Mesh(UInt spatial_dimension, const std::shared_ptr<Array<Real>> & nodes,
        const ID & id = "mesh", const MemoryID & memory_id = 0);
 
   ~Mesh() override;
 
   /// read the mesh from a file
   void read(const std::string & filename,
             const MeshIOType & mesh_io_type = _miot_auto);
   /// write the mesh to a file
   void write(const std::string & filename,
              const MeshIOType & mesh_io_type = _miot_auto);
 
 protected:
   void makeReady();
 
 private:
   /// initialize the connectivity to NULL and other stuff
   void init();
 
   /// function that computes the bounding box (fills xmin, xmax)
   void computeBoundingBox();
 
   /* ------------------------------------------------------------------------ */
   /* Distributed memory methods and accessors                                 */
   /* ------------------------------------------------------------------------ */
 public:
 protected:
   /// patitionate the mesh among the processors involved in their computation
   virtual void distributeImpl(
       Communicator & communicator,
       const std::function<Int(const Element &, const Element &)> &
           edge_weight_function,
       const std::function<Int(const Element &)> & vertex_weight_function);
 
 public:
   /// with the arguments to pass to the partitionner
   template <typename... pack>
   std::enable_if_t<are_named_argument<pack...>::value>
   distribute(pack &&... _pack) {
     distributeImpl(
         OPTIONAL_NAMED_ARG(communicator, Communicator::getWorldCommunicator()),
         OPTIONAL_NAMED_ARG(edge_weight_function,
                            [](auto &&, auto &&) { return 1; }),
         OPTIONAL_NAMED_ARG(vertex_weight_function, [](auto &&) { return 1; }));
   }
 
   /// defines is the mesh is distributed or not
   inline bool isDistributed() const { return this->is_distributed; }
 
   /* ------------------------------------------------------------------------ */
   /* Periodicity methods and accessors                                        */
   /* ------------------------------------------------------------------------ */
 public:
   /// set the periodicity in a given direction
   void makePeriodic(const SpatialDirection & direction);
   void makePeriodic(const SpatialDirection & direction, const ID & list_1,
                     const ID & list_2);
 
 protected:
   void makePeriodic(const SpatialDirection & direction,
                     const Array<UInt> & list_left,
                     const Array<UInt> & list_right);
 
   /// Removes the face that the mesh is periodic
   void wipePeriodicInfo();
 
   inline void addPeriodicSlave(UInt slave, UInt master);
 
   template <typename T>
   void synchronizePeriodicSlaveDataWithMaster(Array<T> & data);
 
   // update the periodic synchronizer (creates it if it does not exists)
   void updatePeriodicSynchronizer();
 
 public:
   /// defines if the mesh is periodic or not
   inline bool isPeriodic() const { return this->is_periodic; }
 
   inline bool isPeriodic(const SpatialDirection & /*direction*/) const {
     return this->is_periodic;
   }
 
   class PeriodicSlaves;
 
   /// get the master node for a given slave nodes, except if node not a slave
   inline UInt getPeriodicMaster(UInt slave) const;
 
   /// get an iterable list of slaves for a given master node
   inline decltype(auto) getPeriodicSlaves(UInt master) const;
 
   /* ------------------------------------------------------------------------ */
   /* General Methods                                                          */
   /* ------------------------------------------------------------------------ */
 public:
   /// function to print the containt of the class
   void printself(std::ostream & stream, int indent = 0) const override;
 
   /// extract coordinates of nodes from an element
   template <typename T>
   inline void
   extractNodalValuesFromElement(const Array<T> & nodal_values, T * local_coord,
                                 const UInt * connectivity, UInt n_nodes,
                                 UInt nb_degree_of_freedom) const;
 
   // /// extract coordinates of nodes from a reversed element
   // inline void extractNodalCoordinatesFromPBCElement(Real * local_coords,
   //                                                   UInt * connectivity,
   //                                                   UInt n_nodes);
 
   /// add a Array of connectivity for the given ElementType and GhostType .
   inline void addConnectivityType(ElementType type,
                                   GhostType ghost_type = _not_ghost);
 
   /* ------------------------------------------------------------------------ */
   template <class Event> inline void sendEvent(Event & event) {
     //    if(event.getList().size() != 0)
     EventHandlerManager<MeshEventHandler>::sendEvent<Event>(event);
   }
 
   /// prepare the  event to remove the elements listed
   void eraseElements(const Array<Element> & elements);
 
   /* ------------------------------------------------------------------------ */
   template <typename T>
   inline void removeNodesFromArray(Array<T> & vect,
                                    const Array<UInt> & new_numbering);
 
   /// initialize normals
   void initNormals();
 
   /// init facets' mesh
   Mesh & initMeshFacets(const ID & id = "mesh_facets");
 
   /// define parent mesh
   void defineMeshParent(const Mesh & mesh);
 
   /// get global connectivity array
   void getGlobalConnectivity(ElementTypeMapArray<UInt> & global_connectivity);
 
 public:
   void getAssociatedElements(const Array<UInt> & node_list,
                              Array<Element> & elements);
 
 private:
   /// fills the nodes_to_elements structure
   void fillNodesToElements();
 
   /// update the global ids, nodes type, ...
   std::tuple<UInt, UInt> updateGlobalData(NewNodesEvent & nodes_event,
                                           NewElementsEvent & elements_event);
 
   void registerGlobalDataUpdater(
       std::unique_ptr<MeshGlobalDataUpdater> && global_data_updater);
   /* ------------------------------------------------------------------------ */
   /* Accessors                                                                */
   /* ------------------------------------------------------------------------ */
 public:
   /// get the id of the mesh
   AKANTU_GET_MACRO(ID, Memory::id, const ID &);
 
   /// get the id of the mesh
   AKANTU_GET_MACRO(MemoryID, Memory::memory_id, const MemoryID &);
 
   /// get the spatial dimension of the mesh = number of component of the
   /// coordinates
   AKANTU_GET_MACRO(SpatialDimension, spatial_dimension, UInt);
 
   /// get the nodes Array aka coordinates
   AKANTU_GET_MACRO(Nodes, *nodes, const Array<Real> &);
   AKANTU_GET_MACRO_NOT_CONST(Nodes, *nodes, Array<Real> &);
 
   /// get the normals for the elements
   AKANTU_GET_MACRO_BY_ELEMENT_TYPE(Normals, normals, Real);
 
   /// get the number of nodes
   AKANTU_GET_MACRO(NbNodes, nodes->size(), UInt);
 
   /// get the Array of global ids of the nodes (only used in parallel)
   AKANTU_GET_MACRO(GlobalNodesIds, *nodes_global_ids, const Array<UInt> &);
   // AKANTU_GET_MACRO_NOT_CONST(GlobalNodesIds, *nodes_global_ids, Array<UInt>
   // &);
 
   /// get the global id of a node
   inline UInt getNodeGlobalId(UInt local_id) const;
 
   /// get the global id of a node
   inline UInt getNodeLocalId(UInt global_id) const;
 
   /// get the global number of nodes
   inline UInt getNbGlobalNodes() const;
 
   /// get the nodes type Array
   AKANTU_GET_MACRO(NodesFlags, *nodes_flags, const Array<NodeFlag> &);
 
 protected:
   AKANTU_GET_MACRO_NOT_CONST(NodesFlags, *nodes_flags, Array<NodeFlag> &);
 
 public:
   inline NodeFlag getNodeFlag(UInt local_id) const;
   inline Int getNodePrank(UInt local_id) const;
 
   /// say if a node is a pure ghost node
   inline bool isPureGhostNode(UInt n) const;
 
   /// say if a node is pur local or master node
   inline bool isLocalOrMasterNode(UInt n) const;
 
   inline bool isLocalNode(UInt n) const;
   inline bool isMasterNode(UInt n) const;
   inline bool isSlaveNode(UInt n) const;
 
   inline bool isPeriodicSlave(UInt n) const;
   inline bool isPeriodicMaster(UInt n) const;
 
   const Vector<Real> & getLowerBounds() const { return bbox.getLowerBounds(); }
   const Vector<Real> & getUpperBounds() const { return bbox.getUpperBounds(); }
   AKANTU_GET_MACRO(BBox, bbox, const BBox &);
 
   const Vector<Real> & getLocalLowerBounds() const {
     return bbox_local.getLowerBounds();
   }
   const Vector<Real> & getLocalUpperBounds() const {
     return bbox_local.getUpperBounds();
   }
   AKANTU_GET_MACRO(LocalBBox, bbox_local, const BBox &);
 
   /// get the connectivity Array for a given type
   AKANTU_GET_MACRO_BY_ELEMENT_TYPE_CONST(Connectivity, connectivities, UInt);
   AKANTU_GET_MACRO_BY_ELEMENT_TYPE(Connectivity, connectivities, UInt);
   AKANTU_GET_MACRO(Connectivities, connectivities,
                    const ElementTypeMapArray<UInt> &);
 
   /// get the number of element of a type in the mesh
   inline UInt getNbElement(ElementType type,
                            GhostType ghost_type = _not_ghost) const;
 
   /// get the number of element for a given ghost_type and a given dimension
   inline UInt getNbElement(UInt spatial_dimension = _all_dimensions,
                            GhostType ghost_type = _not_ghost,
                            ElementKind kind = _ek_not_defined) const;
 
   /// compute the barycenter of a given element
   inline void getBarycenter(const Element & element,
                             Vector<Real> & barycenter) const;
 
   void getBarycenters(Array<Real> & barycenter, ElementType type,
                       GhostType ghost_type) const;
 
+  /// compute the center of an inscribed circle of a triangular element
+  inline void getIncenter(const Element & element,
+                          Vector<Real> & incenter) const;
+
   /// get the element connected to a subelement (element of lower dimension)
   const auto & getElementToSubelement() const;
 
   /// get the element connected to a subelement
   const auto & getElementToSubelement(ElementType el_type,
                                       GhostType ghost_type = _not_ghost) const;
 
   /// get the elements connected to a subelement
   const auto & getElementToSubelement(const Element & element) const;
 
   /// get the subelement (element of lower dimension) connected to a element
   const auto & getSubelementToElement() const;
 
   /// get the subelement connected to an element
   const auto &
   getSubelementToElement(ElementType el_type,
                          GhostType ghost_type = _not_ghost) const;
 
   /// get the subelement (element of lower dimension) connected to a element
   VectorProxy<Element> getSubelementToElement(const Element & element) const;
 
   /// get connectivity of a given element
   inline VectorProxy<UInt> getConnectivity(const Element & element) const;
   inline Vector<UInt>
   getConnectivityWithPeriodicity(const Element & element) const;
 
 protected:
   /// get the element connected to a subelement (element of lower dimension)
   auto & getElementToSubelementNC();
   auto & getSubelementToElementNC();
   inline auto & getElementToSubelementNC(const Element & element);
   inline VectorProxy<Element> getSubelementToElementNC(const Element & element);
   /// get the element connected to a subelement
   auto & getElementToSubelementNC(ElementType el_type,
                                   GhostType ghost_type = _not_ghost);
   /// get the subelement connected to an element
   auto & getSubelementToElementNC(ElementType el_type,
                                   GhostType ghost_type = _not_ghost);
 
   inline VectorProxy<UInt> getConnectivityNC(const Element & element);
 
 public:
   /// get a name field associated to the mesh
   template <typename T>
   inline const Array<T> & getData(const ID & data_name, ElementType el_type,
                                   GhostType ghost_type = _not_ghost) const;
 
   /// get a name field associated to the mesh
   template <typename T>
   inline Array<T> & getData(const ID & data_name, ElementType el_type,
                             GhostType ghost_type = _not_ghost);
 
   /// get a name field associated to the mesh
   template <typename T>
   inline const ElementTypeMapArray<T> & getData(const ID & data_name) const;
 
   /// get a name field associated to the mesh
   template <typename T>
   inline ElementTypeMapArray<T> & getData(const ID & data_name);
 
   template <typename T>
   ElementTypeMap<UInt> getNbDataPerElem(ElementTypeMapArray<T> & array);
 
   template <typename T>
   std::shared_ptr<dumpers::Field>
   createFieldFromAttachedData(const std::string & field_id,
                               const std::string & group_name,
                               ElementKind element_kind);
 
   /// templated getter returning the pointer to data in MeshData (modifiable)
   template <typename T>
   inline Array<T> &
   getDataPointer(const std::string & data_name, ElementType el_type,
                  GhostType ghost_type = _not_ghost, UInt nb_component = 1,
                  bool size_to_nb_element = true,
                  bool resize_with_parent = false);
 
   template <typename T>
   inline Array<T> & getDataPointer(const ID & data_name, ElementType el_type,
                                    GhostType ghost_type, UInt nb_component,
                                    bool size_to_nb_element,
                                    bool resize_with_parent, const T & defaul_);
 
   /// Facets mesh accessor
   inline bool hasMeshFacets();
   inline const Mesh & getMeshFacets() const;
   inline Mesh & getMeshFacets();
 
   inline auto hasMeshFacets() const { return mesh_facets != nullptr; }
 
   /// Parent mesh accessor
   inline const Mesh & getMeshParent() const;
 
   inline bool isMeshFacets() const { return this->is_mesh_facets; }
 
   /// return the dumper from a group and and a dumper name
   DumperIOHelper & getGroupDumper(const std::string & dumper_name,
                                   const std::string & group_name);
 
   /* ------------------------------------------------------------------------ */
   /* Wrappers on ElementClass functions                                       */
   /* ------------------------------------------------------------------------ */
 public:
   /// get the number of nodes per element for a given element type
   static inline UInt getNbNodesPerElement(ElementType type);
 
   /// get the number of nodes per element for a given element type considered as
   /// a first order element
   static inline ElementType getP1ElementType(ElementType type);
 
   /// get the kind of the element type
   static inline ElementKind getKind(ElementType type);
 
   /// get spatial dimension of a type of element
   static inline UInt getSpatialDimension(ElementType type);
 
   /// get number of facets of a given element type
   static inline UInt getNbFacetsPerElement(ElementType type);
 
   /// get number of facets of a given element type
   static inline UInt getNbFacetsPerElement(ElementType type, UInt t);
 
   /// get local connectivity of a facet for a given facet type
   static inline auto getFacetLocalConnectivity(ElementType type, UInt t = 0);
 
   /// get connectivity of facets for a given element
   inline auto getFacetConnectivity(const Element & element, UInt t = 0) const;
 
   /// get the number of type of the surface element associated to a given
   /// element type
   static inline UInt getNbFacetTypes(ElementType type, UInt t = 0);
 
   /// get the type of the surface element associated to a given element
   static inline constexpr auto getFacetType(ElementType type, UInt t = 0);
 
   /// get all the type of the surface element associated to a given element
   static inline constexpr auto getAllFacetTypes(ElementType type);
 
   /// get the number of nodes in the given element list
   static inline UInt getNbNodesPerElementList(const Array<Element> & elements);
 
   /* ------------------------------------------------------------------------ */
   /* Element type Iterator                                                    */
   /* ------------------------------------------------------------------------ */
 
   using type_iterator [[deprecated]] =
       ElementTypeMapArray<UInt, ElementType>::type_iterator;
   using ElementTypesIteratorHelper =
       ElementTypeMapArray<UInt, ElementType>::ElementTypesIteratorHelper;
 
   template <typename... pack>
   ElementTypesIteratorHelper elementTypes(pack &&... _pack) const;
 
   [[deprecated("Use elementTypes instead")]] inline decltype(auto)
   firstType(UInt dim = _all_dimensions, GhostType ghost_type = _not_ghost,
             ElementKind kind = _ek_regular) const {
     return connectivities.elementTypes(dim, ghost_type, kind).begin();
   }
 
   [[deprecated("Use elementTypes instead")]] inline decltype(auto)
   lastType(UInt dim = _all_dimensions, GhostType ghost_type = _not_ghost,
            ElementKind kind = _ek_regular) const {
     return connectivities.elementTypes(dim, ghost_type, kind).end();
   }
 
   AKANTU_GET_MACRO(ElementSynchronizer, *element_synchronizer,
                    const ElementSynchronizer &);
   AKANTU_GET_MACRO_NOT_CONST(ElementSynchronizer, *element_synchronizer,
                              ElementSynchronizer &);
   AKANTU_GET_MACRO(NodeSynchronizer, *node_synchronizer,
                    const NodeSynchronizer &);
   AKANTU_GET_MACRO_NOT_CONST(NodeSynchronizer, *node_synchronizer,
                              NodeSynchronizer &);
   AKANTU_GET_MACRO(PeriodicNodeSynchronizer, *periodic_node_synchronizer,
                    const PeriodicNodeSynchronizer &);
   AKANTU_GET_MACRO_NOT_CONST(PeriodicNodeSynchronizer,
                              *periodic_node_synchronizer,
                              PeriodicNodeSynchronizer &);
 
   // AKANTU_GET_MACRO_NOT_CONST(Communicator, *communicator, StaticCommunicator
   // &);
   AKANTU_GET_MACRO(Communicator, *communicator, const auto &);
   AKANTU_GET_MACRO_NOT_CONST(Communicator, *communicator, auto &);
   AKANTU_GET_MACRO(PeriodicMasterSlaves, periodic_master_slave, const auto &);
 
   /* ------------------------------------------------------------------------ */
   /* Private methods for friends                                              */
   /* ------------------------------------------------------------------------ */
 private:
   friend class MeshAccessor;
   friend class MeshUtils;
 
   AKANTU_GET_MACRO(NodesPointer, *nodes, Array<Real> &);
 
   /// get a pointer to the nodes_global_ids Array<UInt> and create it if
   /// necessary
   inline Array<UInt> & getNodesGlobalIdsPointer();
 
   /// get a pointer to the nodes_type Array<Int> and create it if necessary
   inline Array<NodeFlag> & getNodesFlagsPointer();
 
   /// get a pointer to the connectivity Array for the given type and create it
   /// if necessary
   inline Array<UInt> &
   getConnectivityPointer(ElementType type, GhostType ghost_type = _not_ghost);
 
   /// get the ghost element counter
   inline Array<UInt> & getGhostsCounters(ElementType type,
                                          GhostType ghost_type = _ghost) {
     AKANTU_DEBUG_ASSERT(ghost_type != _not_ghost,
                         "No ghost counter for _not_ghost elements");
     return ghosts_counters(type, ghost_type);
   }
 
   /// get a pointer to the element_to_subelement Array for the given type and
   /// create it if necessary
   inline Array<std::vector<Element>> &
   getElementToSubelementPointer(ElementType type,
                                 GhostType ghost_type = _not_ghost);
 
   /// get a pointer to the subelement_to_element Array for the given type and
   /// create it if necessary
   inline Array<Element> &
   getSubelementToElementPointer(ElementType type,
                                 GhostType ghost_type = _not_ghost);
 
   /* ------------------------------------------------------------------------ */
   /* Class Members                                                            */
   /* ------------------------------------------------------------------------ */
 private:
   /// array of the nodes coordinates
   std::shared_ptr<Array<Real>> nodes;
 
   /// global node ids
   std::shared_ptr<Array<UInt>> nodes_global_ids;
 
   /// node flags (shared/periodic/...)
   std::shared_ptr<Array<NodeFlag>> nodes_flags;
 
   /// processor handling the node when not local or master
   std::unordered_map<UInt, Int> nodes_prank;
 
   /// global number of nodes;
   UInt nb_global_nodes{0};
 
   /// all class of elements present in this mesh (for heterogenous meshes)
   ElementTypeMapArray<UInt> connectivities;
 
   /// count the references on ghost elements
   ElementTypeMapArray<UInt> ghosts_counters;
 
   /// map to normals for all class of elements present in this mesh
   ElementTypeMapArray<Real> normals;
 
   /// the spatial dimension of this mesh
   UInt spatial_dimension{0};
 
   /// size covered by the mesh on each direction
   Vector<Real> size;
   /// global bounding box
   BBox bbox;
 
   /// local bounding box
   BBox bbox_local;
 
   /// Extra data loaded from the mesh file
   // MeshData mesh_data;
 
   /// facets' mesh
   std::unique_ptr<Mesh> mesh_facets;
 
   /// parent mesh (this is set for mesh_facets meshes)
   const Mesh * mesh_parent{nullptr};
 
   /// defines if current mesh is mesh_facets or not
   bool is_mesh_facets{false};
 
   /// defines if the mesh is centralized or distributed
   bool is_distributed{false};
 
   /// defines if the mesh is periodic
   bool is_periodic{false};
 
   /// Communicator on which mesh is distributed
   Communicator * communicator;
 
   /// Element synchronizer
   std::unique_ptr<ElementSynchronizer> element_synchronizer;
 
   /// Node synchronizer
   std::unique_ptr<NodeSynchronizer> node_synchronizer;
 
   /// Node synchronizer for periodic nodes
   std::unique_ptr<PeriodicNodeSynchronizer> periodic_node_synchronizer;
 
   using NodesToElements = std::vector<std::unique_ptr<std::set<Element>>>;
 
   /// class to update global data using external knowledge
   std::unique_ptr<MeshGlobalDataUpdater> global_data_updater;
 
   /// This info is stored to simplify the dynamic changes
   NodesToElements nodes_to_elements;
 
   /// periodicity local info
   std::unordered_map<UInt, UInt> periodic_slave_master;
   std::unordered_multimap<UInt, UInt> periodic_master_slave;
 };
 
 /// standard output stream operator
 inline std::ostream & operator<<(std::ostream & stream, const Mesh & _this) {
   _this.printself(stream);
   return stream;
 }
 
 } // namespace akantu
 
 /* -------------------------------------------------------------------------- */
 /* Inline functions                                                           */
 /* -------------------------------------------------------------------------- */
 #include "element_type_map_tmpl.hh"
 #include "mesh_inline_impl.hh"
 
 #endif /* AKANTU_MESH_HH_ */
diff --git a/src/mesh/mesh_inline_impl.hh b/src/mesh/mesh_inline_impl.hh
index d2103c799..a55584aa1 100644
--- a/src/mesh/mesh_inline_impl.hh
+++ b/src/mesh/mesh_inline_impl.hh
@@ -1,768 +1,802 @@
 /**
  * @file   mesh_inline_impl.hh
  *
  * @author Guillaume Anciaux <guillaume.anciaux@epfl.ch>
  * @author Dana Christen <dana.christen@epfl.ch>
  * @author Nicolas Richart <nicolas.richart@epfl.ch>
  * @author Marco Vocialta <marco.vocialta@epfl.ch>
  *
  * @date creation: Thu Jul 15 2010
  * @date last modification: Mon Dec 18 2017
  *
  * @brief  Implementation of the inline functions of the mesh class
  *
  *
  * Copyright (©)  2010-2018 EPFL (Ecole Polytechnique Fédérale de Lausanne)
  * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
  *
  * Akantu 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.
  *
  * Akantu 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 Akantu. If not, see <http://www.gnu.org/licenses/>.
  *
  */
 
 /* -------------------------------------------------------------------------- */
 #include "aka_iterators.hh"
 #include "element_class.hh"
 #include "mesh.hh"
 /* -------------------------------------------------------------------------- */
 
 #ifndef AKANTU_MESH_INLINE_IMPL_HH_
 #define AKANTU_MESH_INLINE_IMPL_HH_
 
 namespace akantu {
 
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 inline ElementKind Element::kind() const { return Mesh::getKind(type); }
 
 /* -------------------------------------------------------------------------- */
 /* -------------------------------------------------------------------------- */
 template <typename... pack>
 Mesh::ElementTypesIteratorHelper Mesh::elementTypes(pack &&... _pack) const {
   return connectivities.elementTypes(_pack...);
 }
 
 /* -------------------------------------------------------------------------- */
 inline RemovedNodesEvent::RemovedNodesEvent(const Mesh & mesh,
                                             const std::string & origin)
     : MeshEvent<UInt>(origin),
       new_numbering(mesh.getNbNodes(), 1, "new_numbering") {}
 
 /* -------------------------------------------------------------------------- */
 inline RemovedElementsEvent::RemovedElementsEvent(const Mesh & mesh,
                                                   const ID & new_numbering_id,
                                                   const std::string & origin)
     : MeshEvent<Element>(origin),
       new_numbering(new_numbering_id, mesh.getID(), mesh.getMemoryID()) {}
 
 /* -------------------------------------------------------------------------- */
 template <>
 inline void Mesh::sendEvent<NewElementsEvent>(NewElementsEvent & event) {
   this->nodes_to_elements.resize(nodes->size());
   for (const auto & elem : event.getList()) {
     const Array<UInt> & conn = connectivities(elem.type, elem.ghost_type);
 
     UInt nb_nodes_per_elem = Mesh::getNbNodesPerElement(elem.type);
 
     for (UInt n = 0; n < nb_nodes_per_elem; ++n) {
       UInt node = conn(elem.element, n);
       if (not nodes_to_elements[node]) {
         nodes_to_elements[node] = std::make_unique<std::set<Element>>();
       }
       nodes_to_elements[node]->insert(elem);
     }
   }
 
   EventHandlerManager<MeshEventHandler>::sendEvent(event);
 }
 
 /* -------------------------------------------------------------------------- */
 template <> inline void Mesh::sendEvent<NewNodesEvent>(NewNodesEvent & event) {
   this->computeBoundingBox();
   this->nodes_flags->resize(this->nodes->size(), NodeFlag::_normal);
   EventHandlerManager<MeshEventHandler>::sendEvent(event);
 }
 
 /* -------------------------------------------------------------------------- */
 template <>
 inline void
 Mesh::sendEvent<RemovedElementsEvent>(RemovedElementsEvent & event) {
   this->connectivities.onElementsRemoved(event.getNewNumbering());
   this->fillNodesToElements();
   this->computeBoundingBox();
 
   EventHandlerManager<MeshEventHandler>::sendEvent(event);
 }
 
 /* -------------------------------------------------------------------------- */
 template <>
 inline void Mesh::sendEvent<RemovedNodesEvent>(RemovedNodesEvent & event) {
   const auto & new_numbering = event.getNewNumbering();
   this->removeNodesFromArray(*nodes, new_numbering);
   if (nodes_global_ids and not is_mesh_facets) {
     this->removeNodesFromArray(*nodes_global_ids, new_numbering);
   }
   if (not is_mesh_facets) {
     this->removeNodesFromArray(*nodes_flags, new_numbering);
   }
 
   if (not nodes_to_elements.empty()) {
     std::vector<std::unique_ptr<std::set<Element>>> tmp(
         nodes_to_elements.size());
     auto it = nodes_to_elements.begin();
 
     UInt new_nb_nodes = 0;
     for (auto new_i : new_numbering) {
       if (new_i != UInt(-1)) {
         tmp[new_i] = std::move(*it);
         ++new_nb_nodes;
       }
       ++it;
     }
 
     tmp.resize(new_nb_nodes);
     std::move(tmp.begin(), tmp.end(), nodes_to_elements.begin());
   }
 
   computeBoundingBox();
 
   EventHandlerManager<MeshEventHandler>::sendEvent(event);
 }
 
 /* -------------------------------------------------------------------------- */
 template <typename T>
 inline void Mesh::removeNodesFromArray(Array<T> & vect,
                                        const Array<UInt> & new_numbering) {
   Array<T> tmp(vect.size(), vect.getNbComponent());
   UInt nb_component = vect.getNbComponent();
   UInt new_nb_nodes = 0;
   for (UInt i = 0; i < new_numbering.size(); ++i) {
     UInt new_i = new_numbering(i);
     if (new_i != UInt(-1)) {
       T * to_copy = vect.storage() + i * nb_component;
       std::uninitialized_copy(to_copy, to_copy + nb_component,
                               tmp.storage() + new_i * nb_component);
       ++new_nb_nodes;
     }
   }
 
   tmp.resize(new_nb_nodes);
   vect.copy(tmp);
 }
 
 /* -------------------------------------------------------------------------- */
 inline Array<UInt> & Mesh::getNodesGlobalIdsPointer() {
   AKANTU_DEBUG_IN();
   if (not nodes_global_ids) {
     nodes_global_ids = std::make_shared<Array<UInt>>(
         nodes->size(), 1, getID() + ":nodes_global_ids");
 
     for (auto && global_ids : enumerate(*nodes_global_ids)) {
       std::get<1>(global_ids) = std::get<0>(global_ids);
     }
   }
 
   AKANTU_DEBUG_OUT();
   return *nodes_global_ids;
 }
 
 /* -------------------------------------------------------------------------- */
 inline Array<UInt> & Mesh::getConnectivityPointer(ElementType type,
                                                   GhostType ghost_type) {
   if (connectivities.exists(type, ghost_type)) {
     return connectivities(type, ghost_type);
   }
 
   if (ghost_type != _not_ghost) {
     ghosts_counters.alloc(0, 1, type, ghost_type, 1);
   }
 
   AKANTU_DEBUG_INFO("The connectivity vector for the type " << type
                                                             << " created");
 
   UInt nb_nodes_per_element = Mesh::getNbNodesPerElement(type);
   return connectivities.alloc(0, nb_nodes_per_element, type, ghost_type);
 }
 
 /* -------------------------------------------------------------------------- */
 inline Array<std::vector<Element>> &
 Mesh::getElementToSubelementPointer(ElementType type, GhostType ghost_type) {
   return getDataPointer<std::vector<Element>>("element_to_subelement", type,
                                               ghost_type, 1, true);
 }
 
 /* -------------------------------------------------------------------------- */
 inline Array<Element> &
 Mesh::getSubelementToElementPointer(ElementType type, GhostType ghost_type) {
   auto & array = getDataPointer<Element>(
       "subelement_to_element", type, ghost_type, getNbFacetsPerElement(type),
       false, is_mesh_facets, ElementNull);
   return array;
 }
 
 /* -------------------------------------------------------------------------- */
 inline const auto & Mesh::getElementToSubelement() const {
   return getData<std::vector<Element>>("element_to_subelement");
 }
 
 /* -------------------------------------------------------------------------- */
 inline auto & Mesh::getElementToSubelementNC() {
   return getData<std::vector<Element>>("element_to_subelement");
 }
 
 /* -------------------------------------------------------------------------- */
 inline const auto & Mesh::getElementToSubelement(ElementType type,
                                                  GhostType ghost_type) const {
   return getData<std::vector<Element>>("element_to_subelement", type,
                                        ghost_type);
 }
 
 /* -------------------------------------------------------------------------- */
 inline auto & Mesh::getElementToSubelementNC(ElementType type,
                                              GhostType ghost_type) {
   return getData<std::vector<Element>>("element_to_subelement", type,
                                        ghost_type);
 }
 
 /* -------------------------------------------------------------------------- */
 inline const auto &
 Mesh::getElementToSubelement(const Element & element) const {
   return getData<std::vector<Element>>("element_to_subelement")(element, 0);
 }
 
 /* -------------------------------------------------------------------------- */
 inline auto & Mesh::getElementToSubelementNC(const Element & element) {
   return getData<std::vector<Element>>("element_to_subelement")(element, 0);
 }
 
 /* -------------------------------------------------------------------------- */
 inline const auto & Mesh::getSubelementToElement() const {
   return getData<Element>("subelement_to_element");
 }
 
 /* -------------------------------------------------------------------------- */
 inline auto & Mesh::getSubelementToElementNC() {
   return getData<Element>("subelement_to_element");
 }
 
 /* -------------------------------------------------------------------------- */
 inline const auto & Mesh::getSubelementToElement(ElementType type,
                                                  GhostType ghost_type) const {
   return getData<Element>("subelement_to_element", type, ghost_type);
 }
 
 /* -------------------------------------------------------------------------- */
 inline auto & Mesh::getSubelementToElementNC(ElementType type,
                                              GhostType ghost_type) {
   return getData<Element>("subelement_to_element", type, ghost_type);
 }
 
 /* -------------------------------------------------------------------------- */
 inline VectorProxy<Element>
 Mesh::getSubelementToElement(const Element & element) const {
   return this->getSubelementToElement().get(element);
 }
 
 /* -------------------------------------------------------------------------- */
 inline VectorProxy<Element>
 Mesh::getSubelementToElementNC(const Element & element) {
   return this->getSubelementToElement().get(element);
 }
 
 /* -------------------------------------------------------------------------- */
 template <typename T>
 inline Array<T> &
 Mesh::getDataPointer(const ID & data_name, ElementType el_type,
                      GhostType ghost_type, UInt nb_component,
                      bool size_to_nb_element, bool resize_with_parent) {
   Array<T> & tmp = this->getElementalDataArrayAlloc<T>(
       data_name, el_type, ghost_type, nb_component);
 
   if (size_to_nb_element) {
     if (resize_with_parent) {
       tmp.resize(mesh_parent->getNbElement(el_type, ghost_type));
     } else {
       tmp.resize(this->getNbElement(el_type, ghost_type));
     }
   }
 
   return tmp;
 }
 
 /* -------------------------------------------------------------------------- */
 template <typename T>
 inline Array<T> &
 Mesh::getDataPointer(const ID & data_name, ElementType el_type,
                      GhostType ghost_type, UInt nb_component,
                      bool size_to_nb_element, bool resize_with_parent,
                      const T & defaul_) {
   Array<T> & tmp = this->getElementalDataArrayAlloc<T>(
       data_name, el_type, ghost_type, nb_component);
 
   if (size_to_nb_element) {
     if (resize_with_parent) {
       tmp.resize(mesh_parent->getNbElement(el_type, ghost_type), defaul_);
     } else {
       tmp.resize(this->getNbElement(el_type, ghost_type), defaul_);
     }
   }
 
   return tmp;
 }
 
 /* -------------------------------------------------------------------------- */
 template <typename T>
 inline const Array<T> & Mesh::getData(const ID & data_name, ElementType el_type,
                                       GhostType ghost_type) const {
   return this->getElementalDataArray<T>(data_name, el_type, ghost_type);
 }
 
 /* -------------------------------------------------------------------------- */
 template <typename T>
 inline Array<T> & Mesh::getData(const ID & data_name, ElementType el_type,
                                 GhostType ghost_type) {
   return this->getElementalDataArray<T>(data_name, el_type, ghost_type);
 }
 
 /* -------------------------------------------------------------------------- */
 template <typename T>
 inline const ElementTypeMapArray<T> &
 Mesh::getData(const ID & data_name) const {
   return this->getElementalData<T>(data_name);
 }
 
 /* -------------------------------------------------------------------------- */
 template <typename T>
 inline ElementTypeMapArray<T> & Mesh::getData(const ID & data_name) {
   return this->getElementalData<T>(data_name);
 }
 
 /* -------------------------------------------------------------------------- */
 inline UInt Mesh::getNbElement(ElementType type, GhostType ghost_type) const {
   try {
 
     const Array<UInt> & conn = connectivities(type, ghost_type);
     return conn.size();
   } catch (...) {
     return 0;
   }
 }
 
 /* -------------------------------------------------------------------------- */
 inline UInt Mesh::getNbElement(const UInt spatial_dimension,
                                GhostType ghost_type, ElementKind kind) const {
   AKANTU_DEBUG_ASSERT(spatial_dimension <= 3 || spatial_dimension == UInt(-1),
                       "spatial_dimension is " << spatial_dimension
                                               << " and is greater than 3 !");
   UInt nb_element = 0;
 
   for (auto type : elementTypes(spatial_dimension, ghost_type, kind)) {
     nb_element += getNbElement(type, ghost_type);
   }
 
   return nb_element;
 }
 
 /* -------------------------------------------------------------------------- */
 inline void Mesh::getBarycenter(const Element & element,
                                 Vector<Real> & barycenter) const {
   Vector<UInt> conn = getConnectivity(element);
   Matrix<Real> local_coord(spatial_dimension, conn.size());
   auto node_begin = make_view(*nodes, spatial_dimension).begin();
 
   for (auto && node : enumerate(conn)) {
     local_coord(std::get<0>(node)) =
         Vector<Real>(node_begin[std::get<1>(node)]);
   }
 
   Math::barycenter(local_coord.storage(), conn.size(), spatial_dimension,
                    barycenter.storage());
 }
 
+/* -------------------------------------------------------------------------- */
+inline void Mesh::getIncenter(const Element & element,
+                              Vector<Real> & incenter) const {
+  auto p1_el_type = Mesh::getP1ElementType(element.type);
+  AKANTU_DEBUG_ASSERT(p1_el_type == _triangle_3,
+                      "Incenter can be computed only for triangle elements");
+  auto nb_nodes_per_facet = getNbNodesPerElement(p1_el_type);
+
+  Vector<UInt> conn = getConnectivity(element);
+  Matrix<Real> local_coord(spatial_dimension, nb_nodes_per_facet);
+  Vector<Real> op_side_length(nb_nodes_per_facet);
+  auto node_begin = make_view(*nodes, spatial_dimension).begin();
+
+  for (UInt i : arange(nb_nodes_per_facet)) {
+    local_coord(i) = Vector<Real>(node_begin[conn(i)]);
+  }
+
+  Real perimeter{0};
+  for (UInt i : arange(nb_nodes_per_facet)) {
+    UInt j = (i + 1) % nb_nodes_per_facet;
+    UInt k = (i + 2) % nb_nodes_per_facet;
+    auto vector = Vector<Real>(local_coord(j)) - Vector<Real>(local_coord(k));
+    op_side_length(i) = vector.norm();
+    perimeter += op_side_length(i);
+  }
+
+  for (UInt i : arange(spatial_dimension)) {
+    for (UInt j : arange(nb_nodes_per_facet)) {
+      incenter(i) += op_side_length(j) * local_coord(i, j);
+    }
+    incenter(i) /= perimeter;
+  }
+}
+
 /* -------------------------------------------------------------------------- */
 inline UInt Mesh::getNbNodesPerElement(ElementType type) {
   UInt nb_nodes_per_element = 0;
 #define GET_NB_NODES_PER_ELEMENT(type)                                         \
   nb_nodes_per_element = ElementClass<type>::getNbNodesPerElement()
   AKANTU_BOOST_ALL_ELEMENT_SWITCH(GET_NB_NODES_PER_ELEMENT);
 #undef GET_NB_NODES_PER_ELEMENT
   return nb_nodes_per_element;
 }
 
 /* -------------------------------------------------------------------------- */
 inline ElementType Mesh::getP1ElementType(ElementType type) {
   ElementType p1_type = _not_defined;
 #define GET_P1_TYPE(type) p1_type = ElementClass<type>::getP1ElementType()
 
   AKANTU_BOOST_ALL_ELEMENT_SWITCH(GET_P1_TYPE);
 #undef GET_P1_TYPE
   return p1_type;
 }
 
 /* -------------------------------------------------------------------------- */
 inline ElementKind Mesh::getKind(ElementType type) {
   ElementKind kind = _ek_not_defined;
 #define GET_KIND(type) kind = ElementClass<type>::getKind()
   AKANTU_BOOST_ALL_ELEMENT_SWITCH(GET_KIND);
 #undef GET_KIND
   return kind;
 }
 
 /* -------------------------------------------------------------------------- */
 inline UInt Mesh::getSpatialDimension(ElementType type) {
   UInt spatial_dimension = 0;
 #define GET_SPATIAL_DIMENSION(type)                                            \
   spatial_dimension = ElementClass<type>::getSpatialDimension()
   AKANTU_BOOST_ALL_ELEMENT_SWITCH(GET_SPATIAL_DIMENSION);
 #undef GET_SPATIAL_DIMENSION
 
   return spatial_dimension;
 }
 
 /* -------------------------------------------------------------------------- */
 inline UInt Mesh::getNbFacetTypes(ElementType type,
                                   __attribute__((unused)) UInt t) {
   UInt nb = 0;
 #define GET_NB_FACET_TYPE(type) nb = ElementClass<type>::getNbFacetTypes()
 
   AKANTU_BOOST_ALL_ELEMENT_SWITCH(GET_NB_FACET_TYPE);
 #undef GET_NB_FACET_TYPE
   return nb;
 }
 
 /* -------------------------------------------------------------------------- */
 inline constexpr auto Mesh::getFacetType(ElementType type, UInt t) {
 #define GET_FACET_TYPE(type) return ElementClass<type>::getFacetType(t);
 
   AKANTU_BOOST_ALL_ELEMENT_SWITCH_NO_DEFAULT(GET_FACET_TYPE);
 
 #undef GET_FACET_TYPE
 
   return _not_defined;
 }
 
 /* -------------------------------------------------------------------------- */
 inline constexpr auto Mesh::getAllFacetTypes(ElementType type) {
 #define GET_FACET_TYPE(type) return ElementClass<type>::getFacetTypes();
 
   AKANTU_BOOST_ALL_ELEMENT_SWITCH_NO_DEFAULT(GET_FACET_TYPE);
 #undef GET_FACET_TYPE
 
   return ElementClass<_not_defined>::getFacetTypes();
 }
 
 /* -------------------------------------------------------------------------- */
 inline UInt Mesh::getNbFacetsPerElement(ElementType type) {
   AKANTU_DEBUG_IN();
 
   UInt n_facet = 0;
 #define GET_NB_FACET(type) n_facet = ElementClass<type>::getNbFacetsPerElement()
 
   AKANTU_BOOST_ALL_ELEMENT_SWITCH(GET_NB_FACET);
 #undef GET_NB_FACET
 
   AKANTU_DEBUG_OUT();
   return n_facet;
 }
 
 /* -------------------------------------------------------------------------- */
 inline UInt Mesh::getNbFacetsPerElement(ElementType type, UInt t) {
   AKANTU_DEBUG_IN();
 
   UInt n_facet = 0;
 #define GET_NB_FACET(type)                                                     \
   n_facet = ElementClass<type>::getNbFacetsPerElement(t)
 
   AKANTU_BOOST_ALL_ELEMENT_SWITCH(GET_NB_FACET);
 #undef GET_NB_FACET
 
   AKANTU_DEBUG_OUT();
   return n_facet;
 }
 
 /* -------------------------------------------------------------------------- */
 inline auto Mesh::getFacetLocalConnectivity(ElementType type, UInt t) {
   AKANTU_DEBUG_IN();
 
 #define GET_FACET_CON(type)                                                    \
   AKANTU_DEBUG_OUT();                                                          \
   return ElementClass<type>::getFacetLocalConnectivityPerElement(t)
 
   AKANTU_BOOST_ALL_ELEMENT_SWITCH(GET_FACET_CON);
 #undef GET_FACET_CON
 
   AKANTU_DEBUG_OUT();
   return ElementClass<_not_defined>::getFacetLocalConnectivityPerElement(0);
   // This avoid a compilation warning but will certainly
   // also cause a segfault if reached
 }
 
 /* -------------------------------------------------------------------------- */
 inline auto Mesh::getFacetConnectivity(const Element & element, UInt t) const {
   AKANTU_DEBUG_IN();
 
   Matrix<const UInt> local_facets(getFacetLocalConnectivity(element.type, t));
   Matrix<UInt> facets(local_facets.rows(), local_facets.cols());
 
   const Array<UInt> & conn = connectivities(element.type, element.ghost_type);
 
   for (UInt f = 0; f < facets.rows(); ++f) {
     for (UInt n = 0; n < facets.cols(); ++n) {
       facets(f, n) = conn(element.element, local_facets(f, n));
     }
   }
 
   AKANTU_DEBUG_OUT();
   return facets;
 }
 
 /* -------------------------------------------------------------------------- */
 inline VectorProxy<UInt> Mesh::getConnectivity(const Element & element) const {
   return connectivities.get(element);
 }
 
 /* -------------------------------------------------------------------------- */
 inline VectorProxy<UInt> Mesh::getConnectivityNC(const Element & element) {
   return connectivities.get(element);
 }
 
 /* -------------------------------------------------------------------------- */
 template <typename T>
 inline void Mesh::extractNodalValuesFromElement(
     const Array<T> & nodal_values, T * local_coord, const UInt * connectivity,
     UInt n_nodes, UInt nb_degree_of_freedom) const {
   for (UInt n = 0; n < n_nodes; ++n) {
     memcpy(local_coord + n * nb_degree_of_freedom,
            nodal_values.storage() + connectivity[n] * nb_degree_of_freedom,
            nb_degree_of_freedom * sizeof(T));
   }
 }
 
 /* -------------------------------------------------------------------------- */
 inline void Mesh::addConnectivityType(ElementType type, GhostType ghost_type) {
   getConnectivityPointer(type, ghost_type);
 }
 
 /* -------------------------------------------------------------------------- */
 inline bool Mesh::isPureGhostNode(UInt n) const {
   return ((*nodes_flags)(n)&NodeFlag::_shared_mask) == NodeFlag::_pure_ghost;
 }
 
 /* -------------------------------------------------------------------------- */
 inline bool Mesh::isLocalOrMasterNode(UInt n) const {
   return ((*nodes_flags)(n)&NodeFlag::_local_master_mask) == NodeFlag::_normal;
 }
 
 /* -------------------------------------------------------------------------- */
 inline bool Mesh::isLocalNode(UInt n) const {
   return ((*nodes_flags)(n)&NodeFlag::_shared_mask) == NodeFlag::_normal;
 }
 
 /* -------------------------------------------------------------------------- */
 inline bool Mesh::isMasterNode(UInt n) const {
   return ((*nodes_flags)(n)&NodeFlag::_shared_mask) == NodeFlag::_master;
 }
 
 /* -------------------------------------------------------------------------- */
 inline bool Mesh::isSlaveNode(UInt n) const {
   return ((*nodes_flags)(n)&NodeFlag::_shared_mask) == NodeFlag::_slave;
 }
 
 /* -------------------------------------------------------------------------- */
 inline bool Mesh::isPeriodicSlave(UInt n) const {
   return ((*nodes_flags)(n)&NodeFlag::_periodic_mask) ==
          NodeFlag::_periodic_slave;
 }
 
 /* -------------------------------------------------------------------------- */
 inline bool Mesh::isPeriodicMaster(UInt n) const {
   return ((*nodes_flags)(n)&NodeFlag::_periodic_mask) ==
          NodeFlag::_periodic_master;
 }
 
 /* -------------------------------------------------------------------------- */
 inline NodeFlag Mesh::getNodeFlag(UInt local_id) const {
   return (*nodes_flags)(local_id);
 }
 
 /* -------------------------------------------------------------------------- */
 inline Int Mesh::getNodePrank(UInt local_id) const {
   auto it = nodes_prank.find(local_id);
   return it == nodes_prank.end() ? -1 : it->second;
 }
 
 /* -------------------------------------------------------------------------- */
 inline UInt Mesh::getNodeGlobalId(UInt local_id) const {
   return nodes_global_ids ? (*nodes_global_ids)(local_id) : local_id;
 }
 
 /* -------------------------------------------------------------------------- */
 inline UInt Mesh::getNodeLocalId(UInt global_id) const {
   if (nodes_global_ids == nullptr) {
     return global_id;
   }
   return nodes_global_ids->find(global_id);
 }
 
 /* -------------------------------------------------------------------------- */
 inline UInt Mesh::getNbGlobalNodes() const {
   return nodes_global_ids ? nb_global_nodes : nodes->size();
 }
 
 /* -------------------------------------------------------------------------- */
 inline UInt Mesh::getNbNodesPerElementList(const Array<Element> & elements) {
   UInt nb_nodes_per_element = 0;
   UInt nb_nodes = 0;
   ElementType current_element_type = _not_defined;
 
   for (const auto & el : elements) {
     if (el.type != current_element_type) {
       current_element_type = el.type;
       nb_nodes_per_element = Mesh::getNbNodesPerElement(current_element_type);
     }
 
     nb_nodes += nb_nodes_per_element;
   }
 
   return nb_nodes;
 }
 
 /* -------------------------------------------------------------------------- */
 inline bool Mesh::hasMeshFacets() { return (this->mesh_facets != nullptr); }
 
 /* -------------------------------------------------------------------------- */
 inline Mesh & Mesh::getMeshFacets() {
   if (this->mesh_facets == nullptr) {
     AKANTU_SILENT_EXCEPTION(
         "No facet mesh is defined yet! check the buildFacets functions");
   }
 
   return *this->mesh_facets;
 }
 
 /* -------------------------------------------------------------------------- */
 inline const Mesh & Mesh::getMeshFacets() const {
   if (this->mesh_facets == nullptr) {
     AKANTU_SILENT_EXCEPTION(
         "No facet mesh is defined yet! check the buildFacets functions");
   }
 
   return *this->mesh_facets;
 }
 /* -------------------------------------------------------------------------- */
 inline const Mesh & Mesh::getMeshParent() const {
   if (this->mesh_parent == nullptr) {
     AKANTU_SILENT_EXCEPTION(
         "No parent mesh is defined! This is only valid in a mesh_facets");
   }
 
   return *this->mesh_parent;
 }
 
 /* -------------------------------------------------------------------------- */
 void Mesh::addPeriodicSlave(UInt slave, UInt master) {
   if (master == slave) {
     return;
   }
 
   // if pair already registered
   auto master_slaves = periodic_master_slave.equal_range(master);
   auto slave_it =
       std::find_if(master_slaves.first, master_slaves.second,
                    [&](auto & pair) { return pair.second == slave; });
   if (slave_it == master_slaves.second) {
     // no duplicates
     periodic_master_slave.insert(std::make_pair(master, slave));
     AKANTU_DEBUG_INFO("adding periodic slave, slave gid:"
                       << getNodeGlobalId(slave) << " [lid: " << slave << "]"
                       << ", master gid:" << getNodeGlobalId(master)
                       << " [lid: " << master << "]");
     // std::cout << "adding periodic slave, slave gid:" <<
     // getNodeGlobalId(slave)
     //           << " [lid: " << slave << "]"
     //           << ", master gid:" << getNodeGlobalId(master)
     //           << " [lid: " << master << "]" << std::endl;
   }
 
   periodic_slave_master[slave] = master;
 
   auto set_flag = [&](auto node, auto flag) {
     (*nodes_flags)[node] &= ~NodeFlag::_periodic_mask; // clean periodic flags
     (*nodes_flags)[node] |= flag;
   };
 
   set_flag(slave, NodeFlag::_periodic_slave);
   set_flag(master, NodeFlag::_periodic_master);
 }
 
 /* -------------------------------------------------------------------------- */
 UInt Mesh::getPeriodicMaster(UInt slave) const {
   return periodic_slave_master.at(slave);
 }
 
 /* -------------------------------------------------------------------------- */
 class Mesh::PeriodicSlaves {
   using internal_iterator = std::unordered_multimap<UInt, UInt>::const_iterator;
   std::pair<internal_iterator, internal_iterator> pair;
 
 public:
   PeriodicSlaves(const Mesh & mesh, UInt master)
       : pair(mesh.getPeriodicMasterSlaves().equal_range(master)) {}
 
   PeriodicSlaves(const PeriodicSlaves & other) = default;
   PeriodicSlaves(PeriodicSlaves && other) = default;
   PeriodicSlaves & operator=(const PeriodicSlaves & other) = default;
 
   class const_iterator {
     internal_iterator it;
 
   public:
     const_iterator(internal_iterator it) : it(it) {}
 
     const_iterator operator++() {
       ++it;
       return *this;
     }
     bool operator!=(const const_iterator & other) { return other.it != it; }
     auto operator*() { return it->second; }
   };
 
   auto begin() const { return const_iterator(pair.first); }
   auto end() const { return const_iterator(pair.second); }
 };
 
 /* -------------------------------------------------------------------------- */
 inline decltype(auto) Mesh::getPeriodicSlaves(UInt master) const {
   return PeriodicSlaves(*this, master);
 }
 
 /* -------------------------------------------------------------------------- */
 inline Vector<UInt>
 Mesh::getConnectivityWithPeriodicity(const Element & element) const {
   Vector<UInt> conn = getConnectivity(element);
   if (not isPeriodic()) {
     return conn;
   }
 
   for (auto && node : conn) {
     if (isPeriodicSlave(node)) {
       node = getPeriodicMaster(node);
     }
   }
 
   return conn;
 }
 
 } // namespace akantu
 
 #endif /* AKANTU_MESH_INLINE_IMPL_HH_ */
diff --git a/src/mesh_utils/mesh_utils.cc b/src/mesh_utils/mesh_utils.cc
index 2460af05d..2ae466584 100644
--- a/src/mesh_utils/mesh_utils.cc
+++ b/src/mesh_utils/mesh_utils.cc
@@ -1,981 +1,1021 @@
 /**
  * @file   mesh_utils.cc
  *
  * @author Guillaume Anciaux <guillaume.anciaux@epfl.ch>
  * @author Dana Christen <dana.christen@epfl.ch>
  * @author David Simon Kammer <david.kammer@epfl.ch>
  * @author Nicolas Richart <nicolas.richart@epfl.ch>
  * @author Leonardo Snozzi <leonardo.snozzi@epfl.ch>
  * @author Marco Vocialta <marco.vocialta@epfl.ch>
  *
  * @date creation: Fri Aug 20 2010
  * @date last modification: Wed Feb 21 2018
  *
  * @brief  All mesh utils necessary for various tasks
  *
  *
  * Copyright (©)  2010-2018 EPFL (Ecole Polytechnique Fédérale de Lausanne)
  * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
  *
  * Akantu 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.
  *
  * Akantu 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 Akantu. If not, see <http://www.gnu.org/licenses/>.
  *
  */
 
 /* -------------------------------------------------------------------------- */
 #include "mesh_utils.hh"
 #include "element_synchronizer.hh"
 #include "fe_engine.hh"
 #include "mesh_accessor.hh"
 #include "mesh_iterators.hh"
 /* -------------------------------------------------------------------------- */
 #include <limits>
 #include <numeric>
 #include <queue>
 #include <set>
 /* -------------------------------------------------------------------------- */
 
 namespace akantu {
 
 /* -------------------------------------------------------------------------- */
 void MeshUtils::buildNode2Elements(const Mesh & mesh,
                                    CSR<Element> & node_to_elem,
                                    UInt spatial_dimension,
                                    ElementKind el_kind) {
   AKANTU_DEBUG_IN();
   if (spatial_dimension == _all_dimensions) {
     spatial_dimension = mesh.getSpatialDimension();
   }
 
   /// count number of occurrence of each node
   UInt nb_nodes = mesh.getNbNodes();
 
   /// array for the node-element list
   node_to_elem.resizeRows(nb_nodes);
   node_to_elem.clearRows();
 
   for_each_element(
       mesh,
       [&](auto && element) {
         Vector<UInt> conn = mesh.getConnectivity(element);
         std::set<UInt> unique_nodes;
         for (auto && node : conn) {
           auto ret = unique_nodes.emplace(node);
           if (ret.second) {
             ++node_to_elem.rowOffset(node);
           }
         }
       },
       _spatial_dimension = spatial_dimension, _element_kind = el_kind);
 
   node_to_elem.countToCSR();
   node_to_elem.resizeCols();
 
   /// rearrange element to get the node-element list
   // Element e;
   node_to_elem.beginInsertions();
 
   for_each_element(
       mesh,
       [&](auto && element) {
         Vector<UInt> conn = mesh.getConnectivity(element);
         std::set<UInt> unique_nodes;
         for (auto && node : conn) {
           auto ret = unique_nodes.emplace(node);
           if (ret.second) {
             node_to_elem.insertInRow(node, element);
           }
         }
       },
       _spatial_dimension = spatial_dimension, _element_kind = el_kind);
 
   node_to_elem.endInsertions();
 
   AKANTU_DEBUG_OUT();
 }
 
 /* -------------------------------------------------------------------------- */
 void MeshUtils::buildNode2ElementsElementTypeMap(const Mesh & mesh,
                                                  CSR<UInt> & node_to_elem,
                                                  ElementType type,
                                                  GhostType ghost_type) {
   AKANTU_DEBUG_IN();
   UInt nb_nodes = mesh.getNbNodes();
 
   UInt nb_nodes_per_element = Mesh::getNbNodesPerElement(type);
   UInt nb_elements = mesh.getConnectivity(type, ghost_type).size();
 
   UInt * conn_val = mesh.getConnectivity(type, ghost_type).storage();
 
   /// array for the node-element list
   node_to_elem.resizeRows(nb_nodes);
   node_to_elem.clearRows();
 
   /// count number of occurrence of each node
   for (UInt el = 0; el < nb_elements; ++el) {
     UInt el_offset = el * nb_nodes_per_element;
     for (UInt n = 0; n < nb_nodes_per_element; ++n) {
       ++node_to_elem.rowOffset(conn_val[el_offset + n]);
     }
   }
 
   /// convert the occurrence array in a csr one
   node_to_elem.countToCSR();
 
   node_to_elem.resizeCols();
   node_to_elem.beginInsertions();
 
   /// save the element index in the node-element list
   for (UInt el = 0; el < nb_elements; ++el) {
     UInt el_offset = el * nb_nodes_per_element;
     for (UInt n = 0; n < nb_nodes_per_element; ++n) {
       node_to_elem.insertInRow(conn_val[el_offset + n], el);
     }
   }
 
   node_to_elem.endInsertions();
 
   AKANTU_DEBUG_OUT();
 }
 
 /* -------------------------------------------------------------------------- */
 void MeshUtils::buildFacets(Mesh & mesh) {
   AKANTU_DEBUG_IN();
 
   UInt spatial_dimension = mesh.getSpatialDimension();
 
   for (auto ghost_type : ghost_types) {
     for (const auto & type :
          mesh.elementTypes(spatial_dimension - 1, ghost_type)) {
       mesh.getConnectivity(type, ghost_type).resize(0);
       // \todo inform the mesh event handler
     }
   }
 
   buildFacetsDimension(mesh, mesh, true, spatial_dimension);
 
   AKANTU_DEBUG_OUT();
 }
 
 /* -------------------------------------------------------------------------- */
 void MeshUtils::buildAllFacets(const Mesh & mesh, Mesh & mesh_facets,
                                UInt to_dimension) {
   AKANTU_DEBUG_IN();
 
   UInt spatial_dimension = mesh.getSpatialDimension();
 
   buildAllFacets(mesh, mesh_facets, spatial_dimension, to_dimension);
 
   AKANTU_DEBUG_OUT();
 }
 /* -------------------------------------------------------------------------- */
 void MeshUtils::buildAllFacets(const Mesh & mesh, Mesh & mesh_facets,
                                UInt from_dimension, UInt to_dimension) {
   AKANTU_DEBUG_IN();
 
   to_dimension = std::max(to_dimension, UInt(0));
 
   AKANTU_DEBUG_ASSERT(
       mesh_facets.isMeshFacets(),
       "The mesh_facets should be initialized with initMeshFacets");
 
   /// generate facets
   buildFacetsDimension(mesh, mesh_facets, false, from_dimension);
 
   /// sort facets and generate sub-facets
   for (UInt i = from_dimension - 1; i > to_dimension; --i) {
     buildFacetsDimension(mesh_facets, mesh_facets, false, i);
   }
 
   AKANTU_DEBUG_OUT();
 }
 
 /* -------------------------------------------------------------------------- */
 void MeshUtils::buildFacetsDimension(const Mesh & mesh, Mesh & mesh_facets,
                                      bool boundary_only, UInt dimension) {
   AKANTU_DEBUG_IN();
 
   // save the current parent of mesh_facets and set it temporarly to mesh since
   // mesh is the one containing the elements for which mesh_facets has the
   // sub-elements
   // example: if the function is called with mesh = mesh_facets
   const Mesh * mesh_facets_parent = nullptr;
   try {
     mesh_facets_parent = &mesh_facets.getMeshParent();
   } catch (...) {
   }
 
   mesh_facets.defineMeshParent(mesh);
   MeshAccessor mesh_accessor(mesh_facets);
 
   UInt spatial_dimension = mesh.getSpatialDimension();
 
   const Array<Real> & mesh_facets_nodes = mesh_facets.getNodes();
   const auto mesh_facets_nodes_it = mesh_facets_nodes.begin(spatial_dimension);
 
   CSR<Element> node_to_elem;
   buildNode2Elements(mesh, node_to_elem, dimension);
 
   Array<UInt> counter;
   std::vector<Element> connected_elements;
 
   NewElementsEvent event(AKANTU_CURRENT_FUNCTION);
 
   // init the SubelementToElement data to improve performance
   for (auto && ghost_type : ghost_types) {
     for (auto && type : mesh.elementTypes(dimension, ghost_type)) {
       auto & subelement_to_element =
           mesh_accessor.getSubelementToElement(type, ghost_type);
       subelement_to_element.resize(mesh.getNbElement(type, ghost_type),
                                    ElementNull);
 
       auto facet_types = mesh.getAllFacetTypes(type);
 
       for (auto && ft : arange(facet_types.size())) {
         auto facet_type = facet_types(ft);
         mesh_accessor.getElementToSubelement(facet_type, ghost_type);
         mesh_accessor.getConnectivity(facet_type, ghost_type);
       }
     }
   }
 
   const ElementSynchronizer * synchronizer = nullptr;
   if (mesh.isDistributed()) {
     synchronizer = &(mesh.getElementSynchronizer());
   }
 
   Element current_element;
   for (auto && ghost_type : ghost_types) {
     GhostType facet_ghost_type = ghost_type;
     current_element.ghost_type = ghost_type;
 
     for (auto && type : mesh.elementTypes(dimension, ghost_type)) {
       auto facet_types = mesh.getAllFacetTypes(type);
       current_element.type = type;
 
       for (auto && ft : arange(facet_types.size())) {
         auto facet_type = facet_types(ft);
         auto nb_element = mesh.getNbElement(type, ghost_type);
 
         auto && element_to_subelement =
             &mesh_accessor.getElementToSubelementNC(facet_type, ghost_type);
         auto && connectivity_facets =
             &mesh_accessor.getConnectivity(facet_type, ghost_type);
 
         auto nb_nodes_per_facet = connectivity_facets->getNbComponent();
         // Vector<UInt> facet(nb_nodes_per_facet);
 
         for (UInt el = 0; el < nb_element; ++el) {
           current_element.element = el;
 
           auto && facets =
               mesh.getFacetConnectivity(current_element, ft).transpose();
 
           for (auto facet : facets) {
             // facet = facets(f);
 
             UInt first_node_nb_elements = node_to_elem.getNbCols(facet(0));
             counter.resize(first_node_nb_elements);
             counter.zero();
 
             // loop over the other nodes to search intersecting elements,
             // which are the elements that share another node with the
             // starting element after first_node
             for (auto && data : enumerate(node_to_elem.getRow(facet(0)))) {
               auto && local_el = std::get<0>(data);
               auto && first_node = std::get<1>(data);
               for (auto n : arange(1, nb_nodes_per_facet)) {
                 auto && node_elements = node_to_elem.getRow(facet(n));
                 counter(local_el) += std::count(
                     node_elements.begin(), node_elements.end(), first_node);
               }
             }
 
             // counting the number of elements connected to the facets and
             // taking the minimum element number, because the facet should
             // be inserted just once
             UInt nb_element_connected_to_facet = 0;
             Element minimum_el = ElementNull;
             connected_elements.clear();
             for (auto && data : enumerate(node_to_elem.getRow(facet(0)))) {
 
               if (not(counter(std::get<0>(data)) == nb_nodes_per_facet - 1)) {
                 continue;
               }
 
               auto && real_el = std::get<1>(data);
 
               ++nb_element_connected_to_facet;
               minimum_el = std::min(minimum_el, real_el);
               connected_elements.push_back(real_el);
             }
 
             if (minimum_el != current_element) {
               continue;
             }
 
             bool full_ghost_facet = false;
 
             UInt n = 0;
             while (n < nb_nodes_per_facet and mesh.isPureGhostNode(facet(n))) {
               ++n;
             }
             if (n == nb_nodes_per_facet) {
               full_ghost_facet = true;
             }
 
             if (full_ghost_facet) {
               continue;
             }
 
             if (boundary_only and nb_element_connected_to_facet != 1) {
               continue;
             }
 
             std::vector<Element> elements;
 
             // build elements_on_facets: linearized_el must come first
             // in order to store the facet in the correct direction
             // and avoid to invert the sign in the normal computation
             elements.reserve(elements.size() + connected_elements.size());
             for (auto && connected_element : connected_elements) {
               elements.push_back(connected_element);
             }
 
             if (nb_element_connected_to_facet == 1) { /// boundary facet
               elements.push_back(ElementNull);
             } else if (nb_element_connected_to_facet == 2) { /// internal facet
               /// check if facet is in between ghost and normal
               /// elements: if it's the case, the facet is either
               /// ghost or not ghost. The criterion to decide this
               /// is arbitrary. It was chosen to check the processor
               /// id (prank) of the two neighboring elements. If
               /// prank of the ghost element is lower than prank of
               /// the normal one, the facet is not ghost, otherwise
               /// it's ghost
               GhostType gt[2] = {_not_ghost, _not_ghost};
 
               for (UInt el = 0; el < connected_elements.size(); ++el) {
                 gt[el] = connected_elements[el].ghost_type;
               }
 
               if ((gt[0] == _not_ghost) xor (gt[1] == _not_ghost)) {
                 UInt prank[2];
                 for (UInt el = 0; el < 2; ++el) {
                   prank[el] = synchronizer->getRank(connected_elements[el]);
                 }
 
                 // ugly trick from Marco detected :P
                 bool ghost_one = (gt[0] != _ghost);
                 if (prank[ghost_one] > prank[!ghost_one]) {
                   facet_ghost_type = _not_ghost;
                 } else {
                   facet_ghost_type = _ghost;
                 }
 
                 connectivity_facets = &mesh_accessor.getConnectivity(
                     facet_type, facet_ghost_type);
                 element_to_subelement = &mesh_accessor.getElementToSubelementNC(
                     facet_type, facet_ghost_type);
               }
             }
 
             element_to_subelement->push_back(elements);
             connectivity_facets->push_back(facet);
 
             /// current facet index
             UInt current_facet = connectivity_facets->size() - 1;
             Element facet_element{facet_type, current_facet, facet_ghost_type};
             event.getList().push_back(facet_element);
             /// loop on every element connected to current facet and
             /// insert current facet in the first free spot of the
             /// subelement_to_element vector
             for (auto & loc_el : elements) {
               if (loc_el == ElementNull) {
                 continue;
               }
 
               auto && subelements =
                   mesh_accessor.getSubelementToElement(loc_el);
 
               for (auto & el : subelements) {
                 if (el != ElementNull) {
                   continue;
                 }
 
                 el = facet_element;
                 break;
               }
             }
 
             /// reset connectivity in case a facet was found in
             /// between ghost and normal elements
             if (facet_ghost_type != ghost_type) {
               facet_ghost_type = ghost_type;
               connectivity_facets =
                   &mesh_accessor.getConnectivity(facet_type, facet_ghost_type);
               element_to_subelement = &mesh_accessor.getElementToSubelement(
                   facet_type, facet_ghost_type);
             }
           }
         }
       }
     }
   }
 
   mesh_facets.sendEvent(event);
 
   // restore the parent of mesh_facet
   if (mesh_facets_parent != nullptr) {
     mesh_facets.defineMeshParent(*mesh_facets_parent);
   }
 
   AKANTU_DEBUG_OUT();
 }
 
 /* -------------------------------------------------------------------------- */
 void MeshUtils::renumberMeshNodes(Mesh & mesh,
                                   Array<UInt> & local_connectivities,
                                   UInt nb_local_element, UInt nb_ghost_element,
                                   ElementType type,
                                   Array<UInt> & old_nodes_numbers) {
   AKANTU_DEBUG_IN();
 
   UInt nb_nodes_per_element = Mesh::getNbNodesPerElement(type);
 
   std::map<UInt, UInt> renumbering_map;
   for (UInt i = 0; i < old_nodes_numbers.size(); ++i) {
     renumbering_map[old_nodes_numbers(i)] = i;
   }
 
   /// renumber the nodes
   renumberNodesInConnectivity(local_connectivities,
                               (nb_local_element + nb_ghost_element) *
                                   nb_nodes_per_element,
                               renumbering_map);
 
   old_nodes_numbers.resize(renumbering_map.size());
   for (auto & renumber_pair : renumbering_map) {
     old_nodes_numbers(renumber_pair.second) = renumber_pair.first;
   }
   renumbering_map.clear();
 
   MeshAccessor mesh_accessor(mesh);
 
   /// copy the renumbered connectivity to the right place
   auto & local_conn = mesh_accessor.getConnectivity(type);
   local_conn.resize(nb_local_element);
 
   if (nb_local_element > 0) {
     memcpy(local_conn.storage(), local_connectivities.storage(),
            nb_local_element * nb_nodes_per_element * sizeof(UInt));
   }
 
   auto & ghost_conn = mesh_accessor.getConnectivity(type, _ghost);
   ghost_conn.resize(nb_ghost_element);
 
   if (nb_ghost_element > 0) {
     std::memcpy(ghost_conn.storage(),
                 local_connectivities.storage() +
                     nb_local_element * nb_nodes_per_element,
                 nb_ghost_element * nb_nodes_per_element * sizeof(UInt));
   }
 
   auto & ghost_counter = mesh_accessor.getGhostsCounters(type, _ghost);
   ghost_counter.resize(nb_ghost_element, 1);
 
   AKANTU_DEBUG_OUT();
 }
 
 /* -------------------------------------------------------------------------- */
 void MeshUtils::renumberNodesInConnectivity(
     Array<UInt> & list_nodes, UInt nb_nodes,
     std::map<UInt, UInt> & renumbering_map) {
   AKANTU_DEBUG_IN();
 
   UInt * connectivity = list_nodes.storage();
   UInt new_node_num = renumbering_map.size();
   for (UInt n = 0; n < nb_nodes; ++n, ++connectivity) {
     UInt & node = *connectivity;
     auto it = renumbering_map.find(node);
     if (it == renumbering_map.end()) {
       UInt old_node = node;
       renumbering_map[old_node] = new_node_num;
       node = new_node_num;
       ++new_node_num;
     } else {
       node = it->second;
     }
   }
 
   AKANTU_DEBUG_OUT();
 }
 
 /* -------------------------------------------------------------------------- */
 void MeshUtils::purifyMesh(Mesh & mesh) {
   AKANTU_DEBUG_IN();
 
   std::map<UInt, UInt> renumbering_map;
 
   RemovedNodesEvent remove_nodes(mesh, AKANTU_CURRENT_FUNCTION);
   Array<UInt> & nodes_removed = remove_nodes.getList();
 
   for (auto ghost_type : ghost_types) {
     for (auto type :
          mesh.elementTypes(_all_dimensions, ghost_type, _ek_not_defined)) {
       UInt nb_nodes_per_element = Mesh::getNbNodesPerElement(type);
 
       Array<UInt> & connectivity = mesh.getConnectivity(type, ghost_type);
       UInt nb_element(connectivity.size());
 
       renumberNodesInConnectivity(
           connectivity, nb_element * nb_nodes_per_element, renumbering_map);
     }
   }
 
   Array<UInt> & new_numbering = remove_nodes.getNewNumbering();
   std::fill(new_numbering.begin(), new_numbering.end(), UInt(-1));
 
   for (auto && pair : renumbering_map) {
     new_numbering(std::get<0>(pair)) = std::get<1>(pair);
   }
 
   for (UInt i = 0; i < new_numbering.size(); ++i) {
     if (new_numbering(i) == UInt(-1)) {
       nodes_removed.push_back(i);
     }
   }
 
   mesh.sendEvent(remove_nodes);
 
   AKANTU_DEBUG_OUT();
 }
 
 /* -------------------------------------------------------------------------- */
 void MeshUtils::flipFacets(
     Mesh & mesh_facets,
     const ElementTypeMapArray<UInt> & remote_global_connectivities,
     GhostType gt_facet) {
   AKANTU_DEBUG_IN();
 
   UInt spatial_dimension = mesh_facets.getSpatialDimension();
 
   /// get global connectivity for local mesh
   ElementTypeMapArray<UInt> local_global_connectivities(
       "local_global_connectivity", mesh_facets.getID(),
       mesh_facets.getMemoryID());
 
   local_global_connectivities.initialize(
       mesh_facets, _spatial_dimension = spatial_dimension - 1,
       _ghost_type = gt_facet, _with_nb_nodes_per_element = true,
       _with_nb_element = true);
 
   mesh_facets.getGlobalConnectivity(local_global_connectivities);
   MeshAccessor mesh_accessor(mesh_facets);
 
   /// loop on every facet
   for (auto type_facet :
        mesh_facets.elementTypes(spatial_dimension - 1, gt_facet)) {
 
     auto & connectivity = mesh_accessor.getConnectivity(type_facet, gt_facet);
     auto & local_global_connectivity =
         local_global_connectivities(type_facet, gt_facet);
     const auto & remote_global_connectivity =
         remote_global_connectivities(type_facet, gt_facet);
 
     auto & element_per_facet =
         mesh_accessor.getElementToSubelementNC(type_facet, gt_facet);
     auto & subfacet_to_facet =
         mesh_accessor.getSubelementToElementNC(type_facet, gt_facet);
 
     auto nb_nodes_per_facet = connectivity.getNbComponent();
     auto nb_nodes_per_P1_facet =
         Mesh::getNbNodesPerElement(Mesh::getP1ElementType(type_facet));
 
     for (auto && data :
          zip(make_view(connectivity, nb_nodes_per_facet),
              make_view(local_global_connectivity, nb_nodes_per_facet),
              make_view(remote_global_connectivity, nb_nodes_per_facet),
              make_view(subfacet_to_facet, subfacet_to_facet.getNbComponent()),
              make_view(element_per_facet))) {
 
       auto & conn = std::get<0>(data);
       auto & local_gconn = std::get<1>(data);
       const auto & remote_gconn = std::get<2>(data);
 
       /// skip facet if connectivities are the same
       if (local_gconn == remote_gconn) {
         continue;
       }
 
       /// re-arrange connectivity
       auto conn_tmp = conn;
       auto begin = local_gconn.begin();
       auto end = local_gconn.end();
 
       AKANTU_DEBUG_ASSERT(std::is_permutation(begin, end, remote_gconn.begin()),
                           "This facets are not just permutation of each other, "
                               << local_gconn << " and " << remote_gconn);
 
       for (auto && data : enumerate(remote_gconn)) {
         auto it = std::find(begin, end, std::get<1>(data));
         AKANTU_DEBUG_ASSERT(it != end, "Node not found");
         UInt new_position = it - begin;
         conn(new_position) = conn_tmp(std::get<0>(data));
         ;
       }
       // std::transform(remote_gconn.begin(), remote_gconn.end(), conn.begin(),
       //                [&](auto && gnode) {
       //                  auto it = std::find(begin, end, gnode);
       //                  AKANTU_DEBUG_ASSERT(it != end, "Node not found");
       //                  return conn_tmp(it - begin);
       //                });
 
       /// if 3D, check if facets are just rotated
       if (spatial_dimension == 3) {
         auto begin = remote_gconn.begin();
         /// find first node
         auto it = std::find(begin, remote_gconn.end(), local_gconn(0));
 
         UInt n;
         UInt start = it - begin;
         /// count how many nodes in the received connectivity follow
         /// the same order of those in the local connectivity
         for (n = 1; n < nb_nodes_per_P1_facet &&
                     local_gconn(n) ==
                         remote_gconn((start + n) % nb_nodes_per_P1_facet);
              ++n) {
           ;
         }
 
         /// skip the facet inversion if facet is just rotated
         if (n == nb_nodes_per_P1_facet) {
           continue;
         }
       }
 
       /// update data to invert facet
       auto & element_per_facet = std::get<4>(data);
       if (element_per_facet[1] !=
           ElementNull) { // by convention the first facet
                          // cannot be a ElementNull
         std::swap(element_per_facet[0], element_per_facet[1]);
       }
 
       auto & subfacets_of_facet = std::get<3>(data);
       std::swap(subfacets_of_facet(0), subfacets_of_facet(1));
     }
   }
 
   AKANTU_DEBUG_OUT();
 }
 
 /*-------------------------------------------------------------------- */
 Real MeshUtils::cosSharpAngleBetween2Facets(SolidMechanicsModel & model,
                                             const Element & facet1,
                                             const Element & facet2) {
   AKANTU_DEBUG_IN();
 
   auto & facets_fe_engine = model.getFEEngine("FacetsFEEngine");
   auto dim = model.getSpatialDimension();
 
   // normal to the first facet
   UInt nb_quad_points1 = facets_fe_engine.getNbIntegrationPoints(facet1.type);
   const auto & normals1 = facets_fe_engine.getNormalsOnIntegrationPoints(
       facet1.type, facet1.ghost_type);
   auto normal1_begin = normals1.begin(dim);
   Vector<Real> facet1_normal(normal1_begin[facet1.element * nb_quad_points1]);
 
   // normal to the second facet
   UInt nb_quad_points2 = facets_fe_engine.getNbIntegrationPoints(facet2.type);
   const auto & normals2 = facets_fe_engine.getNormalsOnIntegrationPoints(
       facet2.type, facet2.ghost_type);
   auto normal2_begin = normals2.begin(dim);
   Vector<Real> facet2_normal(normal2_begin[facet2.element * nb_quad_points2]);
 
   // get abs of dot product between two normals
   Real dot = facet1_normal.dot(facet2_normal);
   dot = std::abs(dot);
 
   AKANTU_DEBUG_OUT();
   return dot;
 }
 
 /*-------------------------------------------------------------------- */
 Real MeshUtils::distanceBetween2Barycenters(const Mesh & mesh_facet1,
                                             const Mesh & mesh_facet2,
                                             const Element & facet1,
                                             const Element & facet2) {
   AKANTU_DEBUG_IN();
   auto dim = mesh_facet1.getSpatialDimension();
   dim = std::max(mesh_facet2.getSpatialDimension(), dim);
 
   Vector<Real> facet1_bary(dim);
   Vector<Real> facet2_bary(dim);
   mesh_facet1.getBarycenter(facet1, facet1_bary);
   mesh_facet2.getBarycenter(facet2, facet2_bary);
   auto dist = std::abs(facet1_bary.distance(facet2_bary));
 
   AKANTU_DEBUG_OUT();
   return dist;
 }
 
 /*-------------------------------------------------------------------- */
-Real MeshUtils::distanceBetweenBarycentersCorrected(const Mesh & mesh_facets,
-                                                    const Element & facet1,
-                                                    const Element & facet2) {
+Real MeshUtils::distanceBetweenIncentersCorrected(const Mesh & mesh_facets,
+                                                  const Element & facet1,
+                                                  const Element & facet2) {
   AKANTU_DEBUG_IN();
   auto dim = mesh_facets.getSpatialDimension();
   AKANTU_DEBUG_ASSERT(dim == 3,
                       "DistanceBetweenBarycentersCorrected works only in 3D");
 
   const Vector<Element> & subfacet_to_el1 =
       mesh_facets.getSubelementToElement(facet1);
   const Vector<Element> && subfacet_to_el2 =
       mesh_facets.getSubelementToElement(facet2);
   std::set<Element> intersection;
 
   for (auto & subfacet1 : subfacet_to_el1) {
     for (auto & subfacet2 : subfacet_to_el2) {
       if (subfacet1 == subfacet2) {
         intersection.emplace(subfacet1);
       }
     }
   }
 
   // verify if facets share 1 subfacet == connected but not coincide
   auto nb_shared_subfacets = intersection.size();
   AKANTU_DEBUG_ASSERT(
       nb_shared_subfacets == 1,
       "Facets " << facet1 << " and " << facet2
                 << " are either not connected or coincide. Outputing zero");
   auto common_subfacet = *intersection.begin();
   auto & pos = mesh_facets.getNodes();
   const auto pos_it = pos.begin(dim);
   // auto subfacet_conn = mesh_facets.getConnectivity(common_subfacet.type,
   //                                                  common_subfacet.ghost_type);
   // auto nb_nodes_subfacet = subfacet_conn.getNbComponent();
   // const auto subfacet_nodes_it =
   //     make_view(subfacet_conn, nb_nodes_subfacet).begin();
   Vector<UInt> subfacet_nodes = mesh_facets.getConnectivity(common_subfacet);
   Vector<Real> A(dim);
   Vector<Real> AB(dim);
   A = Vector<Real>(pos_it[subfacet_nodes(0)]);
   AB = Vector<Real>(pos_it[subfacet_nodes(1)]) -
        Vector<Real>(pos_it[subfacet_nodes(0)]);
 
-  Vector<Real> facet1_bary(dim);
-  Vector<Real> facet2_bary(dim);
-  mesh_facets.getBarycenter(facet1, facet1_bary);
-  mesh_facets.getBarycenter(facet2, facet2_bary);
+  Vector<Real> facet1_inc(dim);
+  Vector<Real> facet2_inc(dim);
+  mesh_facets.getIncenter(facet1, facet1_inc);
+  mesh_facets.getIncenter(facet2, facet2_inc);
 
-  Vector<Real> ABary1 = facet1_bary - A;
-  Vector<Real> ABary2 = facet2_bary - A;
-  Vector<Real> dif = (ABary1.norm() - ABary2.norm()) * AB / AB.norm();
-  Vector<Real> facet2_bary_corrected = facet2_bary + dif;
+  Vector<Real> AInc1 = facet1_inc - A;
+  Vector<Real> AInc2 = facet2_inc - A;
+  Vector<Real> dif = (AInc1.norm() - AInc2.norm()) * AB / AB.norm();
+  Vector<Real> facet2_inc_corrected = facet2_inc + dif;
 
-  auto dist = std::abs(facet1_bary.distance(facet2_bary_corrected));
+  auto dist = std::abs(facet1_inc.distance(facet2_inc_corrected));
 
   AKANTU_DEBUG_OUT();
   return dist;
-} // namespace akantu
+}
+/*-------------------------------------------------------------------- */
+Real MeshUtils::distanceBetweenIncenters(const Mesh & mesh_facets,
+                                         const Element & facet1,
+                                         const Element & facet2) {
+  AKANTU_DEBUG_IN();
+  auto dim = mesh_facets.getSpatialDimension();
+  AKANTU_DEBUG_ASSERT(dim == 3, "DistanceBetweenIncenters works only in 3D");
+
+  Vector<Real> facet1_inc(dim);
+  Vector<Real> facet2_inc(dim);
+  mesh_facets.getIncenter(facet1, facet1_inc);
+  mesh_facets.getIncenter(facet2, facet2_inc);
+
+  auto dist = std::abs(facet1_inc.distance(facet2_inc));
+
+  AKANTU_DEBUG_OUT();
+  return dist;
+}
 /*-------------------------------------------------------------------- */
 Real MeshUtils::getInscribedCircleDiameter(SolidMechanicsModel & model,
                                            const Element & el) {
   AKANTU_DEBUG_IN();
   auto & mesh = model.getMesh();
   auto & mesh_facets = mesh.getMeshFacets();
   auto & facets_fe_engine = model.getFEEngine("FacetsFEEngine");
   auto dim = mesh.getSpatialDimension();
   const auto & pos = mesh.getNodes();
 
   auto nb_nodes_per_facet = mesh_facets.getNbNodesPerElement(el.type);
   Array<Real> coord(0, nb_nodes_per_facet * dim);
   Array<UInt> dummy_list(1, 1, el.element);
   facets_fe_engine.extractNodalToElementField(mesh_facets, pos, coord, el.type,
                                               el.ghost_type, dummy_list);
   Array<Real>::matrix_iterator coord_el = coord.begin(dim, nb_nodes_per_facet);
   Real facet_indiam = facets_fe_engine.getElementInradius(*coord_el, el.type);
 
   AKANTU_DEBUG_OUT();
   return facet_indiam;
 }
 /*-------------------------------------------------------------------- */
+Real MeshUtils::getFacetArea(SolidMechanicsModel & model, const Element & el) {
+  AKANTU_DEBUG_IN();
+
+  auto & mesh = model.getMesh();
+  auto dim = mesh.getSpatialDimension();
+  AKANTU_DEBUG_ASSERT(Mesh::getSpatialDimension(el.type) == dim - 1,
+                      "Element has a wrong dimension");
+  AKANTU_DEBUG_ASSERT(Mesh::getKind(el.type) == _ek_regular,
+                      "Element has a wrong kind: " << Mesh::getKind(el.type));
+
+  auto & fe_engine = model.getFEEngine("FacetsFEEngine");
+  Array<UInt> single_el_array;
+  single_el_array.push_back(el.element);
+
+  Array<Real> dummy(fe_engine.getNbIntegrationPoints(el.type), 1, 1.);
+  Real el_area =
+      fe_engine.integrate(dummy, el.type, el.ghost_type, single_el_array);
+
+  AKANTU_DEBUG_OUT();
+  return el_area;
+}
+/*-------------------------------------------------------------------- */
 std::pair<bool, Array<Element>>
 MeshUtils::areFacetsConnected(const Mesh & mesh_facets, const Element & facet1,
                               const Element & facet2) {
   AKANTU_DEBUG_IN();
   AKANTU_DEBUG_ASSERT(
       facet1.type == facet2.type,
       "Different facet types are entered in areFacetsConnected function");
 
   Array<Element> shared_subs;
   bool facets_connected{false};
 
   const Vector<Element> & sub_to_facet1 =
       mesh_facets.getSubelementToElement(facet1);
   const Vector<Element> & sub_to_facet2 =
       mesh_facets.getSubelementToElement(facet2);
   Vector<Element> common_subfacets;
 
   for (UInt i : arange(sub_to_facet1.size())) {
     for (UInt j : arange(i, sub_to_facet2.size())) {
       auto sub1 = sub_to_facet1(i);
       auto sub2 = sub_to_facet2(j);
       if (sub1 == sub2) {
         shared_subs.push_back(sub1);
         facets_connected = true;
       }
     }
   }
 
   AKANTU_DEBUG_OUT();
   return std::make_pair(facets_connected, shared_subs);
 }
 /*-------------------------------------------------------------------- */
 void MeshUtils::fillElementToSubElementsData(Mesh & mesh) {
   AKANTU_DEBUG_IN();
 
   if (mesh.getNbElement(mesh.getSpatialDimension() - 1) == 0) {
     AKANTU_DEBUG_INFO("There are not facets, add them in the mesh file or call "
                       "the buildFacet method.");
     return;
   }
 
   UInt spatial_dimension = mesh.getSpatialDimension();
   ElementTypeMapArray<Real> barycenters("barycenter_tmp", mesh.getID(),
                                         mesh.getMemoryID());
   barycenters.initialize(mesh, _nb_component = spatial_dimension,
                          _spatial_dimension = _all_dimensions);
 
   Element element;
   for (auto ghost_type : ghost_types) {
     element.ghost_type = ghost_type;
     for (const auto & type : mesh.elementTypes(_all_dimensions, ghost_type)) {
       element.type = type;
 
       UInt nb_element = mesh.getNbElement(type, ghost_type);
       Array<Real> & barycenters_arr = barycenters(type, ghost_type);
       barycenters_arr.resize(nb_element);
       auto bary = barycenters_arr.begin(spatial_dimension);
       auto bary_end = barycenters_arr.end(spatial_dimension);
 
       for (UInt el = 0; bary != bary_end; ++bary, ++el) {
         element.element = el;
         mesh.getBarycenter(element, *bary);
       }
     }
   }
 
   MeshAccessor mesh_accessor(mesh);
   for (Int sp(spatial_dimension); sp >= 1; --sp) {
     if (mesh.getNbElement(sp) == 0) {
       continue;
     }
 
     for (auto ghost_type : ghost_types) {
       for (auto & type : mesh.elementTypes(sp, ghost_type)) {
         auto & subelement_to_element =
             mesh_accessor.getSubelementToElement(type, ghost_type);
         subelement_to_element.resize(mesh.getNbElement(type, ghost_type));
         subelement_to_element.set(ElementNull);
       }
 
       for (auto & type : mesh.elementTypes(sp - 1, ghost_type)) {
         auto & element_to_subelement =
             mesh_accessor.getElementToSubelement(type, ghost_type);
         element_to_subelement.resize(mesh.getNbElement(type, ghost_type));
         element_to_subelement.clear();
       }
     }
 
     CSR<Element> nodes_to_elements;
     buildNode2Elements(mesh, nodes_to_elements, sp);
 
     Element facet_element;
 
     for (auto ghost_type : ghost_types) {
       facet_element.ghost_type = ghost_type;
       for (const auto & type : mesh.elementTypes(sp - 1, ghost_type)) {
         facet_element.type = type;
 
         auto & element_to_subelement =
             mesh_accessor.getElementToSubelement(type, ghost_type);
 
         const auto & connectivity = mesh.getConnectivity(type, ghost_type);
         // element_to_subelement.resize(connectivity.size());
 
         for (auto && data : enumerate(
                  make_view(connectivity, mesh.getNbNodesPerElement(type)))) {
           const auto & facet = std::get<1>(data);
           facet_element.element = std::get<0>(data);
 
           std::map<Element, UInt> element_seen_counter;
           auto nb_nodes_per_facet =
               mesh.getNbNodesPerElement(Mesh::getP1ElementType(type));
 
           // count the number of node in common between the facet and the
           // other element connected to the nodes of the facet
           for (auto node : arange(nb_nodes_per_facet)) {
             for (auto & elem : nodes_to_elements.getRow(facet(node))) {
               auto cit = element_seen_counter.find(elem);
               if (cit != element_seen_counter.end()) {
                 cit->second++;
               } else {
                 element_seen_counter[elem] = 1;
               }
             }
           }
 
           // check which are the connected elements
           std::vector<Element> connected_elements;
           for (auto && cit : element_seen_counter) {
             if (cit.second == nb_nodes_per_facet) {
               connected_elements.push_back(cit.first);
             }
           }
 
           // add the connected elements as sub-elements
           for (auto & connected_element : connected_elements) {
             element_to_subelement(facet_element.element)
                 .push_back(connected_element);
           }
 
           // add the element as sub-element to the connected elements
           for (auto & connected_element : connected_elements) {
             Vector<Element> subelements_to_element =
                 mesh.getSubelementToElement(connected_element);
 
             // find the position where to insert the element
             auto it = std::find(subelements_to_element.begin(),
                                 subelements_to_element.end(), ElementNull);
 
             AKANTU_DEBUG_ASSERT(
                 it != subelements_to_element.end(),
                 "The element "
                     << connected_element << " seems to have too many facets!! ("
                     << (it - subelements_to_element.begin()) << " < "
                     << mesh.getNbFacetsPerElement(connected_element.type)
                     << ")");
 
             *it = facet_element;
           }
         }
       }
     }
   }
 
   AKANTU_DEBUG_OUT();
 }
 
 } // namespace akantu
diff --git a/src/mesh_utils/mesh_utils.hh b/src/mesh_utils/mesh_utils.hh
index 5a065f6c9..524720ee1 100644
--- a/src/mesh_utils/mesh_utils.hh
+++ b/src/mesh_utils/mesh_utils.hh
@@ -1,164 +1,172 @@
 /**
  * @file   mesh_utils.hh
  *
  * @author Guillaume Anciaux <guillaume.anciaux@epfl.ch>
  * @author Dana Christen <dana.christen@epfl.ch>
  * @author David Simon Kammer <david.kammer@epfl.ch>
  * @author Nicolas Richart <nicolas.richart@epfl.ch>
  * @author Leonardo Snozzi <leonardo.snozzi@epfl.ch>
  * @author Marco Vocialta <marco.vocialta@epfl.ch>
  *
  * @date creation: Fri Jun 18 2010
  * @date last modification: Sun Dec 03 2017
  *
  * @brief  All mesh utils necessary for various tasks
  *
  *
  * Copyright (©)  2010-2018 EPFL (Ecole Polytechnique Fédérale de Lausanne)
  * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
  *
  * Akantu 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.
  *
  * Akantu 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 Akantu. If not, see <http://www.gnu.org/licenses/>.
  *
  */
 
 /* -------------------------------------------------------------------------- */
 #include "aka_common.hh"
 #include "aka_csr.hh"
 #include "mesh.hh"
 #include "solid_mechanics_model.hh"
 /* -------------------------------------------------------------------------- */
 #include <vector>
 /* -------------------------------------------------------------------------- */
 
 #ifndef AKANTU_MESH_UTILS_HH_
 #define AKANTU_MESH_UTILS_HH_
 
 namespace akantu {
 
 class MeshUtils {
   /* ------------------------------------------------------------------------ */
   /* Methods                                                                  */
   /* ------------------------------------------------------------------------ */
 public:
   /// build a CSR<Element> that contains for each node the list of connected
   /// elements of a given spatial dimension
   static void buildNode2Elements(const Mesh & mesh, CSR<Element> & node_to_elem,
                                  UInt spatial_dimension = _all_dimensions,
                                  ElementKind el_kind = _ek_not_defined);
 
   /// build a CSR<UInt> that contains for each node the number of
   /// the connected elements of a given ElementType
   static void
   buildNode2ElementsElementTypeMap(const Mesh & mesh, CSR<UInt> & node_to_elem,
                                    ElementType type,
                                    GhostType ghost_type = _not_ghost);
 
   /// build the facets elements on the boundaries of a mesh
   static void buildFacets(Mesh & mesh);
 
   /// build all the facets elements: boundary and internals and store them in
   /// the mesh_facets for element of dimension from_dimension to to_dimension
   static void buildAllFacets(const Mesh & mesh, Mesh & mesh_facets,
                              UInt from_dimension, UInt to_dimension);
 
   /// build all the facets elements: boundary and internals and store them in
   /// the mesh_facets
   static void buildAllFacets(const Mesh & mesh, Mesh & mesh_facets,
                              UInt to_dimension = 0);
 
   /// build facets for a given spatial dimension
   static void buildFacetsDimension(const Mesh & mesh, Mesh & mesh_facets,
                                    bool boundary_only, UInt dimension);
 
   /// take the local_connectivity array as the array of local and ghost
   /// connectivity, renumber the nodes and set the connectivity of the mesh
   static void renumberMeshNodes(Mesh & mesh, Array<UInt> & local_connectivities,
                                 UInt nb_local_element, UInt nb_ghost_element,
                                 ElementType type, Array<UInt> & old_nodes);
 
   /// compute pbc pair for a given direction
   static void computePBCMap(const Mesh & mymesh, UInt dir,
                             std::map<UInt, UInt> & pbc_pair);
   /// compute pbc pair for a surface pair
   static void computePBCMap(const Mesh & mymesh,
                             const std::pair<ID, ID> & surface_pair,
                             std::map<UInt, UInt> & pbc_pair);
 
   /// remove not connected nodes /!\ this functions renumbers the nodes.
   static void purifyMesh(Mesh & mesh);
 
   /// fill the subelement to element and the elements to subelements data
   static void fillElementToSubElementsData(Mesh & mesh);
 
   /// flip facets based on global connectivity
   static void
   flipFacets(Mesh & mesh_facets,
              const ElementTypeMapArray<UInt> & remote_global_connectivities,
              GhostType gt_facet);
 
   /// cosine of sharp angle between two facets
   static Real cosSharpAngleBetween2Facets(SolidMechanicsModel & model,
                                           const Element & facet1,
                                           const Element & facet2);
 
   /// compute distance between barycenters of two facets
   static Real distanceBetween2Barycenters(const Mesh & mesh_facet1,
                                           const Mesh & mesh_facet2,
                                           const Element & facet1,
                                           const Element & facet2);
 
-  /// compute distance between barycenters of alligned facets
-  static Real distanceBetweenBarycentersCorrected(const Mesh & mesh_facet,
-                                                  const Element & facet1,
-                                                  const Element & facet2);
+  /// compute distance between incenters of alligned facets
+  static Real distanceBetweenIncentersCorrected(const Mesh & mesh_facet,
+                                                const Element & facet1,
+                                                const Element & facet2);
+
+  /// compute distance between incenters of 2 random facets
+  static Real distanceBetweenIncenters(const Mesh & mesh_facet,
+                                       const Element & facet1,
+                                       const Element & facet2);
 
   /// get inscribed circle diameter directly by the element
   static Real getInscribedCircleDiameter(SolidMechanicsModel & model,
                                          const Element & el);
 
+  /// get facets area
+  static Real getFacetArea(SolidMechanicsModel & model, const Element & el);
+
   /// check if two facets are connected and by which subfacets
   static std::pair<bool, Array<Element>>
   areFacetsConnected(const Mesh & mesh_facets, const Element & facet1,
                      const Element & facet2);
 
 private:
   /// match pairs that are on the associated pbc's
   static void matchPBCPairs(const Mesh & mymesh, UInt dir,
                             Array<UInt> & selected_left,
                             Array<UInt> & selected_right,
                             std::map<UInt, UInt> & pbc_pair);
 
   /// function used by all the renumbering functions
   static void
   renumberNodesInConnectivity(Array<UInt> & list_nodes, UInt nb_nodes,
                               std::map<UInt, UInt> & renumbering_map);
 
   /* ------------------------------------------------------------------------ */
   /* Accessors                                                                */
   /* ------------------------------------------------------------------------ */
 public:
   /* ------------------------------------------------------------------------ */
   /* Class Members                                                            */
   /* ------------------------------------------------------------------------ */
 private:
 };
 
 } // namespace akantu
 
 /* -------------------------------------------------------------------------- */
 /* inline functions                                                           */
 /* -------------------------------------------------------------------------- */
 #include "mesh_utils_inline_impl.hh"
 
 #endif /* AKANTU_MESH_UTILS_HH_ */
diff --git a/src/model/solid_mechanics/solid_mechanics_model_cohesive/materials/material_cohesive.hh b/src/model/solid_mechanics/solid_mechanics_model_cohesive/materials/material_cohesive.hh
index 670913275..f473779cd 100644
--- a/src/model/solid_mechanics/solid_mechanics_model_cohesive/materials/material_cohesive.hh
+++ b/src/model/solid_mechanics/solid_mechanics_model_cohesive/materials/material_cohesive.hh
@@ -1,259 +1,259 @@
 /**
  * @file   material_cohesive.hh
  *
  * @author Nicolas Richart <nicolas.richart@epfl.ch>
  * @author Seyedeh Mohadeseh Taheri Mousavi <mohadeseh.taherimousavi@epfl.ch>
  * @author Marco Vocialta <marco.vocialta@epfl.ch>
  *
  * @date creation: Fri Jun 18 2010
  * @date last modification: Wed Feb 21 2018
  *
  * @brief  Specialization of the material class for cohesive elements
  *
  *
  * Copyright (©)  2010-2018 EPFL (Ecole Polytechnique Fédérale de Lausanne)
  * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
  *
  * Akantu 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.
  *
  * Akantu 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 Akantu. If not, see <http://www.gnu.org/licenses/>.
  *
  */
 
 /* -------------------------------------------------------------------------- */
 #include "material.hh"
 /* -------------------------------------------------------------------------- */
 #include "cohesive_internal_field.hh"
 /* -------------------------------------------------------------------------- */
 
 #ifndef AKANTU_MATERIAL_COHESIVE_HH_
 #define AKANTU_MATERIAL_COHESIVE_HH_
 
 /* -------------------------------------------------------------------------- */
 namespace akantu {
 class SolidMechanicsModelCohesive;
 }
 
 namespace akantu {
 
 class MaterialCohesive : public Material {
   /* ------------------------------------------------------------------------ */
   /* Constructors/Destructors                                                 */
   /* ------------------------------------------------------------------------ */
 public:
   using MyFEEngineCohesiveType =
       FEEngineTemplate<IntegratorGauss, ShapeLagrange, _ek_cohesive>;
 
 public:
   MaterialCohesive(SolidMechanicsModel & model, const ID & id = "");
   ~MaterialCohesive() override;
 
   /* ------------------------------------------------------------------------ */
   /* Methods                                                                  */
   /* ------------------------------------------------------------------------ */
 public:
   /// initialize the material computed parameter
   void initMaterial() override;
 
   /// compute tractions (including normals and openings)
   void computeTraction(GhostType ghost_type = _not_ghost);
 
   /// assemble residual
   void assembleInternalForces(GhostType ghost_type = _not_ghost) override;
 
   /// check stress for cohesive elements' insertion, by default it
   /// also updates the cohesive elements' data
   virtual void checkInsertion(bool /*check_only*/ = false) {
     AKANTU_TO_IMPLEMENT();
   }
 
   /// interpolate   stress  on   given   positions  for   each  element   (empty
   /// implemantation to avoid the generic call to be done on cohesive elements)
   virtual void interpolateStress(const ElementType /*type*/,
                                  Array<Real> & /*result*/) {}
 
   /// compute the stresses
   void computeAllStresses(GhostType /*ghost_type*/ = _not_ghost) override{};
 
   // add the facet to be handled by the material
   UInt addFacet(const Element & element);
 
 protected:
   virtual void computeTangentTraction(ElementType /*el_type*/,
                                       Array<Real> & /*tangent_matrix*/,
                                       const Array<Real> & /*normal*/,
                                       GhostType /*ghost_type*/ = _not_ghost) {
     AKANTU_TO_IMPLEMENT();
   }
 
   /// compute the normal
   void computeNormal(const Array<Real> & position, Array<Real> & normal,
                      ElementType type, GhostType ghost_type);
 
   /// compute the opening
   void computeOpening(const Array<Real> & displacement, Array<Real> & opening,
                       ElementType type, GhostType ghost_type);
 
   template <ElementType type>
   void computeNormal(const Array<Real> & position, Array<Real> & normal,
                      GhostType ghost_type);
 
   /// assemble stiffness
   void assembleStiffnessMatrix(GhostType ghost_type) override;
 
   /// constitutive law
   virtual void computeTraction(const Array<Real> & normal, ElementType el_type,
                                GhostType ghost_type = _not_ghost) = 0;
 
   /// parallelism functions
   inline UInt getNbData(const Array<Element> & elements,
                         const SynchronizationTag & tag) const override;
 
   inline void packData(CommunicationBuffer & buffer,
                        const Array<Element> & elements,
                        const SynchronizationTag & tag) const override;
 
   inline void unpackData(CommunicationBuffer & buffer,
                          const Array<Element> & elements,
                          const SynchronizationTag & tag) override;
 
 protected:
   void updateEnergies(ElementType el_type) override;
 
   /* ------------------------------------------------------------------------ */
   /* Accessors                                                                */
   /* ------------------------------------------------------------------------ */
 public:
   /// get the opening
   AKANTU_GET_MACRO_BY_ELEMENT_TYPE_CONST(Opening, opening, Real);
 
   /// get the eigen opening
   AKANTU_GET_MACRO_BY_ELEMENT_TYPE(EigenOpening, eigen_opening, Real);
 
   /// get the contact opening
   AKANTU_GET_MACRO_BY_ELEMENT_TYPE_CONST(ContactOpening, contact_opening, Real);
 
   /// get the normal opening
   AKANTU_GET_MACRO_BY_ELEMENT_TYPE_CONST(NormalOpeningNorm, normal_opening_norm,
                                          Real);
 
   /// get the eigen opening
   AKANTU_GET_MACRO_BY_ELEMENT_TYPE_CONST(DeltaMax, delta_max, Real);
 
   /// get the normals
   AKANTU_GET_MACRO_BY_ELEMENT_TYPE_CONST(Normals, normals, Real);
 
   /// get the traction
   AKANTU_GET_MACRO_BY_ELEMENT_TYPE_CONST(Traction, tractions, Real);
 
   /// get damage
   AKANTU_GET_MACRO_BY_ELEMENT_TYPE_CONST(Damage, damage, Real);
 
   /// get facet filter
   AKANTU_GET_MACRO_BY_ELEMENT_TYPE_CONST(FacetFilter, facet_filter, UInt);
   AKANTU_GET_MACRO_BY_ELEMENT_TYPE(FacetFilter, facet_filter, UInt);
   AKANTU_GET_MACRO(FacetFilter, facet_filter,
                    const ElementTypeMapArray<UInt> &);
   // AKANTU_GET_MACRO(ElementFilter, element_filter, const
   // ElementTypeMapArray<UInt> &);
 
   /// compute reversible energy
   Real getReversibleEnergy();
 
   /// compute dissipated energy
   Real getDissipatedEnergy();
 
   /// compute contact energy
   Real getContactEnergy();
 
   /// get energy
   Real getEnergy(const std::string & type) override;
 
   /// return the energy (identified by id) for the provided element
   Real getEnergy(const std::string & energy_id, ElementType type,
                  UInt index) override {
     return Material::getEnergy(energy_id, type, index);
   }
 
   /* ------------------------------------------------------------------------ */
   /* Class Members                                                            */
   /* ------------------------------------------------------------------------ */
 protected:
   /// list of facets assigned to this material
   ElementTypeMapArray<UInt> facet_filter;
 
   /// Link to the cohesive fem object in the model
   FEEngine & fem_cohesive;
 
 private:
   /// reversible energy by quadrature point
   CohesiveInternalField<Real> reversible_energy;
 
   /// total energy by quadrature point
   CohesiveInternalField<Real> total_energy;
 
 protected:
   /// opening in all elements and quadrature points
   CohesiveInternalField<Real> opening;
 
-  /// eigen opening (fix value removed from the computed opening)
+  /// eigen opening in all elements and quadrature points
   CohesiveInternalField<Real> eigen_opening;
 
   /// traction in all elements and quadrature points
   CohesiveInternalField<Real> tractions;
 
   /// traction due to contact
   CohesiveInternalField<Real> contact_tractions;
 
   /// normal openings for contact tractions
   CohesiveInternalField<Real> contact_opening;
 
   /// normal openings
   CohesiveInternalField<Real> normal_opening_norm;
 
   /// tangential openings
   CohesiveInternalField<Real> tangential_opening_norm;
 
   /// maximum displacement
   CohesiveInternalField<Real> delta_max;
 
   /// tell if the previous delta_max state is needed (in iterative schemes)
   bool use_previous_delta_max;
 
   /// tell if the previous opening state is needed (in iterative schemes)
   bool use_previous_opening;
 
   /// damage
   CohesiveInternalField<Real> damage;
 
   /// pointer to the solid mechanics model for cohesive elements
   SolidMechanicsModelCohesive * model;
 
   /// critical stress
   RandomInternalField<Real, FacetInternalField> sigma_c;
 
   /// critical displacement
   Real delta_c;
 
   /// array to temporarily store the normals
   CohesiveInternalField<Real> normals;
 };
 
 } // namespace akantu
 
 /* -------------------------------------------------------------------------- */
 /* inline functions                                                           */
 /* -------------------------------------------------------------------------- */
 #include "cohesive_internal_field_tmpl.hh"
 #include "material_cohesive_inline_impl.hh"
 
 #endif /* AKANTU_MATERIAL_COHESIVE_HH_ */
diff --git a/src/synchronizer/element_synchronizer.cc b/src/synchronizer/element_synchronizer.cc
index 80db73a6a..9b995da40 100644
--- a/src/synchronizer/element_synchronizer.cc
+++ b/src/synchronizer/element_synchronizer.cc
@@ -1,294 +1,294 @@
 /**
  * @file   element_synchronizer.cc
  *
  * @author Guillaume Anciaux <guillaume.anciaux@epfl.ch>
  * @author Dana Christen <dana.christen@epfl.ch>
  * @author Aurelia Isabel Cuba Ramos <aurelia.cubaramos@epfl.ch>
  * @author Nicolas Richart <nicolas.richart@epfl.ch>
  * @author Marco Vocialta <marco.vocialta@epfl.ch>
  *
  * @date creation: Wed Sep 01 2010
  * @date last modification: Tue Feb 20 2018
  *
  * @brief  implementation of a  communicator using a static_communicator for
  * real
  * send/receive
  *
  *
  * Copyright (©)  2010-2018 EPFL (Ecole Polytechnique Fédérale de Lausanne)
  * Laboratory (LSMS - Laboratoire de Simulation en Mécanique des Solides)
  *
  * Akantu 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.
  *
  * Akantu 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 Akantu. If not, see <http://www.gnu.org/licenses/>.
  *
  */
 
 /* -------------------------------------------------------------------------- */
 #include "element_synchronizer.hh"
 #include "aka_common.hh"
 #include "mesh.hh"
 #include "mesh_utils.hh"
 /* -------------------------------------------------------------------------- */
 #include <algorithm>
 #include <iostream>
 #include <map>
 /* -------------------------------------------------------------------------- */
 
 namespace akantu {
 
 #if defined(AKANTU_MODULE)
 #define AKANTU_MODULE_SAVE_ AKANTU_MODULE
 #undef AKANTU_MODULE
 #endif
 
 #define AKANTU_MODULE element_synchronizer
 
 /* -------------------------------------------------------------------------- */
 ElementSynchronizer::ElementSynchronizer(Mesh & mesh, const ID & id,
                                          MemoryID memory_id,
                                          bool register_to_event_manager,
                                          EventHandlerPriority event_priority)
     : SynchronizerImpl<Element>(mesh.getCommunicator(), id, memory_id),
       mesh(mesh), element_to_prank("element_to_prank", id, memory_id) {
   AKANTU_DEBUG_IN();
 
   if (register_to_event_manager) {
     this->mesh.registerEventHandler(*this, event_priority);
   }
 
   AKANTU_DEBUG_OUT();
 }
 
 /* -------------------------------------------------------------------------- */
 ElementSynchronizer::ElementSynchronizer(const ElementSynchronizer & other,
                                          const ID & id,
                                          bool register_to_event_manager,
                                          EventHandlerPriority event_priority)
     : SynchronizerImpl<Element>(other, id), mesh(other.mesh),
       element_to_prank("element_to_prank", id, other.memory_id) {
   AKANTU_DEBUG_IN();
 
   element_to_prank.copy(other.element_to_prank);
 
   if (register_to_event_manager) {
     this->mesh.registerEventHandler(*this, event_priority);
   }
 
   AKANTU_DEBUG_OUT();
 }
 
 /* -------------------------------------------------------------------------- */
 ElementSynchronizer::~ElementSynchronizer() = default;
 
 /* -------------------------------------------------------------------------- */
 void ElementSynchronizer::substituteElements(
     const std::map<Element, Element> & old_to_new_elements) {
   auto found_element_end = old_to_new_elements.end();
 
   // substitute old elements with new ones
   for (auto && sr : iterate_send_recv) {
     for (auto && scheme_pair : communications.iterateSchemes(sr)) {
       auto & list = scheme_pair.second;
       for (auto & el : list) {
         auto found_element_it = old_to_new_elements.find(el);
         if (found_element_it != found_element_end) {
           el = found_element_it->second;
         }
       }
     }
   }
 }
 
 /* -------------------------------------------------------------------------- */
 void ElementSynchronizer::onElementsChanged(
     const Array<Element> & old_elements_list,
     const Array<Element> & new_elements_list,
     const ElementTypeMapArray<UInt> & /*unused*/,
     const ChangedElementsEvent & /*unused*/) {
   // create a map to link old elements to new ones
   std::map<Element, Element> old_to_new_elements;
 
   for (UInt el = 0; el < old_elements_list.size(); ++el) {
     AKANTU_DEBUG_ASSERT(old_to_new_elements.find(old_elements_list(el)) ==
                             old_to_new_elements.end(),
                         "The same element cannot appear twice in the list");
 
     old_to_new_elements[old_elements_list(el)] = new_elements_list(el);
   }
 
   substituteElements(old_to_new_elements);
 
   communications.invalidateSizes();
 }
 
 /* -------------------------------------------------------------------------- */
 void ElementSynchronizer::onElementsRemoved(
     const Array<Element> & element_to_remove,
     const ElementTypeMapArray<UInt> & new_numbering,
     const RemovedElementsEvent & /*unused*/) {
   AKANTU_DEBUG_IN();
 
   this->filterScheme([&](auto && element) {
     return std::find(element_to_remove.begin(), element_to_remove.end(),
                      element) == element_to_remove.end();
   });
 
   this->renumberElements(new_numbering);
 
   communications.invalidateSizes();
 
   AKANTU_DEBUG_OUT();
 }
 
 /* -------------------------------------------------------------------------- */
 void ElementSynchronizer::buildElementToPrank() {
   AKANTU_DEBUG_IN();
 
   UInt spatial_dimension = mesh.getSpatialDimension();
   element_to_prank.initialize(mesh, _spatial_dimension = spatial_dimension,
                               _element_kind = _ek_not_defined,
                               _with_nb_element = true, _default_value = rank);
 
   /// assign prank to all ghost elements
   for (auto && scheme : communications.iterateSchemes(_recv)) {
     auto & recv = scheme.second;
     auto proc = scheme.first;
 
     for (auto & element : recv) {
       element_to_prank(element) = proc;
     }
   }
 
   AKANTU_DEBUG_OUT();
 }
 
 /* -------------------------------------------------------------------------- */
 Int ElementSynchronizer::getRank(const Element & element) const {
   if (not element_to_prank.exists(element.type, element.ghost_type)) {
     // Nicolas: Ok This is nasty I know....
     const_cast<ElementSynchronizer *>(this)->buildElementToPrank();
   }
 
   return element_to_prank(element);
 }
 
 /* -------------------------------------------------------------------------- */
 void ElementSynchronizer::renumberElements(
     const ElementTypeMapArray<UInt> & new_numbering) {
   for (auto && sr : iterate_send_recv) {
     for (auto && scheme_pair : communications.iterateSchemes(sr)) {
       auto & list = scheme_pair.second;
       for (auto && el : list) {
         if (new_numbering.exists(el.type, el.ghost_type)) {
           el.element = new_numbering(el);
         }
       }
     }
   }
 }
 
 /* -------------------------------------------------------------------------- */
 UInt ElementSynchronizer::sanityCheckDataSize(const Array<Element> & elements,
                                               const SynchronizationTag & tag,
                                               bool from_comm_desc) const {
   UInt size = SynchronizerImpl<Element>::sanityCheckDataSize(elements, tag,
                                                              from_comm_desc);
 
   // global connectivities;
   size += mesh.getNbNodesPerElementList(elements) * sizeof(UInt);
 
   // barycenters
   size += (elements.size() * mesh.getSpatialDimension() * sizeof(Real));
   return size;
 }
 
 /* -------------------------------------------------------------------------- */
 void ElementSynchronizer::packSanityCheckData(
     CommunicationBuffer & buffer, const Array<Element> & elements,
     const SynchronizationTag & /*tag*/) const {
   for (auto && element : elements) {
     Vector<Real> barycenter(mesh.getSpatialDimension());
     mesh.getBarycenter(element, barycenter);
     buffer << barycenter;
 
     const auto & conns = mesh.getConnectivity(element.type, element.ghost_type);
     for (auto n : arange(conns.getNbComponent())) {
       buffer << mesh.getNodeGlobalId(conns(element.element, n));
     }
   }
 }
 
 /* -------------------------------------------------------------------------- */
 void ElementSynchronizer::unpackSanityCheckData(CommunicationBuffer & buffer,
                                                 const Array<Element> & elements,
                                                 const SynchronizationTag & tag,
                                                 UInt proc, UInt rank) const {
   auto spatial_dimension = mesh.getSpatialDimension();
 
   std::set<SynchronizationTag> skip_conn_tags{
       SynchronizationTag::_smmc_facets_conn,
-      SynchronizationTag::_giu_global_conn};
+      SynchronizationTag::_giu_global_conn, SynchronizationTag::_border_nodes};
 
   bool is_skip_tag_conn = skip_conn_tags.find(tag) != skip_conn_tags.end();
 
   for (auto && element : elements) {
     Vector<Real> barycenter_loc(spatial_dimension);
     mesh.getBarycenter(element, barycenter_loc);
 
     Vector<Real> barycenter(spatial_dimension);
     buffer >> barycenter;
 
     auto dist = barycenter_loc.distance(barycenter);
     if (not Math::are_float_equal(dist, 0.)) {
       AKANTU_EXCEPTION("Unpacking an unknown value for the element "
                        << element << "(barycenter " << barycenter_loc
                        << " != buffer " << barycenter << ") [" << dist
                        << "] - tag: " << tag << " comm from " << proc << " to "
                        << rank);
     }
 
     const auto & conns = mesh.getConnectivity(element.type, element.ghost_type);
     Vector<UInt> global_conn(conns.getNbComponent());
     Vector<UInt> local_global_conn(conns.getNbComponent());
 
     auto is_same = true;
     for (auto n : arange(global_conn.size())) {
       buffer >> global_conn(n);
 
       auto node = conns(element.element, n);
       local_global_conn(n) = mesh.getNodeGlobalId(node);
 
       is_same &= is_skip_tag_conn or mesh.isPureGhostNode(node) or
                  (local_global_conn(n) == global_conn(n));
     }
 
     if (not is_same) {
       AKANTU_DEBUG_WARNING(
           "The connectivity of the element "
           << element << " " << local_global_conn
           << " does not match the connectivity of the equivalent "
              "element on proc "
           << proc << " " << global_conn << " in communication with tag "
           << tag);
     }
   }
 }
 
 /* -------------------------------------------------------------------------- */
 } // namespace akantu
 
 #if defined(AKANTU_MODULE_SAVE_)
 #undef AKANTU_MODULE
 #define AKANTU_MODULE AKANTU_MODULE_SAVE_
 #undef AKANTU_MODULE_SAVE_
 #endif