diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b7a6290c35d22368bda40551dcb32c0be0fbfea8..c34fa57cd936e6e9a5fadf1bf3b45785aa26abbf 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -25,5 +25,5 @@ test:coverage:
     - build:coverage
   script:
     - cd build
-    - make unit_tests
+    - make run_unit_tests
     - make coverage
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3fe16ca68deb862550e1f0bf3bd4bc2124e48a92..53ae505bca6492f407a895ef267322bf738454cf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -31,14 +31,23 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
 set(PASTIS_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
 set(PASTIS_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}")
 
+# Change RelWithDebInfo to compile assertions
+SET("CMAKE_CXX_FLAGS_RELWITHDEBINFO"
+   "-g -O2"
+  CACHE STRING "Flags used by the compiler during release builds with debug info and assertions"
+  FORCE )
+SET("CMAKE_C_FLAGS_RELWITHDEBINFO"
+   "-g -O2"
+  CACHE STRING "Flags used by the compiler during release builds with debug info and assertions"
+  FORCE )
+
 # Add new build types
-message("* Adding build types...")
 set(CMAKE_CXX_FLAGS_COVERAGE
-  "-g -Wall -O0 --coverage"
+  "-g -O0 --coverage"
   CACHE STRING "Flags used by the C++ compiler during coverage builds."
   FORCE )
 set(CMAKE_C_FLAGS_COVERAGE
-  "-g -Wall -O0 --coverage"
+  "-g -O0 --coverage"
   CACHE STRING "Flags used by the C compiler during coverage builds."
   FORCE )
 set(CMAKE_EXE_LINKER_FLAGS_COVERAGE
@@ -89,6 +98,28 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
   set(PASTIS_CXX_FLAGS "${PASTIS_CXX_FLAGS} -Wsign-compare -Wunused -Wunused-member-function -Wunused-private-field")
 endif()
 
+#------------------------------------------------------
+# defaults use of MPI
+set(PASTIS_ENABLE_MPI AUTO CACHE STRING
+  "Choose one of: AUTO ON OFF")
+
+if (NOT PASTIS_ENABLE_MPI MATCHES "^(AUTO|ON|OFF)$")
+  message(FATAL_ERROR "PASTIS_ENABLE_MPI='${PASTIS_ENABLE_MPI}'. Must be set to one of AUTO, ON or OFF")
+endif()
+
+# checks for MPI
+if (PASTIS_ENABLE_MPI MATCHES "^(AUTO|ON)$")
+  find_package(MPI)
+endif()
+
+if (${MPI_FOUND})
+  set(PASTIS_CXX_FLAGS "${PASTIS_CXX_FLAGS} ${MPI_CXX_COMPILER_FLAGS}")
+  include_directories(SYSTEM ${MPI_CXX_INCLUDE_DIRS})
+elseif(PASTIS_ENABLE_MPI STREQUAL "ON")
+  message(FATAL_ERROR "Could not find MPI library while requested")
+endif()
+
+set(PASTIS_HAS_MPI ${MPI_FOUND})
 
 #------------------------------------------------------
 
@@ -113,7 +144,7 @@ include(GetKokkosCompilerFlags)
 set_target_properties(kokkos PROPERTIES COMPILE_FLAGS "-w")
 
 # sets Kokkos debug flags when non release build
-if (CMAKE_BUILD_TYPE MATCHES "Release")
+if (CMAKE_BUILD_TYPE MATCHES "^Release$")
   set (KOKKOS_ENABLE_DEBUG OFF)
 else()
   set (KOKKOS_ENABLE_DEBUG ON)
@@ -155,6 +186,9 @@ include_directories(src/output)
 include_directories(src/utils)
 include_directories(src/scheme)
 
+# Pastis generated sources
+include_directories(${PASTIS_BINARY_DIR}/src/utils)
+
 # Pastis tests
 
 set(CATCH_MODULE_PATH "${PASTIS_SOURCE_DIR}/packages/Catch2")
@@ -194,13 +228,13 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Coverage")
   add_custom_target(run_unit_tests
     ALL
     COMMAND ${CMAKE_CTEST_COMMAND} -j ${PROCESSOR_COUNT}
-    DEPENDS unit_tests pastis
+    DEPENDS unit_tests mpi_unit_tests pastis
     COMMENT "Executing unit tests."
     )
 
   add_custom_target(coverage
     ALL
-    COMMAND ${GCOVR} ${GCOVR_OPTIONS}
+    COMMAND ${GCOVR} ${GCOVR_OPTIONS} --exclude-unreachable-branches --sort-percentage
     DEPENDS run_unit_tests
     COMMENT "Running gcovr to build coverage report."
     )
@@ -217,6 +251,13 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Coverage")
 
 endif()
 
+#------------------------------------------------------
+# Search for ParMETIS
+
+if(${MPI_FOUND})
+  find_package(ParMETIS REQUIRED)
+endif()
+
 # -----------------------------------------------------
 
 link_libraries("-rdynamic")
@@ -230,6 +271,9 @@ add_executable(
 # Libraries
 target_link_libraries(
   pastis
-  kokkos
+  PastisMesh
   PastisUtils
-  PastisMesh)
+  kokkos
+  ${PARMETIS_LIBRARIES}
+  ${MPI_CXX_LINK_FLAGS} ${MPI_CXX_LIBRARIES}
+)
diff --git a/cmake/FindParMETIS.cmake b/cmake/FindParMETIS.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..54269b0ae7d3140dbd0900a4ad1cf6b4924d7128
--- /dev/null
+++ b/cmake/FindParMETIS.cmake
@@ -0,0 +1,22 @@
+# Looking for ParMETIS
+
+find_path(PARMETIS_INCLUDE_DIR parmetis.h
+  PATH_SUFFIX include parmetis)
+
+if (EXISTS "${PARMETIS_INCLUDE_DIR}/parmetis.h")
+  message("-- Found parmetis.h in ${PARMETIS_INCLUDE_DIR}")
+  find_library(LIB_PARMETIS parmetis)
+  if("${LIB_PARMETIS}" STREQUAL "LIB_PARMETIS-NOTFOUND")
+    message(FATAL_ERROR "Could not find parmetis library")
+  endif()
+  find_library(LIB_METIS metis)
+  if("${LIB_PARMETIS}" STREQUAL "LIB_METIS-NOTFOUND")
+    message(FATAL_ERROR "Could not find metis library")
+  endif()
+  set(PARMETIS_LIBRARIES ${LIB_PARMETIS} ${LIB_METIS})
+  message("-- Found parmetis/metis libraries ${PARMETIS_LIBRARIES}")
+else()
+  message(FATAL_ERROR "Could not find parmetis.h")
+endif()
+
+mark_as_advanced(PARMETIS_INCLUDE_DIR PARMETIS_LIBRARIES)
diff --git a/src/algebra/TinyMatrix.hpp b/src/algebra/TinyMatrix.hpp
index e693a70f75771a59ddeed83fab2e4f3b7c5883b0..2dde6fc1ac9d72a298b4157d0388fe3045103f16 100644
--- a/src/algebra/TinyMatrix.hpp
+++ b/src/algebra/TinyMatrix.hpp
@@ -12,7 +12,10 @@
 template <size_t N, typename T=double>
 class TinyMatrix
 {
-private:
+ public:
+  using data_type = T;
+
+ private:
   T m_values[N*N];
   static_assert((N>0),"TinyMatrix size must be strictly positive");
 
@@ -173,6 +176,14 @@ public:
     return *this;
   }
 
+  PASTIS_INLINE
+  constexpr void operator+=(const volatile TinyMatrix& A) volatile
+  {
+    for (size_t i=0; i<N*N; ++i) {
+      m_values[i] += A.m_values[i];
+    }
+  }
+
   PASTIS_INLINE
   constexpr TinyMatrix& operator-=(const TinyMatrix& A)
   {
@@ -219,13 +230,7 @@ public:
   }
 
   PASTIS_INLINE
-  constexpr TinyMatrix& operator=(const TinyMatrix& A) noexcept
-  {
-    for (size_t i=0; i<N*N; ++i) {
-      m_values[i] = A.m_values[i];
-    }
-    return *this;
-  }
+  constexpr TinyMatrix& operator=(const TinyMatrix& A) noexcept = default;
 
   PASTIS_INLINE
   constexpr TinyMatrix& operator=(TinyMatrix&& A) noexcept = default;
@@ -238,11 +243,10 @@ public:
     this->_unpackVariadicInput(t, std::forward<Args>(args)...);
   }
 
+  // One does not use the '=default' constructor to avoid (unexpected)
+  // performances issues
   PASTIS_INLINE
-  constexpr TinyMatrix() noexcept
-  {
-    ;
-  }
+  constexpr TinyMatrix() noexcept {}
 
   PASTIS_INLINE
   constexpr TinyMatrix(const ZeroType&) noexcept
@@ -265,18 +269,13 @@ public:
   }
 
   PASTIS_INLINE
-  constexpr TinyMatrix(const TinyMatrix& A) noexcept
-  {
-    for (size_t i=0; i<N*N; ++i) {
-      m_values[i] = A.m_values[i];
-    }
-  }
+  constexpr TinyMatrix(const TinyMatrix&) noexcept = default;
 
   PASTIS_INLINE
   TinyMatrix(TinyMatrix&& A) noexcept = default;
 
   PASTIS_INLINE
-  ~TinyMatrix()=default;
+  ~TinyMatrix() = default;
 };
 
 template <size_t N, typename T>
@@ -297,7 +296,7 @@ template <size_t N, typename T>
 PASTIS_INLINE
 constexpr T det(const TinyMatrix<N,T>& A)
 {
-  static_assert(std::is_arithmetic<T>::value, "determinent is not defined for non arithmetic types");
+  static_assert(std::is_arithmetic<T>::value, "determinent is not defined for non-arithmetic types");
   static_assert(std::is_floating_point<T>::value, "determinent for arbitrary dimension N is defined for floating point types only");
   TinyMatrix<N,T> M = A;
 
@@ -341,7 +340,7 @@ template <typename T>
 PASTIS_INLINE
 constexpr T det(const TinyMatrix<1,T>& A)
 {
-  static_assert(std::is_arithmetic<T>::value, "determinent is not defined for non arithmetic types");
+  static_assert(std::is_arithmetic<T>::value, "determinent is not defined for non-arithmetic types");
   return A(0,0);
 }
 
@@ -349,7 +348,7 @@ template <typename T>
 PASTIS_INLINE
 constexpr T det(const TinyMatrix<2,T>& A)
 {
-  static_assert(std::is_arithmetic<T>::value, "determinent is not defined for non arithmetic types");
+  static_assert(std::is_arithmetic<T>::value, "determinent is not defined for non-arithmetic types");
   return A(0,0)*A(1,1)-A(1,0)*A(0,1);
 }
 
@@ -357,7 +356,7 @@ template <typename T>
 PASTIS_INLINE
 constexpr T det(const TinyMatrix<3,T>& A)
 {
-  static_assert(std::is_arithmetic<T>::value, "determinent is not defined for non arithmetic types");
+  static_assert(std::is_arithmetic<T>::value, "determinent is not defined for non-arithmetic types");
   return
     A(0,0)*(A(1,1)*A(2,2)-A(2,1)*A(1,2))
     -A(1,0)*(A(0,1)*A(2,2)-A(2,1)*A(0,2))
@@ -372,7 +371,7 @@ constexpr TinyMatrix<N-1,T> getMinor(const TinyMatrix<N,T>& A,
 {
   static_assert(N>=2, "minor calculation requires at least 2x2 matrices");
   Assert((I<N) and (J<N));
-  TinyMatrix<N-1, T> M;
+  TinyMatrix<N-1,T> M;
   for (size_t i=0; i<I; ++i) {
     for (size_t j=0; j<J; ++j) {
       M(i,j)=A(i,j);
@@ -400,7 +399,7 @@ template <typename T>
 PASTIS_INLINE
 constexpr TinyMatrix<1,T> inverse(const TinyMatrix<1,T>& A)
 {
-  static_assert(std::is_arithmetic<T>::value, "inverse is not defined for non arithmetic types");
+  static_assert(std::is_arithmetic<T>::value, "inverse is not defined for non-arithmetic types");
   static_assert(std::is_floating_point<T>::value, "inverse is defined for floating point types only");
 
   TinyMatrix<1,T> A_1(1./A(0,0));
@@ -413,7 +412,7 @@ constexpr T cofactor(const TinyMatrix<N,T>& A,
                      const size_t& i,
                      const size_t& j)
 {
-  static_assert(std::is_arithmetic<T>::value, "cofactor is not defined for non arithmetic types");
+  static_assert(std::is_arithmetic<T>::value, "cofactor is not defined for non-arithmetic types");
   const T sign = ((i+j)%2) ? -1 : 1;
 
   return sign * det(getMinor(A, i, j));
@@ -423,7 +422,7 @@ template <typename T>
 PASTIS_INLINE
 constexpr TinyMatrix<2,T> inverse(const TinyMatrix<2,T>& A)
 {
-  static_assert(std::is_arithmetic<T>::value, "inverse is not defined for non arithmetic types");
+  static_assert(std::is_arithmetic<T>::value, "inverse is not defined for non-arithmetic types");
   static_assert(std::is_floating_point<T>::value, "inverse is defined for floating point types only");
 
   const T determinent = det(A);
@@ -439,7 +438,7 @@ template <typename T>
 PASTIS_INLINE
 constexpr TinyMatrix<3,T> inverse(const TinyMatrix<3,T>& A)
 {
-  static_assert(std::is_arithmetic<T>::value, "inverse is not defined for non arithmetic types");
+  static_assert(std::is_arithmetic<T>::value, "inverse is not defined for non-arithmetic types");
   static_assert(std::is_floating_point<T>::value, "inverse is defined for floating point types only");
 
   const T determinent = det(A);
diff --git a/src/algebra/TinyVector.hpp b/src/algebra/TinyVector.hpp
index 7b7d407eefa5bd40a0a6cc96bc11d1438ad24093..2b7a080cadb08f523bc02692b16ca228fdaca9f2 100644
--- a/src/algebra/TinyVector.hpp
+++ b/src/algebra/TinyVector.hpp
@@ -13,6 +13,9 @@
 template <size_t N, typename T=double>
 class TinyVector
 {
+ public:
+  using data_type = T;
+
  private:
   T m_values[N];
   static_assert((N>0),"TinyVector size must be strictly positive");
@@ -150,6 +153,14 @@ class TinyVector
     return *this;
   }
 
+  PASTIS_INLINE
+  constexpr void operator+=(const volatile TinyVector& v) volatile
+  {
+    for (size_t i=0; i<N; ++i) {
+      m_values[i] += v.m_values[i];
+    }
+  }
+
   PASTIS_INLINE
   constexpr TinyVector& operator-=(const TinyVector& v)
   {
@@ -184,13 +195,7 @@ class TinyVector
   }
 
   PASTIS_INLINE
-  const TinyVector& operator=(const TinyVector& v) noexcept
-  {
-    for (size_t i=0; i<N; ++i) {
-      m_values[i] = v.m_values[i];
-    }
-    return *this;
-  }
+  TinyVector& operator=(const TinyVector&) noexcept = default;
 
   PASTIS_INLINE
   constexpr TinyVector& operator=(TinyVector&& v) noexcept = default;
@@ -203,11 +208,10 @@ class TinyVector
     this->_unpackVariadicInput(t, std::forward<Args>(args)...);
   }
 
+  // One does not use the '=default' constructor to avoid (unexpected)
+  // performances issues
   PASTIS_INLINE
-  constexpr TinyVector() noexcept
-  {
-    ;
-  }
+  constexpr TinyVector() noexcept {}
 
   PASTIS_INLINE
   constexpr TinyVector(const ZeroType&) noexcept
@@ -219,12 +223,7 @@ class TinyVector
   }
 
   PASTIS_INLINE
-  constexpr TinyVector(const TinyVector& v) noexcept
-  {
-    for (size_t i=0; i<N; ++i) {
-      m_values[i] = v.m_values[i];
-    }
-  }
+  constexpr TinyVector(const TinyVector&) noexcept = default;
 
   PASTIS_INLINE
   constexpr TinyVector(TinyVector&& v) noexcept = default;
diff --git a/src/main.cpp b/src/main.cpp
index a0ab842a3ca1b0961ce64fddfeb1fb04d68cf714..951eddd4362b509cb2eadde3f97b470895473163 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -22,351 +22,362 @@
 
 #include <GmshReader.hpp>
 
+#include <SynchronizerManager.hpp>
+
 #include <limits>
 #include <map>
 
 int main(int argc, char *argv[])
 {
   std::string filename = initialize(argc, argv);
-
   std::map<std::string, double> method_cost_map;
 
-  try  {
-    if (filename != "") {
-      pout() << "Reading (gmsh) " << rang::style::underline << filename << rang::style::reset << " ...\n";
-      Timer gmsh_timer;
-      gmsh_timer.reset();
-      GmshReader gmsh_reader(filename);
-      method_cost_map["Mesh building"] = gmsh_timer.seconds();
-
-      std::shared_ptr<IMesh> p_mesh = gmsh_reader.mesh();
-
-      switch (p_mesh->meshDimension()) {
-        case 1: {
-          std::vector<std::string> sym_boundary_name_list = {"XMIN", "XMAX"};
-          std::vector<std::shared_ptr<BoundaryConditionDescriptor>> bc_descriptor_list;
-          for (const auto& sym_boundary_name : sym_boundary_name_list){
-            std::shared_ptr<BoundaryDescriptor> boudary_descriptor
-                = std::shared_ptr<BoundaryDescriptor>(new NamedBoundaryDescriptor(sym_boundary_name));
-            SymmetryBoundaryConditionDescriptor* sym_bc_descriptor
-                = new SymmetryBoundaryConditionDescriptor(boudary_descriptor);
-
-            bc_descriptor_list.push_back(std::shared_ptr<BoundaryConditionDescriptor>(sym_bc_descriptor));
-          }
+  SynchronizerManager::create();
+
+  if (filename != "") {
+    pout() << "Reading (gmsh) " << rang::style::underline << filename << rang::style::reset << " ...\n";
+    Timer gmsh_timer;
+    gmsh_timer.reset();
+    GmshReader gmsh_reader(filename);
+    method_cost_map["Mesh building"] = gmsh_timer.seconds();
 
-          using ConnectivityType = Connectivity1D;
-          using MeshType = Mesh<ConnectivityType>;
-          using MeshDataType = MeshData<MeshType>;
-          using UnknownsType = FiniteVolumesEulerUnknowns<MeshDataType>;
-
-          const MeshType& mesh = dynamic_cast<const MeshType&>(*gmsh_reader.mesh());
-
-          Timer timer;
-          timer.reset();
-          MeshDataType mesh_data(mesh);
-
-          std::vector<BoundaryConditionHandler> bc_list;
-          {
-            for (const auto& bc_descriptor : bc_descriptor_list) {
-              switch (bc_descriptor->type()) {
-                case BoundaryConditionDescriptor::Type::symmetry: {
-                  const SymmetryBoundaryConditionDescriptor& sym_bc_descriptor
-                      = dynamic_cast<const SymmetryBoundaryConditionDescriptor&>(*bc_descriptor);
-                  for (size_t i_ref_node_list=0; i_ref_node_list<mesh.connectivity().numberOfRefNodeList();
-                       ++i_ref_node_list) {
-                    const RefNodeList& ref_node_list = mesh.connectivity().refNodeList(i_ref_node_list);
-                    const RefId& ref = ref_node_list.refId();
-                    if (ref == sym_bc_descriptor.boundaryDescriptor()) {
-                      SymmetryBoundaryCondition<MeshType::dimension>* sym_bc
-                          = new SymmetryBoundaryCondition<MeshType::dimension>(MeshFlatNodeBoundary<MeshType::dimension>(mesh, ref_node_list));
-                      std::shared_ptr<SymmetryBoundaryCondition<MeshType::dimension>> bc(sym_bc);
-                      bc_list.push_back(BoundaryConditionHandler(bc));
-                    }
+    std::shared_ptr<IMesh> p_mesh = gmsh_reader.mesh();
+
+    switch (p_mesh->dimension()) {
+      case 1: {
+        std::vector<std::string> sym_boundary_name_list = {"XMIN", "XMAX"};
+        std::vector<std::shared_ptr<BoundaryConditionDescriptor>> bc_descriptor_list;
+        for (const auto& sym_boundary_name : sym_boundary_name_list){
+          std::shared_ptr<BoundaryDescriptor> boudary_descriptor
+              = std::shared_ptr<BoundaryDescriptor>(new NamedBoundaryDescriptor(sym_boundary_name));
+          SymmetryBoundaryConditionDescriptor* sym_bc_descriptor
+              = new SymmetryBoundaryConditionDescriptor(boudary_descriptor);
+
+          bc_descriptor_list.push_back(std::shared_ptr<BoundaryConditionDescriptor>(sym_bc_descriptor));
+        }
+
+        using ConnectivityType = Connectivity1D;
+        using MeshType = Mesh<ConnectivityType>;
+        using MeshDataType = MeshData<MeshType>;
+        using UnknownsType = FiniteVolumesEulerUnknowns<MeshDataType>;
+
+        const MeshType& mesh = dynamic_cast<const MeshType&>(*gmsh_reader.mesh());
+
+        Timer timer;
+        timer.reset();
+        MeshDataType mesh_data(mesh);
+
+        std::vector<BoundaryConditionHandler> bc_list;
+        {
+          for (const auto& bc_descriptor : bc_descriptor_list) {
+            switch (bc_descriptor->type()) {
+              case BoundaryConditionDescriptor::Type::symmetry: {
+                const SymmetryBoundaryConditionDescriptor& sym_bc_descriptor
+                    = dynamic_cast<const SymmetryBoundaryConditionDescriptor&>(*bc_descriptor);
+                for (size_t i_ref_node_list=0; i_ref_node_list<mesh.connectivity().numberOfRefItemList<ItemType::node>();
+                     ++i_ref_node_list) {
+                  const RefNodeList& ref_node_list = mesh.connectivity().refItemList<ItemType::node>(i_ref_node_list);
+                  const RefId& ref = ref_node_list.refId();
+                  if (ref == sym_bc_descriptor.boundaryDescriptor()) {
+                    SymmetryBoundaryCondition<MeshType::Dimension>* sym_bc
+                        = new SymmetryBoundaryCondition<MeshType::Dimension>(MeshFlatNodeBoundary<MeshType::Dimension>(mesh, ref_node_list));
+                    std::shared_ptr<SymmetryBoundaryCondition<MeshType::Dimension>> bc(sym_bc);
+                    bc_list.push_back(BoundaryConditionHandler(bc));
                   }
-                  break;
-                }
-                default: {
-                  perr() << "Unknown BCDescription\n";
-                  std::exit(1);
                 }
+                break;
+              }
+              default: {
+                perr() << "Unknown BCDescription\n";
+                std::exit(1);
               }
             }
           }
+        }
 
-          UnknownsType unknowns(mesh_data);
-
-          unknowns.initializeSod();
+        UnknownsType unknowns(mesh_data);
 
-          AcousticSolver<MeshDataType> acoustic_solver(mesh_data, bc_list);
+        unknowns.initializeSod();
 
-          using Rd = TinyVector<MeshType::dimension>;
+        AcousticSolver<MeshDataType> acoustic_solver(mesh_data, bc_list);
 
-          const CellValue<const double>& Vj = mesh_data.Vj();
+        using Rd = TinyVector<MeshType::Dimension>;
 
-          const double tmax=0.2;
-          double t=0;
+        const CellValue<const double>& Vj = mesh_data.Vj();
 
-          int itermax=std::numeric_limits<int>::max();
-          int iteration=0;
+        const double tmax=0.2;
+        double t=0;
 
-          CellValue<double>& rhoj = unknowns.rhoj();
-          CellValue<double>& ej = unknowns.ej();
-          CellValue<double>& pj = unknowns.pj();
-          CellValue<double>& gammaj = unknowns.gammaj();
-          CellValue<double>& cj = unknowns.cj();
+        int itermax=std::numeric_limits<int>::max();
+        int iteration=0;
 
-          BlockPerfectGas block_eos(rhoj, ej, pj, gammaj, cj);
+        CellValue<double>& rhoj = unknowns.rhoj();
+        CellValue<double>& ej = unknowns.ej();
+        CellValue<double>& pj = unknowns.pj();
+        CellValue<double>& gammaj = unknowns.gammaj();
+        CellValue<double>& cj = unknowns.cj();
 
-          VTKWriter vtk_writer("mesh", 0.01);
+        BlockPerfectGas block_eos(rhoj, ej, pj, gammaj, cj);
 
-          while((t<tmax) and (iteration<itermax)) {
-            vtk_writer.write(mesh, {NamedItemValue{"density", rhoj},
-                                    NamedItemValue{"velocity", unknowns.uj()},
-                                    NamedItemValue{"coords", mesh.xr()}}, t);
-            double dt = 0.4*acoustic_solver.acoustic_dt(Vj, cj);
-            if (t+dt>tmax) {
-              dt=tmax-t;
-            }
-            acoustic_solver.computeNextStep(t,dt, unknowns);
+        VTKWriter vtk_writer("mesh", 0.01);
 
-            block_eos.updatePandCFromRhoE();
-
-            t += dt;
-            ++iteration;
-          }
+        while((t<tmax) and (iteration<itermax)) {
           vtk_writer.write(mesh, {NamedItemValue{"density", rhoj},
                                   NamedItemValue{"velocity", unknowns.uj()},
-                                  NamedItemValue{"coords", mesh.xr()}}, t, true); // forces last output
-
-          pout() << "* " << rang::style::underline << "Final time" << rang::style::reset
-                    << ":  " << rang::fgB::green << t << rang::fg::reset << " (" << iteration << " iterations)\n";
+                                  NamedItemValue{"coords", mesh.xr()},
+                                  NamedItemValue{"cell_owner", mesh.connectivity().cellOwner()},
+                                  NamedItemValue{"node_owner", mesh.connectivity().nodeOwner()}},t);
+          double dt = 0.4*acoustic_solver.acoustic_dt(Vj, cj);
+          if (t+dt>tmax) {
+            dt=tmax-t;
+          }
+          acoustic_solver.computeNextStep(t,dt, unknowns);
 
-          method_cost_map["AcousticSolverWithMesh"] = timer.seconds();
+          block_eos.updatePandCFromRhoE();
 
-          { // gnuplot output for density
-            const CellValue<const Rd>& xj = mesh_data.xj();
-            const CellValue<const double>& rhoj = unknowns.rhoj();
-            std::ofstream fout("rho");
-            for (CellId j=0; j<mesh.numberOfCells(); ++j) {
-              fout << xj[j][0] << ' ' << rhoj[j] << '\n';
-            }
+          t += dt;
+          ++iteration;
+        }
+        vtk_writer.write(mesh, {NamedItemValue{"density", rhoj},
+                                NamedItemValue{"velocity", unknowns.uj()},
+                                NamedItemValue{"coords", mesh.xr()},
+                                NamedItemValue{"cell_owner", mesh.connectivity().cellOwner()},
+                                NamedItemValue{"node_owner", mesh.connectivity().nodeOwner()}}, t, true); // forces last output
+
+        pout() << "* " << rang::style::underline << "Final time" << rang::style::reset
+               << ":  " << rang::fgB::green << t << rang::fg::reset << " (" << iteration << " iterations)\n";
+
+        method_cost_map["AcousticSolverWithMesh"] = timer.seconds();
+
+        { // gnuplot output for density
+          const CellValue<const Rd>& xj = mesh_data.xj();
+          const CellValue<const double>& rhoj = unknowns.rhoj();
+          std::ofstream fout("rho");
+          for (CellId j=0; j<mesh.numberOfCells(); ++j) {
+            fout << xj[j][0] << ' ' << rhoj[j] << '\n';
           }
+        }
 
-          break;
+        break;
+      }
+      case 2: {
+        // test case boundary condition description
+        std::vector<std::string> sym_boundary_name_list = {"XMIN", "XMAX", "YMIN", "YMAX"};
+        std::vector<std::shared_ptr<BoundaryConditionDescriptor>> bc_descriptor_list;
+        for (const auto& sym_boundary_name : sym_boundary_name_list){
+          std::shared_ptr<BoundaryDescriptor> boudary_descriptor
+              = std::shared_ptr<BoundaryDescriptor>(new NamedBoundaryDescriptor(sym_boundary_name));
+          SymmetryBoundaryConditionDescriptor* sym_bc_descriptor
+              = new SymmetryBoundaryConditionDescriptor(boudary_descriptor);
+
+          bc_descriptor_list.push_back(std::shared_ptr<BoundaryConditionDescriptor>(sym_bc_descriptor));
         }
-        case 2: {
-          // test case boundary condition description
-          std::vector<std::string> sym_boundary_name_list = {"XMIN", "XMAX", "YMIN", "YMAX"};
-          std::vector<std::shared_ptr<BoundaryConditionDescriptor>> bc_descriptor_list;
-          for (const auto& sym_boundary_name : sym_boundary_name_list){
-            std::shared_ptr<BoundaryDescriptor> boudary_descriptor
-                = std::shared_ptr<BoundaryDescriptor>(new NamedBoundaryDescriptor(sym_boundary_name));
-            SymmetryBoundaryConditionDescriptor* sym_bc_descriptor
-                = new SymmetryBoundaryConditionDescriptor(boudary_descriptor);
-
-            bc_descriptor_list.push_back(std::shared_ptr<BoundaryConditionDescriptor>(sym_bc_descriptor));
-          }
 
-          using ConnectivityType = Connectivity2D;
-          using MeshType = Mesh<ConnectivityType>;
-          using MeshDataType = MeshData<MeshType>;
-          using UnknownsType = FiniteVolumesEulerUnknowns<MeshDataType>;
-
-          const MeshType& mesh = dynamic_cast<const MeshType&>(*gmsh_reader.mesh());
-
-          Timer timer;
-          timer.reset();
-          MeshDataType mesh_data(mesh);
-
-          std::vector<BoundaryConditionHandler> bc_list;
-          {
-            for (const auto& bc_descriptor : bc_descriptor_list) {
-              switch (bc_descriptor->type()) {
-                case BoundaryConditionDescriptor::Type::symmetry: {
-                  const SymmetryBoundaryConditionDescriptor& sym_bc_descriptor
-                      = dynamic_cast<const SymmetryBoundaryConditionDescriptor&>(*bc_descriptor);
-                  for (size_t i_ref_face_list=0; i_ref_face_list<mesh.connectivity().numberOfRefFaceList();
-                       ++i_ref_face_list) {
-                    const RefFaceList& ref_face_list = mesh.connectivity().refFaceList(i_ref_face_list);
-                    const RefId& ref = ref_face_list.refId();
-                    if (ref == sym_bc_descriptor.boundaryDescriptor()) {
-                      SymmetryBoundaryCondition<MeshType::dimension>* sym_bc
-                          = new SymmetryBoundaryCondition<MeshType::dimension>(MeshFlatNodeBoundary<MeshType::dimension>(mesh, ref_face_list));
-                      std::shared_ptr<SymmetryBoundaryCondition<MeshType::dimension>> bc(sym_bc);
-                      bc_list.push_back(BoundaryConditionHandler(bc));
-                    }
+        using ConnectivityType = Connectivity2D;
+        using MeshType = Mesh<ConnectivityType>;
+        using MeshDataType = MeshData<MeshType>;
+        using UnknownsType = FiniteVolumesEulerUnknowns<MeshDataType>;
+
+        const MeshType& mesh = dynamic_cast<const MeshType&>(*gmsh_reader.mesh());
+
+        Timer timer;
+        timer.reset();
+        MeshDataType mesh_data(mesh);
+
+        std::vector<BoundaryConditionHandler> bc_list;
+        {
+          for (const auto& bc_descriptor : bc_descriptor_list) {
+            switch (bc_descriptor->type()) {
+              case BoundaryConditionDescriptor::Type::symmetry: {
+                const SymmetryBoundaryConditionDescriptor& sym_bc_descriptor
+                    = dynamic_cast<const SymmetryBoundaryConditionDescriptor&>(*bc_descriptor);
+                for (size_t i_ref_face_list=0; i_ref_face_list<mesh.connectivity().numberOfRefItemList<ItemType::face>();
+                     ++i_ref_face_list) {
+                  const RefFaceList& ref_face_list = mesh.connectivity().refItemList<ItemType::face>(i_ref_face_list);
+                  const RefId& ref = ref_face_list.refId();
+                  if (ref == sym_bc_descriptor.boundaryDescriptor()) {
+                    SymmetryBoundaryCondition<MeshType::Dimension>* sym_bc
+                        = new SymmetryBoundaryCondition<MeshType::Dimension>(MeshFlatNodeBoundary<MeshType::Dimension>(mesh, ref_face_list));
+                    std::shared_ptr<SymmetryBoundaryCondition<MeshType::Dimension>> bc(sym_bc);
+                    bc_list.push_back(BoundaryConditionHandler(bc));
                   }
-                  break;
-                }
-                default: {
-                  perr() << "Unknown BCDescription\n";
-                  std::exit(1);
                 }
+                break;
+              }
+              default: {
+                perr() << "Unknown BCDescription\n";
+                std::exit(1);
               }
             }
           }
+        }
 
-          UnknownsType unknowns(mesh_data);
-
-          unknowns.initializeSod();
-
-          AcousticSolver<MeshDataType> acoustic_solver(mesh_data, bc_list);
+        UnknownsType unknowns(mesh_data);
 
-          const CellValue<const double>& Vj = mesh_data.Vj();
+        unknowns.initializeSod();
 
-          const double tmax=0.2;
-          double t=0;
+        AcousticSolver<MeshDataType> acoustic_solver(mesh_data, bc_list);
 
-          int itermax=std::numeric_limits<int>::max();
-          int iteration=0;
+        const CellValue<const double>& Vj = mesh_data.Vj();
 
-          CellValue<double>& rhoj = unknowns.rhoj();
-          CellValue<double>& ej = unknowns.ej();
-          CellValue<double>& pj = unknowns.pj();
-          CellValue<double>& gammaj = unknowns.gammaj();
-          CellValue<double>& cj = unknowns.cj();
+        const double tmax=0.2;
+        double t=0;
 
-          BlockPerfectGas block_eos(rhoj, ej, pj, gammaj, cj);
+        int itermax=std::numeric_limits<int>::max();
+        int iteration=0;
 
-          VTKWriter vtk_writer("mesh", 0.01);
+        CellValue<double>& rhoj = unknowns.rhoj();
+        CellValue<double>& ej = unknowns.ej();
+        CellValue<double>& pj = unknowns.pj();
+        CellValue<double>& gammaj = unknowns.gammaj();
+        CellValue<double>& cj = unknowns.cj();
 
-          while((t<tmax) and (iteration<itermax)) {
-            vtk_writer.write(mesh, {NamedItemValue{"density", rhoj},
-                                    NamedItemValue{"velocity", unknowns.uj()},
-                                    NamedItemValue{"coords", mesh.xr()}}, t);
-            double dt = 0.4*acoustic_solver.acoustic_dt(Vj, cj);
-            if (t+dt>tmax) {
-              dt=tmax-t;
-            }
-            acoustic_solver.computeNextStep(t,dt, unknowns);
+        BlockPerfectGas block_eos(rhoj, ej, pj, gammaj, cj);
 
-            block_eos.updatePandCFromRhoE();
+        VTKWriter vtk_writer("mesh", 0.01);
 
-            t += dt;
-            ++iteration;
-          }
+        while((t<tmax) and (iteration<itermax)) {
           vtk_writer.write(mesh, {NamedItemValue{"density", rhoj},
                                   NamedItemValue{"velocity", unknowns.uj()},
-                                  NamedItemValue{"coords", mesh.xr()}}, t, true); // forces last output
+                                  NamedItemValue{"coords", mesh.xr()},
+                                  NamedItemValue{"cell_owner", mesh.connectivity().cellOwner()},
+                                  NamedItemValue{"node_owner", mesh.connectivity().nodeOwner()}}, t);
+          double dt = 0.4*acoustic_solver.acoustic_dt(Vj, cj);
+          if (t+dt>tmax) {
+            dt=tmax-t;
+          }
+          acoustic_solver.computeNextStep(t,dt, unknowns);
 
-          pout() << "* " << rang::style::underline << "Final time" << rang::style::reset
-                    << ":  " << rang::fgB::green << t << rang::fg::reset << " (" << iteration << " iterations)\n";
+          block_eos.updatePandCFromRhoE();
 
-          method_cost_map["AcousticSolverWithMesh"] = timer.seconds();
-          break;
+          t += dt;
+          ++iteration;
         }
-        case 3: {
-          std::vector<std::string> sym_boundary_name_list = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
-          std::vector<std::shared_ptr<BoundaryConditionDescriptor>> bc_descriptor_list;
-          for (const auto& sym_boundary_name : sym_boundary_name_list){
-            std::shared_ptr<BoundaryDescriptor> boudary_descriptor
-                = std::shared_ptr<BoundaryDescriptor>(new NamedBoundaryDescriptor(sym_boundary_name));
-            SymmetryBoundaryConditionDescriptor* sym_bc_descriptor
-                = new SymmetryBoundaryConditionDescriptor(boudary_descriptor);
-
-            bc_descriptor_list.push_back(std::shared_ptr<BoundaryConditionDescriptor>(sym_bc_descriptor));
-          }
+        vtk_writer.write(mesh, {NamedItemValue{"density", rhoj},
+                                NamedItemValue{"velocity", unknowns.uj()},
+                                NamedItemValue{"coords", mesh.xr()},
+                                NamedItemValue{"cell_owner", mesh.connectivity().cellOwner()},
+                                NamedItemValue{"node_owner", mesh.connectivity().nodeOwner()}}, t, true); // forces last output
 
-          using ConnectivityType = Connectivity3D;
-          using MeshType = Mesh<ConnectivityType>;
-          using MeshDataType = MeshData<MeshType>;
-          using UnknownsType = FiniteVolumesEulerUnknowns<MeshDataType>;
-
-          const MeshType& mesh = dynamic_cast<const MeshType&>(*gmsh_reader.mesh());
-
-          Timer timer;
-          timer.reset();
-          MeshDataType mesh_data(mesh);
-
-          std::vector<BoundaryConditionHandler> bc_list;
-          {
-            for (const auto& bc_descriptor : bc_descriptor_list) {
-              switch (bc_descriptor->type()) {
-                case BoundaryConditionDescriptor::Type::symmetry: {
-                  const SymmetryBoundaryConditionDescriptor& sym_bc_descriptor
-                      = dynamic_cast<const SymmetryBoundaryConditionDescriptor&>(*bc_descriptor);
-                  for (size_t i_ref_face_list=0; i_ref_face_list<mesh.connectivity().numberOfRefFaceList();
-                       ++i_ref_face_list) {
-                    const RefFaceList& ref_face_list = mesh.connectivity().refFaceList(i_ref_face_list);
-                    const RefId& ref = ref_face_list.refId();
-                    if (ref == sym_bc_descriptor.boundaryDescriptor()) {
-                      SymmetryBoundaryCondition<MeshType::dimension>* sym_bc
-                          = new SymmetryBoundaryCondition<MeshType::dimension>(MeshFlatNodeBoundary<MeshType::dimension>(mesh, ref_face_list));
-                      std::shared_ptr<SymmetryBoundaryCondition<MeshType::dimension>> bc(sym_bc);
-                      bc_list.push_back(BoundaryConditionHandler(bc));
-                    }
+        pout() << "* " << rang::style::underline << "Final time" << rang::style::reset
+               << ":  " << rang::fgB::green << t << rang::fg::reset << " (" << iteration << " iterations)\n";
+
+        method_cost_map["AcousticSolverWithMesh"] = timer.seconds();
+        break;
+      }
+      case 3: {
+        std::vector<std::string> sym_boundary_name_list = {"XMIN", "XMAX", "YMIN", "YMAX", "ZMIN", "ZMAX"};
+        std::vector<std::shared_ptr<BoundaryConditionDescriptor>> bc_descriptor_list;
+        for (const auto& sym_boundary_name : sym_boundary_name_list){
+          std::shared_ptr<BoundaryDescriptor> boudary_descriptor
+              = std::shared_ptr<BoundaryDescriptor>(new NamedBoundaryDescriptor(sym_boundary_name));
+          SymmetryBoundaryConditionDescriptor* sym_bc_descriptor
+              = new SymmetryBoundaryConditionDescriptor(boudary_descriptor);
+
+          bc_descriptor_list.push_back(std::shared_ptr<BoundaryConditionDescriptor>(sym_bc_descriptor));
+        }
+
+        using ConnectivityType = Connectivity3D;
+        using MeshType = Mesh<ConnectivityType>;
+        using MeshDataType = MeshData<MeshType>;
+        using UnknownsType = FiniteVolumesEulerUnknowns<MeshDataType>;
+
+        const MeshType& mesh = dynamic_cast<const MeshType&>(*gmsh_reader.mesh());
+
+        Timer timer;
+        timer.reset();
+        MeshDataType mesh_data(mesh);
+
+        std::vector<BoundaryConditionHandler> bc_list;
+        {
+          for (const auto& bc_descriptor : bc_descriptor_list) {
+            switch (bc_descriptor->type()) {
+              case BoundaryConditionDescriptor::Type::symmetry: {
+                const SymmetryBoundaryConditionDescriptor& sym_bc_descriptor
+                    = dynamic_cast<const SymmetryBoundaryConditionDescriptor&>(*bc_descriptor);
+                for (size_t i_ref_face_list=0; i_ref_face_list<mesh.connectivity().numberOfRefItemList<ItemType::face>();
+                     ++i_ref_face_list) {
+                  const RefFaceList& ref_face_list = mesh.connectivity().refItemList<ItemType::face>(i_ref_face_list);
+                  const RefId& ref = ref_face_list.refId();
+                  if (ref == sym_bc_descriptor.boundaryDescriptor()) {
+                    SymmetryBoundaryCondition<MeshType::Dimension>* sym_bc
+                        = new SymmetryBoundaryCondition<MeshType::Dimension>(MeshFlatNodeBoundary<MeshType::Dimension>(mesh, ref_face_list));
+                    std::shared_ptr<SymmetryBoundaryCondition<MeshType::Dimension>> bc(sym_bc);
+                    bc_list.push_back(BoundaryConditionHandler(bc));
                   }
-                  break;
-                }
-                default: {
-                  perr() << "Unknown BCDescription\n";
-                  std::exit(1);
                 }
+                break;
+              }
+              default: {
+                perr() << "Unknown BCDescription\n";
+                std::exit(1);
               }
             }
           }
+        }
 
-          UnknownsType unknowns(mesh_data);
-
-          unknowns.initializeSod();
+        UnknownsType unknowns(mesh_data);
 
-          AcousticSolver<MeshDataType> acoustic_solver(mesh_data, bc_list);
+        unknowns.initializeSod();
 
-          const CellValue<const double>& Vj = mesh_data.Vj();
+        AcousticSolver<MeshDataType> acoustic_solver(mesh_data, bc_list);
 
-          const double tmax=0.2;
-          double t=0;
+        const CellValue<const double>& Vj = mesh_data.Vj();
 
-          int itermax=std::numeric_limits<int>::max();
-          int iteration=0;
+        const double tmax=0.2;
+        double t=0;
 
-          CellValue<double>& rhoj = unknowns.rhoj();
-          CellValue<double>& ej = unknowns.ej();
-          CellValue<double>& pj = unknowns.pj();
-          CellValue<double>& gammaj = unknowns.gammaj();
-          CellValue<double>& cj = unknowns.cj();
+        int itermax=std::numeric_limits<int>::max();
+        int iteration=0;
 
-          BlockPerfectGas block_eos(rhoj, ej, pj, gammaj, cj);
+        CellValue<double>& rhoj = unknowns.rhoj();
+        CellValue<double>& ej = unknowns.ej();
+        CellValue<double>& pj = unknowns.pj();
+        CellValue<double>& gammaj = unknowns.gammaj();
+        CellValue<double>& cj = unknowns.cj();
 
-          VTKWriter vtk_writer("mesh", 0.01);
+        BlockPerfectGas block_eos(rhoj, ej, pj, gammaj, cj);
 
-          while((t<tmax) and (iteration<itermax)) {
-            vtk_writer.write(mesh, {NamedItemValue{"density", rhoj},
-                                    NamedItemValue{"velocity", unknowns.uj()},
-                                    NamedItemValue{"coords", mesh.xr()}}, t);
-            double dt = 0.4*acoustic_solver.acoustic_dt(Vj, cj);
-            if (t+dt>tmax) {
-              dt=tmax-t;
-            }
-            acoustic_solver.computeNextStep(t,dt, unknowns);
-            block_eos.updatePandCFromRhoE();
+        VTKWriter vtk_writer("mesh", 0.01);
 
-            t += dt;
-            ++iteration;
-          }
+        while((t<tmax) and (iteration<itermax)) {
           vtk_writer.write(mesh, {NamedItemValue{"density", rhoj},
                                   NamedItemValue{"velocity", unknowns.uj()},
-                                  NamedItemValue{"coords", mesh.xr()}}, t, true); // forces last output
-
-          pout() << "* " << rang::style::underline << "Final time" << rang::style::reset
-                    << ":  " << rang::fgB::green << t << rang::fg::reset << " (" << iteration << " iterations)\n";
+                                  NamedItemValue{"coords", mesh.xr()},
+                                  NamedItemValue{"cell_owner", mesh.connectivity().cellOwner()},
+                                  NamedItemValue{"node_owner", mesh.connectivity().nodeOwner()}}, t);
+          double dt = 0.4*acoustic_solver.acoustic_dt(Vj, cj);
+          if (t+dt>tmax) {
+            dt=tmax-t;
+          }
+          acoustic_solver.computeNextStep(t,dt, unknowns);
+          block_eos.updatePandCFromRhoE();
 
-          method_cost_map["AcousticSolverWithMesh"] = timer.seconds();
-          break;
+          t += dt;
+          ++iteration;
         }
-      }
+        vtk_writer.write(mesh, {NamedItemValue{"density", rhoj},
+                                NamedItemValue{"velocity", unknowns.uj()},
+                                NamedItemValue{"coords", mesh.xr()},
+                                NamedItemValue{"cell_owner", mesh.connectivity().cellOwner()},
+                                NamedItemValue{"node_owner", mesh.connectivity().nodeOwner()}}, t, true); // forces last output
 
-      pout() << "* "  << rang::fgB::red << "Could not be uglier!" << rang::fg::reset << " (" << __FILE__ << ':' << __LINE__ << ")\n";
+        pout() << "* " << rang::style::underline << "Final time" << rang::style::reset
+               << ":  " << rang::fgB::green << t << rang::fg::reset << " (" << iteration << " iterations)\n";
 
-    } else {
-      perr() << "Connectivity1D defined by number of nodes no more implemented\n";
-      std::exit(0);
+        method_cost_map["AcousticSolverWithMesh"] = timer.seconds();
+        break;
+      }
     }
+
+    pout() << "* "  << rang::fgB::red << "Could not be uglier!" << rang::fg::reset << " (" << __FILE__ << ':' << __LINE__ << ")\n";
+
+  } else {
+    perr() << "Connectivity1D defined by number of nodes no more implemented\n";
+    std::exit(0);
   }
-  catch (const AssertError& error) {
-    perr() << error << '\n';
-    std::exit(1);
-  }
+
+  SynchronizerManager::destroy();
 
   finalize();
 
@@ -377,14 +388,14 @@ int main(int argc, char *argv[])
 
   for (const auto& method_cost : method_cost_map) {
     pout() << "* ["
-              << rang::fgB::cyan
-              << std::setw(size) << std::left
-              << method_cost.first
-              << rang::fg::reset
-              << "] Execution time: "
-              << rang::style::bold
-              << method_cost.second
-              << rang::style::reset << '\n';
+           << rang::fgB::cyan
+           << std::setw(size) << std::left
+           << method_cost.first
+           << rang::fg::reset
+           << "] Execution time: "
+           << rang::style::bold
+           << method_cost.second
+           << rang::style::reset << '\n';
   }
 
   return 0;
diff --git a/src/mesh/CMakeLists.txt b/src/mesh/CMakeLists.txt
index c28b0643fd8bbf1b2677512423d2185d9a7ff970..ad245fb038c4a09e451591ff44098da80cb22106 100644
--- a/src/mesh/CMakeLists.txt
+++ b/src/mesh/CMakeLists.txt
@@ -7,9 +7,11 @@ add_library(
   PastisMesh
   Connectivity.cpp
   ConnectivityComputer.cpp
-  GmshReader.cpp)
+  GmshReader.cpp
+  ConnectivityDispatcher.cpp
+  SynchronizerManager.cpp)
 
-#include_directories(${PASTIS_SOURCE_DIR}/utils)
+include_directories(${PASTIS_BINARY_DIR}/src/utils)
 
 # Additional dependencies
 #add_dependencies(PastisMesh)
diff --git a/src/mesh/CellType.hpp b/src/mesh/CellType.hpp
index 1f444323b5f5073b9aa09d020cc1fc962b1182a5..d3aa4578f769972e06f9df976df16c53737af80f 100644
--- a/src/mesh/CellType.hpp
+++ b/src/mesh/CellType.hpp
@@ -1,9 +1,12 @@
 #ifndef CELL_TYPE_HPP
 #define  CELL_TYPE_HPP
 
+#include <PastisMacros.hpp>
+#include <string_view>
+
 enum class CellType : unsigned short
 {
-  Line,
+  Line = 0,
 
   Triangle,
   Quadrangle,
@@ -14,4 +17,19 @@ enum class CellType : unsigned short
   Hexahedron
 };
 
+PASTIS_INLINE
+std::string_view name(const CellType& cell_type)
+{
+  switch (cell_type) {
+    case CellType::Line: return "line";
+    case CellType::Triangle: return "triangle";
+    case CellType::Quadrangle: return "quadrangle";
+    case CellType::Tetrahedron: return "tetrahedron";
+    case CellType::Pyramid: return "pyramid";
+    case CellType::Prism: return "prism";
+    case CellType::Hexahedron: return "hexahedron";
+    default: return "unknown cell type";
+  }
+}
+
 #endif // CELL_TYPE_HPP
diff --git a/src/mesh/Connectivity.cpp b/src/mesh/Connectivity.cpp
index 856a1452bf78ec323107142fc919b0bda4f79243..3a4b45766d36453e862aaeca2b5d8d855942e495 100644
--- a/src/mesh/Connectivity.cpp
+++ b/src/mesh/Connectivity.cpp
@@ -1,261 +1,178 @@
 #include <Connectivity.hpp>
 #include <map>
 
-template<>
-void Connectivity<3>::_computeCellFaceAndFaceNodeConnectivities()
-{
-  using CellFaceInfo = std::tuple<CellId, unsigned short, bool>;
-
-  const auto& cell_to_node_matrix
-      = this->_getMatrix(ItemType::cell, ItemType::node);
-
-  CellValue<unsigned short> cell_nb_faces(*this);
-  std::map<Face, std::vector<CellFaceInfo>> face_cells_map;
-  for (CellId j=0; j<this->numberOfCells(); ++j) {
-    const auto& cell_nodes = cell_to_node_matrix.rowConst(j);
-
-    switch (m_cell_type[j]) {
-      case CellType::Tetrahedron: {
-        cell_nb_faces[j] = 4;
-        // face 0
-        Face f0({cell_nodes(1),
-                 cell_nodes(2),
-                 cell_nodes(3)});
-        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
-
-        // face 1
-        Face f1({cell_nodes(0),
-                 cell_nodes(3),
-                 cell_nodes(2)});
-        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
+#include <Messenger.hpp>
 
-        // face 2
-        Face f2({cell_nodes(0),
-                 cell_nodes(1),
-                 cell_nodes(3)});
-        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
+#include <ConnectivityDescriptor.hpp>
 
-        // face 3
-        Face f3({cell_nodes(0),
-                 cell_nodes(2),
-                 cell_nodes(1)});
-        face_cells_map[f3].emplace_back(std::make_tuple(j, 3, f3.reversed()));
-        break;
-      }
-      case CellType::Hexahedron: {
-        // face 0
-        Face f0({cell_nodes(3),
-                 cell_nodes(2),
-                 cell_nodes(1),
-                 cell_nodes(0)});
-        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
-
-        // face 1
-        Face f1({cell_nodes(4),
-                 cell_nodes(5),
-                 cell_nodes(6),
-                 cell_nodes(7)});
-        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
-
-        // face 2
-        Face f2({cell_nodes(0),
-                 cell_nodes(4),
-                 cell_nodes(7),
-                 cell_nodes(3)});
-        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
+template<size_t Dimension>
+Connectivity<Dimension>::Connectivity() {}
 
-        // face 3
-        Face f3({cell_nodes(1),
-                 cell_nodes(2),
-                 cell_nodes(6),
-                 cell_nodes(5)});
-        face_cells_map[f3].emplace_back(std::make_tuple(j, 3, f3.reversed()));
+template<size_t Dimension>
+void Connectivity<Dimension>::
+_buildFrom(const ConnectivityDescriptor& descriptor)
+{
+  Assert(descriptor.cell_to_node_vector.size() == descriptor.cell_type_vector.size());
+  Assert(descriptor.cell_number_vector.size() == descriptor.cell_type_vector.size());
+  if constexpr (Dimension>1) {
+    Assert(descriptor.cell_to_face_vector.size() == descriptor.cell_type_vector.size());
+    Assert(descriptor.face_to_node_vector.size() == descriptor.face_number_vector.size());
+    Assert(descriptor.face_owner_vector.size() == descriptor.face_number_vector.size());
+  }
 
-        // face 4
-        Face f4({cell_nodes(0),
-                 cell_nodes(1),
-                 cell_nodes(5),
-                 cell_nodes(4)});
-        face_cells_map[f4].emplace_back(std::make_tuple(j, 4, f4.reversed()));
+  auto& cell_to_node_matrix
+      = m_item_to_item_matrix[itemTId(ItemType::cell)][itemTId(ItemType::node)];
+  cell_to_node_matrix = descriptor.cell_to_node_vector;
 
-        // face 5
-        Face f5({cell_nodes(3),
-                 cell_nodes(7),
-                 cell_nodes(6),
-                 cell_nodes(2)});
-        face_cells_map[f5].emplace_back(std::make_tuple(j, 5, f5.reversed()));
+  {
+    WeakCellValue<CellType> cell_type(*this);
+    parallel_for(this->numberOfCells(), PASTIS_LAMBDA(const CellId& j){
+        cell_type[j] = descriptor.cell_type_vector[j];
+      });
+    m_cell_type = cell_type;
+  }
 
-        cell_nb_faces[j] = 6;
-        break;
-      }
-      default: {
-        perr() << "unexpected cell type!\n";
-        std::exit(0);
-      }
-    }
+  {
+    WeakCellValue<int> cell_number(*this);
+    cell_number = convert_to_array(descriptor.cell_number_vector);
+    m_cell_number = cell_number;
   }
 
   {
-    auto& cell_to_face_matrix
-        = m_item_to_item_matrix[itemTId(ItemType::cell)][itemTId(ItemType::face)];
-    std::vector<std::vector<unsigned int>> cell_to_face_vector(this->numberOfCells());
-    for (CellId j=0; j<cell_to_face_vector.size(); ++j) {
-      cell_to_face_vector[j].resize(cell_nb_faces[j]);
-    }
-    FaceId l=0;
-    for (const auto& face_cells_vector : face_cells_map) {
-      const auto& cells_vector = face_cells_vector.second;
-      for (unsigned short lj=0; lj<cells_vector.size(); ++lj) {
-        const auto& [cell_number, cell_local_face, reversed] = cells_vector[lj];
-        cell_to_face_vector[cell_number][cell_local_face] = l;
-      }
-      ++l;
-    }
-    cell_to_face_matrix = cell_to_face_vector;
+    WeakNodeValue<int> node_number(*this);
+    node_number = convert_to_array(descriptor.node_number_vector);
+    m_node_number = node_number;
   }
 
-  FaceValuePerCell<bool> cell_face_is_reversed(*this);
   {
-    for (const auto& face_cells_vector : face_cells_map) {
-      const auto& cells_vector = face_cells_vector.second;
-      for (unsigned short lj=0; lj<cells_vector.size(); ++lj) {
-        const auto& [cell_number, cell_local_face, reversed] = cells_vector[lj];
-        cell_face_is_reversed(cell_number, cell_local_face) = reversed;
-      }
-    }
+    WeakCellValue<int> cell_global_index(*this);
+    int first_index = 0;
+    parallel_for(this->numberOfCells(), PASTIS_LAMBDA(const CellId& j) {
+      cell_global_index[j] = first_index+j;
+      });
+    m_cell_global_index = cell_global_index;
+  }
 
-    m_cell_face_is_reversed = cell_face_is_reversed;
+  {
+    WeakCellValue<int> cell_owner(*this);
+    cell_owner = convert_to_array(descriptor.cell_owner_vector);
+    m_cell_owner = cell_owner;
   }
 
   {
-    auto& face_to_node_matrix
-        = m_item_to_item_matrix[itemTId(ItemType::face)][itemTId(ItemType::node)];
+    const int rank = parallel::rank();
+    WeakCellValue<bool> cell_is_owned(*this);
+    parallel_for(this->numberOfCells(), PASTIS_LAMBDA(const CellId& j) {
+        cell_is_owned[j] = (m_cell_owner[j] == rank);
+      });
+    m_cell_is_owned = cell_is_owned;
+  }
 
-    std::vector<std::vector<unsigned int>> face_to_node_vector(face_cells_map.size());
-    int l=0;
-    for (const auto& face_info : face_cells_map) {
-      const Face& face = face_info.first;
-      face_to_node_vector[l] = face.nodeIdList();
-      ++l;
-    }
-    face_to_node_matrix = face_to_node_vector;
+  {
+    WeakNodeValue<int> node_owner(*this);
+    node_owner = convert_to_array(descriptor.node_owner_vector);
+    m_node_owner = node_owner;
   }
 
   {
-    int l=0;
-    for (const auto& face_cells_vector : face_cells_map) {
-      const Face& face = face_cells_vector.first;
-      m_face_number_map[face] = l;
-      ++l;
-    }
+    const int rank = parallel::rank();
+    WeakNodeValue<bool> node_is_owned(*this);
+    parallel_for(this->numberOfNodes(), PASTIS_LAMBDA(const NodeId& r) {
+        node_is_owned[r] = (m_node_owner[r] == rank);
+      });
+    m_node_is_owned = node_is_owned;
   }
 
-#warning check that the number of cell per faces is <=2
-}
+  m_ref_node_list_vector = descriptor.template refItemListVector<ItemType::node>();
+  m_ref_cell_list_vector = descriptor.template refItemListVector<ItemType::cell>();
 
-template<>
-void Connectivity<2>::_computeCellFaceAndFaceNodeConnectivities()
-{
-  const auto& cell_to_node_matrix
-      = this->_getMatrix(ItemType::cell, ItemType::node);
+  if constexpr (Dimension>1) {
+    m_item_to_item_matrix[itemTId(ItemType::face)][itemTId(ItemType::node)] = descriptor.face_to_node_vector;
 
-  // In 2D faces are simply define
-  using CellFaceId = std::pair<CellId, unsigned short>;
-  std::map<Face, std::vector<CellFaceId>> face_cells_map;
-  for (CellId j=0; j<this->numberOfCells(); ++j) {
-    const auto& cell_nodes = cell_to_node_matrix.rowConst(j);
-    for (unsigned short r=0; r<cell_nodes.length; ++r) {
-      NodeId node0_id = cell_nodes(r);
-      NodeId node1_id = cell_nodes((r+1)%cell_nodes.length);
-      if (node1_id<node0_id) {
-        std::swap(node0_id, node1_id);
+    m_item_to_item_matrix[itemTId(ItemType::cell)][itemTId(ItemType::face)] = descriptor.cell_to_face_vector;
+
+    {
+      FaceValuePerCell<bool> cell_face_is_reversed(*this);
+      for (CellId j=0; j<descriptor.cell_face_is_reversed_vector.size(); ++j) {
+        const auto& face_cells_vector = descriptor.cell_face_is_reversed_vector[j];
+        for (unsigned short lj=0; lj<face_cells_vector.size(); ++lj) {
+          cell_face_is_reversed(j,lj) = face_cells_vector[lj];
+        }
       }
-      face_cells_map[Face({node0_id, node1_id})].push_back(std::make_pair(j, r));
+      m_cell_face_is_reversed = cell_face_is_reversed;
     }
-  }
 
-  {
-    FaceId l=0;
-    for (const auto& face_cells_vector : face_cells_map) {
-      const Face& face = face_cells_vector.first;
-      m_face_number_map[face] = l;
-      ++l;
+    {
+      WeakFaceValue<int> face_number(*this);
+      face_number = convert_to_array(descriptor.face_number_vector);
+      m_face_number = face_number;
     }
-  }
 
-  {
-    std::vector<std::vector<unsigned int>> face_to_node_vector(face_cells_map.size());
-    int l=0;
-    for (const auto& face_info : face_cells_map) {
-      const Face& face = face_info.first;
-      face_to_node_vector[l] = {face.m_node0_id, face.m_node1_id};
-      ++l;
+    {
+      WeakFaceValue<int> face_owner(*this);
+      face_owner = convert_to_array(descriptor.face_owner_vector);
+      m_face_owner = face_owner;
     }
-    auto& face_to_node_matrix
-        = m_item_to_item_matrix[itemTId(ItemType::face)][itemTId(ItemType::node)];
-    face_to_node_matrix = face_to_node_vector;
-  }
 
-  {
-    std::vector<std::vector<unsigned int>> face_to_cell_vector(face_cells_map.size());
-    int l=0;
-    for (const auto& face_cells_vector : face_cells_map) {
-      const auto& [face, cell_info_vector] = face_cells_vector;
-      for (const auto& cell_info : cell_info_vector) {
-        face_to_cell_vector[l].push_back(cell_info.second);
-      }
-      ++l;
+    {
+      const int rank = parallel::rank();
+      WeakFaceValue<bool> face_is_owned(*this);
+      parallel_for(this->numberOfFaces(), PASTIS_LAMBDA(const FaceId& l) {
+          face_is_owned[l] = (m_face_owner[l] == rank);
+        });
+      m_face_is_owned = face_is_owned;
     }
-    auto& face_to_cell_matrix
-        = m_item_to_item_matrix[itemTId(ItemType::face)][itemTId(ItemType::cell)];
-    face_to_cell_matrix = face_to_cell_vector;
-  }
-}
 
+    m_ref_face_list_vector = descriptor.template refItemListVector<ItemType::face>();
 
-template<size_t Dimension>
-Connectivity<Dimension>::
-Connectivity(const std::vector<std::vector<unsigned int>>& cell_by_node_vector,
-             const std::vector<CellType>& cell_type_vector)
-{
-  Assert(cell_by_node_vector.size() == cell_type_vector.size());
+    if constexpr (Dimension>2) {
+      m_item_to_item_matrix[itemTId(ItemType::edge)][itemTId(ItemType::node)] = descriptor.edge_to_node_vector;
 
-  auto& cell_to_node_matrix
-      = m_item_to_item_matrix[itemTId(ItemType::cell)][itemTId(ItemType::node)];
-  cell_to_node_matrix = cell_by_node_vector;
+      m_item_to_item_matrix[itemTId(ItemType::face)][itemTId(ItemType::edge)] = descriptor.face_to_edge_vector;
 
-  Assert(this->numberOfCells()>0);
+      m_item_to_item_matrix[itemTId(ItemType::cell)][itemTId(ItemType::edge)] = descriptor.cell_to_edge_vector;
 
-  {
-    CellValue<CellType> cell_type(*this);
-    parallel_for(this->numberOfCells(), PASTIS_LAMBDA(const CellId& j){
-        cell_type[j] = cell_type_vector[j];
-      });
-    m_cell_type = cell_type;
-  }
-  {
-    CellValue<double> inv_cell_nb_nodes(*this);
-    parallel_for(this->numberOfCells(), PASTIS_LAMBDA(const CellId& j){
-        const auto& cell_nodes = cell_to_node_matrix.rowConst(j);
-        inv_cell_nb_nodes[j] = 1./cell_nodes.length;
-      });
-    m_inv_cell_nb_nodes = inv_cell_nb_nodes;
-  }
+      {
+        EdgeValuePerFace<bool> face_edge_is_reversed(*this);
+        for (FaceId l=0; l<descriptor.face_edge_is_reversed_vector.size(); ++l) {
+          const auto& edge_faces_vector = descriptor.face_edge_is_reversed_vector[l];
+          for (unsigned short el=0; el<edge_faces_vector.size(); ++el) {
+            face_edge_is_reversed(l,el) = edge_faces_vector[el];
+          }
+        }
+        m_face_edge_is_reversed = face_edge_is_reversed;
+      }
 
-  if constexpr (Dimension>1) {
-    this->_computeCellFaceAndFaceNodeConnectivities();
-  }
-}
+      {
+        WeakEdgeValue<int> edge_number(*this);
+        edge_number = convert_to_array(descriptor.edge_number_vector);
+        m_edge_number = edge_number;
+      }
 
+      {
+        WeakEdgeValue<int> edge_owner(*this);
+        edge_owner = convert_to_array(descriptor.edge_owner_vector);
+        m_edge_owner = edge_owner;
+      }
 
-template Connectivity1D::
-Connectivity(const std::vector<std::vector<unsigned int>>& cell_by_node_vector,
-             const std::vector<CellType>& cell_type_vector);
+      {
+        const int rank = parallel::rank();
+        WeakEdgeValue<bool> edge_is_owned(*this);
+        parallel_for(this->numberOfEdges(), PASTIS_LAMBDA(const EdgeId& e) {
+            edge_is_owned[e] = (m_edge_owner[e] == rank);
+          });
+        m_edge_is_owned = edge_is_owned;
+      }
+
+      m_ref_edge_list_vector = descriptor.template refItemListVector<ItemType::edge>();
+    }
+  }
+}
 
-template Connectivity2D::
-Connectivity(const std::vector<std::vector<unsigned int>>& cell_by_node_vector,
-             const std::vector<CellType>& cell_type_vector);
+template Connectivity1D::Connectivity();
+template Connectivity2D::Connectivity();
+template Connectivity3D::Connectivity();
 
-template Connectivity3D::
-Connectivity(const std::vector<std::vector<unsigned int>>& cell_by_node_vector,
-             const std::vector<CellType>& cell_type_vector);
+template void Connectivity1D::_buildFrom(const ConnectivityDescriptor&);
+template void Connectivity2D::_buildFrom(const ConnectivityDescriptor&);
+template void Connectivity3D::_buildFrom(const ConnectivityDescriptor&);
diff --git a/src/mesh/Connectivity.hpp b/src/mesh/Connectivity.hpp
index 01c2da0ffff27d15f65b8a841522b37ad9285d7d..7e1ebd1198094a1f214e87e26106ea28cfb0a4f1 100644
--- a/src/mesh/Connectivity.hpp
+++ b/src/mesh/Connectivity.hpp
@@ -7,6 +7,8 @@
 #include <PastisOStream.hpp>
 #include <PastisUtils.hpp>
 
+#include <PastisTraits.hpp>
+
 #include <TinyVector.hpp>
 
 #include <ItemValue.hpp>
@@ -20,294 +22,261 @@
 #include <ConnectivityComputer.hpp>
 
 #include <vector>
-#include <unordered_map>
 #include <algorithm>
 
 #include <CellType.hpp>
 
+#include <CSRGraph.hpp>
+
 #include <RefId.hpp>
 #include <ItemType.hpp>
-#include <RefNodeList.hpp>
-#include <RefFaceList.hpp>
+#include <RefItemList.hpp>
 
-#include <tuple>
-#include <algorithm>
 
-template <size_t Dimension>
-class Connectivity;
+#include <SynchronizerManager.hpp>
 
-template <size_t Dimension>
-class ConnectivityFace;
+#include <set>
 
-template<>
-class ConnectivityFace<1>
-{
- public:
-  friend struct Hash;
-  struct Hash
-  {
-    size_t operator()(const ConnectivityFace& f) const;
-  };
-};
+class ConnectivityDescriptor;
 
-template<>
-class ConnectivityFace<2>
+template <size_t Dim>
+class Connectivity final
+    : public IConnectivity
 {
  public:
-  friend struct Hash;
-  struct Hash
-  {
-    size_t operator()(const ConnectivityFace& f) const {
-      size_t hash = 0;
-      hash ^= std::hash<unsigned int>()(f.m_node0_id);
-      hash ^= std::hash<unsigned int>()(f.m_node1_id) >> 1;
-      return hash;
-    }
-  };
+  PASTIS_INLINE
+  static std::shared_ptr<Connectivity<Dim>>
+  build(const ConnectivityDescriptor&);
 
-  unsigned int m_node0_id;
-  unsigned int m_node1_id;
+ private:
+  constexpr static auto& itemTId = ItemTypeId<Dim>::itemTId;
 
-  friend std::ostream& operator<<(std::ostream& os, const ConnectivityFace& f)
-  {
-    os << f.m_node0_id << ' ' << f.m_node1_id << ' ';
-    return os;
-  }
+ public:
+  static constexpr size_t Dimension = Dim;
 
   PASTIS_INLINE
-  bool operator==(const ConnectivityFace& f) const
+  size_t dimension() const final
   {
-    return ((m_node0_id == f.m_node0_id) and
-            (m_node1_id == f.m_node1_id));
+    return Dimension;
   }
+ private:
+  ConnectivityMatrix m_item_to_item_matrix[Dimension+1][Dimension+1];
+  WeakCellValue<const CellType> m_cell_type;
 
-  PASTIS_INLINE
-  bool operator<(const ConnectivityFace& f) const
-  {
-    return ((m_node0_id<f.m_node0_id) or
-            ((m_node0_id == f.m_node0_id) and
-             (m_node1_id<f.m_node1_id)));
-  }
+  WeakCellValue<const int> m_cell_global_index;
 
-  PASTIS_INLINE
-  ConnectivityFace& operator=(const ConnectivityFace&) = default;
+  WeakCellValue<const int> m_cell_number;
+  WeakFaceValue<const int> m_face_number;
+  WeakEdgeValue<const int> m_edge_number;
+  WeakNodeValue<const int> m_node_number;
 
-  PASTIS_INLINE
-  ConnectivityFace& operator=(ConnectivityFace&&) = default;
+  WeakCellValue<const int> m_cell_owner;
+  WeakCellValue<const bool> m_cell_is_owned;
 
-  PASTIS_INLINE
-  ConnectivityFace(const std::vector<unsigned int>& given_node_id_list)
-  {
-    Assert(given_node_id_list.size()==2);
-#warning rework this dirty constructor
-    const auto& [min, max] = std::minmax(given_node_id_list[0], given_node_id_list[1]);
-    m_node0_id = min;
-    m_node1_id = max;
-  }
+  WeakFaceValue<const int> m_face_owner;
+  WeakFaceValue<const bool> m_face_is_owned;
 
-  PASTIS_INLINE
-  ConnectivityFace(const ConnectivityFace&) = default;
+  WeakEdgeValue<const int> m_edge_owner;
+  WeakEdgeValue<const bool> m_edge_is_owned;
 
-  PASTIS_INLINE
-  ConnectivityFace(ConnectivityFace&&) = default;
+  WeakNodeValue<const int> m_node_owner;
+  WeakNodeValue<const bool> m_node_is_owned;
 
-  PASTIS_INLINE
-  ~ConnectivityFace() = default;
-};
+  WeakFaceValuePerCell<const bool> m_cell_face_is_reversed;
+  WeakEdgeValuePerFace<const bool> m_face_edge_is_reversed;
 
-template <>
-class ConnectivityFace<3>
-{
- private:
-  friend class Connectivity<3>;
-  friend struct Hash;
-  struct Hash
-  {
-    size_t operator()(const ConnectivityFace& f) const {
-      size_t hash = 0;
-      for (size_t i=0; i<f.m_node_id_list.size(); ++i) {
-        hash ^= std::hash<unsigned int>()(f.m_node_id_list[i]) >> i;
-      }
-      return hash;
-    }
-  };
+  WeakNodeValuePerCell<const unsigned short> m_cell_local_numbers_in_their_nodes;
+  WeakEdgeValuePerCell<const unsigned short> m_cell_local_numbers_in_their_edges;
+  WeakFaceValuePerCell<const unsigned short> m_cell_local_numbers_in_their_faces;
+
+  WeakCellValuePerFace<const unsigned short> m_face_local_numbers_in_their_cells;
+  WeakEdgeValuePerFace<const unsigned short> m_face_local_numbers_in_their_edges;
+  WeakNodeValuePerFace<const unsigned short> m_face_local_numbers_in_their_nodes;
+
+  WeakCellValuePerEdge<const unsigned short> m_edge_local_numbers_in_their_cells;
+  WeakFaceValuePerEdge<const unsigned short> m_edge_local_numbers_in_their_faces;
+  WeakNodeValuePerEdge<const unsigned short> m_edge_local_numbers_in_their_nodes;
 
-  bool m_reversed;
-  std::vector<NodeId::base_type> m_node_id_list;
+  WeakCellValuePerNode<const unsigned short> m_node_local_numbers_in_their_cells;
+  WeakEdgeValuePerNode<const unsigned short> m_node_local_numbers_in_their_edges;
+  WeakFaceValuePerNode<const unsigned short> m_node_local_numbers_in_their_faces;
 
-  friend std::ostream& operator<<(std::ostream& os, const ConnectivityFace& f)
+  ConnectivityComputer m_connectivity_computer;
+
+  std::vector<RefCellList> m_ref_cell_list_vector;
+  std::vector<RefFaceList> m_ref_face_list_vector;
+  std::vector<RefEdgeList> m_ref_edge_list_vector;
+  std::vector<RefNodeList> m_ref_node_list_vector;
+
+  WeakCellValue<const double> m_inv_cell_nb_nodes;
+
+  void _computeCellFaceAndFaceNodeConnectivities();
+
+  template <typename SubItemValuePerItemType>
+  PASTIS_INLINE
+  const SubItemValuePerItemType&
+  _lazzyBuildItemNumberInTheirChild(const SubItemValuePerItemType& sub_item_value_per_item) const
   {
-    for (auto id : f.m_node_id_list) {
-      os << id << ' ';
+    using ReversedItemOfItem = typename SubItemValuePerItemType::ItemOfItemType::Reversed;
+    if (not sub_item_value_per_item.isBuilt()) {
+      const_cast<SubItemValuePerItemType&>(sub_item_value_per_item)
+          = m_connectivity_computer
+          . computeLocalItemNumberInChildItem<ReversedItemOfItem>(*this);
     }
-    return os;
+    return sub_item_value_per_item;
   }
 
+  friend class ConnectivityComputer;
+
   PASTIS_INLINE
-  const bool& reversed() const
+  const ConnectivityMatrix& _getMatrix(const ItemType& item_type_0,
+                                       const ItemType& item_type_1) const
   {
-    return m_reversed;
+    const ConnectivityMatrix& connectivity_matrix
+        = m_item_to_item_matrix[itemTId(item_type_0)][itemTId(item_type_1)];
+    if (not connectivity_matrix.isBuilt()) {
+      const_cast<ConnectivityMatrix&>(connectivity_matrix)
+          = m_connectivity_computer
+          . computeConnectivityMatrix(*this, item_type_0, item_type_1);
+
+    }
+    return connectivity_matrix;
   }
 
+ public:
   PASTIS_INLINE
-  const std::vector<unsigned int>& nodeIdList() const
+  const auto& cellType() const
   {
-    return m_node_id_list;
+    return m_cell_type;
   }
 
   PASTIS_INLINE
-  std::vector<unsigned int> _sort(const std::vector<unsigned int>& node_list)
+  const auto& cellNumber() const
   {
-    const auto min_id = std::min_element(node_list.begin(), node_list.end());
-    const int shift = std::distance(node_list.begin(), min_id);
-
-    std::vector<unsigned int> rotated_node_list(node_list.size());
-    if (node_list[(shift+1)%node_list.size()] > node_list[(shift+node_list.size()-1)%node_list.size()]) {
-      for (size_t i=0; i<node_list.size(); ++i) {
-        rotated_node_list[i] = node_list[(shift+node_list.size()-i)%node_list.size()];
-        m_reversed = true;
-      }
-    } else {
-      for (size_t i=0; i<node_list.size(); ++i) {
-        rotated_node_list[i] = node_list[(shift+i)%node_list.size()];
-      }
-    }
+    return m_cell_number;
+  }
 
-    return rotated_node_list;
+  PASTIS_INLINE
+  const auto& faceNumber() const
+  {
+    return m_face_number;
   }
 
   PASTIS_INLINE
-  ConnectivityFace(const std::vector<unsigned int>& given_node_id_list)
-      : m_reversed(false),
-        m_node_id_list(_sort(given_node_id_list))
+  const auto& edgeNumber() const
   {
-    ;
+    return m_edge_number;
   }
 
- public:
-  bool operator==(const ConnectivityFace& f) const
+  PASTIS_INLINE
+  const auto& nodeNumber() const
   {
-    if (m_node_id_list.size() == f.nodeIdList().size()) {
-      for (size_t j=0; j<m_node_id_list.size(); ++j) {
-        if (m_node_id_list[j] != f.nodeIdList()[j]) {
-          return false;
-        }
-      }
-      return true;
-    }
-    return false;
+    return m_node_number;
   }
 
+  template <ItemType item_type>
   PASTIS_INLINE
-  bool operator<(const ConnectivityFace& f) const
+  const auto& number() const
   {
-    const size_t min_nb_nodes = std::min(f.m_node_id_list.size(), m_node_id_list.size());
-    for (size_t i=0; i<min_nb_nodes; ++i) {
-      if (m_node_id_list[i] <  f.m_node_id_list[i]) return true;
-      if (m_node_id_list[i] != f.m_node_id_list[i]) return false;
+    if constexpr(item_type == ItemType::cell) {
+      return m_cell_number;
+    } else if constexpr(item_type == ItemType::face) {
+      return m_face_number;
+    } else if constexpr(item_type == ItemType::edge) {
+      return m_edge_number;
+    } else if constexpr(item_type == ItemType::node) {
+      return m_node_number;
+    } else {
+      static_assert(is_false_item_type_v<item_type>, "unknown ItemType");
     }
-    return m_node_id_list.size() < f.m_node_id_list.size();
   }
 
   PASTIS_INLINE
-  ConnectivityFace& operator=(const ConnectivityFace&) = default;
+  const auto& cellOwner() const
+  {
+    return m_cell_owner;
+  }
 
   PASTIS_INLINE
-  ConnectivityFace& operator=(ConnectivityFace&&) = default;
+  const auto& faceOwner() const
+  {
+    return m_face_owner;
+  }
 
   PASTIS_INLINE
-  ConnectivityFace(const ConnectivityFace&) = default;
+  const auto& edgeOwner() const
+  {
+    perr() << __FILE__ << ':' << __LINE__ << ": edge owner not built\n";
+    std::terminate();
+    return m_edge_owner;
+  }
 
   PASTIS_INLINE
-  ConnectivityFace(ConnectivityFace&&) = default;
-
+  const auto& nodeOwner() const
+  {
+    return m_node_owner;
+  }
 
+  template <ItemType item_type>
   PASTIS_INLINE
-  ConnectivityFace() = delete;
+  const auto& owner() const
+  {
+    if constexpr(item_type == ItemType::cell) {
+      return m_cell_owner;
+    } else if constexpr(item_type == ItemType::face) {
+      return m_face_owner;
+    } else if constexpr(item_type == ItemType::edge) {
+      return m_edge_owner;
+    } else if constexpr(item_type == ItemType::node) {
+      return m_node_owner;
+    } else {
+      static_assert(is_false_item_type_v<item_type>, "unknown ItemType");
+    }
+  }
 
   PASTIS_INLINE
-  ~ConnectivityFace() = default;
-};
-
-
-template <size_t Dimension>
-class Connectivity final
-    : public IConnectivity
-{
- private:
-  constexpr static auto& itemTId = ItemTypeId<Dimension>::itemTId;
-
- public:
-  static constexpr size_t dimension = Dimension;
-
- private:
-  ConnectivityMatrix m_item_to_item_matrix[Dimension+1][Dimension+1];
-  CellValue<const CellType> m_cell_type;
-
-  FaceValuePerCell<const bool> m_cell_face_is_reversed;
-
-  NodeValuePerCell<const unsigned short> m_cell_local_numbers_in_their_nodes;
-  EdgeValuePerCell<const unsigned short> m_cell_local_numbers_in_their_edges;
-  FaceValuePerCell<const unsigned short> m_cell_local_numbers_in_their_faces;
-
-  CellValuePerFace<const unsigned short> m_face_local_numbers_in_their_cells;
-  EdgeValuePerFace<const unsigned short> m_face_local_numbers_in_their_edges;
-  NodeValuePerFace<const unsigned short> m_face_local_numbers_in_their_nodes;
-
-  CellValuePerEdge<const unsigned short> m_edge_local_numbers_in_their_cells;
-  FaceValuePerEdge<const unsigned short> m_edge_local_numbers_in_their_faces;
-  NodeValuePerEdge<const unsigned short> m_edge_local_numbers_in_their_nodes;
-
-  CellValuePerNode<const unsigned short> m_node_local_numbers_in_their_cells;
-  EdgeValuePerNode<const unsigned short> m_node_local_numbers_in_their_edges;
-  FaceValuePerNode<const unsigned short> m_node_local_numbers_in_their_faces;
-
-  ConnectivityComputer m_connectivity_computer;
-
-  std::vector<RefFaceList> m_ref_face_list;
-  std::vector<RefNodeList> m_ref_node_list;
-
-  CellValue<const double> m_inv_cell_nb_nodes;
-
-  using Face = ConnectivityFace<Dimension>;
-
-  std::unordered_map<Face, FaceId, typename Face::Hash> m_face_number_map;
-
-  void _computeCellFaceAndFaceNodeConnectivities();
+  const auto& cellIsOwned() const
+  {
+    return m_cell_is_owned;
+  }
 
-  template <typename SubItemValuePerItemType>
   PASTIS_INLINE
-  const SubItemValuePerItemType&
-  _lazzyBuildItemNumberInTheirChild(const SubItemValuePerItemType& sub_item_value_per_item) const
+  const auto& faceIsOwned() const
   {
-    if (not sub_item_value_per_item.isBuilt()) {
-      const_cast<SubItemValuePerItemType&>(sub_item_value_per_item)
-          = m_connectivity_computer
-          . computeLocalItemNumberInChildItem<SubItemValuePerItemType::item_t,
-                                              SubItemValuePerItemType::sub_item_t>(*this);
-    }
-    return sub_item_value_per_item;
+    return m_face_is_owned;
   }
 
-  friend class ConnectivityComputer;
+  PASTIS_INLINE
+  const auto& edgeIsOwned() const
+  {
+    perr() << __FILE__ << ':' << __LINE__ << ": edge is owned not built\n";
+    std::terminate();
+    return m_edge_is_owned;
+  }
 
   PASTIS_INLINE
-  const ConnectivityMatrix& _getMatrix(const ItemType& item_type_0,
-                                       const ItemType& item_type_1) const
+  const auto& nodeIsOwned() const
   {
-    const ConnectivityMatrix& connectivity_matrix
-        = m_item_to_item_matrix[itemTId(item_type_0)][itemTId(item_type_1)];
-    if (not connectivity_matrix.isBuilt()) {
-      const_cast<ConnectivityMatrix&>(connectivity_matrix)
-          = m_connectivity_computer
-          . computeConnectivityMatrix(*this, item_type_0, item_type_1);
+    return m_node_is_owned;
+  }
 
+  template <ItemType item_type>
+  PASTIS_INLINE
+  const auto& isOwned() const
+  {
+    if constexpr(item_type == ItemType::cell) {
+      return m_cell_is_owned;
+    } else if constexpr(item_type == ItemType::face) {
+      return m_face_is_owned;
+    } else if constexpr(item_type == ItemType::edge) {
+      return m_edge_is_owned;
+    } else if constexpr(item_type == ItemType::node) {
+      return m_node_is_owned;
+    } else {
+      static_assert(is_false_item_type_v<item_type>, "unknown ItemType");
     }
-    return connectivity_matrix;
   }
 
- public:
-
   PASTIS_INLINE
   const bool& isConnectivityMatrixBuilt(const ItemType& item_type_0,
                                         const ItemType& item_type_1) const
@@ -398,14 +367,20 @@ class Connectivity final
     return this->template getItemToItemMatrix<ItemType::node, ItemType::edge>();
   }
 
-
   PASTIS_INLINE
   const auto& cellFaceIsReversed() const
   {
-    static_assert(dimension>1, "reversed faces makes no sense in dimension 1");
+    static_assert(Dimension>1, "reversed faces makes no sense in dimension 1");
     return m_cell_face_is_reversed;
   }
 
+  PASTIS_INLINE
+  const auto& faceEdgeIsReversed() const
+  {
+    static_assert(Dimension>2, "reversed edges makes no sense in dimension 1 or 2");
+    return m_face_edge_is_reversed;
+  }
+
   PASTIS_INLINE
   const auto& cellLocalNumbersInTheirNodes() const
   {
@@ -415,7 +390,7 @@ class Connectivity final
   PASTIS_INLINE
   const auto& cellLocalNumbersInTheirEdges() const
   {
-    if constexpr (dimension>2) {
+    if constexpr (Dimension>2) {
       return _lazzyBuildItemNumberInTheirChild(m_cell_local_numbers_in_their_edges);
     } else {
       return cellLocalNumbersInTheirFaces();
@@ -425,7 +400,7 @@ class Connectivity final
   PASTIS_INLINE
   const auto& cellLocalNumbersInTheirFaces() const
   {
-    if constexpr (dimension>1) {
+    if constexpr (Dimension>1) {
       return _lazzyBuildItemNumberInTheirChild(m_cell_local_numbers_in_their_faces);
     } else {
       return cellLocalNumbersInTheirNodes();
@@ -435,7 +410,7 @@ class Connectivity final
   PASTIS_INLINE
   const auto& faceLocalNumbersInTheirCells() const
   {
-    if constexpr(dimension>1) {
+    if constexpr(Dimension>1) {
       return _lazzyBuildItemNumberInTheirChild(m_face_local_numbers_in_their_cells);
     } else {
       return nodeLocalNumbersInTheirCells();
@@ -445,21 +420,21 @@ class Connectivity final
   PASTIS_INLINE
   const auto& faceLocalNumbersInTheirEdges() const
   {
-    static_assert(dimension>2,"this function has no meaning in 1d or 2d");
+    static_assert(Dimension>2,"this function has no meaning in 1d or 2d");
     return _lazzyBuildItemNumberInTheirChild(m_face_local_numbers_in_their_edges);
   }
 
   PASTIS_INLINE
   const auto& faceLocalNumbersInTheirNodes() const
   {
-    static_assert(dimension>1,"this function has no meaning in 1d");
+    static_assert(Dimension>1,"this function has no meaning in 1d");
     return _lazzyBuildItemNumberInTheirChild(m_face_local_numbers_in_their_nodes);
   }
 
   PASTIS_INLINE
   const auto& edgeLocalNumbersInTheirCells() const
   {
-    if constexpr (dimension>2) {
+    if constexpr (Dimension>2) {
       return _lazzyBuildItemNumberInTheirChild(m_edge_local_numbers_in_their_cells);
     } else {
       return faceLocalNumbersInTheirCells();
@@ -469,14 +444,14 @@ class Connectivity final
   PASTIS_INLINE
   const auto& edgeLocalNumbersInTheirFaces() const
   {
-    static_assert(dimension>2, "this function has no meaning in 1d or 2d");
+    static_assert(Dimension>2, "this function has no meaning in 1d or 2d");
     return _lazzyBuildItemNumberInTheirChild(m_edge_local_numbers_in_their_faces);
   }
 
   PASTIS_INLINE
   const auto& edgeLocalNumbersInTheirNodes() const
   {
-    static_assert(dimension>2, "this function has no meaning in 1d or 2d");
+    static_assert(Dimension>2, "this function has no meaning in 1d or 2d");
     return _lazzyBuildItemNumberInTheirChild(m_edge_local_numbers_in_their_nodes);
   }
 
@@ -489,45 +464,117 @@ class Connectivity final
   PASTIS_INLINE
   const auto& nodeLocalNumbersInTheirEdges() const
   {
-    static_assert(dimension>2, "this function has no meaning in 1d or 2d");
+    static_assert(Dimension>2, "this function has no meaning in 1d or 2d");
     return _lazzyBuildItemNumberInTheirChild(m_node_local_numbers_in_their_edges);
   }
 
   PASTIS_INLINE
   const auto& nodeLocalNumbersInTheirFaces() const
   {
-    static_assert(dimension>1,"this function has no meaning in 1d");
+    static_assert(Dimension>1,"this function has no meaning in 1d");
     return _lazzyBuildItemNumberInTheirChild(m_node_local_numbers_in_their_faces);
   }
 
-  void addRefFaceList(const RefFaceList& ref_face_list)
+  template <ItemType item_type>
+  size_t numberOfRefItemList() const
   {
-    m_ref_face_list.push_back(ref_face_list);
-  }
+    if constexpr (item_type == ItemType::cell) {
+      return m_ref_cell_list_vector.size();
+    } else if constexpr (item_type == ItemType::face) {
+      return m_ref_face_list_vector.size();
+    } else if constexpr (item_type == ItemType::edge) {
+      return m_ref_edge_list_vector.size();
+    } else if constexpr (item_type == ItemType::node) {
+      return m_ref_node_list_vector.size();
+    } else {
+      static_assert(is_false_item_type_v<item_type>, "Unexpected item type");
+    }
 
-  size_t numberOfRefFaceList() const
-  {
-    return m_ref_face_list.size();
   }
 
-  const RefFaceList& refFaceList(const size_t& i) const
+  template <ItemType item_type>
+  const RefItemList<item_type>& refItemList(const size_t& i) const
   {
-    return m_ref_face_list[i];
+    if constexpr (item_type == ItemType::cell) {
+      return m_ref_cell_list_vector[i];
+    } else if constexpr (item_type == ItemType::face) {
+      return m_ref_face_list_vector[i];
+    } else if constexpr (item_type == ItemType::edge) {
+      return m_ref_edge_list_vector[i];
+    } else if constexpr (item_type == ItemType::node) {
+      return m_ref_node_list_vector[i];
+    } else {
+      static_assert(is_false_item_type_v<item_type>, "Unexpected item type");
+    }
   }
 
-  void addRefNodeList(const RefNodeList& ref_node_list)
+  template <ItemType item_type>
+  void addRefItemList(const RefItemList<item_type>& ref_item_list)
   {
-    m_ref_node_list.push_back(ref_node_list);
+    if constexpr (item_type == ItemType::cell) {
+      m_ref_cell_list_vector.push_back(ref_item_list);
+    } else if constexpr (item_type == ItemType::face) {
+      m_ref_face_list_vector.push_back(ref_item_list);
+    } else if constexpr (item_type == ItemType::edge) {
+      m_ref_edge_list_vector.push_back(ref_item_list);
+    } else if constexpr (item_type == ItemType::node) {
+      m_ref_node_list_vector.push_back(ref_item_list);
+    } else {
+      static_assert(is_false_item_type_v<item_type>, "Unexpected item type");
+    }
   }
 
-  size_t numberOfRefNodeList() const
+  PASTIS_INLINE
+  CSRGraph cellToCellGraph() const
   {
-    return m_ref_node_list.size();
-  }
+    std::vector<std::set<int>> cell_cells(this->numberOfCells());
+    if constexpr (true) {
+      const auto& face_to_cell_matrix
+          = this->faceToCellMatrix();
 
-  const RefNodeList& refNodeList(const size_t& i) const
-  {
-    return m_ref_node_list[i];
+      for (FaceId l=0; l<this->numberOfFaces(); ++l) {
+        const auto& face_to_cell = face_to_cell_matrix[l];
+        if (face_to_cell.size() > 1) {
+          const CellId cell_0 = face_to_cell[0];
+          const CellId cell_1 = face_to_cell[1];
+
+          cell_cells[cell_0].insert(cell_1);
+          cell_cells[cell_1].insert(cell_0);
+        }
+      }
+    } else {
+      const auto& node_to_cell_matrix
+          = this->nodeToCellMatrix();
+
+      for (NodeId l=0; l<this->numberOfNodes(); ++l) {
+        const auto& node_to_cell = node_to_cell_matrix[l];
+        for (size_t i_cell=0; i_cell<node_to_cell.size(); ++i_cell) {
+          const CellId cell_0 = node_to_cell[i_cell];
+          for (size_t j_cell=0; j_cell<i_cell; ++j_cell) {
+            const CellId cell_1 = node_to_cell[j_cell];
+            cell_cells[cell_0].insert(cell_1);
+            cell_cells[cell_1].insert(cell_0);
+          }
+        }
+      }
+    }
+
+    Array<int> entries(this->numberOfCells()+1);
+    entries[0]=0;
+    for (size_t j=0; j<this->numberOfCells(); ++j) {
+      entries[j+1] = entries[j]+cell_cells[j].size();
+    }
+    Array<int> neighbors(entries[this->numberOfCells()]);
+    {
+      size_t k=0;
+      for (size_t j=0; j<this->numberOfCells(); ++j) {
+        for (CellId cell_id : cell_cells[j]) {
+          neighbors[k] = m_cell_global_index[cell_id];
+          ++k;
+        }
+      }
+    }
+    return CSRGraph(entries, neighbors);
   }
 
   PASTIS_INLINE
@@ -562,35 +609,48 @@ class Connectivity final
     return cell_to_node_matrix.numRows();
   }
 
-  const CellValue<const double>& invCellNbNodes() const
+  CellValue<const double> invCellNbNodes() const
   {
-#warning add calculation on demand when variables will be defined
-    return m_inv_cell_nb_nodes;
-  }
+    if (not m_inv_cell_nb_nodes.isBuilt()) {
+      const auto& cell_to_node_matrix
+          = m_item_to_item_matrix[itemTId(ItemType::cell)][itemTId(ItemType::node)];
 
-  unsigned int getFaceNumber(const std::vector<unsigned int>& face_nodes) const
-  {
-    const Face face(face_nodes);
-    auto i_face = m_face_number_map.find(face);
-    if (i_face == m_face_number_map.end()) {
-      perr() << "Face " << face << " not found!\n";
-      throw std::exception();
-      std::exit(0);
+      WeakCellValue<double> inv_cell_nb_nodes(*this);
+      parallel_for(this->numberOfCells(), PASTIS_LAMBDA(const CellId& j) {
+          const auto& cell_nodes = cell_to_node_matrix.rowConst(j);
+          inv_cell_nb_nodes[j] = 1./cell_nodes.length;
+        });
+      const_cast<WeakCellValue<const double>&>(m_inv_cell_nb_nodes) = inv_cell_nb_nodes;
     }
-    return i_face->second;
+
+    return m_inv_cell_nb_nodes;
   }
 
   Connectivity(const Connectivity&) = delete;
 
-  Connectivity(const std::vector<std::vector<unsigned int>>& cell_by_node_vector,
-               const std::vector<CellType>& cell_type_vector);
+ private:
+  Connectivity();
+  void _buildFrom(const ConnectivityDescriptor& descriptor);
 
+ public:
   ~Connectivity()
   {
-    ;
+    auto& manager = SynchronizerManager::instance();
+    manager.deleteConnectivitySynchronizer(this);
   }
 };
 
+template <size_t Dim>
+PASTIS_INLINE
+std::shared_ptr<Connectivity<Dim>>
+Connectivity<Dim>::build(const ConnectivityDescriptor& descriptor)
+{
+  std::shared_ptr<Connectivity<Dim>> connectivity_ptr(new Connectivity<Dim>);
+  connectivity_ptr->_buildFrom(descriptor);
+
+  return connectivity_ptr;
+}
+
 using Connectivity3D = Connectivity<3>;
 using Connectivity2D = Connectivity<2>;
 using Connectivity1D = Connectivity<1>;
diff --git a/src/mesh/ConnectivityComputer.cpp b/src/mesh/ConnectivityComputer.cpp
index d6a77b13e6f124b825c54cbf422567281784fb79..294b15591eac18ab8ac11951b74a0effd84c1827 100644
--- a/src/mesh/ConnectivityComputer.cpp
+++ b/src/mesh/ConnectivityComputer.cpp
@@ -24,7 +24,7 @@ computeConnectivityMatrix(const ConnectivityType& connectivity,
   } else {
     perr() << "unable to compute connectivity "
               << itemName(item_type) << " -> " << itemName(child_item_type) << '\n';
-    std::exit(0);
+    std::terminate();
   }
 
   return item_to_child_item_matrix;
@@ -76,18 +76,22 @@ _computeInverse(const ConnectivityMatrix& item_to_child_matrix) const
   return ConnectivityMatrix(child_to_items_vector);
 }
 
-template <ItemType item_type,
-          ItemType child_item_type,
+template <typename ItemOfItem,
           typename ConnectivityType>
-SubItemValuePerItem<const unsigned short, child_item_type, item_type>
+WeakSubItemValuePerItem<const unsigned short, typename ItemOfItem::Reversed>
 ConnectivityComputer::computeLocalItemNumberInChildItem(const ConnectivityType& connectivity) const
 {
+  using ReversedItemOfItem = typename ItemOfItem::Reversed;
+
+  constexpr ItemType item_type = ReversedItemOfItem::item_type;
+  constexpr ItemType child_item_type = ReversedItemOfItem::sub_item_type;
+
   const ConnectivityMatrix& child_item_to_items_matrix
       = connectivity._getMatrix(child_item_type, item_type);
   const ConnectivityMatrix& item_to_child_items_matrix
       = connectivity._getMatrix(item_type, child_item_type);
 
-  SubItemValuePerItem<unsigned short, child_item_type, item_type> item_number_in_child_item(connectivity);
+  WeakSubItemValuePerItem<unsigned short, ReversedItemOfItem> item_number_in_child_item(connectivity);
   for (ItemIdT<item_type> r=0; r<item_to_child_items_matrix.numRows(); ++r) {
     const auto& item_to_child_items = item_to_child_items_matrix.rowConst(r);
     for (unsigned short J=0; J<item_to_child_items.length; ++J) {
@@ -108,51 +112,89 @@ ConnectivityComputer::computeLocalItemNumberInChildItem(const ConnectivityType&
 
 // 1D
 
-template SubItemValuePerItem<const unsigned short, ItemType::cell, ItemType::node>
+template WeakSubItemValuePerItem<const unsigned short, CellOfNode>
+ConnectivityComputer::
+computeLocalItemNumberInChildItem<NodeOfCell>(const Connectivity1D&) const;
+
+template WeakSubItemValuePerItem<const unsigned short, NodeOfCell>
 ConnectivityComputer::
-computeLocalItemNumberInChildItem<ItemType::node,
-                                  ItemType::cell>(const Connectivity1D&) const;
+computeLocalItemNumberInChildItem<CellOfNode>(const Connectivity1D&) const;
 
-template SubItemValuePerItem<const unsigned short, ItemType::cell, ItemType::face>
+// 2D
+
+template WeakSubItemValuePerItem<const unsigned short, CellOfNode>
 ConnectivityComputer::
-computeLocalItemNumberInChildItem<ItemType::face,
-                                  ItemType::cell>(const Connectivity1D&) const;
+computeLocalItemNumberInChildItem<NodeOfCell>(const Connectivity2D&) const;
 
-template SubItemValuePerItem<const unsigned short, ItemType::node, ItemType::cell>
+template WeakSubItemValuePerItem<const unsigned short, CellOfFace>
 ConnectivityComputer::
-computeLocalItemNumberInChildItem<ItemType::cell,
-                                  ItemType::node>(const Connectivity1D&) const;
+computeLocalItemNumberInChildItem<FaceOfCell>(const Connectivity2D&) const;
 
-// 2D
+template WeakSubItemValuePerItem<const unsigned short, FaceOfNode>
+ConnectivityComputer::
+computeLocalItemNumberInChildItem<NodeOfFace>(const Connectivity2D&) const;
 
-template SubItemValuePerItem<const unsigned short, ItemType::cell, ItemType::node>
+template WeakSubItemValuePerItem<const unsigned short, FaceOfCell>
 ConnectivityComputer::
-computeLocalItemNumberInChildItem<ItemType::node,
-                                  ItemType::cell>(const Connectivity2D&) const;
+computeLocalItemNumberInChildItem<CellOfFace>(const Connectivity2D&) const;
 
-template SubItemValuePerItem<const unsigned short, ItemType::cell, ItemType::face>
+template WeakSubItemValuePerItem<const unsigned short, NodeOfFace>
 ConnectivityComputer::
-computeLocalItemNumberInChildItem<ItemType::face,
-                                  ItemType::cell>(const Connectivity2D&) const;
+computeLocalItemNumberInChildItem<FaceOfNode>(const Connectivity2D&) const;
 
-template SubItemValuePerItem<const unsigned short, ItemType::node, ItemType::cell>
+template WeakSubItemValuePerItem<const unsigned short, NodeOfCell>
 ConnectivityComputer::
-computeLocalItemNumberInChildItem<ItemType::cell,
-                                  ItemType::node>(const Connectivity2D&) const;
+computeLocalItemNumberInChildItem<CellOfNode>(const Connectivity2D&) const;
 
 // 3D
 
-template SubItemValuePerItem<const unsigned short, ItemType::cell, ItemType::node>
+template WeakSubItemValuePerItem<const unsigned short, CellOfNode>
+ConnectivityComputer::
+computeLocalItemNumberInChildItem<NodeOfCell>(const Connectivity3D&) const;
+
+template WeakSubItemValuePerItem<const unsigned short, CellOfEdge>
+ConnectivityComputer::
+computeLocalItemNumberInChildItem<EdgeOfCell>(const Connectivity3D&) const;
+
+template WeakSubItemValuePerItem<const unsigned short, CellOfFace>
+ConnectivityComputer::
+computeLocalItemNumberInChildItem<FaceOfCell>(const Connectivity3D&) const;
+
+
+template WeakSubItemValuePerItem<const unsigned short, FaceOfNode>
+ConnectivityComputer::
+computeLocalItemNumberInChildItem<NodeOfFace>(const Connectivity3D&) const;
+
+template WeakSubItemValuePerItem<const unsigned short, FaceOfEdge>
+ConnectivityComputer::
+computeLocalItemNumberInChildItem<EdgeOfFace>(const Connectivity3D&) const;
+
+template WeakSubItemValuePerItem<const unsigned short, FaceOfCell>
+ConnectivityComputer::
+computeLocalItemNumberInChildItem<CellOfFace>(const Connectivity3D&) const;
+
+
+template WeakSubItemValuePerItem<const unsigned short, EdgeOfNode>
+ConnectivityComputer::
+computeLocalItemNumberInChildItem<NodeOfEdge>(const Connectivity3D&) const;
+
+template WeakSubItemValuePerItem<const unsigned short, EdgeOfFace>
+ConnectivityComputer::
+computeLocalItemNumberInChildItem<FaceOfEdge>(const Connectivity3D&) const;
+
+template WeakSubItemValuePerItem<const unsigned short, EdgeOfCell>
+ConnectivityComputer::
+computeLocalItemNumberInChildItem<CellOfEdge>(const Connectivity3D&) const;
+
+
+template WeakSubItemValuePerItem<const unsigned short, NodeOfEdge>
 ConnectivityComputer::
-computeLocalItemNumberInChildItem<ItemType::node,
-                                  ItemType::cell>(const Connectivity3D&) const;
+computeLocalItemNumberInChildItem<EdgeOfNode>(const Connectivity3D&) const;
 
-template SubItemValuePerItem<const unsigned short, ItemType::cell, ItemType::face>
+template WeakSubItemValuePerItem<const unsigned short, NodeOfFace>
 ConnectivityComputer::
-computeLocalItemNumberInChildItem<ItemType::face,
-                                  ItemType::cell>(const Connectivity3D&) const;
+computeLocalItemNumberInChildItem<FaceOfNode>(const Connectivity3D&) const;
 
-template SubItemValuePerItem<const unsigned short, ItemType::node, ItemType::cell>
+template WeakSubItemValuePerItem<const unsigned short, NodeOfCell>
 ConnectivityComputer::
-computeLocalItemNumberInChildItem<ItemType::cell,
-                                  ItemType::node>(const Connectivity3D&) const;
+computeLocalItemNumberInChildItem<CellOfNode>(const Connectivity3D&) const;
diff --git a/src/mesh/ConnectivityComputer.hpp b/src/mesh/ConnectivityComputer.hpp
index 8b4fdfc3ab20bf2d6290d234a13b833dc6451cb6..d5c6019d54b57d6f18ad1eab0af3797f2e0108cb 100644
--- a/src/mesh/ConnectivityComputer.hpp
+++ b/src/mesh/ConnectivityComputer.hpp
@@ -18,10 +18,9 @@ class ConnectivityComputer
                             const ItemType& child_item_type) const;
 
 
-  template <ItemType item_type,
-            ItemType child_item_type,
+  template <typename ItemOfItem,
             typename ConnectivityType>
-  SubItemValuePerItem<const unsigned short, child_item_type, item_type>
+  WeakSubItemValuePerItem<const unsigned short, typename ItemOfItem::Reversed>
   computeLocalItemNumberInChildItem(const ConnectivityType& connectivity) const;
 
   ConnectivityComputer(const ConnectivityComputer&) = default;
diff --git a/src/mesh/ConnectivityDescriptor.hpp b/src/mesh/ConnectivityDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e20b05c11ac585b4293fc233bd0919efce48e5ea
--- /dev/null
+++ b/src/mesh/ConnectivityDescriptor.hpp
@@ -0,0 +1,121 @@
+#ifndef CONNECTIVITY_DESCRIPTOR_HPP
+#define CONNECTIVITY_DESCRIPTOR_HPP
+
+#include <RefItemList.hpp>
+#include <PastisTraits.hpp>
+#include <ItemOfItemType.hpp>
+
+#include <vector>
+
+class ConnectivityDescriptor
+{
+ private:
+  std::vector<RefCellList> m_ref_cell_list_vector;
+  std::vector<RefFaceList> m_ref_face_list_vector;
+  std::vector<RefEdgeList> m_ref_edge_list_vector;
+  std::vector<RefNodeList> m_ref_node_list_vector;
+
+ public:
+  std::vector<std::vector<unsigned int>> cell_to_node_vector;
+  std::vector<std::vector<unsigned int>> cell_to_face_vector;
+  std::vector<std::vector<unsigned int>> cell_to_edge_vector;
+
+  std::vector<std::vector<unsigned int>> face_to_node_vector;
+  std::vector<std::vector<unsigned int>> face_to_edge_vector;
+
+  std::vector<std::vector<unsigned int>> edge_to_node_vector;
+
+  template <typename ItemOfItemT>
+  auto& itemOfItemVector()
+  {
+    if constexpr (std::is_same_v<ItemOfItemT,NodeOfCell>) {
+      return cell_to_node_vector;
+    } else if constexpr (std::is_same_v<ItemOfItemT,FaceOfCell>) {
+      return cell_to_face_vector;
+    } else if constexpr (std::is_same_v<ItemOfItemT,EdgeOfCell>) {
+      return cell_to_edge_vector;
+    } else if constexpr (std::is_same_v<ItemOfItemT,EdgeOfFace>) {
+      return face_to_edge_vector;
+    } else if constexpr (std::is_same_v<ItemOfItemT,NodeOfFace>) {
+      return face_to_node_vector;
+    } else if constexpr (std::is_same_v<ItemOfItemT,NodeOfEdge>) {
+      return edge_to_node_vector;
+    } else {
+      static_assert(is_false_v<ItemOfItemT>, "Unexpected item of item type");
+    }
+  }
+
+  std::vector<Array<bool>> cell_face_is_reversed_vector;
+  std::vector<Array<bool>> face_edge_is_reversed_vector;
+
+  std::vector<CellType> cell_type_vector;
+
+  std::vector<int> cell_number_vector;
+  std::vector<int> face_number_vector;
+  std::vector<int> edge_number_vector;
+  std::vector<int> node_number_vector;
+
+  template <ItemType item_type>
+  const std::vector<int>& itemNumberVector() const
+  {
+    if constexpr (item_type == ItemType::cell) {
+      return cell_number_vector;
+    } else if constexpr (item_type == ItemType::face) {
+      return face_number_vector;
+    } else if constexpr (item_type == ItemType::edge) {
+      return edge_number_vector;
+    } else if constexpr (item_type == ItemType::node) {
+      return node_number_vector;
+    } else {
+      static_assert(is_false_item_type_v<item_type>, "Unexpected item type");
+    }
+  }
+
+  std::vector<int> cell_owner_vector;
+  std::vector<int> face_owner_vector;
+  std::vector<int> edge_owner_vector;
+  std::vector<int> node_owner_vector;
+
+  template <ItemType item_type>
+  const std::vector<RefItemList<item_type>>& refItemListVector() const
+  {
+    if constexpr (item_type == ItemType::cell) {
+      return m_ref_cell_list_vector;
+    } else if constexpr (item_type == ItemType::face) {
+      return m_ref_face_list_vector;
+    } else if constexpr (item_type == ItemType::edge) {
+      return m_ref_edge_list_vector;
+    } else if constexpr (item_type == ItemType::node) {
+      return m_ref_node_list_vector;
+    } else {
+      static_assert(is_false_item_type_v<item_type>, "Unexpected item type");
+    }
+  }
+
+  template <ItemType item_type>
+  void addRefItemList(const RefItemList<item_type>& ref_item_list)
+  {
+    if constexpr (item_type == ItemType::cell) {
+      m_ref_cell_list_vector.push_back(ref_item_list);
+    } else if constexpr (item_type == ItemType::face) {
+      m_ref_face_list_vector.push_back(ref_item_list);
+    } else if constexpr (item_type == ItemType::edge) {
+      m_ref_edge_list_vector.push_back(ref_item_list);
+    } else if constexpr (item_type == ItemType::node) {
+      m_ref_node_list_vector.push_back(ref_item_list);
+    } else {
+      static_assert(is_false_item_type_v<item_type>, "Unexpected item type");
+    }
+  }
+
+  ConnectivityDescriptor& operator=(const ConnectivityDescriptor&) = delete;
+  ConnectivityDescriptor& operator=(ConnectivityDescriptor&&) = delete;
+
+  ConnectivityDescriptor() = default;
+  ConnectivityDescriptor(const ConnectivityDescriptor&) = default;
+  ConnectivityDescriptor(ConnectivityDescriptor&&) = delete;
+  ~ConnectivityDescriptor() = default;
+};
+
+
+#endif // CONNECTIVITY_DESCRIPTOR_HPP
diff --git a/src/mesh/ConnectivityDispatcher.cpp b/src/mesh/ConnectivityDispatcher.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..aa26cb6f71422cfaf646f3d52ab22ff882e0f72a
--- /dev/null
+++ b/src/mesh/ConnectivityDispatcher.cpp
@@ -0,0 +1,683 @@
+#include <ConnectivityDispatcher.hpp>
+#include <Partitioner.hpp>
+
+#include <ItemOfItemType.hpp>
+
+#include <unordered_map>
+
+template <int Dimension>
+template <ItemType item_type>
+void
+ConnectivityDispatcher<Dimension>::_buildNewOwner()
+{
+  if constexpr (item_type == ItemType::cell) {
+    CSRGraph connectivity_graph = m_connectivity.cellToCellGraph();
+    Partitioner P;
+
+    CellValue<int> cell_new_owner(m_connectivity);
+    cell_new_owner = P.partition(connectivity_graph);
+
+    this->_dispatchedInfo<ItemType::cell>().m_new_owner = cell_new_owner;
+  } else {
+    const auto& item_to_cell_matrix
+        = m_connectivity.template getItemToItemMatrix<item_type,ItemType::cell>();
+
+    const auto& cell_number = m_connectivity.cellNumber();
+    const auto& cell_new_owner = this->_dispatchedInfo<ItemType::cell>().m_new_owner;
+
+    using ItemId = ItemIdT<item_type>;
+    ItemValue<int, item_type> item_new_owner(m_connectivity);
+    parallel_for(item_new_owner.size(), PASTIS_LAMBDA(const ItemId& l) {
+        const auto& item_to_cell = item_to_cell_matrix[l];
+        CellId Jmin = item_to_cell[0];
+
+        for (size_t j=1; j<item_to_cell.size(); ++j) {
+          const CellId J = item_to_cell[j];
+          if (cell_number[J] < cell_number[Jmin]) {
+            Jmin=J;
+          }
+        }
+        item_new_owner[l] = cell_new_owner[Jmin];
+      });
+
+    synchronize(item_new_owner);
+    this->_dispatchedInfo<item_type>().m_new_owner = item_new_owner;
+  }
+}
+
+template <int Dimension>
+template <ItemType item_type>
+void ConnectivityDispatcher<Dimension>::
+_buildItemToExchangeLists()
+{
+  this->_buildItemListToSend<item_type>();
+  this->_buildNumberOfItemToExchange<item_type>();
+  if constexpr (item_type == ItemType::cell) {
+    this->_buildCellNumberIdMap();
+  }
+  this->_buildRecvItemIdCorrespondanceByProc<item_type>();
+}
+
+template <int Dimension>
+template <ItemType item_type>
+void
+ConnectivityDispatcher<Dimension>::_buildItemListToSend()
+{
+  if constexpr (item_type == ItemType::cell) {
+    const auto& node_to_cell_matrix
+        = m_connectivity.nodeToCellMatrix();
+    const auto& cell_to_node_matrix
+        = m_connectivity.cellToNodeMatrix();
+
+    const auto& cell_new_owner = this->_dispatchedInfo<ItemType::cell>().m_new_owner;
+
+    std::vector<std::vector<CellId>> cell_vector_to_send_by_proc(parallel::size());
+    Array<bool> send_to_rank(parallel::size());
+    for (CellId j=0; j<m_connectivity.numberOfCells(); ++j) {
+      send_to_rank.fill(false);
+      const auto& cell_to_node = cell_to_node_matrix[j];
+
+      for (size_t R=0; R<cell_to_node.size(); ++R) {
+        const NodeId& r = cell_to_node[R];
+        const auto& node_to_cell = node_to_cell_matrix[r];
+        for (size_t K=0; K<node_to_cell.size(); ++K) {
+          const CellId& k = node_to_cell[K];
+          send_to_rank[cell_new_owner[k]] = true;
+        }
+      }
+
+      for (size_t k=0; k<send_to_rank.size(); ++k) {
+        if (send_to_rank[k]) {
+          cell_vector_to_send_by_proc[k].push_back(j);
+        }
+      }
+    }
+
+    auto& cell_list_to_send_by_proc = this->_dispatchedInfo<ItemType::cell>().m_list_to_send_by_proc;
+    cell_list_to_send_by_proc.resize(parallel::size());
+    for (size_t i=0; i<parallel::size(); ++i) {
+      cell_list_to_send_by_proc[i] = convert_to_array(cell_vector_to_send_by_proc[i]);
+    }
+  } else {
+    const auto& cell_list_to_send_by_proc = this->_dispatchedInfo<ItemType::cell>().m_list_to_send_by_proc;
+
+    using ItemId = ItemIdT<item_type>;
+    const auto& cell_to_sub_item_matrix = m_connectivity.template getItemToItemMatrix<ItemType::cell,item_type>();
+
+    auto& item_list_to_send_by_proc = this->_dispatchedInfo<item_type>().m_list_to_send_by_proc;
+    item_list_to_send_by_proc.resize(parallel::size());
+
+    for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+      Array<bool> tag(m_connectivity.template numberOf<item_type>());
+      tag.fill(false);
+      std::vector<ItemId> item_id_vector;
+      for (size_t j=0; j<cell_list_to_send_by_proc[i_rank].size(); ++j) {
+        const CellId& cell_id = cell_list_to_send_by_proc[i_rank][j];
+        const auto& cell_sub_item_list = cell_to_sub_item_matrix[cell_id];
+        for (size_t r=0; r<cell_sub_item_list.size(); ++r) {
+          const ItemId& item_id = cell_sub_item_list[r];
+          if (not tag[item_id]) {
+            item_id_vector.push_back(item_id);
+            tag[item_id] = true;
+          }
+        }
+      }
+      item_list_to_send_by_proc[i_rank] = convert_to_array(item_id_vector);
+    }
+  }
+}
+
+template <int Dimension>
+template <ItemType item_type>
+void
+ConnectivityDispatcher<Dimension>::_buildNumberOfItemToExchange()
+{
+  const auto& item_list_to_send_by_proc = this->_dispatchedInfo<item_type>().m_list_to_send_by_proc;
+  Array<unsigned int> nb_item_to_send_by_proc(parallel::size());
+  for (size_t i=0; i<parallel::size(); ++i) {
+    nb_item_to_send_by_proc[i] = item_list_to_send_by_proc[i].size();
+  }
+  this->_dispatchedInfo<item_type>().m_list_to_send_size_by_proc = nb_item_to_send_by_proc;
+
+  this->_dispatchedInfo<item_type>().m_list_to_recv_size_by_proc
+      = parallel::allToAll(nb_item_to_send_by_proc);
+}
+
+
+template <int Dimension>
+template<typename DataType, ItemType item_type, typename ConnectivityPtr>
+void
+ConnectivityDispatcher<Dimension>::
+_gatherFrom(const ItemValue<DataType, item_type, ConnectivityPtr>& data_to_gather,
+            std::vector<std::remove_const_t<DataType>>& gathered_vector)
+{
+  std::vector<Array<const DataType>> recv_item_data_by_proc = this->exchange(data_to_gather);
+
+  const auto& recv_id_correspondance_by_proc = this->_dispatchedInfo<item_type>().m_recv_id_correspondance_by_proc;
+  Assert(recv_id_correspondance_by_proc.size()==parallel::size());
+
+  gathered_vector.resize(this->_dispatchedInfo<item_type>().m_number_to_id_map.size());
+  for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+    Assert(recv_id_correspondance_by_proc[i_rank].size()==recv_item_data_by_proc[i_rank].size());
+    for (size_t r=0; r<recv_id_correspondance_by_proc[i_rank].size(); ++r) {
+      const auto& item_id = recv_id_correspondance_by_proc[i_rank][r];
+      gathered_vector[item_id] = recv_item_data_by_proc[i_rank][r];
+    }
+  }
+}
+
+template <int Dimension>
+template<typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+void ConnectivityDispatcher<Dimension>::
+_gatherFrom(const SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& data_to_gather,
+            std::vector<Array<std::remove_const_t<DataType>>>& gathered_vector)
+{
+  using MutableDataType = std::remove_const_t<DataType>;
+
+  constexpr ItemType item_type = ItemOfItem::item_type;
+  using ItemId = ItemIdT<item_type>;
+
+  const auto& item_list_to_send_by_proc = this->_dispatchedInfo<item_type>().m_list_to_send_by_proc;
+
+  std::vector<Array<MutableDataType>> data_to_send_by_proc(parallel::size());
+
+  for (size_t i_rank=0; i_rank < parallel::size(); ++i_rank) {
+    std::vector<MutableDataType> data_by_item_vector;
+    for (size_t j=0; j<item_list_to_send_by_proc[i_rank].size(); ++j) {
+      const ItemId& item_id = item_list_to_send_by_proc[i_rank][j];
+      const auto& item_data = data_to_gather.itemValues(item_id);
+      for (size_t l=0; l<item_data.size(); ++l) {
+        data_by_item_vector.push_back(item_data[l]);
+      }
+    }
+    data_to_send_by_proc[i_rank] = convert_to_array(data_by_item_vector);
+  }
+
+  const auto& number_of_sub_item_per_item_to_recv_by_proc =
+      this->_dispatchedInfo<ItemOfItem>().m_number_of_sub_item_per_item_to_recv_by_proc;
+
+  std::vector<Array<MutableDataType>> recv_data_to_gather_by_proc(parallel::size());
+  for (size_t i_rank=0; i_rank < parallel::size(); ++i_rank) {
+    recv_data_to_gather_by_proc[i_rank]
+        = Array<MutableDataType>(sum(number_of_sub_item_per_item_to_recv_by_proc[i_rank]));
+  }
+
+  parallel::exchange(data_to_send_by_proc, recv_data_to_gather_by_proc);
+
+  const auto& item_list_to_recv_size_by_proc =
+      this->_dispatchedInfo<item_type>().m_list_to_recv_size_by_proc;
+
+  for (size_t i_rank=0; i_rank < parallel::size(); ++i_rank) {
+    int l=0;
+    for (size_t i=0; i<item_list_to_recv_size_by_proc[i_rank]; ++i) {
+      Array<MutableDataType> data_vector(number_of_sub_item_per_item_to_recv_by_proc[i_rank][i]);
+      for (size_t k=0; k<data_vector.size(); ++k) {
+        data_vector[k] = recv_data_to_gather_by_proc[i_rank][l++];
+      }
+      gathered_vector.emplace_back(data_vector);
+    }
+  }
+}
+
+template <int Dimension>
+void
+ConnectivityDispatcher<Dimension>::
+_buildCellNumberIdMap()
+{
+  const auto recv_cell_number_by_proc = this->exchange(m_connectivity.template number<ItemType::cell>());
+  auto& cell_number_id_map = this->_dispatchedInfo<ItemType::cell>().m_number_to_id_map;
+  for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+    CellId cell_id=0;
+    for (size_t i=0; i<recv_cell_number_by_proc[i_rank].size(); ++i) {
+      const int cell_number = recv_cell_number_by_proc[i_rank][i];
+      auto [iterator, inserted] = cell_number_id_map.insert(std::make_pair(cell_number, cell_id));
+      if (inserted) ++cell_id;
+    }
+  }
+}
+
+template <int Dimension>
+template <typename ItemOfItemT>
+void
+ConnectivityDispatcher<Dimension>::_buildSubItemNumberToIdMap()
+{
+  static_assert(ItemOfItemT::item_type == ItemType::cell, "Dispatcher requires to be built using cell as master entities");
+
+  const auto& cell_sub_item_number_to_recv_by_proc
+      = this->_dispatchedInfo<ItemOfItemT>().m_sub_item_numbers_to_recv_by_proc;
+
+  auto& sub_item_number_id_map = this->_dispatchedInfo<ItemOfItemT::sub_item_type>().m_number_to_id_map;
+  for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+    int sub_item_id=0;
+    for (size_t i=0; i<cell_sub_item_number_to_recv_by_proc[i_rank].size(); ++i) {
+      int sub_item_number = cell_sub_item_number_to_recv_by_proc[i_rank][i];
+      auto [iterator, inserted] = sub_item_number_id_map.insert(std::make_pair(sub_item_number, sub_item_id));
+      if (inserted) sub_item_id++;
+    }
+  }
+}
+
+template <int Dimension>
+template <typename SubItemOfItemT>
+void
+ConnectivityDispatcher<Dimension>::_buildNumberOfSubItemPerItemToRecvByProc()
+{
+  const auto& item_to_sub_item_matrix
+      = m_connectivity.template getItemToItemMatrix<SubItemOfItemT::item_type,
+                                                    SubItemOfItemT::sub_item_type>();
+
+  ItemValue<int, SubItemOfItemT::item_type> number_of_sub_item_per_item(m_connectivity);
+
+  using ItemId = ItemIdT<SubItemOfItemT::item_type>;
+  parallel_for(number_of_sub_item_per_item.size(), PASTIS_LAMBDA(const ItemId& j){
+      number_of_sub_item_per_item[j] = item_to_sub_item_matrix[j].size();
+    });
+
+  this->_dispatchedInfo<SubItemOfItemT>().m_number_of_sub_item_per_item_to_recv_by_proc =
+      this->exchange(number_of_sub_item_per_item);
+}
+
+template <int Dimension>
+template <typename SubItemOfItemT>
+void
+ConnectivityDispatcher<Dimension>::_buildSubItemNumbersToRecvByProc()
+{
+  const std::vector<Array<const int>> sub_item_numbers_to_send_by_proc =
+      [&] () {
+        const auto& item_to_sub_item_matrix
+            = m_connectivity.template getItemToItemMatrix<SubItemOfItemT::item_type,
+                                                          SubItemOfItemT::sub_item_type>();
+
+        const auto& sub_item_number = m_connectivity.template number<SubItemOfItemT::sub_item_type>();
+
+        using ItemId = ItemIdT<SubItemOfItemT::item_type>;
+        using SubItemId = ItemIdT<SubItemOfItemT::sub_item_type>;
+
+        std::vector<Array<const int>> sub_item_numbers_to_send_by_proc(parallel::size());
+        for (size_t i_rank=0; i_rank < parallel::size(); ++i_rank) {
+          const auto& item_list_to_send_by_proc
+              = this->_dispatchedInfo<SubItemOfItemT::item_type>().m_list_to_send_by_proc;
+
+          std::vector<int> sub_item_numbers_by_item_vector;
+          for (size_t j=0; j<item_list_to_send_by_proc[i_rank].size(); ++j) {
+            const ItemId& item_id = item_list_to_send_by_proc[i_rank][j];
+            const auto& sub_item_list = item_to_sub_item_matrix[item_id];
+            for (size_t r=0; r<sub_item_list.size(); ++r) {
+              const SubItemId& sub_item_id = sub_item_list[r];
+              sub_item_numbers_by_item_vector.push_back(sub_item_number[sub_item_id]);
+            }
+          }
+          sub_item_numbers_to_send_by_proc[i_rank] = convert_to_array(sub_item_numbers_by_item_vector);
+        }
+        return sub_item_numbers_to_send_by_proc;
+      } ();
+
+  const auto& number_of_sub_item_per_item_to_recv_by_proc =
+      this->_dispatchedInfo<SubItemOfItemT>().m_number_of_sub_item_per_item_to_recv_by_proc;
+
+  std::vector<Array<int>> sub_item_numbers_to_recv_by_proc(parallel::size());
+  for (size_t i_rank=0; i_rank < parallel::size(); ++i_rank) {
+    sub_item_numbers_to_recv_by_proc[i_rank]
+        = Array<int>(sum(number_of_sub_item_per_item_to_recv_by_proc[i_rank]));
+  }
+  parallel::exchange(sub_item_numbers_to_send_by_proc, sub_item_numbers_to_recv_by_proc);
+
+  auto& const_sub_item_numbers_to_recv_by_proc =
+      this->_dispatchedInfo<SubItemOfItemT>().m_sub_item_numbers_to_recv_by_proc;
+
+  const_sub_item_numbers_to_recv_by_proc.resize(parallel::size());
+  for (size_t i_rank=0; i_rank < parallel::size(); ++i_rank) {
+    const_sub_item_numbers_to_recv_by_proc[i_rank] = sub_item_numbers_to_recv_by_proc[i_rank];
+  }
+}
+
+template <int Dimension>
+template <typename ItemOfItemT>
+void
+ConnectivityDispatcher<Dimension>::_buildItemToSubItemDescriptor()
+{
+  constexpr ItemType item_type = ItemOfItemT::item_type;
+  constexpr ItemType sub_item_type = ItemOfItemT::sub_item_type;
+
+  const auto& item_list_to_recv_size_by_proc =
+      this->_dispatchedInfo<item_type>().m_list_to_recv_size_by_proc;
+
+  const auto& number_of_sub_item_per_item_to_recv_by_proc =
+      this->_dispatchedInfo<ItemOfItemT>().m_number_of_sub_item_per_item_to_recv_by_proc;
+
+  const auto& sub_item_number_id_map =
+      this->_dispatchedInfo<sub_item_type>().m_number_to_id_map;
+
+  const auto& recv_item_of_item_numbers_by_proc =
+      this->_dispatchedInfo<ItemOfItemT>().m_sub_item_numbers_to_recv_by_proc;
+
+  for (size_t i_rank=0; i_rank < parallel::size(); ++i_rank) {
+    int l=0;
+    for (size_t i=0; i<item_list_to_recv_size_by_proc[i_rank]; ++i) {
+      std::vector<unsigned int> sub_item_vector;
+      for (int k=0; k<number_of_sub_item_per_item_to_recv_by_proc[i_rank][i]; ++k) {
+        const auto& searched_sub_item_id =
+            sub_item_number_id_map.find(recv_item_of_item_numbers_by_proc[i_rank][l++]);
+        Assert(searched_sub_item_id != sub_item_number_id_map.end());
+        sub_item_vector.push_back(searched_sub_item_id->second);
+      }
+      m_new_descriptor.itemOfItemVector<ItemOfItemT>().emplace_back(sub_item_vector);
+    }
+  }
+}
+
+template <int Dimension>
+template <ItemType item_type>
+void
+ConnectivityDispatcher<Dimension>::
+_buildRecvItemIdCorrespondanceByProc()
+{
+  const auto& item_list_to_send_by_proc = this->_dispatchedInfo<item_type>().m_list_to_send_by_proc;
+  using ItemId = ItemIdT<item_type>;
+
+  std::vector<Array<const ItemId>> recv_item_id_correspondance_by_proc(parallel::size());
+  const ItemValue<const int,item_type>& item_number =
+      m_connectivity.template number<item_type>();
+
+  std::vector<Array<const int>> send_item_number_by_proc(parallel::size());
+  for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+    Array<int> send_item_number(item_list_to_send_by_proc[i_rank].size());
+    const Array<const ItemId> send_item_id = item_list_to_send_by_proc[i_rank];
+    parallel_for(send_item_number.size(), PASTIS_LAMBDA(const size_t& j){
+        send_item_number[j] = item_number[send_item_id[j]];
+      });
+    send_item_number_by_proc[i_rank] = send_item_number;
+  }
+
+  const auto& item_list_to_recv_size_by_proc = this->_dispatchedInfo<item_type>().m_list_to_recv_size_by_proc;
+  std::vector<Array<int>> recv_item_number_by_proc(parallel::size());
+  for (size_t i_rank=0; i_rank < parallel::size(); ++i_rank) {
+    recv_item_number_by_proc[i_rank] = Array<int>(item_list_to_recv_size_by_proc[i_rank]);
+  }
+  parallel::exchange(send_item_number_by_proc, recv_item_number_by_proc);
+
+  const auto& item_number_to_id_map = this->_dispatchedInfo<item_type>().m_number_to_id_map;
+  for (size_t i_rank=0; i_rank<item_list_to_recv_size_by_proc.size(); ++i_rank) {
+    Array<ItemId> item_id_correspondance(item_list_to_recv_size_by_proc[i_rank]);
+    for (size_t l=0; l<item_list_to_recv_size_by_proc[i_rank]; ++l) {
+      const int& item_number = recv_item_number_by_proc[i_rank][l];
+      const auto& searched_item_id = item_number_to_id_map.find(item_number);
+      Assert(searched_item_id != item_number_to_id_map.end());
+      item_id_correspondance[l] = searched_item_id->second;
+    }
+    recv_item_id_correspondance_by_proc[i_rank] = item_id_correspondance;
+  }
+  this->_dispatchedInfo<item_type>().m_recv_id_correspondance_by_proc = recv_item_id_correspondance_by_proc;
+}
+
+template <int Dimension>
+template <ItemType item_type>
+void
+ConnectivityDispatcher<Dimension>::_buildItemReferenceList()
+{
+  using ItemId = ItemIdT<item_type>;
+
+  // Getting references
+  Array<const size_t> number_of_item_ref_list_per_proc
+      = parallel::allGather(m_connectivity.template numberOfRefItemList<item_type>());
+
+  const size_t number_of_item_list_sender
+      = [&] () {
+          size_t number_of_item_list_sender=0;
+          for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
+            number_of_item_list_sender
+                += (number_of_item_ref_list_per_proc[i_rank] > 0);
+          }
+          return number_of_item_list_sender;
+        }();
+
+  if (number_of_item_list_sender > 0) {
+    if (number_of_item_list_sender > 1) {
+      perr() << __FILE__ << ':' << __LINE__ << ": "
+             << rang::fgB::red
+             <<"need to check that knowing procs know the same item_ref_lists!"
+             << rang::fg::reset << '\n';
+    }
+
+    if (number_of_item_list_sender < parallel::size()) {
+      const size_t sender_rank
+          = [&] () {
+              size_t i_rank = 0;
+              for (; i_rank < parallel::size(); ++i_rank) {
+                if (number_of_item_ref_list_per_proc[i_rank] > 0) {
+                  break;
+                }
+              }
+              return i_rank;
+            }();
+
+      Assert(number_of_item_list_sender < parallel::size());
+
+      // sending references tags
+      Array<RefId::TagNumberType> ref_tag_list{number_of_item_ref_list_per_proc[sender_rank]};
+      if (parallel::rank() == sender_rank){
+        for (size_t i_item_ref_list=0; i_item_ref_list<m_connectivity.template numberOfRefItemList<item_type>();
+             ++i_item_ref_list) {
+          auto item_ref_list = m_connectivity.template refItemList<item_type>(i_item_ref_list);
+          ref_tag_list[i_item_ref_list] = item_ref_list.refId().tagNumber();
+        }
+      }
+      parallel::broadcast(ref_tag_list, sender_rank);
+
+      // sending references name size
+      Array<size_t> ref_name_size_list{number_of_item_ref_list_per_proc[sender_rank]};
+      if (parallel::rank() == sender_rank){
+        for (size_t i_item_ref_list=0; i_item_ref_list<m_connectivity.template numberOfRefItemList<item_type>();
+             ++i_item_ref_list) {
+          auto item_ref_list = m_connectivity.template refItemList<item_type>(i_item_ref_list);
+          ref_name_size_list[i_item_ref_list] = item_ref_list.refId().tagName().size();
+        }
+      }
+      parallel::broadcast(ref_name_size_list, sender_rank);
+
+      // sending references name size
+      Array<RefId::TagNameType::value_type> ref_name_cat{sum(ref_name_size_list)};
+      if (parallel::rank() == sender_rank){
+        size_t i_char=0;
+        for (size_t i_item_ref_list=0; i_item_ref_list<m_connectivity.template numberOfRefItemList<item_type>();
+             ++i_item_ref_list) {
+          auto item_ref_list = m_connectivity.template refItemList<item_type>(i_item_ref_list);
+          for (auto c : item_ref_list.refId().tagName()) {
+            ref_name_cat[i_char++] = c;
+          }
+        }
+      }
+      parallel::broadcast(ref_name_cat, sender_rank);
+
+      std::vector<RefId> ref_id_list
+          = [&] () {
+              std::vector<RefId> ref_id_list;
+              ref_id_list.reserve(ref_name_size_list.size());
+              size_t begining=0;
+              for (size_t i_ref=0; i_ref < ref_name_size_list.size(); ++i_ref) {
+                const size_t size = ref_name_size_list[i_ref];
+                ref_id_list.emplace_back(ref_tag_list[i_ref],
+                                         std::string{&(ref_name_cat[begining]), size});
+                begining += size;
+              }
+              return ref_id_list;
+            } ();
+
+      using block_type = int32_t;
+      constexpr size_t block_size = sizeof(block_type);
+      const size_t nb_block = ref_id_list.size()/block_size + (ref_id_list.size()%block_size != 0);
+      for (size_t i_block=0; i_block<nb_block; ++i_block) {
+        ItemValue<block_type, item_type> item_references(m_connectivity);
+        item_references.fill(0);
+
+        if (m_connectivity.template numberOfRefItemList<item_type>() > 0) {
+          const size_t max_i_ref = std::min(ref_id_list.size(), block_size*(i_block+1));
+          for (size_t i_ref=block_size*i_block, i=0; i_ref<max_i_ref; ++i_ref, ++i) {
+            block_type ref_bit{1<<i};
+            auto item_ref_list = m_connectivity.template refItemList<item_type>(i_ref);
+
+            const auto& item_list = item_ref_list.list();
+            for (size_t i_item=0; i_item<item_list.size(); ++i_item) {
+              const ItemId& item_id = item_list[i_item];
+              item_references[item_id] |= ref_bit;
+            }
+          }
+        }
+
+        const auto& nb_item_to_send_by_proc =
+            this->_dispatchedInfo<item_type>().m_list_to_send_size_by_proc;
+
+        const auto& send_item_id_by_proc =
+            this->_dispatchedInfo<item_type>().m_list_to_send_by_proc;
+
+        std::vector<Array<const block_type>> send_item_refs_by_proc(parallel::size());
+
+        for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+          Array<block_type> send_item_refs(nb_item_to_send_by_proc[i_rank]);
+          const Array<const ItemId> send_item_id = send_item_id_by_proc[i_rank];
+          parallel_for(send_item_id.size(), PASTIS_LAMBDA(const size_t& l) {
+              const ItemId& item_id = send_item_id[l];
+              send_item_refs[l] = item_references[item_id];
+            });
+          send_item_refs_by_proc[i_rank] = send_item_refs;
+        }
+
+        std::vector<Array<block_type>> recv_item_refs_by_proc(parallel::size());
+        for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+          recv_item_refs_by_proc[i_rank] = Array<block_type>(this->_dispatchedInfo<item_type>().m_list_to_recv_size_by_proc[i_rank]);
+        }
+        parallel::exchange(send_item_refs_by_proc, recv_item_refs_by_proc);
+
+        const auto& recv_item_id_correspondance_by_proc =
+            this->_dispatchedInfo<item_type>().m_recv_id_correspondance_by_proc;
+        std::vector<block_type> item_refs(m_new_descriptor.template itemNumberVector<item_type>().size());
+        for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+          for (size_t r=0; r<recv_item_refs_by_proc[i_rank].size(); ++r) {
+            const ItemId& item_id = recv_item_id_correspondance_by_proc[i_rank][r];
+            item_refs[item_id] = recv_item_refs_by_proc[i_rank][r];
+          }
+        }
+
+        const size_t max_i_ref = std::min(ref_id_list.size(), block_size*(i_block+1));
+        for (size_t i_ref=block_size*i_block, i=0; i_ref<max_i_ref; ++i_ref, ++i) {
+          block_type ref_bit{1<<i};
+
+          std::vector<ItemId> item_id_vector;
+
+          for (uint32_t i_item=0; i_item<item_refs.size(); ++i_item) {
+            const ItemId item_id{i_item};
+            if (item_refs[item_id] & ref_bit) {
+              item_id_vector.push_back(item_id);
+            }
+          }
+
+          Array<const ItemId> item_id_array = convert_to_array(item_id_vector);
+
+          m_new_descriptor.addRefItemList(RefItemList<item_type>(ref_id_list[i_ref], item_id_array));
+        }
+      }
+    }
+  }
+}
+
+template <int Dimension>
+void
+ConnectivityDispatcher<Dimension>::_dispatchEdges()
+{
+  if constexpr (Dimension>2) {
+    this->_buildNumberOfSubItemPerItemToRecvByProc<EdgeOfCell>();
+    this->_buildSubItemNumbersToRecvByProc<EdgeOfCell>();
+    this->_buildSubItemNumberToIdMap<EdgeOfCell>();
+    this->_buildItemToExchangeLists<ItemType::edge>();
+
+    this->_gatherFrom(m_connectivity.template number<ItemType::edge>(), m_new_descriptor.edge_number_vector);
+
+    this->_buildItemToSubItemDescriptor<EdgeOfCell>();
+
+    this->_buildNumberOfSubItemPerItemToRecvByProc<NodeOfEdge>();
+    this->_buildSubItemNumbersToRecvByProc<NodeOfEdge>();
+    this->_buildItemToSubItemDescriptor<NodeOfEdge>();
+
+    this->_buildNumberOfSubItemPerItemToRecvByProc<EdgeOfFace>();
+    this->_buildSubItemNumbersToRecvByProc<EdgeOfFace>();
+    this->_buildItemToSubItemDescriptor<EdgeOfFace>();
+
+    this->_gatherFrom(m_connectivity.faceEdgeIsReversed(), m_new_descriptor.face_edge_is_reversed_vector);
+
+    this->_gatherFrom(this->_dispatchedInfo<ItemType::edge>().m_new_owner, m_new_descriptor.edge_owner_vector);
+
+    this->_buildItemReferenceList<ItemType::edge>();
+  }
+}
+
+template <int Dimension>
+void
+ConnectivityDispatcher<Dimension>::_dispatchFaces()
+{
+  if constexpr (Dimension>1) {
+    this->_buildNumberOfSubItemPerItemToRecvByProc<FaceOfCell>();
+    this->_buildSubItemNumbersToRecvByProc<FaceOfCell>();
+    this->_buildSubItemNumberToIdMap<FaceOfCell>();
+    this->_buildItemToExchangeLists<ItemType::face>();
+
+    this->_buildNumberOfSubItemPerItemToRecvByProc<NodeOfFace>();
+    this->_buildSubItemNumbersToRecvByProc<NodeOfFace>();
+    this->_buildItemToSubItemDescriptor<NodeOfFace>();
+
+    this->_gatherFrom(m_connectivity.template number<ItemType::face>(), m_new_descriptor.face_number_vector);
+
+    this->_buildItemToSubItemDescriptor<FaceOfCell>();
+
+    this->_gatherFrom(m_connectivity.cellFaceIsReversed(), m_new_descriptor.cell_face_is_reversed_vector);
+
+    this->_gatherFrom(this->_dispatchedInfo<ItemType::face>().m_new_owner, m_new_descriptor.face_owner_vector);
+
+    this->_buildItemReferenceList<ItemType::face>();
+  }
+}
+
+
+template <int Dimension>
+ConnectivityDispatcher<Dimension>::ConnectivityDispatcher(const ConnectivityType& connectivity)
+    : m_connectivity(connectivity)
+{
+  this->_buildNewOwner<ItemType::cell>();
+  this->_buildNewOwner<ItemType::face>();
+  this->_buildNewOwner<ItemType::edge>();
+  this->_buildNewOwner<ItemType::node>();
+
+  this->_buildItemToExchangeLists<ItemType::cell>();
+
+  this->_buildNumberOfSubItemPerItemToRecvByProc<NodeOfCell>();
+
+  this->_buildSubItemNumbersToRecvByProc<NodeOfCell>();
+
+  this->_gatherFrom(m_connectivity.template number<ItemType::cell>(), m_new_descriptor.cell_number_vector);
+
+  this->_buildSubItemNumberToIdMap<NodeOfCell>();
+
+  this->_buildItemToExchangeLists<ItemType::node>();
+
+  // Fill new descriptor
+  this->_gatherFrom(m_connectivity.cellType(), m_new_descriptor.cell_type_vector);
+  this->_gatherFrom(this->_dispatchedInfo<ItemType::cell>().m_new_owner, m_new_descriptor.cell_owner_vector);
+
+  this->_gatherFrom(m_connectivity.template number<ItemType::node>(), m_new_descriptor.node_number_vector);
+  this->_gatherFrom(this->_dispatchedInfo<ItemType::node>().m_new_owner, m_new_descriptor.node_owner_vector);
+
+  this->_buildItemToSubItemDescriptor<NodeOfCell>();
+
+  this->_buildItemReferenceList<ItemType::cell>();
+
+  this->_dispatchFaces();
+
+  this->_dispatchEdges();
+
+  this->_buildItemReferenceList<ItemType::node>();
+
+  m_dispatched_connectivity = ConnectivityType::build(m_new_descriptor);
+}
+
+template ConnectivityDispatcher<1>::ConnectivityDispatcher(const ConnectivityType&);
+template ConnectivityDispatcher<2>::ConnectivityDispatcher(const ConnectivityType&);
+template ConnectivityDispatcher<3>::ConnectivityDispatcher(const ConnectivityType&);
diff --git a/src/mesh/ConnectivityDispatcher.hpp b/src/mesh/ConnectivityDispatcher.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e0ce2e88e2690093b6c8d4c16f7f2abc70346674
--- /dev/null
+++ b/src/mesh/ConnectivityDispatcher.hpp
@@ -0,0 +1,294 @@
+#ifndef CONNECTIVITY_DISPATCHER_HPP
+#define CONNECTIVITY_DISPATCHER_HPP
+
+#include <Mesh.hpp>
+#include <ItemValue.hpp>
+#include <ItemValueUtils.hpp>
+
+#include <unordered_map>
+#include <ConnectivityDescriptor.hpp>
+
+template <int Dimension>
+class ConnectivityDispatcher
+{
+ public:
+  using ConnectivityType = Connectivity<Dimension>;
+
+ private:
+  const ConnectivityType& m_connectivity;
+  ConnectivityDescriptor m_new_descriptor;
+  std::shared_ptr<ConnectivityType> m_dispatched_connectivity;
+
+  template <ItemType item_type>
+  struct DispatchedItemInfo
+  {
+    using ItemId = ItemIdT<item_type>;
+    ItemValue<const int, item_type> m_new_owner;
+    Array<const unsigned int> m_list_to_send_size_by_proc;
+    std::vector<Array<const ItemId>> m_list_to_send_by_proc;
+    Array<const unsigned int> m_list_to_recv_size_by_proc;
+    std::unordered_map<int, int> m_number_to_id_map;
+    std::vector<Array<const ItemId>> m_recv_id_correspondance_by_proc;
+  };
+
+  DispatchedItemInfo<ItemType::cell> m_dispatched_cell_info;
+  DispatchedItemInfo<ItemType::face> m_dispatched_face_info;
+  DispatchedItemInfo<ItemType::edge> m_dispatched_edge_info;
+  DispatchedItemInfo<ItemType::node> m_dispatched_node_info;
+
+  template <ItemType item_type>
+  PASTIS_INLINE
+  DispatchedItemInfo<item_type>& _dispatchedInfo()
+  {
+    if constexpr (item_type == ItemType::cell) {
+      return m_dispatched_cell_info;
+    } else if constexpr (item_type == ItemType::face) {
+      return m_dispatched_face_info;
+    } else if constexpr (item_type == ItemType::edge) {
+      return m_dispatched_edge_info;
+    } else {
+      return m_dispatched_node_info;
+    }
+  }
+
+  template <ItemType item_type>
+  PASTIS_INLINE
+  const DispatchedItemInfo<item_type>& _dispatchedInfo() const
+  {
+    if constexpr (item_type == ItemType::cell) {
+      return m_dispatched_cell_info;
+    } else if constexpr (item_type == ItemType::face) {
+      return m_dispatched_face_info;
+    } else if constexpr (item_type == ItemType::edge) {
+      return m_dispatched_edge_info;
+    } else {
+      return m_dispatched_node_info;
+    }
+  }
+
+  template <typename ItemToItem>
+  struct DispatchedItemOfItemInfo
+  {
+    std::vector<Array<const int>> m_number_of_sub_item_per_item_to_recv_by_proc;
+    std::vector<Array<const int>> m_sub_item_numbers_to_recv_by_proc;
+  };
+
+  DispatchedItemOfItemInfo<NodeOfCell> m_dispatched_node_of_cell_info;
+  DispatchedItemOfItemInfo<EdgeOfCell> m_dispatched_edge_of_cell_info;
+  DispatchedItemOfItemInfo<FaceOfCell> m_dispatched_face_of_cell_info;
+
+  DispatchedItemOfItemInfo<NodeOfEdge> m_dispatched_node_of_edge_info;
+  DispatchedItemOfItemInfo<FaceOfEdge> m_dispatched_face_of_edge_info;
+  DispatchedItemOfItemInfo<CellOfEdge> m_dispatched_cell_of_edge_info;
+
+  DispatchedItemOfItemInfo<NodeOfFace> m_dispatched_node_of_face_info;
+  DispatchedItemOfItemInfo<EdgeOfFace> m_dispatched_edge_of_face_info;
+  DispatchedItemOfItemInfo<CellOfFace> m_dispatched_cell_of_face_info;
+
+  DispatchedItemOfItemInfo<EdgeOfNode> m_dispatched_edge_of_node_info;
+  DispatchedItemOfItemInfo<FaceOfNode> m_dispatched_face_of_node_info;
+  DispatchedItemOfItemInfo<CellOfNode> m_dispatched_cell_of_node_info;
+
+  template <typename ItemOfItem>
+  PASTIS_INLINE
+  DispatchedItemOfItemInfo<ItemOfItem>& _dispatchedInfo()
+  {
+    if constexpr (std::is_same_v<NodeOfCell, ItemOfItem>) {
+      return m_dispatched_node_of_cell_info;
+    } else if constexpr (std::is_same_v<EdgeOfCell, ItemOfItem>) {
+      return m_dispatched_edge_of_cell_info;
+    } else if constexpr (std::is_same_v<FaceOfCell, ItemOfItem>) {
+      return m_dispatched_face_of_cell_info;
+    } else if constexpr (std::is_same_v<NodeOfEdge, ItemOfItem>) {
+      return m_dispatched_node_of_edge_info;
+    } else if constexpr (std::is_same_v<FaceOfEdge, ItemOfItem>) {
+      return m_dispatched_face_of_edge_info;
+    } else if constexpr (std::is_same_v<CellOfEdge, ItemOfItem>) {
+      return m_dispatched_cell_of_edge_info;
+    } else if constexpr (std::is_same_v<NodeOfFace, ItemOfItem>) {
+      return m_dispatched_node_of_face_info;
+    } else if constexpr (std::is_same_v<EdgeOfFace, ItemOfItem>) {
+      return m_dispatched_edge_of_face_info;
+    } else if constexpr (std::is_same_v<CellOfFace, ItemOfItem>) {
+      return m_dispatched_cell_of_face_info;
+    } else if constexpr (std::is_same_v<EdgeOfNode, ItemOfItem>) {
+      return m_dispatched_edge_of_node_info;
+    } else if constexpr (std::is_same_v<FaceOfNode, ItemOfItem>) {
+      return m_dispatched_face_of_node_info;
+    } else if constexpr (std::is_same_v<CellOfNode, ItemOfItem>) {
+      return m_dispatched_cell_of_node_info;
+    } else {
+      static_assert(is_false_v<ItemOfItem>, "Unexpected ItemOfItem type");
+    }
+  }
+
+  template <typename ItemOfItem>
+  PASTIS_INLINE
+  const DispatchedItemOfItemInfo<ItemOfItem>& _dispatchedInfo() const
+  {
+    if constexpr (std::is_same_v<NodeOfCell, ItemOfItem>) {
+      return m_dispatched_node_of_cell_info;
+    } else if constexpr (std::is_same_v<EdgeOfCell, ItemOfItem>) {
+      return m_dispatched_edge_of_cell_info;
+    } else if constexpr (std::is_same_v<FaceOfCell, ItemOfItem>) {
+      return m_dispatched_face_of_cell_info;
+    } else if constexpr (std::is_same_v<NodeOfEdge, ItemOfItem>) {
+      return m_dispatched_node_of_edge_info;
+    } else if constexpr (std::is_same_v<FaceOfEdge, ItemOfItem>) {
+      return m_dispatched_face_of_edge_info;
+    } else if constexpr (std::is_same_v<CellOfEdge, ItemOfItem>) {
+      return m_dispatched_cell_of_edge_info;
+    } else if constexpr (std::is_same_v<NodeOfFace, ItemOfItem>) {
+      return m_dispatched_node_of_face_info;
+    } else if constexpr (std::is_same_v<EdgeOfFace, ItemOfItem>) {
+      return m_dispatched_edge_of_face_info;
+    } else if constexpr (std::is_same_v<CellOfFace, ItemOfItem>) {
+      return m_dispatched_cell_of_face_info;
+    } else if constexpr (std::is_same_v<EdgeOfNode, ItemOfItem>) {
+      return m_dispatched_edge_of_node_info;
+    } else if constexpr (std::is_same_v<FaceOfNode, ItemOfItem>) {
+      return m_dispatched_face_of_node_info;
+    } else if constexpr (std::is_same_v<CellOfNode, ItemOfItem>) {
+      return m_dispatched_cell_of_node_info;
+    } else {
+      static_assert(is_false_v<ItemOfItem>, "Unexpected ItemOfItem type");
+    }
+  }
+
+  template <ItemType item_type>
+  void _buildNewOwner();
+
+  template <ItemType item_type>
+  void _buildItemListToSend();
+
+  void _buildCellNumberIdMap();
+
+  template <typename ItemOfItemT>
+  void _buildSubItemNumberToIdMap();
+
+  template <ItemType item_type>
+  void _buildItemToExchangeLists();
+
+  template <ItemType item_type>
+  void _buildNumberOfItemToExchange();
+
+  template <typename ItemOfItemT>
+  void _buildItemToSubItemDescriptor();
+
+  void _dispatchEdges();
+  void _dispatchFaces();
+
+  template<typename DataType, ItemType item_type, typename ConnectivityPtr>
+  void _gatherFrom(const ItemValue<DataType, item_type, ConnectivityPtr>& data_to_gather,
+                   std::vector<std::remove_const_t<DataType>>& gathered_vector);
+
+  template<typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+  void _gatherFrom(const SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& data_to_gather,
+                   std::vector<Array<std::remove_const_t<DataType>>>& gathered_vector);
+
+  template <typename SubItemOfItemT>
+  void _buildNumberOfSubItemPerItemToRecvByProc();
+
+  template <typename SubItemOfItemT>
+  void _buildSubItemNumbersToRecvByProc();
+
+  template <ItemType item_type>
+  void _buildRecvItemIdCorrespondanceByProc();
+
+  template <ItemType item_type>
+  void _buildItemReferenceList();
+
+ public:
+  std::shared_ptr<const ConnectivityType>
+  dispatchedConnectivity() const
+  {
+    return m_dispatched_connectivity;
+  }
+
+  template<typename DataType, ItemType item_type, typename ConnectivityPtr>
+  std::vector<Array<const DataType>>
+  exchange(ItemValue<DataType, item_type, ConnectivityPtr> item_value) const
+  {
+    using ItemId = ItemIdT<item_type>;
+    using MutableDataType = std::remove_const_t<DataType>;
+    std::vector<Array<const DataType>> item_value_to_send_by_proc(parallel::size());
+
+    const auto& item_list_to_send_by_proc = this->_dispatchedInfo<item_type>().m_list_to_send_by_proc;
+
+    for (size_t i=0; i<parallel::size(); ++i) {
+      const Array<const ItemId>& item_list = item_list_to_send_by_proc[i];
+      Array<MutableDataType> item_value_list(item_list.size());
+      parallel_for (item_list.size(), PASTIS_LAMBDA(const ItemId& item_id) {
+          item_value_list[item_id] = item_value[item_list[item_id]];
+        });
+      item_value_to_send_by_proc[i] = item_value_list;
+    }
+
+    std::vector<Array<MutableDataType>> recv_item_value_by_proc(parallel::size());
+    {
+      const auto& list_to_recv_size_by_proc = this->_dispatchedInfo<item_type>().m_list_to_recv_size_by_proc;
+      for (size_t i=0; i<parallel::size(); ++i) {
+        recv_item_value_by_proc[i] = Array<MutableDataType>(list_to_recv_size_by_proc[i]);
+      }
+    }
+    parallel::exchange(item_value_to_send_by_proc, recv_item_value_by_proc);
+
+    std::vector<Array<const DataType>> const_recv_item_value_by_proc(parallel::size());
+    for (size_t i=0; i<parallel::size(); ++i) {
+      const_recv_item_value_by_proc[i] = recv_item_value_by_proc[i];
+    }
+
+    return const_recv_item_value_by_proc;
+  }
+
+  template<typename DataType, ItemType item_type, typename ConnectivityPtr>
+  ItemValue<std::remove_const_t<DataType>, item_type, ConnectivityPtr>
+  dispatch(ItemValue<DataType, item_type, ConnectivityPtr> item_value) const
+  {
+    using ItemId = ItemIdT<item_type>;
+
+    Assert(m_dispatched_connectivity.use_count()> 0,
+           "cannot dispatch quantity before connectivity");
+
+    const auto& item_list_to_send_by_proc = this->_dispatchedInfo<item_type>().m_list_to_send_by_proc;
+
+    using MutableDataType = std::remove_const_t<DataType>;
+    std::vector<Array<DataType>> item_value_to_send_by_proc(parallel::size());
+    for (size_t i=0; i<parallel::size(); ++i) {
+      const Array<const ItemId>& item_list = item_list_to_send_by_proc[i];
+      Array<MutableDataType> item_value_list(item_list.size());
+      parallel_for (item_list.size(), PASTIS_LAMBDA(const ItemId& item_id) {
+          item_value_list[item_id] = item_value[item_list[item_id]];
+        });
+      item_value_to_send_by_proc[i] = item_value_list;
+    }
+
+    const auto& item_list_to_recv_size_by_proc = this->_dispatchedInfo<item_type>().m_list_to_recv_size_by_proc;
+    std::vector<Array<MutableDataType>> recv_item_value_by_proc(parallel::size());
+    for (size_t i=0; i<parallel::size(); ++i) {
+      recv_item_value_by_proc[i] = Array<MutableDataType>(item_list_to_recv_size_by_proc[i]);
+    }
+
+    parallel::exchange(item_value_to_send_by_proc, recv_item_value_by_proc);
+
+    const auto& recv_item_id_correspondance_by_proc =
+        this->_dispatchedInfo<item_type>().m_recv_id_correspondance_by_proc;
+    ItemValue<MutableDataType, item_type> new_item_value(*m_dispatched_connectivity);
+    for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+      const auto& recv_item_id_correspondance = recv_item_id_correspondance_by_proc[i_rank];
+      const auto& recv_item_value = recv_item_value_by_proc[i_rank];
+      parallel_for(recv_item_value.size(), PASTIS_LAMBDA(size_t r) {
+          const ItemId& item_id = recv_item_id_correspondance[r];
+          new_item_value[item_id] = recv_item_value[r];
+        });
+    }
+    return new_item_value;
+  }
+
+  ConnectivityDispatcher(const ConnectivityType& mesh);
+  ConnectivityDispatcher(const ConnectivityDispatcher&) = delete;
+  ~ConnectivityDispatcher() = default;
+};
+
+
+#endif // CONNECTIVITY_DISPATCHER_HPP
diff --git a/src/mesh/ConnectivityMatrix.hpp b/src/mesh/ConnectivityMatrix.hpp
index 53feb3375a9de19370d03a002cf869fe4f039c0f..18b39a67a8839e1a893bb7bf2cec50aa60c407ee 100644
--- a/src/mesh/ConnectivityMatrix.hpp
+++ b/src/mesh/ConnectivityMatrix.hpp
@@ -2,6 +2,7 @@
 #define CONNECTIVITY_MATRIX_HPP
 
 #include <PastisUtils.hpp>
+#include <Array.hpp>
 #include <Kokkos_StaticCrsGraph.hpp>
 
 class ConnectivityMatrix
@@ -20,9 +21,11 @@ class ConnectivityMatrix
     return m_is_built;
   }
 
-  const auto& entries() const
+  auto entries() const
   {
-    return m_host_matrix.entries;
+    return encapsulate(m_host_matrix.entries);
+    // using DataType = typename decltype(m_host_matrix.entries)::value_type;
+    // return Array<DataType>(m_host_matrix.entries);
   }
 
   const auto& rowsMap() const
diff --git a/src/mesh/GmshReader.cpp b/src/mesh/GmshReader.cpp
index b49328d77129789bcbefbd32ed29b34d40ba37e3..1881f99a4eb55e8f6de284ee2b91a05a412aba04 100644
--- a/src/mesh/GmshReader.cpp
+++ b/src/mesh/GmshReader.cpp
@@ -10,29 +10,21 @@
 #include <Connectivity.hpp>
 
 #include <Mesh.hpp>
+#include <MeshData.hpp>
 
-#include <RefFaceList.hpp>
+#include <RefItemList.hpp>
+#include <Messenger.hpp>
 
+#include <ArrayUtils.hpp>
+#include <ItemValueUtils.hpp>
+
+#include <ConnectivityDispatcher.hpp>
+
+#include <unordered_map>
 #include <map>
 #include <regex>
 #include <iomanip>
 
-template<typename T>
-PASTIS_INLINE
-std::string stringify(const T & t)
-{
-  std::ostringstream oss;
-  oss << t;
-  return oss.str();
-}
-
-template<>
-PASTIS_INLINE
-std::string stringify<std::string>(const std::string& t)
-{
-  return t;
-}
-
 class ErrorHandler
 {
  public:
@@ -44,11 +36,11 @@ class ErrorHandler
   };
 
  private:
-  const std::string __filename;	/**< The source file name where the error occured */
-  const size_t __lineNumber;	/**< The line number where exception was raised */
+  const std::string __filename;     /**< The source file name where the error occured */
+  const size_t __lineNumber;        /**< The line number where exception was raised */
   const std::string __errorMessage; /**< The reporting message */
 
-  const Type __type;		/**< the type of the error */
+  const Type __type;                /**< the type of the error */
  public:
   /**
    * Prints the error message
@@ -92,6 +84,7 @@ class ErrorHandler
     ;
   }
 };
+
 void ErrorHandler::writeErrorMessage() const
 {
   switch(__type) {
@@ -138,167 +131,416 @@ ErrorHandler(const std::string& filename,
   ;
 }
 
+template <int Dimension>
+void GmshReader::_dispatch()
+{
+  using ConnectivityType = Connectivity<Dimension>;
+  using Rd = TinyVector<Dimension>;
+  using MeshType = Mesh<ConnectivityType>;
+
+  if (not m_mesh) {
+    ConnectivityDescriptor descriptor;
+    std::shared_ptr connectivity = ConnectivityType::build(descriptor);
+    NodeValue<Rd> xr;
+    m_mesh = std::make_shared<MeshType>(connectivity, xr);
+  }
+  const MeshType& mesh = static_cast<const MeshType&>(*m_mesh);
 
-GmshReader::GmshReader(const std::string& filename)
-    : m_filename(filename)
+  ConnectivityDispatcher<Dimension> dispatcher(mesh.connectivity());
+
+  std::shared_ptr dispatched_connectivity = dispatcher.dispatchedConnectivity();
+  NodeValue<Rd> dispatched_xr = dispatcher.dispatch(mesh.xr());
+
+  m_mesh = std::make_shared<MeshType>(dispatched_connectivity, dispatched_xr);
+}
+
+
+template <size_t Dimension>
+class ConnectivityFace;
+
+template<>
+class ConnectivityFace<2>
 {
-  try {
-    m_fin.open(m_filename);
-    if (not m_fin) {
-      perr() << "cannot read file '" << m_filename << "'\n";
-      std::exit(0);
+ public:
+  friend struct Hash;
+  struct Hash
+  {
+    size_t operator()(const ConnectivityFace& f) const {
+      size_t hash = 0;
+      hash ^= std::hash<unsigned int>()(f.m_node0_id);
+      hash ^= std::hash<unsigned int>()(f.m_node1_id) >> 1;
+      return hash;
     }
+  };
 
-    // gmsh 2.2 format keywords
-    __keywordList["$MeshFormat"]       = MESHFORMAT;
-    __keywordList["$EndMeshFormat"]    = ENDMESHFORMAT;
-    __keywordList["$Nodes"]            = NODES;
-    __keywordList["$EndNodes"]         = ENDNODES;
-    __keywordList["$Elements"]         = ELEMENTS;
-    __keywordList["$EndElements"]      = ENDELEMENTS;
-    __keywordList["$PhysicalNames"]    = PHYSICALNAMES;
-    __keywordList["$EndPhysicalNames"] = ENDPHYSICALNAMES;
-
-    __numberOfPrimitiveNodes.resize(16);
-    __numberOfPrimitiveNodes[ 0] =  2; // edge
-    __numberOfPrimitiveNodes[ 1] =  3; // triangle
-    __numberOfPrimitiveNodes[ 2] =  4; // quadrangle
-    __numberOfPrimitiveNodes[ 3] =  4; // Tetrahedron
-    __numberOfPrimitiveNodes[ 4] =  8; // Hexaredron
-    __numberOfPrimitiveNodes[ 5] =  6; // Prism
-    __numberOfPrimitiveNodes[ 6] =  5; // Pyramid
-    __numberOfPrimitiveNodes[ 7] =  3; // second order edge
-    __numberOfPrimitiveNodes[ 8] =  6; // second order triangle
-    __numberOfPrimitiveNodes[ 9] =  9; // second order quadrangle
-    __numberOfPrimitiveNodes[10] = 10; // second order tetrahedron
-    __numberOfPrimitiveNodes[11] = 27; // second order hexahedron
-    __numberOfPrimitiveNodes[12] = 18; // second order prism
-    __numberOfPrimitiveNodes[13] = 14; // second order pyramid
-    __numberOfPrimitiveNodes[14] =  1; // point
-
-    __primitivesNames[0]     = "edges";
-    __supportedPrimitives[0] = true;
-    __primitivesNames[1]     = "triangles";
-    __supportedPrimitives[1] = true;
-    __primitivesNames[2]     = "quadrangles";
-    __supportedPrimitives[2] = true;
-    __primitivesNames[3]     = "tetrahedra";
-    __supportedPrimitives[3] = true;
-    __primitivesNames[4]     = "hexahedra";
-    __supportedPrimitives[4] = true;
-    __primitivesNames[5]     = "prisms";
-    __supportedPrimitives[5] = false;
-    __primitivesNames[6]     = "pyramids";
-    __supportedPrimitives[6] = false;
-    __primitivesNames[7]     = "second order edges";
-    __supportedPrimitives[7] = false;
-    __primitivesNames[8]     = "second order triangles";
-    __supportedPrimitives[8] = false;
-    __primitivesNames[9]     = "second order quadrangles";
-    __supportedPrimitives[9] = false;
-    __primitivesNames[10]    = "second order tetrahedra";
-    __supportedPrimitives[10]= false;
-    __primitivesNames[11]    = "second order hexahedra";
-    __supportedPrimitives[11]= false;
-    __primitivesNames[12]    = "second order prisms";
-    __supportedPrimitives[12]= false;
-    __primitivesNames[13]    = "second order pyramids";
-    __supportedPrimitives[13]= false;
-    __primitivesNames[14]    = "point";
-    __supportedPrimitives[14]= true;
-
-    pout() << "Reading file '" << m_filename << "'\n";
-
-    // Getting vertices list
-    GmshReader::Keyword kw = this->__nextKeyword();
-    switch(kw.second) {
-      // case NOD: {
-      //   this->__readGmsh1();
-      //   break;
-      // }
-      case MESHFORMAT: {
-        double fileVersion = this->_getReal();
-        if (fileVersion != 2.2) {
-          throw ErrorHandler(__FILE__,__LINE__,
-                             "Cannot read Gmsh format '"+stringify(fileVersion)+"'",
-                             ErrorHandler::normal);
-        }
-        int fileType = this->_getInteger();
-        __binary = (fileType == 1);
-        if ((fileType < 0) or (fileType > 1)) {
-          throw ErrorHandler(__FILE__,__LINE__,
-                             "Cannot read Gmsh file type '"+stringify(fileType)+"'",
-                             ErrorHandler::normal);
-        }
+ private:
+  const std::vector<int>& m_node_number_vector;
 
-        int dataSize = this->_getInteger();
-        if (dataSize != sizeof(double)) {
-          throw ErrorHandler(__FILE__,__LINE__,
-                             "Data size not supported '"+stringify(dataSize)+"'",
-                             ErrorHandler::normal);
-        }
+  unsigned int m_node0_id;
+  unsigned int m_node1_id;
+
+  bool m_reversed;
+
+ public:
+
+  std::vector<unsigned int> nodeIdList() const
+  {
+    return {m_node0_id, m_node1_id};
+  }
+
+  bool reversed() const
+  {
+    return m_reversed;
+  }
+
+  PASTIS_INLINE
+  bool operator==(const ConnectivityFace& f) const
+  {
+    return ((m_node0_id == f.m_node0_id) and
+            (m_node1_id == f.m_node1_id));
+  }
+
+  PASTIS_INLINE
+  bool operator<(const ConnectivityFace& f) const
+  {
+    return ((m_node_number_vector[m_node0_id] < m_node_number_vector[f.m_node0_id]) or
+            ((m_node_number_vector[m_node0_id] == m_node_number_vector[f.m_node0_id]) and
+             (m_node_number_vector[m_node1_id]<m_node_number_vector[f.m_node1_id])));
+  }
+
+  PASTIS_INLINE
+  ConnectivityFace(const std::vector<unsigned int>& node_id_list,
+                   const std::vector<int>& node_number_vector)
+      : m_node_number_vector(node_number_vector)
+  {
+    Assert(node_id_list.size()==2);
+
+    if (m_node_number_vector[node_id_list[0]] < m_node_number_vector[node_id_list[1]]) {
+      m_node0_id = node_id_list[0];
+      m_node1_id = node_id_list[1];
+      m_reversed = false;
+    } else {
+      m_node0_id = node_id_list[1];
+      m_node1_id = node_id_list[0];
+      m_reversed = true;
+    }
+  }
+
+  PASTIS_INLINE
+  ConnectivityFace(const ConnectivityFace&) = default;
+
+  PASTIS_INLINE
+  ~ConnectivityFace() = default;
+};
+
+template <>
+class ConnectivityFace<3>
+{
+ private:
+  friend class GmshReader;
+  friend struct Hash;
+  struct Hash
+  {
+    size_t operator()(const ConnectivityFace& f) const {
+      size_t hash = 0;
+      for (size_t i=0; i<f.m_node_id_list.size(); ++i) {
+        hash ^= std::hash<unsigned int>()(f.m_node_id_list[i]) >> i;
+      }
+      return hash;
+    }
+  };
+
+ private:
+  bool m_reversed;
+  std::vector<NodeId::base_type> m_node_id_list;
+  const std::vector<int>& m_node_number_vector;
+
+  PASTIS_INLINE
+  std::vector<unsigned int> _sort(const std::vector<unsigned int>& node_list)
+  {
+    const auto min_id = std::min_element(node_list.begin(), node_list.end());
+    const int shift = std::distance(node_list.begin(), min_id);
+
+    std::vector<unsigned int> rotated_node_list(node_list.size());
+    if (node_list[(shift+1)%node_list.size()] > node_list[(shift+node_list.size()-1)%node_list.size()]) {
+      for (size_t i=0; i<node_list.size(); ++i) {
+        rotated_node_list[i] = node_list[(shift+node_list.size()-i)%node_list.size()];
+        m_reversed = true;
+      }
+    } else {
+      for (size_t i=0; i<node_list.size(); ++i) {
+        rotated_node_list[i] = node_list[(shift+i)%node_list.size()];
+      }
+    }
+
+    return rotated_node_list;
+  }
+
+ public:
+  PASTIS_INLINE
+  const bool& reversed() const
+  {
+    return m_reversed;
+  }
+
+  PASTIS_INLINE
+  const std::vector<unsigned int>& nodeIdList() const
+  {
+    return m_node_id_list;
+  }
+
+  PASTIS_INLINE
+  ConnectivityFace(const std::vector<unsigned int>& given_node_id_list,
+                   const std::vector<int>& node_number_vector)
+      : m_reversed(false),
+        m_node_id_list(_sort(given_node_id_list)),
+        m_node_number_vector(node_number_vector)
+  {
+    ;
+  }
 
-        if (__binary) {
-          //       int one=0;
-
-          //       fseek(__ifh,1L,SEEK_CUR);
-          //       fread(reinterpret_cast<char*>(&one),sizeof(int),1,__ifh);
-
-          //       if (one == 1) {
-          // 	__convertEndian = false;
-          //       } else {
-          // 	invertEndianess(one);
-
-          // 	if (one == 1) {
-          // 	  __convertEndian = true;
-          // 	} else {
-          // 	  throw ErrorHandler(__FILE__,__LINE__,
-          // 	  		     "Cannot determine endianess",
-          // 	  		     ErrorHandler::normal);
-          // 	}
-          // }
-
-          //       pout() << "- Binary format: ";
-          // #ifdef    WORDS_BIGENDIAN
-          //       if (not __convertEndian) {
-          // 	pout() << "big endian\n";
-          //       } else {
-          // 	pout() << "little endian\n";
-          //       }
-          // #else  // WORDS_BIGENDIAN
-          //       if (not __convertEndian) {
-          // 	pout() << "little endian\n";
-          //       } else {
-          // 	pout() << "big endian\n";
-          //       }
-          // #endif // WORDS_BIGENDIAN
+ public:
+  bool operator==(const ConnectivityFace& f) const
+  {
+    if (m_node_id_list.size() == f.nodeIdList().size()) {
+      for (size_t j=0; j<m_node_id_list.size(); ++j) {
+        if (m_node_id_list[j] != f.nodeIdList()[j]) {
+          return false;
         }
+      }
+      return true;
+    }
+    return false;
+  }
 
-        kw = this->__nextKeyword();
-        if (kw.second != ENDMESHFORMAT) {
+  PASTIS_INLINE
+  bool operator<(const ConnectivityFace& f) const
+  {
+    const size_t min_nb_nodes = std::min(f.m_node_id_list.size(), m_node_id_list.size());
+    for (size_t i=0; i<min_nb_nodes; ++i) {
+      if (m_node_id_list[i] <  f.m_node_id_list[i]) return true;
+      if (m_node_id_list[i] != f.m_node_id_list[i]) return false;
+    }
+    return m_node_id_list.size() < f.m_node_id_list.size();
+  }
+
+  PASTIS_INLINE
+  ConnectivityFace(const ConnectivityFace&) = default;
+
+  PASTIS_INLINE
+  ConnectivityFace() = delete;
+
+  PASTIS_INLINE
+  ~ConnectivityFace() = default;
+};
+
+GmshReader::GmshReader(const std::string& filename)
+    : m_filename(filename)
+{
+  if (parallel::rank() == 0) {
+    try {
+      m_fin.open(m_filename);
+      if (not m_fin) {
+        perr() << "cannot read file '" << m_filename << "'\n";
+        std::exit(0);
+      }
+
+      // gmsh 2.2 format keywords
+      __keywordList["$MeshFormat"]       = MESHFORMAT;
+      __keywordList["$EndMeshFormat"]    = ENDMESHFORMAT;
+      __keywordList["$Nodes"]            = NODES;
+      __keywordList["$EndNodes"]         = ENDNODES;
+      __keywordList["$Elements"]         = ELEMENTS;
+      __keywordList["$EndElements"]      = ENDELEMENTS;
+      __keywordList["$PhysicalNames"]    = PHYSICALNAMES;
+      __keywordList["$EndPhysicalNames"] = ENDPHYSICALNAMES;
+
+      __numberOfPrimitiveNodes.resize(16);
+      __numberOfPrimitiveNodes[ 0] =  2; // edge
+      __numberOfPrimitiveNodes[ 1] =  3; // triangle
+      __numberOfPrimitiveNodes[ 2] =  4; // quadrangle
+      __numberOfPrimitiveNodes[ 3] =  4; // Tetrahedron
+      __numberOfPrimitiveNodes[ 4] =  8; // Hexaredron
+      __numberOfPrimitiveNodes[ 5] =  6; // Prism
+      __numberOfPrimitiveNodes[ 6] =  5; // Pyramid
+      __numberOfPrimitiveNodes[ 7] =  3; // second order edge
+      __numberOfPrimitiveNodes[ 8] =  6; // second order triangle
+      __numberOfPrimitiveNodes[ 9] =  9; // second order quadrangle
+      __numberOfPrimitiveNodes[10] = 10; // second order tetrahedron
+      __numberOfPrimitiveNodes[11] = 27; // second order hexahedron
+      __numberOfPrimitiveNodes[12] = 18; // second order prism
+      __numberOfPrimitiveNodes[13] = 14; // second order pyramid
+      __numberOfPrimitiveNodes[14] =  1; // point
+
+      __primitivesNames[0]     = "edges";
+      __supportedPrimitives[0] = true;
+      __primitivesNames[1]     = "triangles";
+      __supportedPrimitives[1] = true;
+      __primitivesNames[2]     = "quadrangles";
+      __supportedPrimitives[2] = true;
+      __primitivesNames[3]     = "tetrahedra";
+      __supportedPrimitives[3] = true;
+      __primitivesNames[4]     = "hexahedra";
+      __supportedPrimitives[4] = true;
+      __primitivesNames[5]     = "prisms";
+      __supportedPrimitives[5] = false;
+      __primitivesNames[6]     = "pyramids";
+      __supportedPrimitives[6] = false;
+      __primitivesNames[7]     = "second order edges";
+      __supportedPrimitives[7] = false;
+      __primitivesNames[8]     = "second order triangles";
+      __supportedPrimitives[8] = false;
+      __primitivesNames[9]     = "second order quadrangles";
+      __supportedPrimitives[9] = false;
+      __primitivesNames[10]    = "second order tetrahedra";
+      __supportedPrimitives[10]= false;
+      __primitivesNames[11]    = "second order hexahedra";
+      __supportedPrimitives[11]= false;
+      __primitivesNames[12]    = "second order prisms";
+      __supportedPrimitives[12]= false;
+      __primitivesNames[13]    = "second order pyramids";
+      __supportedPrimitives[13]= false;
+      __primitivesNames[14]    = "point";
+      __supportedPrimitives[14]= true;
+
+      pout() << "Reading file '" << m_filename << "'\n";
+
+      // Getting vertices list
+      GmshReader::Keyword kw = this->__nextKeyword();
+      switch(kw.second) {
+        // case NOD: {
+        //   this->__readGmsh1();
+        //   break;
+        // }
+        case MESHFORMAT: {
+          double fileVersion = this->_getReal();
+          if (fileVersion != 2.2) {
+            throw ErrorHandler(__FILE__,__LINE__,
+                               "Cannot read Gmsh format '"+std::to_string(fileVersion)+"'",
+                               ErrorHandler::normal);
+          }
+          int fileType = this->_getInteger();
+          __binary = (fileType == 1);
+          if ((fileType < 0) or (fileType > 1)) {
+            throw ErrorHandler(__FILE__,__LINE__,
+                               "Cannot read Gmsh file type '"+std::to_string(fileType)+"'",
+                               ErrorHandler::normal);
+          }
+
+          int dataSize = this->_getInteger();
+          if (dataSize != sizeof(double)) {
+            throw ErrorHandler(__FILE__,__LINE__,
+                               "Data size not supported '"+std::to_string(dataSize)+"'",
+                               ErrorHandler::normal);
+          }
+
+          if (__binary) {
+            //       int one=0;
+
+            //       fseek(__ifh,1L,SEEK_CUR);
+            //       fread(reinterpret_cast<char*>(&one),sizeof(int),1,__ifh);
+
+            //       if (one == 1) {
+            // 	__convertEndian = false;
+            //       } else {
+            // 	invertEndianess(one);
+
+            // 	if (one == 1) {
+            // 	  __convertEndian = true;
+            // 	} else {
+            // 	  throw ErrorHandler(__FILE__,__LINE__,
+            // 	  		     "Cannot determine endianess",
+            // 	  		     ErrorHandler::normal);
+            // 	}
+            // }
+
+            //       pout() << "- Binary format: ";
+            // #ifdef    WORDS_BIGENDIAN
+            //       if (not __convertEndian) {
+            // 	pout() << "big endian\n";
+            //       } else {
+            // 	pout() << "little endian\n";
+            //       }
+            // #else  // WORDS_BIGENDIAN
+            //       if (not __convertEndian) {
+            // 	pout() << "little endian\n";
+            //       } else {
+            // 	pout() << "big endian\n";
+            //       }
+            // #endif // WORDS_BIGENDIAN
+          }
+
+          kw = this->__nextKeyword();
+          if (kw.second != ENDMESHFORMAT) {
+            throw ErrorHandler(__FILE__,__LINE__,
+                               "reading file '"+m_filename
+                               +"': expecting $EndMeshFormat, '"+kw.first+"' was found",
+                               ErrorHandler::normal);
+          }
+
+          this->__readGmshFormat2_2();
+
+          break;
+        }
+        default: {
           throw ErrorHandler(__FILE__,__LINE__,
-                             "reading file '"+m_filename
-                             +"': expecting $EndMeshFormat, '"+kw.first+"' was found",
+                             "cannot determine format version of '"+m_filename+"'",
                              ErrorHandler::normal);
         }
+      }
 
-        this->__readGmshFormat2_2();
-
+      this->__proceedData();
+      // this->__createMesh();
+    }
+    catch(const ErrorHandler& e) {
+      e.writeErrorMessage();
+      std::exit(0);
+    }
+  }
+  pout() << std::flush;
+  if (parallel::size() > 1) {
+    pout() << "Sequential mesh read! Need to be dispatched\n" << std::flush;
+
+    const int mesh_dimension
+        = [&]() {
+            int mesh_dimension = -1; // unknown mesh dimension
+            if (m_mesh) {
+              mesh_dimension = m_mesh->dimension();
+            }
+
+            Array<int> dimensions = parallel::allGather(mesh_dimension);
+            std::set<int> dimension_set;
+            for (size_t i=0; i<dimensions.size(); ++i) {
+              const int i_dimension = dimensions[i];
+              if (i_dimension != -1) {
+                dimension_set.insert(i_dimension);
+              }
+            }
+            if (dimension_set.size() != 1) {
+              std::cerr << "error dimensions of read mesh parts differ!\n";
+              std::exit(1);
+            }
+
+            return *begin(dimension_set);
+          }();
+    switch (mesh_dimension) {
+      case 1: {
+        this->_dispatch<1>();
+        break;
+      }
+      case 2: {
+        this->_dispatch<2>();
+        break;
+      }
+      case 3: {
+        this->_dispatch<3>();
         break;
       }
       default: {
-        throw ErrorHandler(__FILE__,__LINE__,
-                           "cannot determine format version of '"+m_filename+"'",
-                           ErrorHandler::normal);
+        perr() << "unexpected dimension " << mesh_dimension << '\n';
+        std::exit(1);
       }
     }
-
-    this->__proceedData();
-    // this->__createMesh();
-  }
-  catch(const ErrorHandler& e) {
-    e.writeErrorMessage();
-    std::exit(0);
   }
 }
 
@@ -371,7 +613,7 @@ void GmshReader::__readVertices()
 //     if ((elementType < 0) or (elementType > 14)) {
 //       throw ErrorHandler(__FILE__,__LINE__,
 // 			 "reading file '"+m_filename
-// 			 +"': unknown element type '"+stringify(elementType)+"'",
+// 			 +"': unknown element type '"+std::to_string(elementType)+"'",
 // 			 ErrorHandler::normal);
 //     }
 //     __elementType[i] = elementType;
@@ -409,19 +651,20 @@ GmshReader::__readElements2_2()
                        ErrorHandler::normal);
   }
 
+  __elementNumber.resize(numberOfElements);
   __elementType.resize(numberOfElements);
   __references.resize(numberOfElements);
   __elementVertices.resize(numberOfElements);
 
   if (not __binary) {
     for (int i=0; i<numberOfElements; ++i) {
-      this->_getInteger(); // drops element number
+      __elementNumber[i] = this->_getInteger();
       const int elementType = this->_getInteger()-1;
 
       if ((elementType < 0) or (elementType > 14)) {
         throw ErrorHandler(__FILE__,__LINE__,
                            "reading file '"+m_filename
-                           +"': unknown element type '"+stringify(elementType)+"'",
+                           +"': unknown element type '"+std::to_string(elementType)+"'",
                            ErrorHandler::normal);
       }
       __elementType[i] = elementType;
@@ -451,7 +694,7 @@ GmshReader::__readElements2_2()
     //   if ((elementType < 0) or (elementType > 14)) {
     // 	 throw ErrorHandler(__FILE__,__LINE__,
     // 	 		   "reading file '"+m_filename
-    // 	 		   +"': unknown element type '"+stringify(elementType)+"'",
+    // 	 		   +"': unknown element type '"+std::to_string(elementType)+"'",
     // 	 		   ErrorHandler::normal);
     //   }
 
@@ -525,6 +768,325 @@ __readPhysicalNames2_2()
   }
 }
 
+template <size_t Dimension>
+void
+GmshReader::__computeCellFaceAndFaceNodeConnectivities(ConnectivityDescriptor& descriptor)
+{
+  static_assert((Dimension==2) or (Dimension==3),
+                "Invalid dimension to compute cell-face connectivities");
+  using CellFaceInfo = std::tuple<CellId, unsigned short, bool>;
+  using Face = ConnectivityFace<Dimension>;
+
+  const auto& node_number_vector = descriptor.node_number_vector;
+  Array<unsigned short> cell_nb_faces(descriptor.cell_to_node_vector.size());
+  std::map<Face, std::vector<CellFaceInfo>> face_cells_map;
+  for (CellId j=0; j<descriptor.cell_to_node_vector.size(); ++j) {
+    const auto& cell_nodes = descriptor.cell_to_node_vector[j];
+
+    if constexpr (Dimension==2) {
+      switch (descriptor.cell_type_vector[j]) {
+      case CellType::Triangle: {
+        cell_nb_faces[j] = 3;
+        // face 0
+        Face f0({cell_nodes[1], cell_nodes[2]}, node_number_vector);
+        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
+
+        // face 1
+        Face f1({cell_nodes[2], cell_nodes[0]}, node_number_vector);
+        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
+
+        // face 2
+        Face f2({cell_nodes[0], cell_nodes[1]}, node_number_vector);
+        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
+        break;
+      }
+      case CellType::Quadrangle: {
+        cell_nb_faces[j] = 4;
+        // face 0
+        Face f0({cell_nodes[0], cell_nodes[1]}, node_number_vector);
+        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
+
+        // face 1
+        Face f1({cell_nodes[1], cell_nodes[2]}, node_number_vector);
+        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
+
+        // face 2
+        Face f2({cell_nodes[2], cell_nodes[3]}, node_number_vector);
+        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
+
+        // face 3
+        Face f3({cell_nodes[3], cell_nodes[0]}, node_number_vector);
+        face_cells_map[f3].emplace_back(std::make_tuple(j, 3, f3.reversed()));
+        break;
+      }
+      default: {
+        perr() << name(descriptor.cell_type_vector[j])
+               << ": unexpected cell type in dimension 2!\n";
+        std::terminate();
+      }
+      }
+    } else if constexpr (Dimension==3) {
+      switch (descriptor.cell_type_vector[j]) {
+      case CellType::Tetrahedron: {
+        cell_nb_faces[j] = 4;
+        // face 0
+        Face f0({cell_nodes[1],
+                 cell_nodes[2],
+                 cell_nodes[3]}, node_number_vector);
+        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
+
+        // face 1
+        Face f1({cell_nodes[0],
+                 cell_nodes[3],
+                 cell_nodes[2]}, node_number_vector);
+        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
+
+        // face 2
+        Face f2({cell_nodes[0],
+                 cell_nodes[1],
+                 cell_nodes[3]}, node_number_vector);
+        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
+
+        // face 3
+        Face f3({cell_nodes[0],
+                 cell_nodes[2],
+                 cell_nodes[1]}, node_number_vector);
+        face_cells_map[f3].emplace_back(std::make_tuple(j, 3, f3.reversed()));
+        break;
+      }
+      case CellType::Hexahedron: {
+        // face 0
+        Face f0({cell_nodes[3],
+                 cell_nodes[2],
+                 cell_nodes[1],
+                 cell_nodes[0]}, node_number_vector);
+        face_cells_map[f0].emplace_back(std::make_tuple(j, 0, f0.reversed()));
+
+        // face 1
+        Face f1({cell_nodes[4],
+                 cell_nodes[5],
+                 cell_nodes[6],
+                 cell_nodes[7]}, node_number_vector);
+        face_cells_map[f1].emplace_back(std::make_tuple(j, 1, f1.reversed()));
+
+        // face 2
+        Face f2({cell_nodes[0],
+                 cell_nodes[4],
+                 cell_nodes[7],
+                 cell_nodes[3]}, node_number_vector);
+        face_cells_map[f2].emplace_back(std::make_tuple(j, 2, f2.reversed()));
+
+        // face 3
+        Face f3({cell_nodes[1],
+                 cell_nodes[2],
+                 cell_nodes[6],
+                 cell_nodes[5]}, node_number_vector);
+        face_cells_map[f3].emplace_back(std::make_tuple(j, 3, f3.reversed()));
+
+        // face 4
+        Face f4({cell_nodes[0],
+                 cell_nodes[1],
+                 cell_nodes[5],
+                 cell_nodes[4]}, node_number_vector);
+        face_cells_map[f4].emplace_back(std::make_tuple(j, 4, f4.reversed()));
+
+        // face 5
+        Face f5({cell_nodes[3],
+                 cell_nodes[7],
+                 cell_nodes[6],
+                 cell_nodes[2]}, node_number_vector);
+        face_cells_map[f5].emplace_back(std::make_tuple(j, 5, f5.reversed()));
+
+        cell_nb_faces[j] = 6;
+        break;
+      }
+      default: {
+        perr() << name(descriptor.cell_type_vector[j])
+               << ": unexpected cell type in dimension 3!\n";
+        std::terminate();
+      }
+      }
+    }
+  }
+
+  {
+    descriptor.cell_to_face_vector.resize(descriptor.cell_to_node_vector.size());
+    for (CellId j=0; j<descriptor.cell_to_face_vector.size(); ++j) {
+      descriptor.cell_to_face_vector[j].resize(cell_nb_faces[j]);
+    }
+    FaceId l=0;
+    for (const auto& face_cells_vector : face_cells_map) {
+      const auto& cells_vector = face_cells_vector.second;
+      for (unsigned short lj=0; lj<cells_vector.size(); ++lj) {
+        const auto& [cell_number, cell_local_face,  reversed] = cells_vector[lj];
+        descriptor.cell_to_face_vector[cell_number][cell_local_face] = l;
+      }
+      ++l;
+    }
+  }
+
+  {
+    descriptor.cell_face_is_reversed_vector.resize(descriptor.cell_to_node_vector.size());
+    for (CellId j=0; j<descriptor.cell_face_is_reversed_vector.size(); ++j) {
+      descriptor.cell_face_is_reversed_vector[j] = Array<bool>(cell_nb_faces[j]);
+    }
+    for (const auto& face_cells_vector : face_cells_map) {
+      const auto& cells_vector = face_cells_vector.second;
+      for (unsigned short lj=0; lj<cells_vector.size(); ++lj) {
+        const auto& [cell_number, cell_local_face, reversed] = cells_vector[lj];
+        descriptor.cell_face_is_reversed_vector[cell_number][cell_local_face] = reversed;
+      }
+    }
+  }
+
+  {
+    descriptor.face_to_node_vector.resize(face_cells_map.size());
+    int l=0;
+    for (const auto& face_info : face_cells_map) {
+      const Face& face = face_info.first;
+      descriptor.face_to_node_vector[l] = face.nodeIdList();
+      ++l;
+    }
+  }
+
+  {
+    // Face numbers may change if numbers are provided in the file
+    descriptor.face_number_vector.resize(face_cells_map.size());
+    for (size_t l=0; l<face_cells_map.size(); ++l) {
+      descriptor.face_number_vector[l] = l;
+    }
+  }
+}
+
+
+template <size_t Dimension>
+void
+GmshReader::__computeFaceEdgeAndEdgeNodeAndCellEdgeConnectivities(ConnectivityDescriptor& descriptor)
+{
+  static_assert(Dimension==3, "Invalid dimension to compute face-edge connectivities");
+  using FaceEdgeInfo = std::tuple<FaceId, unsigned short, bool>;
+  using Edge = ConnectivityFace<2>;
+
+  const auto& node_number_vector = descriptor.node_number_vector;
+  Array<unsigned short> face_nb_edges(descriptor.face_to_node_vector.size());
+  std::map<Edge, std::vector<FaceEdgeInfo>> edge_faces_map;
+  for (FaceId l=0; l<descriptor.face_to_node_vector.size(); ++l) {
+    const auto& face_nodes = descriptor.face_to_node_vector[l];
+
+    face_nb_edges[l] = face_nodes.size();
+    for (size_t r=0; r<face_nodes.size()-1; ++r) {
+      Edge e({face_nodes[r], face_nodes[r+1]}, node_number_vector);
+      edge_faces_map[e].emplace_back(std::make_tuple(l, r, e.reversed()));
+    }
+    {
+      Edge e({face_nodes[face_nodes.size()-1], face_nodes[0]}, node_number_vector);
+      edge_faces_map[e].emplace_back(std::make_tuple(l, face_nodes.size()-1, e.reversed()));
+    }
+  }
+
+  std::unordered_map<Edge,EdgeId,typename Edge::Hash> edge_id_map;
+  {
+    descriptor.face_to_edge_vector.resize(descriptor.face_to_node_vector.size());
+    for (FaceId l=0; l<descriptor.face_to_node_vector.size(); ++l) {
+      descriptor.face_to_edge_vector[l].resize(face_nb_edges[l]);
+    }
+    EdgeId e=0;
+    for (const auto& edge_faces_vector : edge_faces_map) {
+      const auto& faces_vector = edge_faces_vector.second;
+      for (unsigned short l=0; l<faces_vector.size(); ++l) {
+        const auto& [face_number, face_local_edge,  reversed] = faces_vector[l];
+        descriptor.face_to_edge_vector[face_number][face_local_edge] = e;
+      }
+      edge_id_map[edge_faces_vector.first] = e;
+      ++e;
+    }
+  }
+
+  {
+    descriptor.face_edge_is_reversed_vector.resize(descriptor.face_to_node_vector.size());
+    for (FaceId j=0; j<descriptor.face_edge_is_reversed_vector.size(); ++j) {
+      descriptor.face_edge_is_reversed_vector[j] = Array<bool>(face_nb_edges[j]);
+    }
+    for (const auto& edge_faces_vector : edge_faces_map) {
+      const auto& faces_vector = edge_faces_vector.second;
+      for (unsigned short lj=0; lj<faces_vector.size(); ++lj) {
+        const auto& [face_number, face_local_edge, reversed] = faces_vector[lj];
+        descriptor.face_edge_is_reversed_vector[face_number][face_local_edge] = reversed;
+      }
+    }
+  }
+
+  {
+    descriptor.edge_to_node_vector.resize(edge_faces_map.size());
+    int e=0;
+    for (const auto& edge_info : edge_faces_map) {
+      const Edge& edge = edge_info.first;
+      descriptor.edge_to_node_vector[e] = edge.nodeIdList();
+      ++e;
+    }
+  }
+
+  {
+    // Edge numbers may change if numbers are provided in the file
+    descriptor.edge_number_vector.resize(edge_faces_map.size());
+    for (size_t e=0; e<edge_faces_map.size(); ++e) {
+      descriptor.edge_number_vector[e] = e;
+    }
+  }
+
+  {
+    descriptor.cell_to_node_vector.reserve(descriptor.cell_to_node_vector.size());
+    for (CellId j=0; j<descriptor.cell_to_node_vector.size(); ++j) {
+      const auto& cell_nodes = descriptor.cell_to_node_vector[j];
+
+      switch (descriptor.cell_type_vector[j]) {
+      case CellType::Tetrahedron: {
+        constexpr int local_edge[6][2] = {{0,1}, {0,2}, {0,3}, {1,2}, {2,3}, {3,1}};
+        std::vector<unsigned int> cell_edge_vector;
+        cell_edge_vector.reserve(6);
+        for (int i_edge=0; i_edge<6; ++i_edge) {
+          const auto e = local_edge[i_edge];
+          Edge edge{{cell_nodes[e[0]],cell_nodes[e[1]]}, node_number_vector};
+          auto i = edge_id_map.find(edge);
+          if (i == edge_id_map.end()) {
+            std::cerr << "could not find this edge!\n";
+            std::terminate();
+          }
+          cell_edge_vector.push_back(i->second);
+        }
+        descriptor.cell_to_edge_vector.emplace_back(cell_edge_vector);
+        break;
+      }
+      case CellType::Hexahedron: {
+        constexpr int local_edge[12][2] = {{0,1}, {1,2}, {2,3}, {3,0},
+                                           {4,5}, {5,6}, {6,7}, {7,4},
+                                           {0,4}, {1,5}, {2,6}, {3,7}};
+        std::vector<unsigned int> cell_edge_vector;
+        cell_edge_vector.reserve(12);
+        for (int i_edge=0; i_edge<12; ++i_edge) {
+          const auto e = local_edge[i_edge];
+          Edge edge{{cell_nodes[e[0]],cell_nodes[e[1]]}, node_number_vector};
+          auto i = edge_id_map.find(edge);
+          if (i == edge_id_map.end()) {
+            std::cerr << "could not find this edge!\n";
+            std::terminate();
+          }
+          cell_edge_vector.push_back(i->second);
+        }
+        descriptor.cell_to_edge_vector.emplace_back(cell_edge_vector);
+        break;
+      }
+      default: {
+        perr() << name(descriptor.cell_type_vector[j])
+               << ": unexpected cell type in dimension 3!\n";
+        std::terminate();
+      }
+      }
+    }
+  }
+}
+
+
 void
 GmshReader::__proceedData()
 {
@@ -558,32 +1120,37 @@ GmshReader::__proceedData()
           case 0: { // edges
             __edges = Array<Edge>(elementNumber[i]);
             __edges_ref.resize(elementNumber[i]);
+            __edges_number.resize(elementNumber[i]);
             break;
           }
           case 1: { // triangles
             __triangles = Array<Triangle>(elementNumber[i]);
             __triangles_ref.resize(elementNumber[i]);
+            __triangles_number.resize(elementNumber[i]);
             break;
           }
           case  2: { // quadrangles
             __quadrangles = Array<Quadrangle>(elementNumber[i]);
             __quadrangles_ref.resize(elementNumber[i]);
+            __quadrangles_number.resize(elementNumber[i]);
             break;
           }
           case 3: { // tetrahedra
             __tetrahedra = Array<Tetrahedron>(elementNumber[i]);
             __tetrahedra_ref.resize(elementNumber[i]);
+            __tetrahedra_number.resize(elementNumber[i]);
             break;
           }
           case  4: {// hexahedra
             __hexahedra = Array<Hexahedron>(elementNumber[i]);
             __hexahedra_ref.resize(elementNumber[i]);
+            __hexahedra_number.resize(elementNumber[i]);
             break;
           }
-            // Ignored entities
           case 14: {// point
             __points = Array<Point>(elementNumber[i]);
             __points_ref.resize(elementNumber[i]);
+            __points_number.resize(elementNumber[i]);
             break;
           }
             // Unsupported entities
@@ -628,7 +1195,6 @@ GmshReader::__proceedData()
   __verticesCorrepondance.resize(maxNumber+1,-1);
 
   for (size_t i=0; i<__verticesNumbers.size(); ++i) {
-
     __verticesCorrepondance[__verticesNumbers[i]] = i;
   }
 
@@ -647,13 +1213,14 @@ GmshReader::__proceedData()
         if ((a<0) or (b<0)) {
           throw ErrorHandler(__FILE__,__LINE__,
                              "reading file '"+m_filename
-                             +"': error reading element "+stringify(i)
+                             +"': error reading element "+std::to_string(i)
                              +" [bad vertices definition]",
                              ErrorHandler::normal);
         }
         __edges[edgeNumber]
             = Edge(a, b);
         __edges_ref[edgeNumber] = __references[i];
+        __edges_number[edgeNumber] = __elementNumber[i];
         edgeNumber++; // one more edge
         break;
       }
@@ -666,13 +1233,14 @@ GmshReader::__proceedData()
         if ((a<0) or (b<0) or (c<0)) {
           throw ErrorHandler(__FILE__,__LINE__,
                              "reading file '"+m_filename
-                             +"': error reading element "+stringify(i)
+                             +"': error reading element "+std::to_string(i)
                              +" [bad vertices definition]",
                              ErrorHandler::normal);
         }
         __triangles[triangleNumber]
             = Triangle(a, b, c);
         __triangles_ref[triangleNumber] = __references[i];
+        __triangles_number[triangleNumber] = __elementNumber[i];
         triangleNumber++; // one more triangle
         break;
       }
@@ -686,12 +1254,13 @@ GmshReader::__proceedData()
         if ((a<0) or (b<0) or (c<0) or (d<0)) {
           throw ErrorHandler(__FILE__,__LINE__,
                              "reading file '"+m_filename
-                             +"': error reading element "+stringify(i)
+                             +"': error reading element "+std::to_string(i)
                              +" [bad vertices definition]",
                              ErrorHandler::normal);
         }
         __quadrangles[quadrilateralNumber] = Quadrangle(a,b,c,d);
         __quadrangles_ref[quadrilateralNumber] = __references[i];
+        __quadrangles_number[quadrilateralNumber] = __elementNumber[i];
         quadrilateralNumber++; // one more quadrangle
         break;
       }
@@ -705,12 +1274,13 @@ GmshReader::__proceedData()
         if ((a<0) or (b<0) or (c<0) or (d<0)) {
           throw ErrorHandler(__FILE__,__LINE__,
                              "reading file '"+m_filename
-                             +"': error reading element "+stringify(i)
+                             +"': error reading element "+std::to_string(i)
                              +" [bad vertices definition]",
                              ErrorHandler::normal);
         }
         __tetrahedra[tetrahedronNumber] = Tetrahedron(a, b, c, d);
         __tetrahedra_ref[tetrahedronNumber] = __references[i];
+        __tetrahedra_number[tetrahedronNumber] = __elementNumber[i];
         tetrahedronNumber++; // one more tetrahedron
         break;
       }
@@ -728,7 +1298,7 @@ GmshReader::__proceedData()
         if ((a<0) or (b<0) or (c<0) or (d<0) or (e<0) or (f<0) or (g<0) or (h<0)) {
           throw ErrorHandler(__FILE__,__LINE__,
                              "reading file '"+m_filename
-                             +"': error reading element "+stringify(i)
+                             +"': error reading element "+std::to_string(i)
                              +" [bad vertices definition]",
                              ErrorHandler::normal);
         }
@@ -736,6 +1306,7 @@ GmshReader::__proceedData()
             = Hexahedron(a, b, c, d,
                          e, f, g, h);
         __hexahedra_ref[hexahedronNumber] = __references[i];
+        __hexahedra_number[hexahedronNumber] = __elementNumber[i];
         hexahedronNumber++; // one more hexahedron
         break;
       }
@@ -745,6 +1316,7 @@ GmshReader::__proceedData()
         const int a = __verticesCorrepondance[elementVertices[0]];
         __points[point_number] = a;
         __points_ref[point_number] = __references[i];
+        __points_number[point_number] = __elementNumber[i];
         point_number++;
         break;
       }
@@ -794,54 +1366,270 @@ GmshReader::__proceedData()
   if ((dimension3_mask, elementNumber)>0) {
     const size_t nb_cells = (dimension3_mask, elementNumber);
 
-    std::vector<CellType> cell_type_vector(nb_cells);
+    ConnectivityDescriptor descriptor;
+
+    descriptor.node_number_vector = __verticesNumbers;
+    descriptor.cell_type_vector.resize(nb_cells);
+    descriptor.cell_number_vector.resize(nb_cells);
+    descriptor.cell_to_node_vector.resize(nb_cells);
 
-    std::vector<std::vector<unsigned int>> cell_by_node_vector(nb_cells);
     const size_t nb_tetrahedra = __tetrahedra.size();
     for (size_t j=0; j<nb_tetrahedra; ++j) {
-      cell_by_node_vector[j].resize(4);
+      descriptor.cell_to_node_vector[j].resize(4);
       for (int r=0; r<4; ++r) {
-        cell_by_node_vector[j][r] = __tetrahedra[j][r];
+        descriptor.cell_to_node_vector[j][r] = __tetrahedra[j][r];
       }
-      cell_type_vector[j] = CellType::Tetrahedron;
+      descriptor.cell_type_vector[j] = CellType::Tetrahedron;
+      descriptor.cell_number_vector[j] = __tetrahedra_number[j];
     }
     const size_t nb_hexahedra = __hexahedra.size();
     for (size_t j=0; j<nb_hexahedra; ++j) {
       const size_t jh = nb_tetrahedra+j;
-      cell_by_node_vector[jh].resize(8);
+      descriptor.cell_to_node_vector[jh].resize(8);
       for (int r=0; r<8; ++r) {
-        cell_by_node_vector[jh][r] = __hexahedra[j][r];
+        descriptor.cell_to_node_vector[jh][r] = __hexahedra[j][r];
       }
-      cell_type_vector[jh] = CellType::Hexahedron;
+      descriptor.cell_type_vector[jh] = CellType::Hexahedron;
+      descriptor.cell_number_vector[jh] = __hexahedra_number[j];
     }
 
-    std::shared_ptr<Connectivity3D> p_connectivity(new Connectivity3D(cell_by_node_vector,
-                                                                      cell_type_vector));
-    Connectivity3D& connectivity = *p_connectivity;
+    std::map<unsigned int, std::vector<unsigned int>> ref_cells_map;
+    for (unsigned int r=0; r<__tetrahedra_ref.size(); ++r) {
+      const unsigned int elem_number = __tetrahedra_ref[r];
+      const unsigned int& ref = __tetrahedra_ref[r];
+      ref_cells_map[ref].push_back(elem_number);
+    }
 
-    std::map<unsigned int, std::vector<unsigned int>> ref_faces_map;
-    for (unsigned int f=0; f<__triangles.size(); ++f) {
-      const unsigned int face_number
-          = connectivity.getFaceNumber({__triangles[f][0], __triangles[f][1], __triangles[f][2]});
-      const unsigned int& ref = __triangles_ref[f];
-      ref_faces_map[ref].push_back(face_number);
+    for (unsigned int j=0; j<__hexahedra_ref.size(); ++j) {
+      const size_t elem_number = nb_tetrahedra+j;
+      const unsigned int& ref = __hexahedra_ref[j];
+      ref_cells_map[ref].push_back(elem_number);
     }
-    for (unsigned int f=0; f<__quadrangles.size(); ++f) {
-      const unsigned int face_number
-          = connectivity.getFaceNumber({__quadrangles[f][0], __quadrangles[f][1],
-                                        __quadrangles[f][2], __quadrangles[f][3]});
-      const unsigned int& ref = __quadrangles_ref[f];
-      ref_faces_map[ref].push_back(face_number);
+
+
+    for (const auto& ref_cell_list : ref_cells_map) {
+      Array<CellId> cell_list(ref_cell_list.second.size());
+      for (size_t j=0; j<ref_cell_list.second.size(); ++j) {
+        cell_list[j]=ref_cell_list.second[j];
+      }
+      const PhysicalRefId& physical_ref_id = m_physical_ref_map.at(ref_cell_list.first);
+      descriptor.addRefItemList(RefCellList(physical_ref_id.refId(), cell_list));
     }
-    for (const auto& ref_face_list : ref_faces_map) {
-      Array<FaceId> face_list(ref_face_list.second.size());
-      for (size_t j=0; j<ref_face_list.second.size(); ++j) {
-        face_list[j]=ref_face_list.second[j];
+
+    this->__computeCellFaceAndFaceNodeConnectivities<3>(descriptor);
+
+    const auto& node_number_vector = descriptor.node_number_vector;
+
+    {
+      using Face = ConnectivityFace<3>;
+      const std::unordered_map<Face, FaceId, typename Face::Hash> face_to_id_map
+          = [&]  {
+              std::unordered_map<Face, FaceId, typename Face::Hash> face_to_id_map;
+              for (FaceId l=0; l<descriptor.face_to_node_vector.size(); ++l) {
+                const auto& node_vector = descriptor.face_to_node_vector[l];
+                face_to_id_map[Face(node_vector, node_number_vector)] = l;
+              }
+              return face_to_id_map;
+            } ();
+
+      std::unordered_map<int, FaceId> face_number_id_map
+          = [&] {
+              std::unordered_map<int, FaceId> face_number_id_map;
+              for (size_t l=0; l< descriptor.face_number_vector.size(); ++l) {
+                face_number_id_map[descriptor.face_number_vector[l]] = l;
+              }
+              Assert(face_number_id_map.size() == descriptor.face_number_vector.size());
+              return face_number_id_map;
+            } ();
+
+      std::map<unsigned int, std::vector<unsigned int>> ref_faces_map;
+      for (unsigned int f=0; f<__triangles.size(); ++f) {
+        const unsigned int face_id
+            = [&]{
+                auto i = face_to_id_map.find(Face({__triangles[f][0], __triangles[f][1], __triangles[f][2]},
+                                                  node_number_vector));
+                if (i == face_to_id_map.end()) {
+                  std::cerr << "face not found!\n";
+                  std::terminate();
+                }
+                return i->second;
+              }();
+
+        const unsigned int& ref = __triangles_ref[f];
+        ref_faces_map[ref].push_back(face_id);
+
+        if (descriptor.face_number_vector[face_id] != __quadrangles_number[f]) {
+          if (auto i_face = face_number_id_map.find(__quadrangles_number[f]);
+              i_face != face_number_id_map.end()) {
+            const int other_face_id = i_face->second;
+            std::swap(descriptor.face_number_vector[face_id],
+                      descriptor.face_number_vector[other_face_id]);
+
+            face_number_id_map.erase(descriptor.face_number_vector[face_id]);
+            face_number_id_map.erase(descriptor.face_number_vector[other_face_id]);
+
+            face_number_id_map[descriptor.face_number_vector[face_id]]=face_id;
+            face_number_id_map[descriptor.face_number_vector[other_face_id]]=other_face_id;
+          } else {
+            face_number_id_map.erase(descriptor.face_number_vector[face_id]);
+            descriptor.face_number_vector[face_id] = __quadrangles_number[f];
+            face_number_id_map[descriptor.face_number_vector[face_id]] = face_id;
+          }
+        }
       }
-      const PhysicalRefId& physical_ref_id = m_physical_ref_map.at(ref_face_list.first);
-      connectivity.addRefFaceList(RefFaceList(physical_ref_id.refId(), face_list));
+
+      for (unsigned int f=0; f<__quadrangles.size(); ++f) {
+        const unsigned int face_id
+            = [&]{
+                auto i = face_to_id_map.find(Face({__quadrangles[f][0], __quadrangles[f][1],
+                                                   __quadrangles[f][2], __quadrangles[f][3]}, node_number_vector));
+                if (i == face_to_id_map.end()) {
+                  std::cerr << "face not found!\n";
+                  std::terminate();
+                }
+                return i->second;
+              }();
+
+        const unsigned int& ref = __quadrangles_ref[f];
+        ref_faces_map[ref].push_back(face_id);
+
+        if (descriptor.face_number_vector[face_id] != __quadrangles_number[f]) {
+          if (auto i_face = face_number_id_map.find(__quadrangles_number[f]);
+              i_face != face_number_id_map.end()) {
+            const int other_face_id = i_face->second;
+            std::swap(descriptor.face_number_vector[face_id],
+                      descriptor.face_number_vector[other_face_id]);
+
+            face_number_id_map.erase(descriptor.face_number_vector[face_id]);
+            face_number_id_map.erase(descriptor.face_number_vector[other_face_id]);
+
+            face_number_id_map[descriptor.face_number_vector[face_id]]=face_id;
+            face_number_id_map[descriptor.face_number_vector[other_face_id]]=other_face_id;
+          } else {
+            face_number_id_map.erase(descriptor.face_number_vector[face_id]);
+            descriptor.face_number_vector[face_id] = __quadrangles_number[f];
+            face_number_id_map[descriptor.face_number_vector[face_id]] = face_id;
+          }
+        }
+      }
+
+      for (const auto& ref_face_list : ref_faces_map) {
+        Array<FaceId> face_list(ref_face_list.second.size());
+        for (size_t j=0; j<ref_face_list.second.size(); ++j) {
+          face_list[j]=ref_face_list.second[j];
+        }
+        const PhysicalRefId& physical_ref_id = m_physical_ref_map.at(ref_face_list.first);
+        descriptor.addRefItemList(RefFaceList{physical_ref_id.refId(), face_list});
+      }
+    }
+    this->__computeFaceEdgeAndEdgeNodeAndCellEdgeConnectivities<3>(descriptor);
+
+    {
+      using Edge = ConnectivityFace<2>;
+      const auto& node_number_vector = descriptor.node_number_vector;
+      const std::unordered_map<Edge, EdgeId, typename Edge::Hash> edge_to_id_map
+          = [&]  {
+              std::unordered_map<Edge, EdgeId, typename Edge::Hash> edge_to_id_map;
+              for (EdgeId l=0; l<descriptor.edge_to_node_vector.size(); ++l) {
+                const auto& node_vector = descriptor.edge_to_node_vector[l];
+                edge_to_id_map[Edge(node_vector, node_number_vector)] = l;
+              }
+              return edge_to_id_map;
+            } ();
+
+      std::unordered_map<int, EdgeId> edge_number_id_map
+          = [&] {
+              std::unordered_map<int, EdgeId> edge_number_id_map;
+              for (size_t l=0; l< descriptor.edge_number_vector.size(); ++l) {
+                edge_number_id_map[descriptor.edge_number_vector[l]] = l;
+              }
+              Assert(edge_number_id_map.size() == descriptor.edge_number_vector.size());
+              return edge_number_id_map;
+            } ();
+
+      std::map<unsigned int, std::vector<unsigned int>> ref_edges_map;
+      for (unsigned int e=0; e<__edges.size(); ++e) {
+        const unsigned int edge_id
+            = [&]{
+                auto i = edge_to_id_map.find(Edge({__edges[e][0], __edges[e][1]}, node_number_vector));
+                if (i == edge_to_id_map.end()) {
+                  std::cerr << "edge " << __edges[e][0] << " not found!\n";
+                  std::terminate();
+                }
+                return i->second;
+              }();
+        const unsigned int& ref = __edges_ref[e];
+        ref_edges_map[ref].push_back(edge_id);
+
+        if (descriptor.edge_number_vector[edge_id] != __edges_number[e]) {
+          if (auto i_edge = edge_number_id_map.find(__edges_number[e]);
+              i_edge != edge_number_id_map.end()) {
+            const int other_edge_id = i_edge->second;
+            std::swap(descriptor.edge_number_vector[edge_id],
+                      descriptor.edge_number_vector[other_edge_id]);
+
+            edge_number_id_map.erase(descriptor.edge_number_vector[edge_id]);
+            edge_number_id_map.erase(descriptor.edge_number_vector[other_edge_id]);
+
+            edge_number_id_map[descriptor.edge_number_vector[edge_id]]=edge_id;
+            edge_number_id_map[descriptor.edge_number_vector[other_edge_id]]=other_edge_id;
+          } else {
+            edge_number_id_map.erase(descriptor.edge_number_vector[edge_id]);
+            descriptor.edge_number_vector[edge_id] = __edges_number[e];
+            edge_number_id_map[descriptor.edge_number_vector[edge_id]] = edge_id;
+          }
+        }
+      }
+
+      for (const auto& ref_edge_list : ref_edges_map) {
+        Array<EdgeId> edge_list(ref_edge_list.second.size());
+        for (size_t j=0; j<ref_edge_list.second.size(); ++j) {
+          edge_list[j]=ref_edge_list.second[j];
+        }
+        const PhysicalRefId& physical_ref_id = m_physical_ref_map.at(ref_edge_list.first);
+        descriptor.addRefItemList(RefEdgeList{physical_ref_id.refId(), edge_list});
+      }
+    }
+
+    std::map<unsigned int, std::vector<unsigned int>> ref_points_map;
+    for (unsigned int r=0; r<__points.size(); ++r) {
+      const unsigned int point_number = __points[r];
+      const unsigned int& ref = __points_ref[r];
+      ref_points_map[ref].push_back(point_number);
+    }
+
+    for (const auto& ref_point_list : ref_points_map) {
+      Array<NodeId> point_list(ref_point_list.second.size());
+      for (size_t j=0; j<ref_point_list.second.size(); ++j) {
+        point_list[j]=ref_point_list.second[j];
+      }
+      const PhysicalRefId& physical_ref_id = m_physical_ref_map.at(ref_point_list.first);
+      descriptor.addRefItemList(RefNodeList(physical_ref_id.refId(), point_list));
     }
 
+    descriptor.cell_owner_vector.resize(nb_cells);
+    std::fill(descriptor.cell_owner_vector.begin(),
+              descriptor.cell_owner_vector.end(),
+              parallel::rank());
+
+    descriptor.face_owner_vector.resize(descriptor.face_number_vector.size());
+    std::fill(descriptor.face_owner_vector.begin(),
+              descriptor.face_owner_vector.end(),
+              parallel::rank());
+
+    descriptor.edge_owner_vector.resize(descriptor.edge_number_vector.size());
+    std::fill(descriptor.edge_owner_vector.begin(),
+              descriptor.edge_owner_vector.end(),
+              parallel::rank());
+
+    descriptor.node_owner_vector.resize(descriptor.node_number_vector.size());
+    std::fill(descriptor.node_owner_vector.begin(),
+              descriptor.node_owner_vector.end(),
+              parallel::rank());
+
+    std::shared_ptr p_connectivity = Connectivity3D::build(descriptor);
+    Connectivity3D& connectivity = *p_connectivity;
+
     using MeshType = Mesh<Connectivity3D>;
     using Rd = TinyVector<3, double>;
 
@@ -851,42 +1639,117 @@ GmshReader::__proceedData()
       xr[i][1] = __vertices[i][1];
       xr[i][2] = __vertices[i][2];
     }
-    m_mesh = std::shared_ptr<IMesh>(new MeshType(p_connectivity, xr));
+    m_mesh = std::make_shared<MeshType>(p_connectivity, xr);
 
   } else if ((dimension2_mask, elementNumber)>0) {
     const size_t nb_cells = (dimension2_mask, elementNumber);
 
-    std::vector<CellType> cell_type_vector(nb_cells);
+    ConnectivityDescriptor descriptor;
+
+    descriptor.node_number_vector = __verticesNumbers;
+    descriptor.cell_type_vector.resize(nb_cells);
+    descriptor.cell_number_vector.resize(nb_cells);
+    descriptor.cell_to_node_vector.resize(nb_cells);
 
-    std::vector<std::vector<unsigned int>> cell_by_node_vector(nb_cells);
     const size_t nb_triangles = __triangles.size();
     for (size_t j=0; j<nb_triangles; ++j) {
-      cell_by_node_vector[j].resize(3);
+      descriptor.cell_to_node_vector[j].resize(3);
       for (int r=0; r<3; ++r) {
-        cell_by_node_vector[j][r] = __triangles[j][r];
+        descriptor.cell_to_node_vector[j][r] = __triangles[j][r];
       }
-      cell_type_vector[j] = CellType::Triangle;
+      descriptor.cell_type_vector[j] = CellType::Triangle;
+      descriptor.cell_number_vector[j] = __triangles_number[j];
     }
 
     const size_t nb_quadrangles = __quadrangles.size();
     for (size_t j=0; j<nb_quadrangles; ++j) {
       const size_t jq = j+nb_triangles;
-      cell_by_node_vector[jq].resize(4);
+      descriptor.cell_to_node_vector[jq].resize(4);
       for (int r=0; r<4; ++r) {
-        cell_by_node_vector[jq][r] = __quadrangles[j][r];
+        descriptor.cell_to_node_vector[jq][r] = __quadrangles[j][r];
       }
-      cell_type_vector[jq] = CellType::Quadrangle;
+      descriptor.cell_type_vector[jq] = CellType::Quadrangle;
+      descriptor.cell_number_vector[jq] = __quadrangles_number[j];
     }
 
-    std::shared_ptr<Connectivity2D> p_connectivity(new Connectivity2D(cell_by_node_vector,
-                                                                      cell_type_vector));
-    Connectivity2D& connectivity = *p_connectivity;
+    std::map<unsigned int, std::vector<unsigned int>> ref_cells_map;
+    for (unsigned int r=0; r<__triangles_ref.size(); ++r) {
+      const unsigned int elem_number = __triangles_ref[r];
+      const unsigned int& ref = __triangles_ref[r];
+      ref_cells_map[ref].push_back(elem_number);
+    }
+
+    for (unsigned int j=0; j<__quadrangles_ref.size(); ++j) {
+      const size_t elem_number = nb_triangles+j;
+      const unsigned int& ref = __quadrangles_ref[j];
+      ref_cells_map[ref].push_back(elem_number);
+    }
+
+    for (const auto& ref_cell_list : ref_cells_map) {
+      Array<CellId> cell_list(ref_cell_list.second.size());
+      for (size_t j=0; j<ref_cell_list.second.size(); ++j) {
+        cell_list[j]=ref_cell_list.second[j];
+      }
+      const PhysicalRefId& physical_ref_id = m_physical_ref_map.at(ref_cell_list.first);
+      descriptor.addRefItemList(RefCellList(physical_ref_id.refId(), cell_list));
+    }
+
+    this->__computeCellFaceAndFaceNodeConnectivities<2>(descriptor);
+
+    using Face = ConnectivityFace<2>;
+    const auto& node_number_vector = descriptor.node_number_vector;
+    const std::unordered_map<Face, FaceId, typename Face::Hash> face_to_id_map
+        = [&]  {
+            std::unordered_map<Face, FaceId, typename Face::Hash> face_to_id_map;
+            for (FaceId l=0; l<descriptor.face_to_node_vector.size(); ++l) {
+              const auto& node_vector = descriptor.face_to_node_vector[l];
+              face_to_id_map[Face(node_vector, node_number_vector)] = l;
+            }
+            return face_to_id_map;
+          } ();
+
+    std::unordered_map<int, FaceId> face_number_id_map
+        = [&] {
+            std::unordered_map<int, FaceId> face_number_id_map;
+            for (size_t l=0; l< descriptor.face_number_vector.size(); ++l) {
+              face_number_id_map[descriptor.face_number_vector[l]] = l;
+            }
+            Assert(face_number_id_map.size() == descriptor.face_number_vector.size());
+            return face_number_id_map;
+          } ();
 
     std::map<unsigned int, std::vector<unsigned int>> ref_faces_map;
     for (unsigned int e=0; e<__edges.size(); ++e) {
-      const unsigned int edge_number = connectivity.getFaceNumber({__edges[e][0], __edges[e][1]});
+      const unsigned int edge_id
+          = [&]{
+              auto i = face_to_id_map.find(Face({__edges[e][0], __edges[e][1]}, node_number_vector));
+              if (i == face_to_id_map.end()) {
+                std::cerr << "face " << __edges[e][0] << " not found!\n";
+                std::terminate();
+              }
+              return i->second;
+            }();
       const unsigned int& ref = __edges_ref[e];
-      ref_faces_map[ref].push_back(edge_number);
+      ref_faces_map[ref].push_back(edge_id);
+
+      if (descriptor.face_number_vector[edge_id] != __edges_number[e]) {
+        if (auto i_face = face_number_id_map.find(__edges_number[e]);
+            i_face != face_number_id_map.end()) {
+          const int other_edge_id = i_face->second;
+          std::swap(descriptor.face_number_vector[edge_id],
+                    descriptor.face_number_vector[other_edge_id]);
+
+          face_number_id_map.erase(descriptor.face_number_vector[edge_id]);
+          face_number_id_map.erase(descriptor.face_number_vector[other_edge_id]);
+
+          face_number_id_map[descriptor.face_number_vector[edge_id]]=edge_id;
+          face_number_id_map[descriptor.face_number_vector[other_edge_id]]=other_edge_id;
+        } else {
+          face_number_id_map.erase(descriptor.face_number_vector[edge_id]);
+          descriptor.face_number_vector[edge_id] = __edges_number[e];
+          face_number_id_map[descriptor.face_number_vector[edge_id]] = edge_id;
+        }
+      }
     }
 
     for (const auto& ref_face_list : ref_faces_map) {
@@ -895,7 +1758,7 @@ GmshReader::__proceedData()
         face_list[j]=ref_face_list.second[j];
       }
       const PhysicalRefId& physical_ref_id = m_physical_ref_map.at(ref_face_list.first);
-      connectivity.addRefFaceList(RefFaceList(physical_ref_id.refId(), face_list));
+      descriptor.addRefItemList(RefFaceList{physical_ref_id.refId(), face_list});
     }
 
     std::map<unsigned int, std::vector<unsigned int>> ref_points_map;
@@ -911,9 +1774,27 @@ GmshReader::__proceedData()
         point_list[j]=ref_point_list.second[j];
       }
       const PhysicalRefId& physical_ref_id = m_physical_ref_map.at(ref_point_list.first);
-      connectivity.addRefNodeList(RefNodeList(physical_ref_id.refId(), point_list));
+      descriptor.addRefItemList(RefNodeList(physical_ref_id.refId(), point_list));
     }
 
+    descriptor.cell_owner_vector.resize(nb_cells);
+    std::fill(descriptor.cell_owner_vector.begin(),
+              descriptor.cell_owner_vector.end(),
+              parallel::rank());
+
+    descriptor.face_owner_vector.resize(descriptor.face_number_vector.size());
+    std::fill(descriptor.face_owner_vector.begin(),
+              descriptor.face_owner_vector.end(),
+              parallel::rank());
+
+    descriptor.node_owner_vector.resize(descriptor.node_number_vector.size());
+    std::fill(descriptor.node_owner_vector.begin(),
+              descriptor.node_owner_vector.end(),
+              parallel::rank());
+
+    std::shared_ptr p_connectivity = Connectivity2D::build(descriptor);
+    Connectivity2D& connectivity = *p_connectivity;
+
     using MeshType = Mesh<Connectivity2D>;
     using Rd = TinyVector<2, double>;
 
@@ -922,25 +1803,27 @@ GmshReader::__proceedData()
       xr[i][0] = __vertices[i][0];
       xr[i][1] = __vertices[i][1];
     }
-    m_mesh = std::shared_ptr<IMesh>(new MeshType(p_connectivity, xr));
+    m_mesh = std::make_shared<MeshType>(p_connectivity, xr);
 
   } else if ((dimension1_mask, elementNumber)>0) {
     const size_t nb_cells = (dimension1_mask, elementNumber);
 
-    std::vector<CellType> cell_type_vector(nb_cells);
+    ConnectivityDescriptor descriptor;
+
+    descriptor.node_number_vector = __verticesNumbers;
+    descriptor.cell_type_vector.resize(nb_cells);
+    descriptor.cell_number_vector.resize(nb_cells);
+    descriptor.cell_to_node_vector.resize(nb_cells);
 
-    std::vector<std::vector<unsigned int>> cell_by_node_vector(nb_cells);
     for (size_t j=0; j<nb_cells; ++j) {
-      cell_by_node_vector[j].resize(2);
+      descriptor.cell_to_node_vector[j].resize(2);
       for (int r=0; r<2; ++r) {
-        cell_by_node_vector[j][r] = __edges[j][r];
+        descriptor.cell_to_node_vector[j][r] = __edges[j][r];
       }
-      cell_type_vector[j] = CellType::Line;
+      descriptor.cell_type_vector[j] = CellType::Line;
+      descriptor.cell_number_vector[j] =  __edges_number[j];
     }
 
-    std::shared_ptr<Connectivity1D> p_connectivity(new Connectivity1D(cell_by_node_vector,
-                                                                      cell_type_vector));
-    Connectivity1D& connectivity = *p_connectivity;
 
     std::map<unsigned int, std::vector<unsigned int>> ref_points_map;
     for (unsigned int r=0; r<__points.size(); ++r) {
@@ -955,9 +1838,22 @@ GmshReader::__proceedData()
         point_list[j]=ref_point_list.second[j];
       }
       const PhysicalRefId& physical_ref_id = m_physical_ref_map.at(ref_point_list.first);
-      connectivity.addRefNodeList(RefNodeList(physical_ref_id.refId(), point_list));
+      descriptor.addRefItemList(RefNodeList(physical_ref_id.refId(), point_list));
     }
 
+    descriptor.cell_owner_vector.resize(nb_cells);
+    std::fill(descriptor.cell_owner_vector.begin(),
+              descriptor.cell_owner_vector.end(),
+              parallel::rank());
+
+    descriptor.node_owner_vector.resize(descriptor.node_number_vector.size());
+    std::fill(descriptor.node_owner_vector.begin(),
+              descriptor.node_owner_vector.end(),
+              parallel::rank());
+
+    std::shared_ptr p_connectivity = Connectivity1D::build(descriptor);
+    Connectivity1D& connectivity = *p_connectivity;
+
     using MeshType = Mesh<Connectivity1D>;
     using Rd = TinyVector<1, double>;
 
@@ -966,7 +1862,7 @@ GmshReader::__proceedData()
       xr[i][0] = __vertices[i][0];
     }
 
-    m_mesh = std::shared_ptr<IMesh>(new MeshType(p_connectivity, xr));
+    m_mesh = std::make_shared<MeshType>(p_connectivity, xr);
 
   } else {
     perr() << "*** using a dimension 0 mesh is forbidden!\n";
diff --git a/src/mesh/GmshReader.hpp b/src/mesh/GmshReader.hpp
index ba9ffe2502b4df46f59308f411747c7c2deaaac0..cd482e637c90a68c1bd208a51285db06d5f93741 100644
--- a/src/mesh/GmshReader.hpp
+++ b/src/mesh/GmshReader.hpp
@@ -14,6 +14,8 @@
 #include <map>
 #include <fstream>
 
+class ConnectivityDescriptor;
+
 class GmshReader
 {
 public:
@@ -83,26 +85,32 @@ private:
   using Point = unsigned int;
   Array<Point> __points;
   std::vector<int> __points_ref;
+  std::vector<int> __points_number;
 
   using Edge = TinyVector<2,unsigned int>;
   Array<Edge> __edges;
   std::vector<int> __edges_ref;
+  std::vector<int> __edges_number;
 
   using Triangle = TinyVector<3,unsigned int>;
   Array<Triangle> __triangles;
   std::vector<int> __triangles_ref;
+  std::vector<int> __triangles_number;
 
   using Quadrangle = TinyVector<4,unsigned int>;
   Array<Quadrangle> __quadrangles;
   std::vector<int> __quadrangles_ref;
+  std::vector<int> __quadrangles_number;
 
   using Tetrahedron = TinyVector<4,unsigned int>;
   Array<Tetrahedron> __tetrahedra;
   std::vector<int> __tetrahedra_ref;
+  std::vector<int> __tetrahedra_number;
 
   using Hexahedron = TinyVector<8,unsigned int>;
   Array<Hexahedron> __hexahedra;
   std::vector<int> __hexahedra_ref;
+  std::vector<int> __hexahedra_number;
 
   /**
    * Gmsh format provides a numbered, none ordrered and none dense
@@ -110,6 +118,11 @@ private:
    */
   std::vector<int> __verticesCorrepondance;
 
+  /**
+   * elements types
+   */
+  std::vector<int> __elementNumber;
+
   /**
    * elements types
    */
@@ -153,7 +166,7 @@ private:
     int i;
     m_fin >> i;
 
-    return std::move(i);
+    return i;
   }
 
   double _getReal()
@@ -161,7 +174,7 @@ private:
     double d;
     m_fin >> d;
 
-    return std::move(d);
+    return d;
   }
 
   /**
@@ -246,6 +259,15 @@ private:
    */
   void __readPhysicalNames2_2();
 
+  template <size_t Dimension>
+  void __computeCellFaceAndFaceNodeConnectivities(ConnectivityDescriptor& descriptor);
+
+  template <size_t Dimension>
+  void __computeFaceEdgeAndEdgeNodeAndCellEdgeConnectivities(ConnectivityDescriptor& descriptor);
+
+  template <int Dimension>
+  void _dispatch();
+
   std::shared_ptr<IMesh> m_mesh;
 public:
 
@@ -256,8 +278,6 @@ public:
 
   GmshReader(const std::string& filename);
   ~GmshReader() = default;
-
-
 };
 
 #endif // GMSH_READER_HPP
diff --git a/src/mesh/IConnectivity.hpp b/src/mesh/IConnectivity.hpp
index f60db961d80f939c64139f8746a674e4ce4de91e..63ae03f3aa9e30f477db20c044e90822dd8dd046 100644
--- a/src/mesh/IConnectivity.hpp
+++ b/src/mesh/IConnectivity.hpp
@@ -2,15 +2,16 @@
 #define ICONNECTIVITY_HPP
 
 #include <ItemType.hpp>
+#include <ItemOfItemType.hpp>
 #include <ConnectivityMatrix.hpp>
+#include <memory>
 
-class IConnectivity
+class IConnectivity : public std::enable_shared_from_this<IConnectivity>
 {
  protected:
   template <typename DataType,
-            ItemType sub_item_type,
-            ItemType item_type,
-            typename Allowed>
+            typename ItemOfItem,
+            typename ConnectivityPtr>
   friend
   class SubItemValuePerItem;
 
@@ -19,6 +20,13 @@ class IConnectivity
              const ItemType& item_type_1) const = 0;
 
  public:
+  virtual size_t dimension() const = 0;
+
+  std::shared_ptr<const IConnectivity> shared_ptr() const
+  {
+    return this->shared_from_this();
+  }
+
   virtual size_t numberOfNodes() const = 0;
   virtual size_t numberOfEdges() const = 0;
   virtual size_t numberOfFaces() const = 0;
diff --git a/src/mesh/ItemOfItemType.hpp b/src/mesh/ItemOfItemType.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..53a7d5ffe8d339d99e8be2e2fed0639542f83772
--- /dev/null
+++ b/src/mesh/ItemOfItemType.hpp
@@ -0,0 +1,34 @@
+#ifndef ITEM_OF_ITEM_TYPE_HPP
+#define ITEM_OF_ITEM_TYPE_HPP
+
+#include <ItemType.hpp>
+
+template <ItemType sub_item_t,
+          ItemType item_t>
+struct ItemOfItemType
+{
+  static_assert(sub_item_t != item_t, "item and its sub-item cannot be of same type");
+
+  constexpr static ItemType sub_item_type = sub_item_t;
+  constexpr static ItemType item_type = item_t;
+
+  using Reversed = ItemOfItemType<item_type, sub_item_type>;
+};
+
+using FaceOfCell = ItemOfItemType<ItemType::face, ItemType::cell>;
+using EdgeOfCell = ItemOfItemType<ItemType::edge, ItemType::cell>;
+using NodeOfCell = ItemOfItemType<ItemType::node, ItemType::cell>;
+
+using CellOfFace = ItemOfItemType<ItemType::cell, ItemType::face>;
+using EdgeOfFace = ItemOfItemType<ItemType::edge, ItemType::face>;
+using NodeOfFace = ItemOfItemType<ItemType::node, ItemType::face>;
+
+using CellOfEdge = ItemOfItemType<ItemType::cell, ItemType::edge>;
+using FaceOfEdge = ItemOfItemType<ItemType::face, ItemType::edge>;
+using NodeOfEdge = ItemOfItemType<ItemType::node, ItemType::edge>;
+
+using CellOfNode = ItemOfItemType<ItemType::cell, ItemType::node>;
+using FaceOfNode = ItemOfItemType<ItemType::face, ItemType::node>;
+using EdgeOfNode = ItemOfItemType<ItemType::edge, ItemType::node>;
+
+#endif // ITEM_OF_ITEM_TYPE_HPP
diff --git a/src/mesh/ItemToItemMatrix.hpp b/src/mesh/ItemToItemMatrix.hpp
index bf7e86ebca7d279a03586a6ad183b24887457b23..bf62537d33b805aee07fddd9e886aa2ed48a922c 100644
--- a/src/mesh/ItemToItemMatrix.hpp
+++ b/src/mesh/ItemToItemMatrix.hpp
@@ -67,7 +67,7 @@ class ItemToItemMatrix
     return m_connectivity_matrix.numEntries();
   }
 
-  const auto& entries() const
+  auto entries() const
   {
     return m_connectivity_matrix.entries();
   }
@@ -83,7 +83,7 @@ class ItemToItemMatrix
   PASTIS_INLINE
   const auto& operator[](const IndexType& source_id) const
   {
-    static_assert(std::is_same<IndexType, SourceItemId>(),
+    static_assert(std::is_same_v<IndexType, SourceItemId>,
                   "ItemToItemMatrix must be indexed using correct ItemId");
     using RowType = decltype(m_connectivity_matrix.rowConst(source_id));
     return SubItemList<RowType>(m_connectivity_matrix.rowConst(source_id));
diff --git a/src/mesh/ItemType.hpp b/src/mesh/ItemType.hpp
index 1b5f3cb452e898dc957ff7f96fb07c4955312f16..e9de6153098342eeaa0306e3754fd8b03e45644c 100644
--- a/src/mesh/ItemType.hpp
+++ b/src/mesh/ItemType.hpp
@@ -118,4 +118,8 @@ struct ItemTypeId<3>
   }
 };
 
+template <ItemType item_type>
+PASTIS_INLINE
+constexpr bool is_false_item_type_v = false;
+
 #endif // ITEM_TYPE_HPP
diff --git a/src/mesh/ItemValue.hpp b/src/mesh/ItemValue.hpp
index bfea6dd3dbc294a02bb3ca8d060100bf9098f8bc..827daa32d0e912dd43c13f4113f5ceb2566f034d 100644
--- a/src/mesh/ItemValue.hpp
+++ b/src/mesh/ItemValue.hpp
@@ -2,6 +2,7 @@
 #define ITEM_VALUE_HPP
 
 #include <PastisAssert.hpp>
+#include <PastisOStream.hpp>
 
 #include <Array.hpp>
 
@@ -9,101 +10,174 @@
 #include <ItemId.hpp>
 
 #include <IConnectivity.hpp>
+#include <memory>
 
 template <typename DataType,
-          ItemType item_type>
+          ItemType item_type,
+          typename ConnectivityPtr = std::shared_ptr<const IConnectivity>>
 class ItemValue
 {
  public:
-  static const ItemType item_t{item_type};
+  static constexpr ItemType item_t{item_type};
   using data_type = DataType;
 
   using ItemId = ItemIdT<item_type>;
   using index_type = ItemId;
 
  private:
-  bool m_is_built{false};
+  using ConnectivitySharedPtr = std::shared_ptr<const IConnectivity>;
+  using ConnectivityWeakPtr = std::weak_ptr<const IConnectivity>;
+
+  static_assert(std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr> or
+                std::is_same_v<ConnectivityPtr, ConnectivityWeakPtr>);
+
+  ConnectivityPtr m_connectivity_ptr;
 
   Array<DataType> m_values;
 
-  // Allows const version to access our data
-  friend ItemValue<std::add_const_t<DataType>,
-                   item_type>;
+  // Allow const std:shared_ptr version to access our data
+  friend ItemValue<std::add_const_t<DataType>, item_type,
+                   ConnectivitySharedPtr>;
+
+  // Allow const std:weak_ptr version to access our data
+  friend ItemValue<std::add_const_t<DataType>, item_type,
+                   ConnectivityWeakPtr>;
+
+  friend PASTIS_INLINE
+  ItemValue<std::remove_const_t<DataType>,item_type, ConnectivityPtr>
+  copy(const ItemValue<DataType, item_type, ConnectivityPtr>& source)
+  {
+    ItemValue<std::remove_const_t<DataType>, item_type, ConnectivityPtr> image(source);
+
+    image.m_values = copy(source.m_values);
+    return image;
+  }
 
  public:
-  PASTIS_FORCEINLINE
-  const bool& isBuilt() const
+  PASTIS_INLINE
+  bool isBuilt() const noexcept
+  {
+    return m_connectivity_ptr.use_count() != 0;
+  }
+
+  PASTIS_INLINE
+  std::shared_ptr<const IConnectivity> connectivity_ptr() const noexcept
   {
-    return m_is_built;
+    if constexpr (std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr>) {
+      return m_connectivity_ptr;
+    } else {
+      return m_connectivity_ptr.lock();
+    }
   }
 
   PASTIS_INLINE
-  size_t size() const
+  size_t size() const noexcept(NO_ASSERT)
   {
-    Assert(m_is_built);
+    Assert(this->isBuilt());
     return m_values.size();
   }
 
+  PASTIS_INLINE
+  void fill(const DataType& data) const noexcept
+  {
+    static_assert(not std::is_const_v<DataType>,
+                  "Cannot modify ItemValue of const");
+    m_values.fill(data);
+  }
+
   // Following Kokkos logic, these classes are view and const view does allow
   // changes in data
   PASTIS_FORCEINLINE
   DataType& operator[](const ItemId& i) const noexcept(NO_ASSERT)
   {
-    Assert(m_is_built);
+    Assert(this->isBuilt());
     return m_values[i];
   }
 
   template <typename IndexType>
-  DataType& operator[](const IndexType& i) const noexcept(NO_ASSERT)
+  DataType& operator[](const IndexType&) const noexcept(NO_ASSERT)
   {
-    static_assert(std::is_same<IndexType,ItemId>(),
+    static_assert(std::is_same_v<IndexType,ItemId>,
                   "ItemValue must be indexed by ItemId");
-    return m_values[i];
   }
 
   PASTIS_INLINE
-  size_t numberOfItems() const
+  size_t numberOfItems() const noexcept(NO_ASSERT)
   {
-    Assert(m_is_built);
+    Assert(this->isBuilt());
     return m_values.size();
   }
 
   template <typename DataType2>
   PASTIS_INLINE
   ItemValue&
-  operator=(const ItemValue<DataType2, item_type>& value_per_item)
+  operator=(const Array<DataType2>& values) noexcept(NO_ASSERT)
+  {
+    // ensures that DataType is the same as source DataType2
+    static_assert(std::is_same_v<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>,
+                  "Cannot assign ItemValue of different type");
+    // ensures that const is not lost through copy
+    static_assert(((std::is_const_v<DataType2> and std::is_const_v<DataType>)
+                   or not std::is_const_v<DataType2>),
+                  "Cannot assign ItemValue of const to ItemValue of non-const");
+
+    Assert((m_values.size() == values.size()),
+           "Cannot assign an array of values of a different size\n");
+
+    Assert ((values.size() == 0) or this->isBuilt(),
+            "Cannot assign array of values to a non-built ItemValue\n");
+
+    m_values = values;
+
+    return *this;
+  }
+
+  template <typename DataType2,
+            typename ConnectivityPtr2>
+  PASTIS_INLINE
+  ItemValue&
+  operator=(const ItemValue<DataType2, item_type, ConnectivityPtr2>& value_per_item) noexcept
   {
     // ensures that DataType is the same as source DataType2
-    static_assert(std::is_same<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>(),
+    static_assert(std::is_same_v<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>,
                   "Cannot assign ItemValue of different type");
     // ensures that const is not lost through copy
-    static_assert(((std::is_const<DataType2>() and std::is_const<DataType>())
-                   or not std::is_const<DataType2>()),
-                  "Cannot assign  ItemValue of const to ItemValue of non-const");
+    static_assert(((std::is_const_v<DataType2> and std::is_const_v<DataType>)
+                   or not std::is_const_v<DataType2>),
+                  "Cannot assign ItemValue of const to ItemValue of non-const");
 
     m_values = value_per_item.m_values;
-    m_is_built = value_per_item.m_is_built;
+    if constexpr (std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr> and
+                  std::is_same_v<ConnectivityPtr2, ConnectivityWeakPtr>) {
+      m_connectivity_ptr = value_per_item.m_connectivity_ptr.lock();
+    } else {
+      m_connectivity_ptr = value_per_item.m_connectivity_ptr;
+    }
 
     return *this;
   }
 
-  template <typename DataType2>
+  template <typename DataType2,
+            typename ConnectivityPtr2>
   PASTIS_INLINE
-  ItemValue(const ItemValue<DataType2, item_type>& value_per_item)
+  ItemValue(const ItemValue<DataType2, item_type, ConnectivityPtr2>& value_per_item) noexcept
   {
     this->operator=(value_per_item);
   }
 
+  PASTIS_INLINE
   ItemValue() = default;
 
-  ItemValue(const IConnectivity& connectivity)
-      : m_is_built{true},
-        m_values(connectivity.numberOf<item_type>())
+  PASTIS_INLINE
+  ItemValue(const IConnectivity& connectivity) noexcept
+      : m_connectivity_ptr{connectivity.shared_ptr()},
+        m_values{connectivity.numberOf<item_type>()}
   {
-    static_assert(not std::is_const<DataType>(),
+    static_assert(not std::is_const_v<DataType>,
                   "Cannot allocate ItemValue of const data: only view is supported"); ;
   }
 
+  PASTIS_INLINE
   ~ItemValue() = default;
 };
 
@@ -119,4 +193,22 @@ using FaceValue = ItemValue<DataType, ItemType::face>;
 template <typename DataType>
 using CellValue = ItemValue<DataType, ItemType::cell>;
 
+// Weak versions: should not be used outside of Connectivity
+
+template <typename DataType,
+          ItemType item_type>
+using WeakItemValue = ItemValue<DataType, item_type, std::weak_ptr<const IConnectivity>>;
+
+template <typename DataType>
+using WeakNodeValue = WeakItemValue<DataType, ItemType::node>;
+
+template <typename DataType>
+using WeakEdgeValue = WeakItemValue<DataType, ItemType::edge>;
+
+template <typename DataType>
+using WeakFaceValue = WeakItemValue<DataType, ItemType::face>;
+
+template <typename DataType>
+using WeakCellValue = WeakItemValue<DataType, ItemType::cell>;
+
 #endif // ITEM_VALUE_HPP
diff --git a/src/mesh/ItemValueUtils.hpp b/src/mesh/ItemValueUtils.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..6c45be4c283a69a729eb7d1cdda25480ba96d2d6
--- /dev/null
+++ b/src/mesh/ItemValueUtils.hpp
@@ -0,0 +1,311 @@
+#ifndef ITEM_VALUE_UTILS_HPP
+#define ITEM_VALUE_UTILS_HPP
+
+#include <Messenger.hpp>
+#include <ItemValue.hpp>
+
+#include <Connectivity.hpp>
+
+#include <SynchronizerManager.hpp>
+#include <Synchronizer.hpp>
+
+template <typename DataType,
+          ItemType item_type>
+std::remove_const_t<DataType>
+min(const ItemValue<DataType, item_type>& item_value)
+{
+  using ItemValueType = ItemValue<DataType, item_type>;
+  using data_type = std::remove_const_t<typename ItemValueType::data_type>;
+  using index_type = typename ItemValueType::index_type;
+
+  const auto& is_owned
+      = [&] (const IConnectivity& connectivity) {
+          Assert((connectivity.dimension()>0) and (connectivity.dimension()<=3),
+                 "unexpected connectivity dimension");
+
+          switch (connectivity.dimension()) {
+            case 1: {
+              const auto& connectivity_1d = static_cast<const Connectivity1D&>(connectivity);
+              return connectivity_1d.isOwned<item_type>();
+              break;
+            }
+            case 2: {
+              const auto& connectivity_2d = static_cast<const Connectivity2D&>(connectivity);
+              return connectivity_2d.isOwned<item_type>();
+              break;
+            }
+            case 3: {
+              const auto& connectivity_3d = static_cast<const Connectivity3D&>(connectivity);
+              return connectivity_3d.isOwned<item_type>();
+              break;
+            }
+            default: {
+              perr() << __FILE__ << ':' << __LINE__ << ": unexpected dimension\n";
+              std::terminate();
+            }
+          }
+        } (*item_value.connectivity_ptr());
+
+  using IsOwnedType = std::remove_reference_t<decltype(is_owned)>;
+
+  class ItemValueMin
+  {
+   private:
+    const ItemValueType& m_item_value;
+    const IsOwnedType& m_is_owned;
+
+   public:
+    PASTIS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_item_value.size(), *this, reduced_value);
+      return reduced_value;
+    }
+
+    PASTIS_INLINE
+    void operator()(const index_type& i, data_type& data) const
+    {
+      if ((m_is_owned[i]) and (m_item_value[i] < data)) {
+        data = m_item_value[i];
+      }
+    }
+
+    PASTIS_INLINE
+    void join(volatile data_type& dst,
+              const volatile data_type& src) const
+    {
+      if (src < dst) {
+        dst = src;
+      }
+    }
+
+    PASTIS_INLINE
+    void init(data_type& value) const
+    {
+      value = std::numeric_limits<data_type>::max();
+    }
+
+    PASTIS_INLINE
+    ItemValueMin(const ItemValueType& item_value,
+                 const IsOwnedType& is_owned)
+        : m_item_value(item_value),
+          m_is_owned(is_owned)
+    {
+      ;
+    }
+
+    PASTIS_INLINE
+    ~ItemValueMin() = default;
+  };
+
+  const DataType local_min = ItemValueMin{item_value, is_owned};
+  return parallel::allReduceMin(local_min);
+}
+
+template <typename DataType,
+          ItemType item_type>
+std::remove_const_t<DataType>
+max(const ItemValue<DataType, item_type>& item_value)
+{
+  using ItemValueType = ItemValue<DataType, item_type>;
+  using data_type = std::remove_const_t<typename ItemValueType::data_type>;
+  using index_type = typename ItemValueType::index_type;
+
+  const auto& is_owned
+      = [&] (const IConnectivity& connectivity) {
+          Assert((connectivity.dimension()>0) and (connectivity.dimension()<=3),
+                 "unexpected connectivity dimension");
+
+          switch (connectivity.dimension()) {
+            case 1: {
+              const auto& connectivity_1d = static_cast<const Connectivity1D&>(connectivity);
+              return connectivity_1d.isOwned<item_type>();
+              break;
+            }
+            case 2: {
+              const auto& connectivity_2d = static_cast<const Connectivity2D&>(connectivity);
+              return connectivity_2d.isOwned<item_type>();
+              break;
+            }
+            case 3: {
+              const auto& connectivity_3d = static_cast<const Connectivity3D&>(connectivity);
+              return connectivity_3d.isOwned<item_type>();
+              break;
+            }
+            default: {
+              perr() << __FILE__ << ':' << __LINE__ << ": unexpected dimension\n";
+              std::terminate();
+            }
+          }
+        } (*item_value.connectivity_ptr());
+
+  using IsOwnedType = std::remove_reference_t<decltype(is_owned)>;
+
+  class ItemValueMax
+  {
+   private:
+    const ItemValueType& m_item_value;
+    const IsOwnedType& m_is_owned;
+
+   public:
+    PASTIS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_item_value.size(), *this, reduced_value);
+      return reduced_value;
+    }
+
+    PASTIS_INLINE
+    void operator()(const index_type& i, data_type& data) const
+    {
+      if ((m_is_owned[i]) and (m_item_value[i] > data)) {
+        data = m_item_value[i];
+      }
+    }
+
+    PASTIS_INLINE
+    void join(volatile data_type& dst,
+              const volatile data_type& src) const
+    {
+      if (src > dst) {
+        dst = src;
+      }
+    }
+
+    PASTIS_INLINE
+    void init(data_type& value) const
+    {
+      value = std::numeric_limits<data_type>::min();
+    }
+
+    PASTIS_INLINE
+    ItemValueMax(const ItemValueType& item_value,
+                 const IsOwnedType& is_owned)
+        : m_item_value(item_value),
+          m_is_owned(is_owned)
+    {
+      ;
+    }
+
+    PASTIS_INLINE
+    ~ItemValueMax() = default;
+  };
+
+  const DataType local_max = ItemValueMax{item_value};
+  return parallel::allReduceMax(local_max);
+}
+
+
+template <typename DataType,
+          ItemType item_type>
+std::remove_const_t<DataType>
+sum(const ItemValue<DataType, item_type>& item_value)
+{
+  using ItemValueType = ItemValue<DataType, item_type>;
+  using data_type = std::remove_const_t<typename ItemValueType::data_type>;
+  using index_type = typename ItemValueType::index_type;
+
+  const auto& is_owned
+      = [&] (const IConnectivity& connectivity) {
+          Assert((connectivity.dimension()>0) and (connectivity.dimension()<=3),
+                 "unexpected connectivity dimension");
+
+          switch (connectivity.dimension()) {
+            case 1: {
+              const auto& connectivity_1d = static_cast<const Connectivity1D&>(connectivity);
+              return connectivity_1d.isOwned<item_type>();
+              break;
+            }
+            case 2: {
+              const auto& connectivity_2d = static_cast<const Connectivity2D&>(connectivity);
+              return connectivity_2d.isOwned<item_type>();
+              break;
+            }
+            case 3: {
+              const auto& connectivity_3d = static_cast<const Connectivity3D&>(connectivity);
+              return connectivity_3d.isOwned<item_type>();
+              break;
+            }
+            default: {
+              perr() << __FILE__ << ':' << __LINE__ << ": unexpected dimension\n";
+              std::terminate();
+            }
+          }
+        } (*item_value.connectivity_ptr());
+
+  using IsOwnedType = std::remove_reference_t<decltype(is_owned)>;
+
+  class ItemValueSum
+  {
+   private:
+    const ItemValueType& m_item_value;
+    const IsOwnedType& m_is_owned;
+
+   public:
+    PASTIS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_item_value.size(), *this, reduced_value);
+      return reduced_value;
+    }
+
+    PASTIS_INLINE
+    void operator()(const index_type& i, data_type& data) const
+    {
+      if (m_is_owned[i]) {
+        data += m_item_value[i];
+      }
+    }
+
+    PASTIS_INLINE
+    void join(volatile data_type& dst,
+              const volatile data_type& src) const
+    {
+      dst += src;
+    }
+
+    PASTIS_INLINE
+    void init(data_type& value) const
+    {
+      if constexpr (std::is_arithmetic_v<data_type>) {
+        value = 0;
+      } else {
+        value = zero;
+      }
+    }
+
+    PASTIS_INLINE
+    ItemValueSum(const ItemValueType& item_value,
+                 const IsOwnedType& is_owned)
+        : m_item_value(item_value),
+          m_is_owned(is_owned)
+    {
+      ;
+    }
+
+    PASTIS_INLINE
+    ~ItemValueSum() = default;
+  };
+
+  const DataType local_sum = ItemValueSum{item_value, is_owned};
+  return parallel::allReduceSum(local_sum);
+}
+
+template <typename DataType,
+          ItemType item_type,
+          typename ConnectivityPtr>
+void synchronize(ItemValue<DataType, item_type, ConnectivityPtr>& item_value)
+{
+  static_assert(not std::is_const_v<DataType>, "cannot synchronize ItemValue of const data");
+  if (parallel::size() > 1) {
+    auto& manager = SynchronizerManager::instance();
+    const IConnectivity* connectivity = item_value.connectivity_ptr().get();
+    Synchronizer& synchronizer = manager.getConnectivitySynchronizer(connectivity);
+    synchronizer.synchronize(item_value);
+  }
+}
+
+#endif // ITEM_VALUE_UTILS_HPP
diff --git a/src/mesh/Mesh.hpp b/src/mesh/Mesh.hpp
index 1c5924a12bdead13b68f773a16a8537a42234555..b103e22946019e8ff2c50a056e8c86a75fbf4c05 100644
--- a/src/mesh/Mesh.hpp
+++ b/src/mesh/Mesh.hpp
@@ -4,11 +4,13 @@
 #include <ItemValue.hpp>
 #include <TinyVector.hpp>
 
+#include <CSRGraph.hpp>
+
 #include <memory>
 
 struct IMesh
 {
-  virtual size_t meshDimension() const = 0;
+  virtual size_t dimension() const = 0;
   ~IMesh() = default;
 };
 
@@ -18,19 +20,19 @@ class Mesh final : public IMesh
 public:
   using Connectivity = ConnectivityType;
 
-  static constexpr size_t dimension = ConnectivityType::dimension;
-  using Rd = TinyVector<dimension>;
+  static constexpr size_t Dimension = ConnectivityType::Dimension;
+  using Rd = TinyVector<Dimension>;
 
 private:
-  const std::shared_ptr<Connectivity> m_connectivity;
+  const std::shared_ptr<const Connectivity> m_connectivity;
   NodeValue<const Rd> m_xr;
   NodeValue<Rd> m_mutable_xr;
 
 public:
   PASTIS_INLINE
-  size_t meshDimension() const
+  size_t dimension() const
   {
-    return dimension;
+    return Dimension;
   }
 
   PASTIS_INLINE
@@ -63,7 +65,6 @@ public:
     return m_xr;
   }
 
-  [[deprecated("should rework this class: quite ugly")]]
   PASTIS_INLINE
   NodeValue<Rd> mutableXr() const
   {
@@ -71,7 +72,7 @@ public:
   }
 
   PASTIS_INLINE
-  Mesh(const std::shared_ptr<Connectivity>& connectivity,
+  Mesh(const std::shared_ptr<const Connectivity>& connectivity,
        NodeValue<Rd>& xr)
       : m_connectivity{connectivity},
         m_xr{xr},
diff --git a/src/mesh/MeshData.hpp b/src/mesh/MeshData.hpp
index f9a2aeb575ac5265073d47ce2c4025da39db7d51..3d5cc9b753ae4835e78f70b729b831a1cffc06ba 100644
--- a/src/mesh/MeshData.hpp
+++ b/src/mesh/MeshData.hpp
@@ -15,12 +15,12 @@ class MeshData
  public:
   using  MeshType = M;
 
-  static constexpr size_t dimension = MeshType::dimension;
-  static_assert(dimension>0, "dimension must be strictly positive");
+  static constexpr size_t Dimension = MeshType::Dimension;
+  static_assert(Dimension>0, "dimension must be strictly positive");
 
-  using Rd = TinyVector<dimension>;
+  using Rd = TinyVector<Dimension>;
 
-  static constexpr double inv_dimension = 1./dimension;
+  static constexpr double inv_Dimension = 1./Dimension;
 
  private:
   const MeshType& m_mesh;
@@ -33,7 +33,7 @@ class MeshData
   PASTIS_INLINE
   void _updateCenter()
   { // Computes vertices isobarycenter
-    if constexpr (dimension == 1) {
+    if constexpr (Dimension == 1) {
       const NodeValue<const Rd>& xr = m_mesh.xr();
 
       const auto& cell_to_node_matrix
@@ -81,17 +81,17 @@ class MeshData
         for (size_t R=0; R<cell_nodes.size(); ++R) {
           sum_cjr_xr += (xr[cell_nodes[R]], m_Cjr(j,R));
         }
-        Vj[j] = inv_dimension * sum_cjr_xr;
+        Vj[j] = inv_Dimension * sum_cjr_xr;
       });
     m_Vj = Vj;
   }
 
   PASTIS_INLINE
   void _updateCjr() {
-    if constexpr (dimension == 1) {
+    if constexpr (Dimension == 1) {
       // Cjr/njr/ljr are constant overtime
     }
-    else if constexpr (dimension == 2) {
+    else if constexpr (Dimension == 2) {
       const NodeValue<const Rd>& xr = m_mesh.xr();
       const auto& cell_to_node_matrix
           = m_mesh.connectivity().cellToNodeMatrix();
@@ -125,7 +125,7 @@ class MeshData
           });
         m_njr = njr;
       }
-    } else if (dimension ==3) {
+    } else if (Dimension ==3) {
       const NodeValue<const Rd>& xr = m_mesh.xr();
 
       NodeValuePerFace<Rd> Nlr(m_mesh.connectivity());
@@ -178,13 +178,11 @@ class MeshData
               const FaceId& l = cell_faces[L];
               const auto& face_nodes = face_to_node_matrix[l];
 
-#warning should this lambda be replaced by a precomputed correspondance?
               auto local_node_number_in_cell
                   = [&](const NodeId& node_number) {
                       for (size_t i_node=0; i_node<cell_nodes.size(); ++i_node) {
                         if (node_number == cell_nodes[i_node]) {
                           return i_node;
-                          break;
                         }
                       }
                       return std::numeric_limits<size_t>::max();
@@ -223,35 +221,41 @@ class MeshData
         m_njr = njr;
       }
     }
-    static_assert((dimension<=3), "only 1d, 2d and 3d are implemented");
+    static_assert((Dimension<=3), "only 1d, 2d and 3d are implemented");
   }
 
  public:
+  PASTIS_INLINE
   const MeshType& mesh() const
   {
     return m_mesh;
   }
 
+  PASTIS_INLINE
   const NodeValuePerCell<const Rd>& Cjr() const
   {
     return m_Cjr;
   }
 
+  PASTIS_INLINE
   const NodeValuePerCell<const double>& ljr() const
   {
     return m_ljr;
   }
 
+  PASTIS_INLINE
   const NodeValuePerCell<const Rd>& njr() const
   {
     return m_njr;
   }
 
+  PASTIS_INLINE
   const CellValue<const Rd>& xj() const
   {
     return m_xj;
   }
 
+  PASTIS_INLINE
   const CellValue<const double>& Vj() const
   {
     return m_Vj;
@@ -267,7 +271,7 @@ class MeshData
   MeshData(const MeshType& mesh)
       : m_mesh(mesh)
   {
-    if constexpr (dimension==1) {
+    if constexpr (Dimension==1) {
       // in 1d Cjr are computed once for all
       {
         NodeValuePerCell<Rd> Cjr(m_mesh.connectivity());
diff --git a/src/mesh/MeshNodeBoundary.hpp b/src/mesh/MeshNodeBoundary.hpp
index a52a91c99d61c028fdc9f634c896d0ce06aa15dd..ff8ef4a920d0bc2a767b1a0d677c2157a1fe8a9a 100644
--- a/src/mesh/MeshNodeBoundary.hpp
+++ b/src/mesh/MeshNodeBoundary.hpp
@@ -7,14 +7,15 @@
 #include <Kokkos_Vector.hpp>
 #include <TinyVector.hpp>
 
-#include <RefNodeList.hpp>
-#include <RefFaceList.hpp>
+#include <RefItemList.hpp>
 
 #include <ConnectivityMatrix.hpp>
 #include <IConnectivity.hpp>
 #include <iostream>
 
-template <size_t dimension>
+#include <Messenger.hpp>
+
+template <size_t Dimension>
 class MeshNodeBoundary
 {
  protected:
@@ -32,11 +33,11 @@ class MeshNodeBoundary
   MeshNodeBoundary(const MeshType& mesh,
                    const RefFaceList& ref_face_list)
   {
-    static_assert(dimension == MeshType::dimension);
+    static_assert(Dimension == MeshType::Dimension);
     const auto& face_to_cell_matrix
         = mesh.connectivity().faceToCellMatrix();
 
-    const Array<const FaceId>& face_list = ref_face_list.faceList();
+    const Array<const FaceId>& face_list = ref_face_list.list();
     parallel_for(face_list.size(), PASTIS_LAMBDA(const int& l){
         const auto& face_cells = face_to_cell_matrix[face_list[l]];
         if (face_cells.size()>1) {
@@ -47,7 +48,7 @@ class MeshNodeBoundary
 
     Kokkos::vector<unsigned int> node_ids;
     // not enough but should reduce significantly the number of resizing
-    node_ids.reserve(dimension*face_list.size());
+    node_ids.reserve(Dimension*face_list.size());
     const auto& face_to_node_matrix
         = mesh.connectivity().faceToNodeMatrix();
 
@@ -72,9 +73,9 @@ class MeshNodeBoundary
 
   template <typename MeshType>
   MeshNodeBoundary(const MeshType&, const RefNodeList& ref_node_list)
-      : m_node_list(ref_node_list.nodeList())
+      : m_node_list(ref_node_list.list())
   {
-    static_assert(dimension == MeshType::dimension);
+    static_assert(Dimension == MeshType::Dimension);
   }
 
   MeshNodeBoundary() = default;
@@ -84,12 +85,12 @@ class MeshNodeBoundary
 };
 
 
-template <size_t dimension>
+template <size_t Dimension>
 class MeshFlatNodeBoundary
-    : public MeshNodeBoundary<dimension>
+    : public MeshNodeBoundary<Dimension>
 {
  public:
-  using Rd = TinyVector<dimension, double>;
+  using Rd = TinyVector<Dimension, double>;
 
  private:
   const Rd m_outgoing_normal;
@@ -120,7 +121,7 @@ class MeshFlatNodeBoundary
   template <typename MeshType>
   MeshFlatNodeBoundary(const MeshType& mesh,
                        const RefFaceList& ref_face_list)
-      : MeshNodeBoundary<dimension>(mesh, ref_face_list),
+      : MeshNodeBoundary<Dimension>(mesh, ref_face_list),
         m_outgoing_normal(_getOutgoingNormal(mesh))
   {
     ;
@@ -129,7 +130,7 @@ class MeshFlatNodeBoundary
   template <typename MeshType>
   MeshFlatNodeBoundary(const MeshType& mesh,
                        const RefNodeList& ref_node_list)
-      : MeshNodeBoundary<dimension>(mesh, ref_node_list),
+      : MeshNodeBoundary<Dimension>(mesh, ref_node_list),
         m_outgoing_normal(_getOutgoingNormal(mesh))
   {
     ;
@@ -149,7 +150,7 @@ _checkBoundaryIsFlat(const TinyVector<2,double>& normal,
                      const TinyVector<2,double>& xmax,
                      const MeshType& mesh) const
 {
-  static_assert(MeshType::dimension == 2);
+  static_assert(MeshType::Dimension == 2);
   using R2 = TinyVector<2,double>;
 
   const R2 origin = 0.5*(xmin+xmax);
@@ -173,7 +174,7 @@ TinyVector<1,double>
 MeshFlatNodeBoundary<1>::
 _getNormal(const MeshType&)
 {
-  static_assert(MeshType::dimension == 1);
+  static_assert(MeshType::Dimension == 1);
   using R = TinyVector<1,double>;
 
   if (m_node_list.size() != 1) {
@@ -181,7 +182,7 @@ _getNormal(const MeshType&)
     std::exit(1);
   }
 
-  return R(1);
+  return R{1};
 }
 
 template <>
@@ -191,7 +192,7 @@ TinyVector<2,double>
 MeshFlatNodeBoundary<2>::
 _getNormal(const MeshType& mesh)
 {
-  static_assert(MeshType::dimension == 2);
+  static_assert(MeshType::Dimension == 2);
   using R2 = TinyVector<2,double>;
 
   const NodeValue<const R2>& xr = mesh.xr();
@@ -212,6 +213,21 @@ _getNormal(const MeshType& mesh)
     }
   }
 
+  Array<R2> xmin_array = parallel::allGather(xmin);
+  Array<R2> xmax_array = parallel::allGather(xmax);
+  for (size_t i=0; i<xmin_array.size(); ++i) {
+    const R2& x = xmin_array[i];
+    if ((x[0]<xmin[0]) or ((x[0] == xmin[0]) and (x[1] < xmin[1]))) {
+      xmin = x;
+    }
+  }
+  for (size_t i=0; i<xmax_array.size(); ++i) {
+    const R2& x = xmax_array[i];
+    if ((x[0]>xmax[0]) or ((x[0] == xmax[0]) and (x[1] > xmax[1]))) {
+      xmax = x;
+    }
+  }
+
   if (xmin == xmax) {
     perr() << "xmin==xmax (" << xmin << "==" << xmax << ") unable to compute normal";
     std::exit(1);
@@ -234,7 +250,7 @@ TinyVector<3,double>
 MeshFlatNodeBoundary<3>::
 _getNormal(const MeshType& mesh)
 {
-  static_assert(MeshType::dimension == 3);
+  static_assert(MeshType::Dimension == 3);
   using R3 = TinyVector<3,double>;
 
 
@@ -271,6 +287,37 @@ _getNormal(const MeshType& mesh)
       zmax = x;
     }
   }
+  Array<R3> xmin_array = parallel::allGather(xmin);
+  Array<R3> xmax_array = parallel::allGather(xmax);
+  Array<R3> ymin_array = parallel::allGather(ymin);
+  Array<R3> ymax_array = parallel::allGather(ymax);
+  Array<R3> zmin_array = parallel::allGather(zmin);
+  Array<R3> zmax_array = parallel::allGather(zmax);
+
+  for (size_t i=0; i<xmin_array.size(); ++i) {
+    const R3& x = xmin_array[i];
+    if (x[0] < xmin[0]) { xmin = x; }
+  }
+  for (size_t i=0; i<ymin_array.size(); ++i) {
+    const R3& x = ymin_array[i];
+    if (x[1] < ymin[1]) { ymin = x; }
+  }
+  for (size_t i=0; i<zmin_array.size(); ++i) {
+    const R3& x = zmin_array[i];
+    if (x[2] < zmin[2]) { zmin = x; }
+  }
+  for (size_t i=0; i<xmax_array.size(); ++i) {
+    const R3& x = xmax_array[i];
+    if (x[0] > xmax[0]) { xmax = x; }
+  }
+  for (size_t i=0; i<ymax_array.size(); ++i) {
+    const R3& x = ymax_array[i];
+    if (x[1] > ymax[1]) { ymax = x; }
+  }
+  for (size_t i=0; i<zmax_array.size(); ++i) {
+    const R3& x = zmax_array[i];
+    if (x[2] > zmax[2]) { zmax = x; }
+  }
 
   const R3 u = xmax-xmin;
   const R3 v = ymax-ymin;
@@ -306,7 +353,6 @@ _getNormal(const MeshType& mesh)
 
   normal *= 1./sqrt(normal_l2);
 
-#warning Add flatness test
   // this->_checkBoundaryIsFlat(normal, xmin, xmax, mesh);
 
   return normal;
@@ -319,28 +365,41 @@ TinyVector<1,double>
 MeshFlatNodeBoundary<1>::
 _getOutgoingNormal(const MeshType& mesh)
 {
-  static_assert(MeshType::dimension == 1);
+  static_assert(MeshType::Dimension == 1);
   using R = TinyVector<1,double>;
 
   const R normal = this->_getNormal(mesh);
 
-  const NodeValue<const R>& xr = mesh.xr();
-  const auto& cell_to_node_matrix
-      = mesh.connectivity().cellToNodeMatrix();
+  double max_height = 0;
 
-  const auto& node_to_cell_matrix
-      = mesh.connectivity().nodeToCellMatrix();
+  if (m_node_list.size()>0) {
+    const NodeValue<const R>& xr = mesh.xr();
+    const auto& cell_to_node_matrix
+        = mesh.connectivity().cellToNodeMatrix();
 
-  const NodeId r0 = m_node_list[0];
-  const CellId j0 = node_to_cell_matrix[r0][0];
-  const auto& j0_nodes = cell_to_node_matrix[j0];
-  double max_height = 0;
-  for (size_t r=0; r<j0_nodes.size(); ++r) {
-    const double height = (xr[j0_nodes[r]]-xr[r0], normal);
+    const auto& node_to_cell_matrix
+        = mesh.connectivity().nodeToCellMatrix();
+
+    const NodeId r0 = m_node_list[0];
+    const CellId j0 = node_to_cell_matrix[r0][0];
+    const auto& j0_nodes = cell_to_node_matrix[j0];
+
+    for (size_t r=0; r<j0_nodes.size(); ++r) {
+      const double height = (xr[j0_nodes[r]]-xr[r0], normal);
+      if (std::abs(height) > std::abs(max_height)) {
+        max_height = height;
+      }
+    }
+  }
+
+  Array<double> max_height_array = parallel::allGather(max_height);
+  for (size_t i=0; i<max_height_array.size(); ++i) {
+    const double height = max_height_array[i];
     if (std::abs(height) > std::abs(max_height)) {
       max_height = height;
     }
   }
+
   if (max_height > 0) {
     return -normal;
   } else {
@@ -355,28 +414,40 @@ TinyVector<2,double>
 MeshFlatNodeBoundary<2>::
 _getOutgoingNormal(const MeshType& mesh)
 {
-  static_assert(MeshType::dimension == 2);
+  static_assert(MeshType::Dimension == 2);
   using R2 = TinyVector<2,double>;
 
   const R2 normal = this->_getNormal(mesh);
 
-  const NodeValue<const R2>& xr = mesh.xr();
-  const auto& cell_to_node_matrix
-      = mesh.connectivity().cellToNodeMatrix();
+  double max_height = 0;
 
-  const auto& node_to_cell_matrix
-      = mesh.connectivity().nodeToCellMatrix();
+  if (m_node_list.size()>0) {
+    const NodeValue<const R2>& xr = mesh.xr();
+    const auto& cell_to_node_matrix
+        = mesh.connectivity().cellToNodeMatrix();
+
+    const auto& node_to_cell_matrix
+        = mesh.connectivity().nodeToCellMatrix();
+
+    const NodeId r0 = m_node_list[0];
+    const CellId j0 = node_to_cell_matrix[r0][0];
+    const auto& j0_nodes = cell_to_node_matrix[j0];
+    for (size_t r=0; r<j0_nodes.size(); ++r) {
+      const double height = (xr[j0_nodes[r]]-xr[r0], normal);
+      if (std::abs(height) > std::abs(max_height)) {
+        max_height = height;
+      }
+    }
+  }
 
-  const NodeId r0 = m_node_list[0];
-  const CellId j0 = node_to_cell_matrix[r0][0];
-  const auto& j0_nodes = cell_to_node_matrix[j0];
-  double max_height = 0;
-  for (size_t r=0; r<j0_nodes.size(); ++r) {
-    const double height = (xr[j0_nodes[r]]-xr[r0], normal);
+  Array<double> max_height_array = parallel::allGather(max_height);
+  for (size_t i=0; i<max_height_array.size(); ++i) {
+    const double height = max_height_array[i];
     if (std::abs(height) > std::abs(max_height)) {
       max_height = height;
     }
   }
+
   if (max_height > 0) {
     return -normal;
   } else {
@@ -391,28 +462,42 @@ TinyVector<3,double>
 MeshFlatNodeBoundary<3>::
 _getOutgoingNormal(const MeshType& mesh)
 {
-  static_assert(MeshType::dimension == 3);
+  static_assert(MeshType::Dimension == 3);
   using R3 = TinyVector<3,double>;
 
   const R3 normal = this->_getNormal(mesh);
 
-  const NodeValue<const R3>& xr = mesh.xr();
-  const auto& cell_to_node_matrix
-      = mesh.connectivity().cellToNodeMatrix();
+  double max_height = 0;
 
-  const auto& node_to_cell_matrix
-      = mesh.connectivity().nodeToCellMatrix();
+  if (m_node_list.size()>0) {
+    const NodeValue<const R3>& xr = mesh.xr();
+    const auto& cell_to_node_matrix
+        = mesh.connectivity().cellToNodeMatrix();
 
-  const NodeId r0 = m_node_list[0];
-  const CellId j0 = node_to_cell_matrix[r0][0];
-  const auto& j0_nodes = cell_to_node_matrix[j0];
-  double max_height = 0;
-  for (size_t r=0; r<j0_nodes.size(); ++r) {
-    const double height = (xr[j0_nodes[r]]-xr[r0], normal);
+    const auto& node_to_cell_matrix
+        = mesh.connectivity().nodeToCellMatrix();
+
+    const NodeId r0 = m_node_list[0];
+    const CellId j0 = node_to_cell_matrix[r0][0];
+    const auto& j0_nodes = cell_to_node_matrix[j0];
+
+    for (size_t r=0; r<j0_nodes.size(); ++r) {
+      const double height = (xr[j0_nodes[r]]-xr[r0], normal);
+      if (std::abs(height) > std::abs(max_height)) {
+        max_height = height;
+      }
+    }
+
+  }
+
+  Array<double> max_height_array = parallel::allGather(max_height);
+  for (size_t i=0; i<max_height_array.size(); ++i) {
+    const double height = max_height_array[i];
     if (std::abs(height) > std::abs(max_height)) {
       max_height = height;
     }
   }
+
   if (max_height > 0) {
     return -normal;
   } else {
diff --git a/src/mesh/RefFaceList.hpp b/src/mesh/RefFaceList.hpp
deleted file mode 100644
index ab66943936c091735c404b287d587a15d81a1a9f..0000000000000000000000000000000000000000
--- a/src/mesh/RefFaceList.hpp
+++ /dev/null
@@ -1,40 +0,0 @@
-#ifndef REF_FACE_LIST_HPP
-#define REF_FACE_LIST_HPP
-
-#include <Array.hpp>
-#include <RefId.hpp>
-
-class RefFaceList
-{
- private:
-  const RefId m_ref_id;
-  const Array<const FaceId> m_face_id_list;
-
- public:
-  const RefId& refId() const
-  {
-    return m_ref_id;
-  }
-
-  const Array<const FaceId>& faceList() const
-  {
-    return m_face_id_list;
-  }
-
-  RefFaceList(const RefId& ref_id,
-              const Array<const FaceId>& face_id_list)
-      : m_ref_id(ref_id),
-        m_face_id_list(face_id_list)
-  {
-    ;
-  }
-
-  RefFaceList& operator=(const RefFaceList&) = default;
-  RefFaceList& operator=(RefFaceList&&) = default;
-
-  RefFaceList() = default;
-  RefFaceList(const RefFaceList&) = default;
-  ~RefFaceList() = default;
-};
-
-#endif // REF_FACE_LIST_HPP
diff --git a/src/mesh/RefId.hpp b/src/mesh/RefId.hpp
index 775b1a7132d822c52dc6b48fb6bbc528216d58cb..dbd4f1a1b113b885b1040fdb3a0372f881e56a0c 100644
--- a/src/mesh/RefId.hpp
+++ b/src/mesh/RefId.hpp
@@ -6,14 +6,18 @@
 
 class RefId
 {
+ public:
+  using TagNumberType = unsigned int;
+  using TagNameType = std::string;
+
  private:
-  unsigned int m_tag_number;
-  std::string m_tag_name;
+  TagNumberType m_tag_number;
+  TagNameType m_tag_name;
 
  public:
   friend std::ostream& operator<<(std::ostream& os, const RefId& ref_id)
   {
-    if (ref_id.m_tag_name.size()>0) {
+    if (std::to_string(ref_id.m_tag_number) != ref_id.m_tag_name) {
       os << ref_id.m_tag_name << '(' << ref_id.m_tag_number << ')';
     } else {
       os << ref_id.m_tag_number;
@@ -34,12 +38,12 @@ class RefId
              (m_tag_name<ref_id.m_tag_name)));
   }
 
-  const unsigned int& tagNumber() const
+  TagNumberType tagNumber() const
   {
     return m_tag_number;
   }
 
-  const std::string& tagName() const
+  const TagNameType& tagName() const
   {
     return m_tag_name;
   }
@@ -49,16 +53,18 @@ class RefId
   RefId() = default;
   RefId(const RefId&) = default;
   RefId(RefId&&) = default;
-  explicit RefId(const unsigned int& tag_number,
-                 const std::string& tag_name)
+
+  explicit RefId(const TagNumberType& tag_number,
+                 const TagNameType& tag_name)
       : m_tag_number(tag_number),
         m_tag_name(tag_name)
   {
     ;
   }
 
-  explicit RefId(const unsigned int& tag_number)
-      : m_tag_number(tag_number)
+  explicit RefId(const TagNumberType& tag_number)
+      : m_tag_number(tag_number),
+        m_tag_name(std::to_string(tag_number))
   {
     ;
   }
diff --git a/src/mesh/RefItemList.hpp b/src/mesh/RefItemList.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..dea69f7ec79e2ca139eeb9f01600e687f6bea3d4
--- /dev/null
+++ b/src/mesh/RefItemList.hpp
@@ -0,0 +1,50 @@
+#ifndef REF_ITEM_LIST_HPP
+#define REF_ITEM_LIST_HPP
+
+#include <Array.hpp>
+#include <RefId.hpp>
+#include <ItemId.hpp>
+
+template <ItemType item_type>
+class RefItemList
+{
+ public:
+  using ItemId = ItemIdT<item_type>;
+
+ private:
+  RefId m_ref_id;
+  Array<const ItemId> m_item_id_list;
+
+ public:
+  const RefId& refId() const
+  {
+    return m_ref_id;
+  }
+
+  const Array<const ItemId>& list() const
+  {
+    return m_item_id_list;
+  }
+
+  RefItemList(const RefId& ref_id,
+              const Array<const ItemId>& item_id_list)
+      : m_ref_id(ref_id),
+        m_item_id_list(item_id_list)
+  {
+    ;
+  }
+
+  RefItemList& operator=(const RefItemList&) = default;
+  RefItemList& operator=(RefItemList&&) = default;
+
+  RefItemList() = default;
+  RefItemList(const RefItemList&) = default;
+  ~RefItemList() = default;
+};
+
+using RefNodeList = RefItemList<ItemType::node>;
+using RefEdgeList = RefItemList<ItemType::edge>;
+using RefFaceList = RefItemList<ItemType::face>;
+using RefCellList = RefItemList<ItemType::cell>;
+
+#endif // REF_ITEM_LIST_HPP
diff --git a/src/mesh/RefNodeList.hpp b/src/mesh/RefNodeList.hpp
deleted file mode 100644
index 462d23bb3d35a6384fcd5176d0584419a586abc3..0000000000000000000000000000000000000000
--- a/src/mesh/RefNodeList.hpp
+++ /dev/null
@@ -1,40 +0,0 @@
-#ifndef REF_NODE_LIST_HPP
-#define REF_NODE_LIST_HPP
-
-#include <Array.hpp>
-#include <RefId.hpp>
-
-class RefNodeList
-{
- private:
-  RefId m_ref_id;
-  Array<const NodeId> m_node_id_list;
-
- public:
-  const RefId& refId() const
-  {
-    return m_ref_id;
-  }
-
-  const Array<const NodeId>& nodeList() const
-  {
-    return m_node_id_list;
-  }
-
-  RefNodeList(const RefId& ref_id,
-              const Array<const NodeId>& node_id_list)
-      : m_ref_id(ref_id),
-        m_node_id_list(node_id_list)
-  {
-    ;
-  }
-
-  RefNodeList& operator=(const RefNodeList&) = default;
-  RefNodeList& operator=(RefNodeList&&) = default;
-
-  RefNodeList() = default;
-  RefNodeList(const RefNodeList&) = default;
-  ~RefNodeList() = default;
-};
-
-#endif // REF_NODE_LIST_HPP
diff --git a/src/mesh/SubItemValuePerItem.hpp b/src/mesh/SubItemValuePerItem.hpp
index edb73dd5cb216c1b5bf9df339204447cd062cb17..450554edfc2fc0d3729f9300bb0d5dc516f91f72 100644
--- a/src/mesh/SubItemValuePerItem.hpp
+++ b/src/mesh/SubItemValuePerItem.hpp
@@ -6,47 +6,58 @@
 #include <PastisAssert.hpp>
 
 #include <Array.hpp>
-#include <ItemType.hpp>
 
 #include <ItemId.hpp>
 
 #include <ConnectivityMatrix.hpp>
 #include <IConnectivity.hpp>
 
-template <typename DataType,
-          ItemType sub_item_type,
-          ItemType item_type,
-          typename Allowed=void>
-class SubItemValuePerItem;
+#include <ItemType.hpp>
+#include <ItemOfItemType.hpp>
+
+#include <memory>
 
 template <typename DataType,
-          ItemType sub_item_type,
-          ItemType item_type>
-class SubItemValuePerItem<DataType,
-                          sub_item_type,
-                          item_type,
-                          std::enable_if_t<sub_item_type != item_type>>
+          typename ItemOfItem,
+          typename ConnectivityPtr = std::shared_ptr<const IConnectivity>>
+class SubItemValuePerItem
 {
  public:
-  static const ItemType item_t{item_type};
-  static const ItemType sub_item_t{sub_item_type};
+  static constexpr ItemType item_type{ItemOfItem::item_type};
+  static constexpr ItemType sub_item_type{ItemOfItem::sub_item_type};
 
+  using ItemOfItemType = ItemOfItem;
   using data_type = DataType;
   using ItemId = ItemIdT<item_type>;
   using index_type = ItemId;
 
  private:
-  bool m_is_built{false};
+  using ConnectivitySharedPtr = std::shared_ptr<const IConnectivity>;
+  using ConnectivityWeakPtr = std::weak_ptr<const IConnectivity>;
+
+  static_assert(std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr> or
+                std::is_same_v<ConnectivityPtr, ConnectivityWeakPtr>);
+
+  ConnectivityPtr m_connectivity_ptr;
 
   typename ConnectivityMatrix::HostRowType m_host_row_map;
   Array<DataType> m_values;
 
-  // Allows const version to access our data
+  // Allow const std:shared_ptr version to access our data
   friend SubItemValuePerItem<std::add_const_t<DataType>,
-                             sub_item_type,
-                             item_type>;
+                             ItemOfItem,
+                             ConnectivitySharedPtr>;
+
+  // Allow const std:weak_ptr version to access our data
+  friend SubItemValuePerItem<std::add_const_t<DataType>,
+                             ItemOfItem,
+                             ConnectivityWeakPtr>;
 
  public:
+  using ToShared = SubItemValuePerItem<DataType,
+                                       ItemOfItem,
+                                       ConnectivitySharedPtr>;
+
   class SubView
   {
    public:
@@ -72,7 +83,7 @@ class SubItemValuePerItem<DataType,
     }
 
     PASTIS_INLINE
-    const size_t& size() const
+    size_t size() const noexcept
     {
       return m_size;
     }
@@ -80,7 +91,7 @@ class SubItemValuePerItem<DataType,
     SubView(const SubView&) = delete;
 
     PASTIS_INLINE
-    SubView(SubView&&) = default;
+    SubView(SubView&&) noexcept = default;
 
     PASTIS_INLINE
     SubView(const Array<DataType>& values,
@@ -94,10 +105,10 @@ class SubItemValuePerItem<DataType,
     }
   };
 
-  PASTIS_FORCEINLINE
-  const bool& isBuilt() const
+  PASTIS_INLINE
+  bool isBuilt() const noexcept
   {
-    return m_is_built;
+    return m_connectivity_ptr.use_count() != 0;
   }
 
   // Following Kokkos logic, these classes are view and const view does allow
@@ -105,7 +116,7 @@ class SubItemValuePerItem<DataType,
   PASTIS_FORCEINLINE
   DataType& operator()(const ItemId& j, const size_t& r) const noexcept(NO_ASSERT)
   {
-    Assert(m_is_built);
+    Assert(this->isBuilt());
     return m_values[m_host_row_map(size_t{j})+r];
   }
 
@@ -113,15 +124,16 @@ class SubItemValuePerItem<DataType,
   PASTIS_FORCEINLINE
   DataType& operator()(const IndexType& j, const size_t& r) const noexcept(NO_ASSERT)
   {
-    static_assert(std::is_same<IndexType, size_t>(),
+    static_assert(std::is_same_v<IndexType, ItemId>,
                   "SubItemValuePerItem indexed by ItemId");
+    Assert(this->isBuilt());
     return m_values[m_host_row_map(size_t{j})+r];
   }
 
   PASTIS_INLINE
   size_t numberOfValues() const noexcept(NO_ASSERT)
   {
-    Assert(m_is_built);
+    Assert(this->isBuilt());
     return m_values.size();
   }
 
@@ -130,22 +142,23 @@ class SubItemValuePerItem<DataType,
   PASTIS_FORCEINLINE
   DataType& operator[](const size_t& i) const noexcept(NO_ASSERT)
   {
-    Assert(m_is_built);
+    Assert(this->isBuilt());
     return m_values[i];
   }
 
   template <typename IndexType>
   DataType& operator[](const IndexType& i) const noexcept(NO_ASSERT)
   {
-    static_assert(std::is_same<IndexType, size_t>(),
+    static_assert(std::is_same_v<IndexType, size_t>,
                   "Access to SubItemValuePerItem's array must be indexed by size_t");
+    Assert(this->isBuilt());
     return m_values[i];
   }
 
   PASTIS_INLINE
   size_t numberOfItems() const noexcept(NO_ASSERT)
   {
-    Assert(m_is_built);
+    Assert(this->isBuilt());
     Assert(m_host_row_map.extent(0) != 0);
     return m_host_row_map.extent(0);
   }
@@ -153,14 +166,14 @@ class SubItemValuePerItem<DataType,
   PASTIS_INLINE
   size_t numberOfSubValues(const size_t& i_cell) const noexcept(NO_ASSERT)
   {
-    Assert(m_is_built);
+    Assert(this->isBuilt());
     return m_host_row_map(i_cell+1)-m_host_row_map(i_cell);
   }
 
   PASTIS_INLINE
   SubView itemValues(const size_t& i_cell) noexcept(NO_ASSERT)
   {
-    Assert(m_is_built);
+    Assert(this->isBuilt());
     const auto& cell_begin = m_host_row_map(i_cell);
     const auto& cell_end = m_host_row_map(i_cell+1);
     return SubView(m_values, cell_begin, cell_end);
@@ -171,45 +184,52 @@ class SubItemValuePerItem<DataType,
   PASTIS_INLINE
   SubView itemValues(const size_t& i_cell) const noexcept(NO_ASSERT)
   {
-    Assert(m_is_built);
+    Assert(this->isBuilt());
     const auto& cell_begin = m_host_row_map(i_cell);
     const auto& cell_end = m_host_row_map(i_cell+1);
     return SubView(m_values, cell_begin, cell_end);
   }
 
-  template <typename DataType2>
+  template <typename DataType2,
+            typename ConnectivityPtr2>
   PASTIS_INLINE
   SubItemValuePerItem&
-  operator=(const SubItemValuePerItem<DataType2, sub_item_type, item_type>& sub_item_value_per_item)
+  operator=(const SubItemValuePerItem<DataType2, ItemOfItem, ConnectivityPtr2>& sub_item_value_per_item) noexcept
   {
     // ensures that DataType is the same as source DataType2
-    static_assert(std::is_same<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>(),
+    static_assert(std::is_same_v<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>,
                   "Cannot assign SubItemValuePerItem of different type");
     // ensures that const is not lost through copy
-    static_assert(((std::is_const<DataType2>() and std::is_const<DataType>())
-                   or not std::is_const<DataType2>()),
+    static_assert(((std::is_const_v<DataType2> and std::is_const_v<DataType>)
+                   or not std::is_const_v<DataType2>),
                   "Cannot assign SubItemValuePerItem of const data to SubItemValuePerItem of non-const data");
     m_host_row_map = sub_item_value_per_item.m_host_row_map;
     m_values = sub_item_value_per_item.m_values;
 
-    m_is_built = sub_item_value_per_item.m_is_built;
+    if constexpr (std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr> and
+                  std::is_same_v<ConnectivityPtr2, ConnectivityWeakPtr>) {
+      m_connectivity_ptr = sub_item_value_per_item.m_connectivity_ptr.lock();
+    } else {
+      m_connectivity_ptr = sub_item_value_per_item.m_connectivity_ptr;
+    }
 
     return *this;
   }
 
-  template <typename DataType2>
+  template <typename DataType2,
+            typename ConnectivityPtr2>
   PASTIS_INLINE
-  SubItemValuePerItem(const SubItemValuePerItem<DataType2, sub_item_type, item_type>& sub_item_value_per_item)
+  SubItemValuePerItem(const SubItemValuePerItem<DataType2, ItemOfItem, ConnectivityPtr2>& sub_item_value_per_item) noexcept
   {
     this->operator=(sub_item_value_per_item);
   }
 
   SubItemValuePerItem() = default;
 
-  SubItemValuePerItem(const IConnectivity& connectivity)
-      : m_is_built{true}
+  SubItemValuePerItem(const IConnectivity& connectivity) noexcept
+      : m_connectivity_ptr{connectivity.shared_ptr()}
   {
-    static_assert(not std::is_const<DataType>(),
+    static_assert(not std::is_const_v<DataType>,
                   "Cannot allocate SubItemValuePerItem of const data: only view is supported"); ;
 
     ConnectivityMatrix connectivity_matrix
@@ -225,45 +245,94 @@ class SubItemValuePerItem<DataType,
 // Item values at nodes
 
 template <typename DataType>
-using NodeValuePerEdge = SubItemValuePerItem<DataType, ItemType::node, ItemType::edge>;
+using NodeValuePerEdge = SubItemValuePerItem<DataType, NodeOfEdge>;
+
+template <typename DataType>
+using NodeValuePerFace = SubItemValuePerItem<DataType, NodeOfFace>;
+
+template <typename DataType>
+using NodeValuePerCell = SubItemValuePerItem<DataType, NodeOfCell>;
+
+// Item values at edges
+
+template <typename DataType>
+using EdgeValuePerNode = SubItemValuePerItem<DataType, EdgeOfNode>;
+
+template <typename DataType>
+using EdgeValuePerFace = SubItemValuePerItem<DataType, EdgeOfFace>;
+
+template <typename DataType>
+using EdgeValuePerCell = SubItemValuePerItem<DataType, EdgeOfCell>;
+
+// Item values at faces
+
+template <typename DataType>
+using FaceValuePerNode = SubItemValuePerItem<DataType, FaceOfNode>;
+
+template <typename DataType>
+using FaceValuePerEdge = SubItemValuePerItem<DataType, FaceOfEdge>;
+
+template <typename DataType>
+using FaceValuePerCell = SubItemValuePerItem<DataType, FaceOfCell>;
+
+// Item values at cells
+
+template <typename DataType>
+using CellValuePerNode = SubItemValuePerItem<DataType, CellOfNode>;
+
+template <typename DataType>
+using CellValuePerEdge = SubItemValuePerItem<DataType, CellOfEdge>;
+
+template <typename DataType>
+using CellValuePerFace = SubItemValuePerItem<DataType, CellOfFace>;
+
+// Weak versions: should not be used outside of Connectivity
+// Item values at nodes
+
+template <typename DataType,
+          typename ItemOfItem>
+using WeakSubItemValuePerItem = SubItemValuePerItem<DataType, ItemOfItem, std::weak_ptr<const IConnectivity>>;
+
+template <typename DataType>
+using WeakNodeValuePerEdge = WeakSubItemValuePerItem<DataType, NodeOfEdge>;
 
 template <typename DataType>
-using NodeValuePerFace = SubItemValuePerItem<DataType, ItemType::node, ItemType::face>;
+using WeakNodeValuePerFace = WeakSubItemValuePerItem<DataType, NodeOfFace>;
 
 template <typename DataType>
-using NodeValuePerCell = SubItemValuePerItem<DataType, ItemType::node, ItemType::cell>;
+using WeakNodeValuePerCell = WeakSubItemValuePerItem<DataType, NodeOfCell>;
 
 // Item values at edges
 
 template <typename DataType>
-using EdgeValuePerNode = SubItemValuePerItem<DataType, ItemType::edge, ItemType::node>;
+using WeakEdgeValuePerNode = WeakSubItemValuePerItem<DataType, EdgeOfNode>;
 
 template <typename DataType>
-using EdgeValuePerFace = SubItemValuePerItem<DataType, ItemType::edge, ItemType::face>;
+using WeakEdgeValuePerFace = WeakSubItemValuePerItem<DataType, EdgeOfFace>;
 
 template <typename DataType>
-using EdgeValuePerCell = SubItemValuePerItem<DataType, ItemType::edge, ItemType::cell>;
+using WeakEdgeValuePerCell = WeakSubItemValuePerItem<DataType, EdgeOfCell>;
 
 // Item values at faces
 
 template <typename DataType>
-using FaceValuePerNode = SubItemValuePerItem<DataType, ItemType::face, ItemType::node>;
+using WeakFaceValuePerNode = WeakSubItemValuePerItem<DataType, FaceOfNode>;
 
 template <typename DataType>
-using FaceValuePerEdge = SubItemValuePerItem<DataType, ItemType::face, ItemType::edge>;
+using WeakFaceValuePerEdge = WeakSubItemValuePerItem<DataType, FaceOfEdge>;
 
 template <typename DataType>
-using FaceValuePerCell = SubItemValuePerItem<DataType, ItemType::face, ItemType::cell>;
+using WeakFaceValuePerCell = WeakSubItemValuePerItem<DataType, FaceOfCell>;
 
 // Item values at cells
 
 template <typename DataType>
-using CellValuePerNode = SubItemValuePerItem<DataType, ItemType::cell, ItemType::node>;
+using WeakCellValuePerNode = WeakSubItemValuePerItem<DataType, CellOfNode>;
 
 template <typename DataType>
-using CellValuePerEdge = SubItemValuePerItem<DataType, ItemType::cell, ItemType::edge>;
+using WeakCellValuePerEdge = WeakSubItemValuePerItem<DataType, CellOfEdge>;
 
 template <typename DataType>
-using CellValuePerFace = SubItemValuePerItem<DataType, ItemType::cell, ItemType::face>;
+using WeakCellValuePerFace = WeakSubItemValuePerItem<DataType, CellOfFace>;
 
 #endif // SUBITEM_VALUE_PER_ITEM_HPP
diff --git a/src/mesh/Synchronizer.hpp b/src/mesh/Synchronizer.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a44a17ed3738625d7cfbef3c30c1fb45a3d62621
--- /dev/null
+++ b/src/mesh/Synchronizer.hpp
@@ -0,0 +1,215 @@
+#ifndef SYNCHRONIZER_HPP
+#define SYNCHRONIZER_HPP
+
+#include <ItemValue.hpp>
+#include <Connectivity.hpp>
+
+#include <map>
+
+class Synchronizer
+{
+  template <ItemType item_type>
+  using ExchangeItemTypeInfo = std::vector<Array<const ItemIdT<item_type>>>;
+
+  ExchangeItemTypeInfo<ItemType::cell> m_requested_cell_info;
+  ExchangeItemTypeInfo<ItemType::cell> m_provided_cell_info;
+
+  ExchangeItemTypeInfo<ItemType::face> m_requested_face_info;
+  ExchangeItemTypeInfo<ItemType::face> m_provided_face_info;
+
+  ExchangeItemTypeInfo<ItemType::edge> m_requested_edge_info;
+  ExchangeItemTypeInfo<ItemType::edge> m_provided_edge_info;
+
+  ExchangeItemTypeInfo<ItemType::node> m_requested_node_info;
+  ExchangeItemTypeInfo<ItemType::node> m_provided_node_info;
+
+  template <ItemType item_type>
+  PASTIS_INLINE
+  constexpr auto& _getRequestedItemInfo()
+  {
+    if constexpr (item_type == ItemType::cell) {
+      return m_requested_cell_info;
+    } else if constexpr (item_type == ItemType::face) {
+      return m_requested_face_info;
+    } else if constexpr (item_type == ItemType::edge) {
+      return m_requested_edge_info;
+    } else if constexpr (item_type == ItemType::node) {
+      return m_requested_node_info;
+    }
+  }
+
+  template <ItemType item_type>
+  PASTIS_INLINE
+  constexpr auto& _getProvidedItemInfo()
+  {
+    if constexpr (item_type == ItemType::cell) {
+      return m_provided_cell_info;
+    } else if constexpr (item_type == ItemType::face) {
+      return m_provided_face_info;
+    } else if constexpr (item_type == ItemType::edge) {
+      return m_provided_edge_info;
+    } else if constexpr (item_type == ItemType::node) {
+      return m_provided_node_info;
+    }
+  }
+
+  template <typename ConnectivityType,
+            ItemType item_type>
+  void _buildSynchronizeInfo(const ConnectivityType& connectivity)
+  {
+    const auto& item_owner =  connectivity.template owner<item_type>();
+    using ItemId = ItemIdT<item_type>;
+
+    auto& requested_item_info = this->_getRequestedItemInfo<item_type>();
+    requested_item_info
+        = [&] () {
+            std::vector<std::vector<ItemId>> requested_item_vector_info(parallel::size());
+            for (ItemId item_id=0; item_id<item_owner.size(); ++item_id) {
+              if (const size_t owner = item_owner[item_id]; owner != parallel::rank()) {
+                requested_item_vector_info[owner].emplace_back(item_id);
+              }
+            }
+            std::vector<Array<const ItemId>> requested_item_info(parallel::size());
+            for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+              const auto& requested_item_vector = requested_item_vector_info[i_rank];
+              requested_item_info[i_rank] = convert_to_array(requested_item_vector);
+            }
+            return requested_item_info;
+          }();
+
+    Array<unsigned int> local_number_of_requested_values(parallel::size());
+    for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+      local_number_of_requested_values[i_rank] = requested_item_info[i_rank].size();
+    }
+
+    Array<unsigned int> local_number_of_values_to_send
+        = parallel::allToAll(local_number_of_requested_values);
+
+    std::vector<Array<const int>> requested_item_number_list_by_proc(parallel::size());
+    const auto& item_number = connectivity.template number<item_type>();
+    for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+      const auto& requested_item_info_from_rank = requested_item_info[i_rank];
+      Array<int> item_number_list{requested_item_info_from_rank.size()};
+      parallel_for (requested_item_info_from_rank.size(), PASTIS_LAMBDA(size_t i_item) {
+          item_number_list[i_item] = item_number[requested_item_info_from_rank[i_item]];
+        });
+      requested_item_number_list_by_proc[i_rank] = item_number_list;
+    }
+
+
+    std::vector<Array<int>> provided_item_number_list_by_rank(parallel::size());
+    for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+      provided_item_number_list_by_rank[i_rank] = Array<int>{local_number_of_values_to_send[i_rank]};
+    }
+
+    parallel::exchange(requested_item_number_list_by_proc, provided_item_number_list_by_rank);
+
+    std::map<int, ItemId> item_number_to_id_correspondance;
+    for (ItemId item_id=0; item_id<item_number.size(); ++item_id) {
+      item_number_to_id_correspondance[item_number[item_id]] = item_id;
+    }
+
+    auto& provided_item_info = this->_getProvidedItemInfo<item_type>();
+    provided_item_info
+        = [&] () {
+            std::vector<Array<const ItemId>> provided_item_info(parallel::size());
+            for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+              Array<ItemId> provided_item_id_to_rank{local_number_of_values_to_send[i_rank]};
+              const Array<int>& provided_item_number_to_rank = provided_item_number_list_by_rank[i_rank];
+              for (size_t i=0; i<provided_item_number_to_rank.size(); ++i) {
+                provided_item_id_to_rank[i]
+                    = item_number_to_id_correspondance.find(provided_item_number_to_rank[i])->second;
+              }
+              provided_item_info[i_rank] = provided_item_id_to_rank;
+            }
+            return provided_item_info;
+          } ();
+  }
+
+  template <typename ConnectivityType,
+            typename DataType,
+            ItemType item_type,
+            typename ConnectivityPtr>
+  PASTIS_INLINE
+  void _synchronize(const ConnectivityType& connectivity,
+                    ItemValue<DataType, item_type, ConnectivityPtr>& item_value)
+  {
+    static_assert(not std::is_abstract_v<ConnectivityType>,
+                  "_synchronize must be called on a concrete connectivity");
+
+    using ItemId = ItemIdT<item_type>;
+
+    const auto& provided_item_info = this->_getProvidedItemInfo<item_type>();
+    const auto& requested_item_info = this->_getRequestedItemInfo<item_type>();
+
+    Assert(requested_item_info.size() == provided_item_info.size());
+
+    if (provided_item_info.size() == 0) {
+      this->_buildSynchronizeInfo<ConnectivityType, item_type>(connectivity);
+    }
+
+    std::vector<Array<const DataType>> provided_data_list(parallel::size());
+    for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+      const Array<const ItemId>& provided_item_info_to_rank = provided_item_info[i_rank];
+      Array<DataType> provided_data{provided_item_info_to_rank.size()};
+      parallel_for(provided_item_info_to_rank.size(), PASTIS_LAMBDA(size_t i) {
+          provided_data[i] = item_value[provided_item_info_to_rank[i]];
+        });
+      provided_data_list[i_rank] = provided_data;
+    }
+
+    std::vector<Array<DataType>> requested_data_list(parallel::size());
+    for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+      const auto& requested_item_info_from_rank = requested_item_info[i_rank];
+      requested_data_list[i_rank] = Array<DataType>{requested_item_info_from_rank.size()};
+    }
+
+    parallel::exchange(provided_data_list, requested_data_list);
+
+    for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+      const auto& requested_item_info_from_rank = requested_item_info[i_rank];
+      const auto& requested_data = requested_data_list[i_rank];
+      parallel_for(requested_item_info_from_rank.size(), PASTIS_LAMBDA(size_t i) {
+          item_value[requested_item_info_from_rank[i]] = requested_data[i];
+        });
+    }
+  }
+
+ public:
+  template <typename DataType,
+            ItemType item_type,
+            typename ConnectivityPtr>
+  PASTIS_INLINE
+  void synchronize(ItemValue<DataType, item_type, ConnectivityPtr>& item_value)
+  {
+    Assert(item_value.connectivity_ptr().use_count()>0, "No connectivity is associated to this ItemValue");
+    const IConnectivity& connectivity = *item_value.connectivity_ptr();
+
+    switch (connectivity.dimension()) {
+      case 1: {
+        this->_synchronize(static_cast<const Connectivity1D&>(connectivity), item_value);
+        break;
+      }
+      case 2: {
+        this->_synchronize(static_cast<const Connectivity2D&>(connectivity), item_value);
+        break;
+      }
+      case 3: {
+        this->_synchronize(static_cast<const Connectivity3D&>(connectivity), item_value);
+        break;
+      }
+      default: {
+        perr() << __FILE__ << ':' << __LINE__ << ": unexpected dimension\n";
+        std::terminate();
+      }
+    }
+  }
+
+  PASTIS_INLINE
+  Synchronizer()
+  {
+    ;
+  }
+};
+
+#endif // SYNCHRONIZER_HPP
diff --git a/src/mesh/SynchronizerManager.cpp b/src/mesh/SynchronizerManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1db720e396a40e0d15c952c5fefbbfd7f5b7597e
--- /dev/null
+++ b/src/mesh/SynchronizerManager.cpp
@@ -0,0 +1,53 @@
+#include <SynchronizerManager.hpp>
+#include <PastisAssert.hpp>
+
+#include <Messenger.hpp>
+#include <Synchronizer.hpp>
+
+SynchronizerManager*
+SynchronizerManager::m_instance{nullptr};
+
+SynchronizerManager::
+~SynchronizerManager()
+{
+  if (m_connectivity_synchronizer_map.size() > 0)
+  {
+    perr() << __FILE__ << ':' << __LINE__
+           << ": warning: some connectivities are still registered\n";;
+  }
+}
+
+void SynchronizerManager::create()
+{
+  Assert(m_instance == nullptr, "SynchronizerManager is already created");
+  m_instance = new SynchronizerManager;
+}
+
+void SynchronizerManager::destroy()
+{
+  Assert(m_instance != nullptr, "SynchronizerManager was not created!");
+  delete m_instance;
+  m_instance = nullptr;
+}
+
+
+void
+SynchronizerManager::
+deleteConnectivitySynchronizer(const IConnectivity* connectivity)
+{
+  m_connectivity_synchronizer_map.erase(connectivity);
+}
+
+Synchronizer&
+SynchronizerManager::
+getConnectivitySynchronizer(const IConnectivity* connectivity)
+{
+  if (auto connectivity_synchronizer = m_connectivity_synchronizer_map.find(connectivity);
+      connectivity_synchronizer != m_connectivity_synchronizer_map.end()) {
+    return (*connectivity_synchronizer->second);
+  } else {
+    std::shared_ptr synchronizer = std::make_shared<Synchronizer>();
+    m_connectivity_synchronizer_map[connectivity] = synchronizer;
+    return *synchronizer;
+  }
+}
diff --git a/src/mesh/SynchronizerManager.hpp b/src/mesh/SynchronizerManager.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..3fdf57d9b97cebb782a0c7c4ab1cbf5ec1fc4904
--- /dev/null
+++ b/src/mesh/SynchronizerManager.hpp
@@ -0,0 +1,37 @@
+#ifndef SYNCHRONIZER_MANAGER_HPP
+#define SYNCHRONIZER_MANAGER_HPP
+
+#include <PastisMacros.hpp>
+#include <PastisAssert.hpp>
+
+#include <memory>
+#include <map>
+
+class IConnectivity;
+class Synchronizer;
+
+class SynchronizerManager
+{
+ private:
+  std::map<const IConnectivity*, std::shared_ptr<Synchronizer>> m_connectivity_synchronizer_map;
+
+  static SynchronizerManager* m_instance;
+  SynchronizerManager() = default;
+  ~SynchronizerManager();
+
+ public:
+  static void create();
+  static void destroy();
+
+  PASTIS_INLINE
+  static SynchronizerManager& instance()
+  {
+    Assert(m_instance != nullptr, "SynchronizerManager was not created!");
+    return *m_instance;
+  }
+
+  void deleteConnectivitySynchronizer(const IConnectivity*);
+  Synchronizer& getConnectivitySynchronizer(const IConnectivity*);
+};
+
+#endif // SYNCHRONIZER_MANAGER_HPP
diff --git a/src/output/OutputNamedItemValueSet.hpp b/src/output/OutputNamedItemValueSet.hpp
index 4f056098840f338f6afd869acad83f2f567d7d2f..c7ef0eeb9f24b738647c9aa3320fd6f396d4bbbf 100644
--- a/src/output/OutputNamedItemValueSet.hpp
+++ b/src/output/OutputNamedItemValueSet.hpp
@@ -31,7 +31,8 @@ class NamedItemValue
   NamedItemValue& operator=(const NamedItemValue&) = default;
   NamedItemValue& operator=(NamedItemValue&&) = default;
 
-  NamedItemValue(const std::string& name, const ItemValue<DataType,item_type>& item_value)
+  template <typename ConnectivityPtr>
+  NamedItemValue(const std::string& name, const ItemValue<DataType,item_type, ConnectivityPtr>& item_value)
       : m_name(name),
         m_item_value(item_value)
   {
diff --git a/src/output/VTKWriter.hpp b/src/output/VTKWriter.hpp
index ffed42738c967dde7b799cd7607eece281ce1ed5..16f49cc414693b0a6610e7adea861322f419912e 100644
--- a/src/output/VTKWriter.hpp
+++ b/src/output/VTKWriter.hpp
@@ -9,6 +9,7 @@
 #include <IConnectivity.hpp>
 
 #include <ItemValue.hpp>
+#include <Messenger.hpp>
 #include <OutputNamedItemValueSet.hpp>
 
 class VTKWriter
@@ -19,6 +20,72 @@ class VTKWriter
   double m_last_time;
   const double m_time_period;
 
+  std::string _getFilenamePVTU()
+  {
+    std::ostringstream sout;
+    sout << m_base_filename;
+    sout << '.' << std::setfill('0') << std::setw(4) << m_file_number << ".pvtu";
+    return sout.str();
+  }
+  std::string _getFilenameVTU(const int& rank_number) const
+  {
+    std::ostringstream sout;
+    sout << m_base_filename;
+    if (parallel::size() > 1) {
+      sout << '-' << std::setfill('0') << std::setw(4) << rank_number;
+    }
+    sout << '.' << std::setfill('0') << std::setw(4) << m_file_number << ".vtu";
+    return sout.str();
+  }
+
+  template <typename DataType>
+  void _write_node_pvtu(std::ofstream& os,
+                        const std::string& name,
+                        const NodeValue<const DataType>&)
+  {
+    os << "<PDataArray type=\"" << VTKType<DataType>::name
+       << "\" Name=\"" << name << "\"/>\n";
+  }
+
+  template <size_t N,
+            typename DataType>
+  void _write_node_pvtu(std::ofstream& os,
+                        const std::string& name,
+                        const NodeValue<const TinyVector<N, DataType>>&)
+  {
+    os << "<PDataArray type=\"" << VTKType<DataType>::name
+       << "\" Name=\"" << name << "\" NumberOfComponents=\"" << N <<  "\"/>\n";
+  }
+
+  template <typename DataType>
+  void _write_node_pvtu(std::ofstream&,
+                        const std::string&,
+                        const CellValue<const DataType>&) {}
+
+  template <typename DataType>
+  void _write_cell_pvtu(std::ofstream& os,
+                        const std::string& name,
+                        const CellValue<const DataType>&)
+  {
+    os << "<PDataArray type=\"" << VTKType<DataType>::name
+       << "\" Name=\"" << name << "\"/>\n";
+  }
+
+  template <size_t N,
+            typename DataType>
+  void _write_cell_pvtu(std::ofstream& os,
+                        const std::string& name,
+                        const CellValue<const TinyVector<N, DataType>>&)
+  {
+    os << "<PDataArray type=\"" << VTKType<DataType>::name
+       << "\" Name=\"" << name << "\" NumberOfComponents=\"" << N <<  "\"/>\n";
+  }
+
+  template <typename DataType>
+  void _write_cell_pvtu(std::ofstream&,
+                        const std::string&,
+                        const NodeValue<const DataType>&) {}
+
   template <typename DataType> struct VTKType {};
 
   template <typename DataType>
@@ -139,112 +206,160 @@ class VTKWriter
     } else {
       return;
     }
-    std::ostringstream sout;
-    sout << m_base_filename << '.' << std::setfill('0') << std::setw(4) << m_file_number << ".vtu" << std::ends;
-    std::ofstream fout(sout.str());
-    fout << "<?xml version=\"1.0\"?>\n";
-    fout << "<VTKFile type=\"UnstructuredGrid\">\n";
-    fout << "<UnstructuredGrid>\n";
-    fout << "<Piece NumberOfPoints=\""<< mesh.numberOfNodes()
-         << "\" NumberOfCells=\"" << mesh.numberOfCells() << "\">\n";
-    fout << "<CellData>\n";
-    for(const auto& [name, item_value_variant] : output_named_item_value_set) {
-      std::visit([&, name=name](auto&& item_value) {
-                   return this->_write_cell_value(fout, name, item_value);
-                 }, item_value_variant);
-    }
-    fout << "</CellData>\n";
-    fout << "<PointData>\n";
-    for(const auto& [name, item_value_variant] : output_named_item_value_set) {
-      std::visit([&, name=name](auto&& item_value) {
-                   return this->_write_node_value(fout, name, item_value);
-                 }, item_value_variant);
-    }
-    fout << "</PointData>\n";
-    fout << "<Points>\n";
-    {
-      using Rd = TinyVector<MeshType::dimension>;
-      const NodeValue<const Rd>& xr = mesh.xr();
-      Array<TinyVector<3>> positions(mesh.numberOfNodes());
-      for (NodeId r=0; r<mesh.numberOfNodes(); ++r) {
-        for (unsigned short i=0; i<MeshType::dimension; ++i) {
-          positions[r][i] = xr[r][i];
-        }
-        for (unsigned short i=MeshType::dimension; i<3; ++i) {
-          positions[r][i] = 0;
-        }
-      }
-      _write_array(fout, "Positions", positions);
-    }
-    fout << "</Points>\n";
 
-    fout << "<Cells>\n";
+    if (parallel::rank() == 0)
+    { // write PVTK file
+      std::ofstream fout(_getFilenamePVTU());
+      fout << "<?xml version=\"1.0\"?>\n";
+      fout << "<VTKFile type=\"PUnstructuredGrid\">\n";
+      fout << "<PUnstructuredGrid GhostLevel=\"0\">\n";
+
+      fout << "<PPoints>\n";
+      fout << "<PDataArray Name=\"Positions\" NumberOfComponents=\"3\" type=\"Float64\"/>\n";
+      fout << "</PPoints>\n";
+
+      fout << "<PCells>\n";
+      fout << "<PDataArray type=\"Int32\" Name=\"connectivity\" NumberOfComponents=\"1\"/>\n";
+      fout << "<PDataArray type=\"UInt32\" Name=\"offsets\" NumberOfComponents=\"1\"/>\n";
+      fout << "<PDataArray type=\"Int8\" Name=\"types\" NumberOfComponents=\"1\"/>\n";
+      for(const auto& [name, item_value_variant] : output_named_item_value_set) {
+        std::visit([&, name=name](auto&& item_value) {
+                     return this->_write_cell_pvtu(fout, name, item_value);
+                   }, item_value_variant);
+      }
+      fout << "</PCells>\n";
 
-    {
-      const auto& cell_to_node_matrix
-          = mesh.connectivity().cellToNodeMatrix();
-      Array<int> connectivity(cell_to_node_matrix.numberOfEntries());
+      fout << "<PPointData>\n";
+      for(const auto& [name, item_value_variant] : output_named_item_value_set) {
+        std::visit([&, name=name](auto&& item_value) {
+                     return this->_write_node_pvtu(fout, name, item_value);
+                   }, item_value_variant);
+      }
+      fout << "</PPointData>\n";
 
-      for (size_t i=0; i<cell_to_node_matrix.numberOfEntries(); ++i) {
-        connectivity[i] = cell_to_node_matrix.entries()[i];
+      fout << "<PCellData>\n";
+      for(const auto& [name, item_value_variant] : output_named_item_value_set) {
+        std::visit([&, name=name](auto&& item_value) {
+                     return this->_write_cell_pvtu(fout, name, item_value);
+                   }, item_value_variant);
       }
-      _write_array(fout, "connectivity", connectivity);
-    }
+      fout << "</PCellData>\n";
 
-    {
-      const auto& cell_to_node_matrix
-          = mesh.connectivity().cellToNodeMatrix();
-      Array<unsigned int> offsets(mesh.numberOfCells());
-      unsigned int offset=0;
-      for (CellId j=0; j<mesh.numberOfCells(); ++j) {
-        const auto& cell_nodes = cell_to_node_matrix[j];
-        offset += cell_nodes.size();
-        offsets[j]=offset;
+      for (size_t i_rank=0; i_rank<parallel::size(); ++i_rank) {
+        fout << "<Piece Source=\""<< _getFilenameVTU(i_rank) << "\"/>\n";
       }
-      _write_array(fout, "offsets", offsets);
+      fout << "</PUnstructuredGrid>\n";
+      fout << "</VTKFile>\n";
+
     }
 
-    {
-      const auto& cell_to_node_matrix
-          = mesh.connectivity().cellToNodeMatrix();
-      Array<int8_t> types(mesh.numberOfCells());
-      for (CellId j=0; j<mesh.numberOfCells(); ++j) {
-        const auto& cell_nodes = cell_to_node_matrix[j];
-        switch (cell_nodes.size()) {
-          case 2: {
-            types[j]=3;
-            break;
-          }
-          case 3: {
-            types[j]=5;
-            break;
-          }
-          case 4: {
-            if (mesh.meshDimension() == 3) {
-              types[j]=10;
-            } else {
-              types[j]=9;
+    { // write VTK files
+      std::ofstream fout(_getFilenameVTU(parallel::rank()));
+      fout << "<?xml version=\"1.0\"?>\n";
+      fout << "<VTKFile type=\"UnstructuredGrid\">\n";
+      fout << "<UnstructuredGrid>\n";
+      fout << "<Piece NumberOfPoints=\""<< mesh.numberOfNodes()
+           << "\" NumberOfCells=\"" << mesh.numberOfCells() << "\">\n";
+      fout << "<CellData>\n";
+      for(const auto& [name, item_value_variant] : output_named_item_value_set) {
+        std::visit([&, name=name](auto&& item_value) {
+                     return this->_write_cell_value(fout, name, item_value);
+                   }, item_value_variant);
+      }
+      fout << "</CellData>\n";
+      fout << "<PointData>\n";
+      for(const auto& [name, item_value_variant] : output_named_item_value_set) {
+        std::visit([&, name=name](auto&& item_value) {
+                     return this->_write_node_value(fout, name, item_value);
+                   }, item_value_variant);
+      }
+      fout << "</PointData>\n";
+      fout << "<Points>\n";
+      {
+        using Rd = TinyVector<MeshType::Dimension>;
+        const NodeValue<const Rd>& xr = mesh.xr();
+        Array<TinyVector<3>> positions(mesh.numberOfNodes());
+        parallel_for(mesh.numberOfNodes(), PASTIS_LAMBDA(NodeId r) {
+            for (unsigned short i=0; i<MeshType::Dimension; ++i) {
+              positions[r][i] = xr[r][i];
+            }
+            for (unsigned short i=MeshType::Dimension; i<3; ++i) {
+              positions[r][i] = 0;
             }
-            break;
-          }
-          case 8: {
-            types[j]=12;
-            break;
-          }
-          default: {
-            types[j]=7;
-            break;
-          }
+          });
+        _write_array(fout, "Positions", positions);
+      }
+      fout << "</Points>\n";
+
+      fout << "<Cells>\n";
+      {
+        const auto& cell_to_node_matrix
+            = mesh.connectivity().cellToNodeMatrix();
+
+        _write_array(fout, "connectivity", cell_to_node_matrix.entries());
+      }
+
+      {
+        const auto& cell_to_node_matrix
+            = mesh.connectivity().cellToNodeMatrix();
+        Array<unsigned int> offsets(mesh.numberOfCells());
+        unsigned int offset=0;
+        for (CellId j=0; j<mesh.numberOfCells(); ++j) {
+          const auto& cell_nodes = cell_to_node_matrix[j];
+          offset += cell_nodes.size();
+          offsets[j]=offset;
         }
+        _write_array(fout, "offsets", offsets);
       }
-      _write_array(fout, "types", types);
-    }
 
-    fout << "</Cells>\n";
-    fout << "</Piece>\n";
-    fout << "</UnstructuredGrid>\n";
-    fout << "</VTKFile>\n";
+      {
+        Array<int8_t> types(mesh.numberOfCells());
+        const auto& cell_type
+            = mesh.connectivity().cellType();
+        parallel_for(mesh.numberOfCells(), PASTIS_LAMBDA(const CellId& j) {
+            switch (cell_type[j]) {
+              case CellType::Line: {
+                types[j]=3;
+                break;
+              }
+              case CellType::Triangle: {
+                types[j]=5;
+                break;
+              }
+              case CellType::Quadrangle: {
+                types[j]=9;
+                break;
+              }
+              case CellType::Tetrahedron: {
+                types[j]=10;
+                break;
+              }
+              case CellType::Pyramid: {
+                types[j]=14;
+                break;
+              }
+              case CellType::Prism: {
+                types[j]=13;
+                break;
+              }
+              case CellType::Hexahedron: {
+                types[j]=12;
+                break;
+              }
+              default: {
+                std::cerr << __FILE__ << ':' << __LINE__ << ": unknown cell type\n";
+                std::exit(1);
+              }
+            }
+          });
+        _write_array(fout, "types", types);
+      }
 
+      fout << "</Cells>\n";
+      fout << "</Piece>\n";
+      fout << "</UnstructuredGrid>\n";
+      fout << "</VTKFile>\n";
+    }
     m_file_number++;
   }
 
diff --git a/src/scheme/AcousticSolver.hpp b/src/scheme/AcousticSolver.hpp
index 4d09a8ede6c6774d2cd0415f7b477730a9ff3507..cb83a583bc6fb4ec064dace3a75964816b6d7b20 100644
--- a/src/scheme/AcousticSolver.hpp
+++ b/src/scheme/AcousticSolver.hpp
@@ -16,6 +16,8 @@
 #include <BoundaryCondition.hpp>
 
 #include <SubItemValuePerItem.hpp>
+#include <Messenger.hpp>
+#include <ItemValueUtils.hpp>
 
 template<typename MeshData>
 class AcousticSolver
@@ -28,10 +30,10 @@ class AcousticSolver
   const typename MeshType::Connectivity& m_connectivity;
   const std::vector<BoundaryConditionHandler>& m_boundary_condition_list;
 
-  constexpr static size_t dimension = MeshType::dimension;
+  constexpr static size_t Dimension = MeshType::Dimension;
 
-  using Rd = TinyVector<dimension>;
-  using Rdd = TinyMatrix<dimension>;
+  using Rd = TinyVector<Dimension>;
+  using Rdd = TinyMatrix<Dimension>;
 
  private:
   PASTIS_INLINE
@@ -134,8 +136,8 @@ class AcousticSolver
           break;
         }
         case BoundaryCondition::symmetry: {
-          const SymmetryBoundaryCondition<dimension>& symmetry_bc
-              = dynamic_cast<const SymmetryBoundaryCondition<dimension>&>(handler.boundaryCondition());
+          const SymmetryBoundaryCondition<Dimension>& symmetry_bc
+              = dynamic_cast<const SymmetryBoundaryCondition<Dimension>&>(handler.boundaryCondition());
           const Rd& n = symmetry_bc.outgoingNormal();
 
           const Rdd I = identity;
@@ -208,13 +210,16 @@ class AcousticSolver
     computeAjr(rhocj, Cjr, ljr, njr);
 
     NodeValuePerCell<const Rdd> Ajr = m_Ajr;
-    const NodeValue<const Rdd> Ar = computeAr(Ajr);
-    const NodeValue<const Rd> br = computeBr(m_Ajr, Cjr, uj, pj);
+    this->computeAr(Ajr);
+    this->computeBr(m_Ajr, Cjr, uj, pj);
 
     this->applyBoundaryConditions();
 
+    synchronize(m_Ar);
+    synchronize(m_br);
+
     NodeValue<Rd>& ur = m_ur;
-    ur = computeUr(Ar, br);
+    ur = computeUr(m_Ar, m_br);
     computeFjr(m_Ajr, ur, Cjr, uj, pj);
   }
 
@@ -264,7 +269,7 @@ class AcousticSolver
         m_Vj_over_cj[j] = 2*Vj[j]/(S*cj[j]);
       });
 
-    return ReduceMin(m_Vj_over_cj);
+    return min(m_Vj_over_cj);
   }
 
   void computeNextStep(const double&, const double& dt,
diff --git a/src/scheme/BoundaryCondition.hpp b/src/scheme/BoundaryCondition.hpp
index 0380f97c021b637a623146cd1be7f18524913e2d..669f546c19faf2e0cf059acef0f90ee2083682a4 100644
--- a/src/scheme/BoundaryCondition.hpp
+++ b/src/scheme/BoundaryCondition.hpp
@@ -6,7 +6,7 @@
 
 #include <Array.hpp>
 
-#include <RefNodeList.hpp>
+#include <RefItemList.hpp>
 #include <MeshNodeBoundary.hpp>
 
 class BoundaryCondition
diff --git a/src/scheme/FiniteVolumesEulerUnknowns.hpp b/src/scheme/FiniteVolumesEulerUnknowns.hpp
index 109e93d5a13acd2ce5c23b2b83576af21665c3c7..390e1c91c95beec72d96ec1389d564971a25026b 100644
--- a/src/scheme/FiniteVolumesEulerUnknowns.hpp
+++ b/src/scheme/FiniteVolumesEulerUnknowns.hpp
@@ -11,8 +11,8 @@ public:
   using MeshDataType = TMeshData;
   using MeshType = typename MeshDataType::MeshType;
 
-  static constexpr size_t dimension = MeshType::dimension;
-  using Rd = TinyVector<dimension>;
+  static constexpr size_t Dimension = MeshType::Dimension;
+  using Rd = TinyVector<Dimension>;
 
 private:
   const MeshDataType& m_mesh_data;
diff --git a/src/utils/Array.hpp b/src/utils/Array.hpp
index 33d8ba7ac1b2546e26b91dafd6d969cb1870b784..a563c6462ecb71bbdc3b3aecf8f9a4c605b56a71 100644
--- a/src/utils/Array.hpp
+++ b/src/utils/Array.hpp
@@ -6,6 +6,9 @@
 
 #include <PastisAssert.hpp>
 
+#include <Kokkos_CopyViews.hpp>
+#include <algorithm>
+
 template <typename DataType>
 class Array
 {
@@ -26,6 +29,19 @@ class Array
     return m_values.extent(0);
   }
 
+  friend PASTIS_INLINE
+  Array<std::remove_const_t<DataType>> copy(const Array<DataType>& source)
+  {
+    Array<std::remove_const_t<DataType>> image(source.size());
+    Kokkos::deep_copy(image.m_values, source.m_values);
+
+    return image;
+  }
+
+  template <typename DataType2, typename ... RT>
+  friend PASTIS_INLINE
+  Array<DataType2> encapsulate(const Kokkos::View<DataType2*, RT...>& values);
+
   PASTIS_INLINE
   DataType& operator[](const index_type& i) const noexcept(NO_ASSERT)
   {
@@ -34,11 +50,15 @@ class Array
   }
 
   PASTIS_INLINE
-  Array(const size_t& size)
-      : m_values("anonymous", size)
+  void fill(const DataType& data) const
   {
     static_assert(not std::is_const<DataType>(),
-                  "Cannot allocate Array of const data: only view is supported");
+                  "Cannot modify Array of const");
+
+    // could consider to use std::fill
+    parallel_for(this->size(), PASTIS_LAMBDA(const index_type& i){
+        m_values[i] = data;
+      });
   }
 
   template <typename DataType2>
@@ -51,7 +71,7 @@ class Array
     // ensures that const is not lost through copy
     static_assert(((std::is_const<DataType2>() and std::is_const<DataType>())
                    or not std::is_const<DataType2>()),
-                  "Cannot assign Array of const to  Array of non-const");
+                  "Cannot assign Array of const to Array of non-const");
     m_values = array.m_values;
     return *this;
   }
@@ -62,9 +82,20 @@ class Array
   PASTIS_INLINE
   Array& operator=(Array&&) = default;
 
+  PASTIS_INLINE
+  Array(const size_t& size)
+      : m_values("anonymous", size)
+  {
+    static_assert(not std::is_const<DataType>(),
+                  "Cannot allocate Array of const data: only view is supported");
+  }
+
   PASTIS_INLINE
   Array() = default;
 
+  PASTIS_INLINE
+  Array(const Array&) = default;
+
   template <typename DataType2>
   PASTIS_INLINE
   Array(const Array<DataType2>& array) noexcept
@@ -75,11 +106,31 @@ class Array
   PASTIS_INLINE
   Array(Array&&) = default;
 
-  PASTIS_INLINE
-  Array(const Array&) = default;
-
   PASTIS_INLINE
   ~Array() = default;
 };
 
+template <typename DataType, typename ... RT>
+PASTIS_INLINE
+Array<DataType> encapsulate(const Kokkos::View<DataType*, RT...>& values)
+{
+  Array<DataType> array;
+  array.m_values = values;
+  return array;
+}
+
+// map, multimap, unordered_map and stack cannot be copied this way
+template <typename Container>
+PASTIS_INLINE
+Array<std::remove_const_t<typename Container::value_type>>
+convert_to_array(const Container& given_container)
+{
+  using DataType = typename Container::value_type;
+  Array<std::remove_const_t<DataType>> array(given_container.size());
+  if (given_container.size()>0) {
+    std::copy(begin(given_container), end(given_container), &(array[0]));
+  }
+  return array;
+}
+
 #endif // ARRAY_HPP
diff --git a/src/utils/ArrayUtils.hpp b/src/utils/ArrayUtils.hpp
index b64452d992b9728dfc2e3a117f790d8893ec155c..51e42309473cd069f0a8946277ae0630665ae686 100644
--- a/src/utils/ArrayUtils.hpp
+++ b/src/utils/ArrayUtils.hpp
@@ -4,107 +4,207 @@
 #include <PastisMacros.hpp>
 #include <PastisUtils.hpp>
 
-template <typename ArrayType>
-class ReduceMin
+#include <Types.hpp>
+
+template <typename DataType,
+          template <typename> typename ArrayT>
+std::remove_const_t<DataType>
+min(const ArrayT<DataType>& array)
 {
- private:
-  const ArrayType& m_array;
+  using ArrayType = ArrayT<DataType>;
   using data_type = std::remove_const_t<typename ArrayType::data_type>;
   using index_type = typename ArrayType::index_type;
 
- public:
-  PASTIS_INLINE
-  operator data_type()
+  class ArrayMin
   {
-    data_type reduced_value;
-    parallel_reduce(m_array.size(), *this, reduced_value);
-    return reduced_value;
-  }
 
-  PASTIS_INLINE
-  void operator()(const index_type& i, data_type& data) const
-  {
-    if (m_array[i] < data) {
-      data = m_array[i];
+   private:
+    const ArrayType m_array;
+
+   public:
+    PASTIS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_array.size(), *this, reduced_value);
+      return reduced_value;
     }
-  }
 
-  PASTIS_INLINE
-  void join(volatile data_type& dst,
-            const volatile data_type& src) const
-  {
-    if (src < dst) {
-      dst = src;
+    PASTIS_INLINE
+    void operator()(const index_type& i, data_type& data) const
+    {
+      if (m_array[i] < data) {
+        data = m_array[i];
+      }
     }
-  }
 
-  PASTIS_INLINE
-  void init(data_type& value) const
-  {
-    value = std::numeric_limits<data_type>::max();
-  }
+    PASTIS_INLINE
+    void join(volatile data_type& dst,
+              const volatile data_type& src) const
+    {
+      if (src < dst) {
+        dst = src;
+      }
+    }
 
-  PASTIS_INLINE
-  ReduceMin(const ArrayType& array)
-      : m_array(array)
-  {
-    ;
-  }
+    PASTIS_INLINE
+    void init(data_type& value) const
+    {
+      value = std::numeric_limits<data_type>::max();
+    }
+
+    PASTIS_INLINE
+    ArrayMin(const ArrayType& array)
+        : m_array(array)
+    {
+      ;
+    }
 
-  PASTIS_INLINE
-  ~ReduceMin() = default;
-};
+    PASTIS_INLINE
+    ~ArrayMin() = default;
+  };
 
 
-template <typename ArrayType>
-class ReduceMax
+  return ArrayMin(array);
+}
+
+template <typename DataType,
+          template <typename> typename ArrayT>
+std::remove_const_t<DataType>
+max(const ArrayT<DataType>& array)
 {
- private:
-  const ArrayType& m_array;
+  using ArrayType = ArrayT<DataType>;
   using data_type = std::remove_const_t<typename ArrayType::data_type>;
   using index_type = typename ArrayType::index_type;
 
- public:
-  PASTIS_INLINE
-  operator data_type()
+  class ArrayMax
   {
-    data_type reduced_value;
-    parallel_reduce(m_array.size(), *this, reduced_value);
-    return reduced_value;
-  }
+   private:
+    const ArrayType m_array;
+
+   public:
+    PASTIS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_array.size(), *this, reduced_value);
+      return reduced_value;
+    }
 
-  PASTIS_INLINE
-  void operator()(const index_type& i, data_type& data) const
-  {
-    if (m_array[i] > data) {
-      data = m_array[i];
+    PASTIS_INLINE
+    void operator()(const index_type& i, data_type& data) const
+    {
+      if (m_array[i] > data) {
+        data = m_array[i];
+      }
     }
-  }
 
-  PASTIS_INLINE
-  void join(volatile data_type& dst,
-            const volatile data_type& src) const
-  {
-    if (src > dst) {
-      dst = src;
+    PASTIS_INLINE
+    void join(volatile data_type& dst,
+              const volatile data_type& src) const
+    {
+      if (src > dst) {
+        dst = src;
+      }
     }
-  }
 
-  PASTIS_INLINE
-  void init(data_type& value) const
-  {
-    value = -std::numeric_limits<data_type>::max();
-  }
+    PASTIS_INLINE
+    void init(data_type& value) const
+    {
+      value = std::numeric_limits<data_type>::min();
+    }
+
+    PASTIS_INLINE
+    ArrayMax(const ArrayType& array)
+        : m_array(array)
+    {
+      ;
+    }
+
+    PASTIS_INLINE
+    ~ArrayMax() = default;
+  };
 
-  PASTIS_INLINE
-  ReduceMax(const ArrayType& array)
-      : m_array(array)
+  return ArrayMax(array);
+}
+
+template <typename DataType,
+          template <typename> typename ArrayT>
+std::remove_const_t<DataType>
+sum(const ArrayT<DataType>& array)
+{
+  using ArrayType = ArrayT<DataType>;
+  using data_type = std::remove_const_t<DataType>;
+  using index_type = typename ArrayType::index_type;
+
+  class ArraySum
   {
-    ;
-  }
+   private:
+    const ArrayType& m_array;
+
+   public:
+    PASTIS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_array.size(), *this, reduced_value);
+      return reduced_value;
+    }
+
+    PASTIS_INLINE
+    void operator()(const index_type& i, data_type& data) const
+    {
+      data += m_array[i];
+    }
+
+    PASTIS_INLINE
+    void join(volatile data_type& dst,
+              const volatile data_type& src) const
+    {
+      dst += src;
+    }
+
+    PASTIS_INLINE
+    void init(data_type& value) const
+    {
+      if constexpr (std::is_arithmetic_v<data_type>) {
+        value = 0;
+      } else {
+        value = zero;
+      }
+    }
+
+    PASTIS_INLINE
+    ArraySum(const ArrayType& array)
+        : m_array(array)
+    {
+      ;
+    }
+
+    PASTIS_INLINE
+    ~ArraySum() = default;
+  };
+
+  return ArraySum(array);
+}
+
+template <template <typename ...SourceT> typename SourceArray,
+          template <typename ...ImageT> typename ImageArray,
+          typename ...SourceT, typename ...ImageT>
+void value_copy(const SourceArray<SourceT...>& source_array,
+                ImageArray<ImageT...>& image_array)
+{
+  using SourceDataType = typename SourceArray<SourceT...>::data_type;
+  using ImageDataType = typename ImageArray<ImageT...>::data_type;
+
+  static_assert(std::is_same_v<std::remove_const_t<SourceDataType>,ImageDataType>);
+  static_assert(not std::is_const_v<ImageDataType>);
+
+  Assert(source_array.size() == image_array.size());
 
-  PASTIS_INLINE
-  ~ReduceMax() = default;
-};
+  parallel_for(source_array.size(), PASTIS_LAMBDA(const size_t& i) {
+      image_array[i] = source_array[i];
+    });
+}
 
 #endif //ARRAY_UTILS_HPP
diff --git a/src/utils/BuildInfo.cpp b/src/utils/BuildInfo.cpp
index 377e4af868fe9f4507e10c48de8fbf6272932433..4df473bd2a9407c99804081bcf6e51a7c6e5eb15 100644
--- a/src/utils/BuildInfo.cpp
+++ b/src/utils/BuildInfo.cpp
@@ -1,6 +1,13 @@
 #include <BuildInfo.hpp>
+#include <pastis_config.hpp>
 #include <pastis_build_info.hpp>
 
+#include <sstream>
+
+#ifdef PASTIS_HAS_MPI
+#include <mpi.h>
+#endif //  PASTIS_HAS_MPI
+
 std::string BuildInfo::type()
 {
   return PASTIS_BUILD_TYPE;
@@ -8,10 +15,28 @@ std::string BuildInfo::type()
 
 std::string BuildInfo::compiler()
 {
-  return PASTIS_BUILD_COMPILER;
+  std::stringstream compiler_info;
+  compiler_info << PASTIS_BUILD_COMPILER
+                << " (" << PASTIS_BUILD_COMPILER_VERSION
+                << ")" << std::ends;
+  return compiler_info.str();
 }
 
 std::string BuildInfo::kokkosDevices()
 {
   return PASTIS_BUILD_KOKKOS_DEVICES;
 }
+
+std::string BuildInfo::mpiLibrary()
+{
+#ifdef PASTIS_HAS_MPI
+  return [](){
+           int length;
+           char mpi_version[MPI_MAX_LIBRARY_VERSION_STRING];
+           MPI_Get_library_version(mpi_version, &length);
+           return std::string(mpi_version);
+         }();
+#else
+  return "none";
+#endif // PASTIS_HAS_MPI
+}
diff --git a/src/utils/BuildInfo.hpp b/src/utils/BuildInfo.hpp
index 4fce84540f71f4d7556b6552146f0e6eaaecdf2d..86bcd1d262f58b9b77ea78375fbadd2684aaa267 100644
--- a/src/utils/BuildInfo.hpp
+++ b/src/utils/BuildInfo.hpp
@@ -8,6 +8,7 @@ struct BuildInfo
   static std::string type();
   static std::string compiler();
   static std::string kokkosDevices();
+  static std::string mpiLibrary();
 };
 
 #endif // BUILD_INFO_HPP
diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt
index 045c6d879715b130dedf6b8135e3fb4ed9421483..0c89bb3efabb8e68d77b820fcd512d759c28a29a 100644
--- a/src/utils/CMakeLists.txt
+++ b/src/utils/CMakeLists.txt
@@ -9,6 +9,8 @@ add_library(
   BacktraceManager.cpp
   ConsoleManager.cpp
   FPEManager.cpp
+  Messenger.cpp
+  Partitioner.cpp
   PastisOStream.cpp
   PastisUtils.cpp
   RevisionInfo.cpp
diff --git a/src/utils/CSRGraph.hpp b/src/utils/CSRGraph.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..2ae461eb082b0822895fdf7fa835a27489557afa
--- /dev/null
+++ b/src/utils/CSRGraph.hpp
@@ -0,0 +1,46 @@
+#ifndef CSR_GRAPH_HPP
+#define CSR_GRAPH_HPP
+
+#include <Array.hpp>
+
+class CSRGraph
+{
+ private:
+  Array<int> m_entries;
+  Array<int> m_neighbors;
+
+ public:
+  size_t numberOfNodes() const
+  {
+    Assert(m_entries.size()>0);
+    return m_entries.size()-1;
+  }
+
+  const Array<int>& entries() const
+  {
+    return m_entries;
+  }
+
+  const Array<int>& neighbors() const
+  {
+    return m_neighbors;
+  }
+
+  CSRGraph& operator=(CSRGraph&&) = default;
+  CSRGraph& operator=(const CSRGraph&) = default;
+
+  CSRGraph(Array<int> entries,
+           Array<int> neighbors)
+      : m_entries(entries),
+        m_neighbors(neighbors)
+  {
+    Assert(m_entries.size()>0);
+  }
+
+  CSRGraph() = default;
+  CSRGraph(CSRGraph&&) = default;
+  CSRGraph(const CSRGraph&) = default;
+  ~CSRGraph() = default;
+};
+
+#endif // CSR_GRAPH_HPP
diff --git a/src/utils/CastArray.hpp b/src/utils/CastArray.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..e6288b686c65f025847d82e95da43fc76ec49b37
--- /dev/null
+++ b/src/utils/CastArray.hpp
@@ -0,0 +1,108 @@
+#ifndef CAST_ARRAY_HPP
+#define CAST_ARRAY_HPP
+
+#include <Array.hpp>
+#include <PastisTraits.hpp>
+
+template <typename DataType,
+          typename CastDataType>
+class CastArray
+{
+ public:
+  using data_type = CastDataType;
+ private:
+  const Array<DataType> m_array;
+  const size_t m_size;
+  CastDataType* const m_values;
+
+ public:
+  PASTIS_INLINE
+  const size_t& size() const
+  {
+    return m_size;
+  }
+
+  PASTIS_INLINE
+  CastDataType& operator[](const size_t& i) const
+  {
+    Assert(i<m_size);
+    return m_values[i];
+  }
+
+  PASTIS_INLINE
+  CastArray& operator=(const CastArray&) = default;
+
+  PASTIS_INLINE
+  CastArray& operator=(CastArray&&) = default;
+
+  PASTIS_INLINE
+  CastArray()
+      : m_size(0),
+        m_values(nullptr)
+  {
+    ;
+  }
+
+  PASTIS_INLINE
+  CastArray(const Array<DataType>& array)
+      : m_array (array),
+        m_size  (sizeof(DataType)*array.size()/sizeof(CastDataType)),
+        m_values((array.size() == 0) ? nullptr : reinterpret_cast<CastDataType*>(&(array[0])))
+  {
+    static_assert((std::is_const_v<CastDataType> and std::is_const_v<DataType>) or
+                  (not std::is_const_v<DataType>), "CastArray cannot remove const attribute");
+
+    if (sizeof(DataType)*array.size() % sizeof(CastDataType)) {
+      std::cerr << "cannot cast array to the chosen data type\n";
+      std::exit(1);
+    }
+  }
+
+  PASTIS_INLINE
+  CastArray(DataType& value)
+      : m_size  (sizeof(DataType)/sizeof(CastDataType)),
+        m_values(reinterpret_cast<CastDataType*>(&(value)))
+  {
+    static_assert((std::is_const_v<CastDataType> and std::is_const_v<DataType>) or
+                  (not std::is_const_v<DataType>), "CastArray cannot remove const attribute");
+    static_assert(is_trivially_castable<DataType>, "Defining CastArray from non trivially castable type is not allowed");
+  }
+
+  PASTIS_INLINE
+  CastArray(DataType&& value) = delete;
+
+  PASTIS_INLINE
+  CastArray(const CastArray&) = default;
+
+  PASTIS_INLINE
+  CastArray(CastArray&&) = default;
+
+  PASTIS_INLINE
+  ~CastArray() = default;
+};
+
+template <typename CastDataType>
+struct cast_array_to
+{
+  template <typename DataType>
+  PASTIS_INLINE
+  static CastArray<DataType, CastDataType>
+  from(const Array<DataType>& array)
+  {
+    return CastArray<DataType, CastDataType>(array);
+  }
+};
+
+template <typename CastDataType>
+struct cast_value_to
+{
+  template <typename DataType>
+  PASTIS_INLINE
+  static CastArray<DataType, CastDataType>
+  from(DataType& value)
+  {
+    return CastArray<DataType, CastDataType>(value);
+  }
+};
+
+#endif // CAST_ARRAY_HPP
diff --git a/src/utils/Messenger.cpp b/src/utils/Messenger.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6dc4056efd81957f3a5a89fefc328d3d86a1d118
--- /dev/null
+++ b/src/utils/Messenger.cpp
@@ -0,0 +1,71 @@
+#include <Messenger.hpp>
+#include <PastisOStream.hpp>
+
+namespace parallel
+{
+
+Messenger* Messenger::m_instance = nullptr;
+
+void Messenger::create(int& argc, char* argv[])
+{
+  if (Messenger::m_instance == nullptr) {
+    Messenger::m_instance = new Messenger(argc, argv);
+  } else {
+    std::cerr << "Messenger already created\n";
+    std::exit(1);
+  }
+}
+
+void Messenger::destroy()
+{
+  // One allows multiple destruction to handle unexpected code exit
+  if (Messenger::m_instance != nullptr) {
+    delete Messenger::m_instance;
+    Messenger::m_instance = nullptr;
+  }
+}
+
+Messenger::
+Messenger([[maybe_unused]] int& argc,
+          [[maybe_unused]] char* argv[])
+{
+#ifdef PASTIS_HAS_MPI
+  MPI_Init(&argc, &argv);
+
+  m_rank = [] () {
+             int rank;
+             MPI_Comm_rank(MPI_COMM_WORLD, &rank);
+             return rank;
+           } ();
+
+  m_size = [] () {
+             int size=0;
+             MPI_Comm_size(MPI_COMM_WORLD, &size);
+             return size;
+           } ();
+
+  if (m_rank != 0) {
+    // LCOV_EXCL_START
+    pout.setOutput(null_stream);
+    perr.setOutput(null_stream);
+    // LCOV_EXCL_STOP
+  }
+#endif // PASTIS_HAS_MPI
+}
+
+Messenger::
+~Messenger()
+{
+#ifdef PASTIS_HAS_MPI
+  MPI_Finalize();
+#endif // PASTIS_HAS_MPI
+}
+
+void Messenger::barrier() const
+{
+#ifdef PASTIS_HAS_MPI
+  MPI_Barrier(MPI_COMM_WORLD);
+#endif // PASTIS_HAS_MPI
+}
+
+} // namespace parallel
diff --git a/src/utils/Messenger.hpp b/src/utils/Messenger.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..4a54994a273b55e74c253135951a59a95c186909
--- /dev/null
+++ b/src/utils/Messenger.hpp
@@ -0,0 +1,685 @@
+#ifndef MESSENGER_HPP
+#define MESSENGER_HPP
+
+#include <PastisMacros.hpp>
+#include <PastisAssert.hpp>
+#include <PastisOStream.hpp>
+
+#include <Array.hpp>
+#include <CastArray.hpp>
+#include <ArrayUtils.hpp>
+
+#include <type_traits>
+#include <vector>
+
+#include <pastis_config.hpp>
+#ifdef PASTIS_HAS_MPI
+#include <mpi.h>
+#endif // PASTIS_HAS_MPI
+
+#include <PastisTraits.hpp>
+
+namespace parallel
+{
+
+class Messenger
+{
+ private:
+  struct helper
+  {
+#ifdef PASTIS_HAS_MPI
+    template<typename DataType>
+    static PASTIS_INLINE
+    MPI_Datatype mpiType()
+    {
+      if constexpr (std::is_const_v<DataType>) {
+        return mpiType<std::remove_const_t<DataType>>();
+      } else {
+        static_assert(std::is_arithmetic_v<DataType>,
+                      "Unexpected arithmetic type! Should not occur!");
+        static_assert(is_false_v<DataType>,
+                      "MPI_Datatype are only defined for arithmetic types!");
+        return MPI_Datatype();
+      }
+    }
+#endif // PASTIS_HAS_MPI
+
+   private:
+    template <typename T,
+              typename Allowed = void>
+    struct split_cast {};
+
+    template <typename T>
+    struct split_cast<T,std::enable_if_t<not(sizeof(T) % sizeof(int64_t))>> {
+      using type = int64_t;
+      static_assert(not(sizeof(T) % sizeof(int64_t)));
+    };
+
+    template <typename T>
+    struct split_cast<T,std::enable_if_t<not(sizeof(T) % sizeof(int32_t))
+                                         and(sizeof(T) % sizeof(int64_t))>> {
+      using type = int32_t;
+      static_assert(not(sizeof(T) % sizeof(int32_t)));
+    };
+
+    template <typename T>
+    struct split_cast<T,std::enable_if_t<not(sizeof(T) % sizeof(int16_t))
+                                         and(sizeof(T) % sizeof(int32_t))
+                                         and(sizeof(T) % sizeof(int64_t))>> {
+      using type = int16_t;
+      static_assert(not(sizeof(T) % sizeof(int16_t)));
+    };
+
+    template <typename T>
+    struct split_cast<T,std::enable_if_t<not(sizeof(T) % sizeof(int8_t))
+                                         and(sizeof(T) % sizeof(int16_t))
+                                         and(sizeof(T) % sizeof(int32_t))
+                                         and(sizeof(T) % sizeof(int64_t))>> {
+      using type = int8_t;
+      static_assert(not(sizeof(T) % sizeof(int8_t)));
+    };
+
+   public:
+    template <typename T>
+    using split_cast_t = typename split_cast<T>::type;
+  };
+
+  static Messenger* m_instance;
+  Messenger(int& argc, char* argv[]);
+
+  size_t m_rank{0};
+  size_t m_size{1};
+
+  template <typename DataType>
+  void _allGather(const DataType& data,
+                  Array<DataType> gather) const
+  {
+    static_assert(std::is_arithmetic_v<DataType>);
+    Assert(gather.size() == m_size); // LCOV_EXCL_LINE
+
+#ifdef PASTIS_HAS_MPI
+    MPI_Datatype mpi_datatype
+        = Messenger::helper::mpiType<DataType>();
+
+    MPI_Allgather(&data, 1,  mpi_datatype,
+                  &(gather[0]), 1, mpi_datatype,
+                  MPI_COMM_WORLD);
+#else // PASTIS_HAS_MPI
+    gather[0] = data;
+#endif // PASTIS_HAS_MPI
+  }
+
+
+
+  template <template <typename ...SendT> typename SendArrayType,
+            template <typename ...RecvT> typename RecvArrayType,
+            typename ...SendT, typename ...RecvT>
+  void _allGather(const SendArrayType<SendT...>& data_array,
+                  RecvArrayType<RecvT...> gather_array) const
+  {
+    Assert(gather_array.size() == data_array.size()*m_size); // LCOV_EXCL_LINE
+
+    using SendDataType = typename SendArrayType<SendT...>::data_type;
+    using RecvDataType = typename RecvArrayType<RecvT...>::data_type;
+
+    static_assert(std::is_same_v<std::remove_const_t<SendDataType>,RecvDataType>);
+    static_assert(std::is_arithmetic_v<SendDataType>);
+
+#ifdef PASTIS_HAS_MPI
+    MPI_Datatype mpi_datatype
+        = Messenger::helper::mpiType<RecvDataType>();
+
+    MPI_Allgather(&(data_array[0]), data_array.size(),  mpi_datatype,
+                  &(gather_array[0]), data_array.size(),  mpi_datatype,
+                  MPI_COMM_WORLD);
+#else // PASTIS_HAS_MPI
+    value_copy(data_array, gather_array);
+#endif // PASTIS_HAS_MPI
+  }
+
+  template <typename DataType>
+  void _broadcast_value([[maybe_unused]] DataType& data,
+                        [[maybe_unused]] const size_t& root_rank) const
+  {
+    static_assert(not std::is_const_v<DataType>);
+    static_assert(std::is_arithmetic_v<DataType>);
+
+#ifdef PASTIS_HAS_MPI
+    MPI_Datatype mpi_datatype
+        = Messenger::helper::mpiType<DataType>();
+
+    MPI_Bcast(&data, 1,  mpi_datatype, root_rank, MPI_COMM_WORLD);
+#endif // PASTIS_HAS_MPI
+  }
+
+  template <typename ArrayType>
+  void _broadcast_array([[maybe_unused]] ArrayType& array,
+                        [[maybe_unused]] const size_t& root_rank) const
+  {
+    using DataType = typename ArrayType::data_type;
+    static_assert(not std::is_const_v<DataType>);
+    static_assert(std::is_arithmetic_v<DataType>);
+
+#ifdef PASTIS_HAS_MPI
+    MPI_Datatype mpi_datatype
+        = Messenger::helper::mpiType<DataType>();
+    MPI_Bcast(&(array[0]), array.size(), mpi_datatype, root_rank, MPI_COMM_WORLD);
+#endif // PASTIS_HAS_MPI
+  }
+
+  template <template <typename ...SendT> typename SendArrayType,
+            template <typename ...RecvT> typename RecvArrayType,
+            typename ...SendT, typename ...RecvT>
+  RecvArrayType<RecvT...> _allToAll(const SendArrayType<SendT...>& sent_array,
+                                    RecvArrayType<RecvT...>& recv_array) const
+  {
+#ifdef PASTIS_HAS_MPI
+    using SendDataType = typename SendArrayType<SendT...>::data_type;
+    using RecvDataType = typename RecvArrayType<RecvT...>::data_type;
+
+    static_assert(std::is_same_v<std::remove_const_t<SendDataType>,RecvDataType>);
+    static_assert(std::is_arithmetic_v<SendDataType>);
+
+    Assert((sent_array.size() % m_size) == 0); // LCOV_EXCL_LINE
+    Assert(sent_array.size() == recv_array.size()); // LCOV_EXCL_LINE
+
+    const size_t count = sent_array.size()/m_size;
+
+    MPI_Datatype mpi_datatype
+        = Messenger::helper::mpiType<SendDataType>();
+
+    MPI_Alltoall(&(sent_array[0]), count, mpi_datatype,
+                 &(recv_array[0]), count, mpi_datatype,
+                 MPI_COMM_WORLD);
+#else  // PASTIS_HAS_MPI
+    value_copy(sent_array, recv_array);
+#endif // PASTIS_HAS_MPI
+    return recv_array;
+  }
+
+  template <template <typename ...SendT> typename SendArrayType,
+            template <typename ...RecvT> typename RecvArrayType,
+            typename ...SendT, typename ...RecvT>
+  void _exchange(const std::vector<SendArrayType<SendT...>>& sent_array_list,
+                 std::vector<RecvArrayType<RecvT...>>& recv_array_list) const
+  {
+    using SendDataType = typename SendArrayType<SendT...>::data_type;
+    using RecvDataType = typename RecvArrayType<RecvT...>::data_type;
+
+    static_assert(std::is_same_v<std::remove_const_t<SendDataType>,RecvDataType>);
+    static_assert(std::is_arithmetic_v<SendDataType>);
+
+#ifdef PASTIS_HAS_MPI
+    std::vector<MPI_Request> request_list;
+
+    MPI_Datatype mpi_datatype
+        = Messenger::helper::mpiType<SendDataType>();
+
+    for (size_t i_send=0; i_send<sent_array_list.size(); ++i_send) {
+      const auto sent_array = sent_array_list[i_send];
+      if (sent_array.size()>0) {
+        MPI_Request request;
+        MPI_Isend(&(sent_array[0]), sent_array.size(), mpi_datatype, i_send, 0, MPI_COMM_WORLD, &request);
+        request_list.push_back(request);
+      }
+    }
+
+    for (size_t i_recv=0; i_recv<recv_array_list.size(); ++i_recv) {
+      auto recv_array = recv_array_list[i_recv];
+      if (recv_array.size()>0) {
+        MPI_Request request;
+        MPI_Irecv(&(recv_array[0]), recv_array.size(), mpi_datatype, i_recv, 0, MPI_COMM_WORLD, &request);
+        request_list.push_back(request);
+      }
+    }
+
+    if (request_list.size()>0) {
+      std::vector<MPI_Status> status_list(request_list.size());
+      if (MPI_SUCCESS != MPI_Waitall(request_list.size(), &(request_list[0]), &(status_list[0]))) {
+        // LCOV_EXCL_START
+        std::cerr << "Communication error!\n";
+        std::exit(1);
+        // LCOV_EXCL_STOP
+      }
+    }
+
+#else // PASTIS_HAS_MPI
+    Assert(sent_array_list.size() == 1);
+    Assert(recv_array_list.size() == 1);
+
+    value_copy(sent_array_list[0], recv_array_list[0]);
+#endif // PASTIS_HAS_MPI
+  }
+
+  template <typename DataType,
+            typename CastDataType>
+  void _exchange_through_cast(const std::vector<Array<DataType>>& sent_array_list,
+                              std::vector<Array<std::remove_const_t<DataType>>>& recv_array_list) const
+  {
+    std::vector<CastArray<DataType, const CastDataType>> sent_cast_array_list;
+    for (size_t i=0; i<sent_array_list.size(); ++i) {
+      sent_cast_array_list.emplace_back(cast_array_to<const CastDataType>::from(sent_array_list[i]));
+    }
+
+    using MutableDataType = std::remove_const_t<DataType>;
+    std::vector<CastArray<MutableDataType, CastDataType>> recv_cast_array_list;
+    for (size_t i=0; i<sent_array_list.size(); ++i) {
+      recv_cast_array_list.emplace_back(recv_array_list[i]);
+    }
+
+    _exchange(sent_cast_array_list, recv_cast_array_list);
+  }
+
+ public:
+  static void create(int& argc, char* argv[]);
+  static void destroy();
+
+  PASTIS_INLINE
+  static Messenger& getInstance()
+  {
+    Assert(m_instance != nullptr); // LCOV_EXCL_LINE
+    return *m_instance;
+  }
+
+  PASTIS_INLINE
+  const size_t& rank() const
+  {
+    return m_rank;
+  }
+
+  PASTIS_INLINE
+  const size_t& size() const
+  {
+    return m_size;
+  }
+
+  void barrier() const;
+
+  template <typename DataType>
+  DataType allReduceMin(const DataType& data) const
+  {
+#ifdef PASTIS_HAS_MPI
+    static_assert(not std::is_const_v<DataType>);
+    static_assert(std::is_arithmetic_v<DataType>);
+
+    MPI_Datatype mpi_datatype
+        = Messenger::helper::mpiType<DataType>();
+
+    DataType min_data = data;
+    MPI_Allreduce(&data, &min_data, 1, mpi_datatype, MPI_MIN, MPI_COMM_WORLD);
+
+    return min_data;
+#else // PASTIS_HAS_MPI
+    return data;
+#endif // PASTIS_HAS_MPI
+  }
+
+  template <typename DataType>
+  DataType allReduceMax(const DataType& data) const
+  {
+#ifdef PASTIS_HAS_MPI
+    static_assert(not std::is_const_v<DataType>);
+    static_assert(std::is_arithmetic_v<DataType>);
+
+    MPI_Datatype mpi_datatype
+        = Messenger::helper::mpiType<DataType>();
+
+    DataType max_data = data;
+    MPI_Allreduce(&data, &max_data, 1, mpi_datatype, MPI_MAX, MPI_COMM_WORLD);
+
+    return max_data;
+#else // PASTIS_HAS_MPI
+    return data;
+#endif // PASTIS_HAS_MPI
+  }
+
+  template <typename DataType>
+  DataType allReduceSum(const DataType& data) const
+  {
+#ifdef PASTIS_HAS_MPI
+    static_assert(not std::is_const_v<DataType>);
+    if constexpr(std::is_arithmetic_v<DataType>) {
+      MPI_Datatype mpi_datatype
+          = Messenger::helper::mpiType<DataType>();
+
+      DataType data_sum = data;
+      MPI_Allreduce(&data, &data_sum, 1, mpi_datatype, MPI_SUM, MPI_COMM_WORLD);
+
+      return data_sum;
+    } else if (is_trivially_castable<DataType>){
+      using InnerDataType = typename DataType::data_type;
+
+      MPI_Datatype mpi_datatype
+          = Messenger::helper::mpiType<InnerDataType>();
+      const int size = sizeof(DataType)/sizeof(InnerDataType);
+      DataType data_sum = data;
+      MPI_Allreduce(&data, &data_sum, size, mpi_datatype, MPI_SUM, MPI_COMM_WORLD);
+
+      return data_sum;
+    }
+#else // PASTIS_HAS_MPI
+    return data;
+#endif // PASTIS_HAS_MPI
+  }
+
+  template <typename DataType>
+  PASTIS_INLINE
+  Array<DataType>
+  allGather(const DataType& data) const
+  {
+    static_assert(not std::is_const_v<DataType>);
+
+    Array<DataType> gather_array(m_size);
+
+    if constexpr(std::is_arithmetic_v<DataType>) {
+      _allGather(data, gather_array);
+    } else  if constexpr(is_trivially_castable<DataType>) {
+      using CastType = helper::split_cast_t<DataType>;
+
+      CastArray cast_value_array = cast_value_to<const CastType>::from(data);
+      CastArray cast_gather_array = cast_array_to<CastType>::from(gather_array);
+
+      _allGather(cast_value_array, cast_gather_array);
+    } else {
+      static_assert(is_false_v<DataType>, "unexpected type of data");
+    }
+    return gather_array;
+  }
+
+  template <typename DataType>
+  PASTIS_INLINE
+  Array<std::remove_const_t<DataType>>
+  allGather(const Array<DataType>& array) const
+  {
+    using MutableDataType = std::remove_const_t<DataType>;
+    Array<MutableDataType> gather_array(m_size*array.size());
+
+    if constexpr(std::is_arithmetic_v<DataType>) {
+      _allGather(array, gather_array);
+    } else  if constexpr(is_trivially_castable<DataType>) {
+      using CastType = helper::split_cast_t<DataType>;
+      using MutableCastType = helper::split_cast_t<MutableDataType>;
+
+      CastArray cast_array = cast_array_to<CastType>::from(array);
+      CastArray cast_gather_array = cast_array_to<MutableCastType>::from(gather_array);
+
+      _allGather(cast_array, cast_gather_array);
+    } else {
+      static_assert(is_false_v<DataType>, "unexpected type of data");
+    }
+    return gather_array;
+  }
+
+  template <typename SendDataType>
+  PASTIS_INLINE
+  Array<std::remove_const_t<SendDataType>>
+  allToAll(const Array<SendDataType>& sent_array) const
+  {
+#ifndef NDEBUG
+    const size_t min_size = allReduceMin(sent_array.size());
+    const size_t max_size = allReduceMax(sent_array.size());
+    Assert(max_size == min_size); // LCOV_EXCL_LINE
+#endif // NDEBUG
+    Assert((sent_array.size() % m_size) == 0); // LCOV_EXCL_LINE
+
+    using DataType = std::remove_const_t<SendDataType>;
+    Array<DataType> recv_array(sent_array.size());
+
+    if constexpr(std::is_arithmetic_v<DataType>) {
+      _allToAll(sent_array, recv_array);
+    } else if constexpr(is_trivially_castable<DataType>) {
+      using CastType = helper::split_cast_t<DataType>;
+
+      auto send_cast_array = cast_array_to<const CastType>::from(sent_array);
+      auto recv_cast_array = cast_array_to<CastType>::from(recv_array);
+      _allToAll(send_cast_array, recv_cast_array);
+    } else {
+      static_assert(is_false_v<DataType>, "unexpected type of data");
+    }
+    return recv_array;
+  }
+
+  template <typename DataType>
+  PASTIS_INLINE
+  void broadcast(DataType& data, const size_t& root_rank) const
+  {
+    static_assert(not std::is_const_v<DataType>,
+                  "cannot broadcast const data");
+    if constexpr(std::is_arithmetic_v<DataType>) {
+      _broadcast_value(data, root_rank);
+    } else if constexpr(is_trivially_castable<DataType>) {
+      using CastType = helper::split_cast_t<DataType>;
+      if constexpr(sizeof(CastType) == sizeof(DataType)) {
+        CastType& cast_data = reinterpret_cast<CastType&>(data);
+        _broadcast_value(cast_data, root_rank);
+      } else {
+        CastArray cast_array = cast_value_to<CastType>::from(data);
+        _broadcast_array(cast_array, root_rank);
+      }
+    } else {
+      static_assert(is_false_v<DataType>, "unexpected type of data");
+    }
+  }
+
+  template <typename DataType>
+  PASTIS_INLINE
+  void broadcast(Array<DataType>& array,
+                 const size_t& root_rank) const
+  {
+    static_assert(not std::is_const_v<DataType>,
+                  "cannot broadcast array of const");
+    if constexpr(std::is_arithmetic_v<DataType>) {
+      size_t size = array.size();
+      _broadcast_value(size, root_rank);
+      if (m_rank != root_rank) {
+        array = Array<DataType>(size); // LCOV_EXCL_LINE
+      }
+      _broadcast_array(array, root_rank);
+    } else if constexpr(is_trivially_castable<DataType>) {
+      size_t size = array.size();
+      _broadcast_value(size, root_rank);
+      if (m_rank != root_rank) {
+        array = Array<DataType>(size); // LCOV_EXCL_LINE
+      }
+
+      using CastType = helper::split_cast_t<DataType>;
+      auto cast_array = cast_array_to<CastType>::from(array);
+      _broadcast_array(cast_array, root_rank);
+    } else{
+      static_assert(is_false_v<DataType>, "unexpected type of data");
+    }
+  }
+
+  template <typename SendDataType,
+            typename RecvDataType>
+  PASTIS_INLINE
+  void exchange(const std::vector<Array<SendDataType>>& send_array_list,
+                std::vector<Array<RecvDataType>>& recv_array_list) const
+  {
+    static_assert(std::is_same_v<std::remove_const_t<SendDataType>,RecvDataType>,
+                  "send and receive data type do not match");
+    static_assert(not std::is_const_v<RecvDataType>,
+                  "receive data type cannot be const");
+    using DataType = std::remove_const_t<SendDataType>;
+
+    Assert(send_array_list.size() == m_size); // LCOV_EXCL_LINE
+    Assert(recv_array_list.size() == m_size); // LCOV_EXCL_LINE
+#ifndef NDEBUG
+    Array<size_t> send_size(m_size);
+    for (size_t i=0; i<m_size; ++i) {
+      send_size[i] = send_array_list[i].size();
+    }
+    Array<size_t> recv_size = allToAll(send_size);
+    bool correct_sizes = true;
+    for (size_t i=0; i<m_size; ++i) {
+      correct_sizes &= (recv_size[i] == recv_array_list[i].size());
+    }
+    Assert(correct_sizes); // LCOV_EXCL_LINE
+#endif // NDEBUG
+
+    if constexpr(std::is_arithmetic_v<DataType>) {
+      _exchange(send_array_list, recv_array_list);
+    } else if constexpr(is_trivially_castable<DataType>) {
+      using CastType = helper::split_cast_t<DataType>;
+      _exchange_through_cast<SendDataType, CastType>(send_array_list, recv_array_list);
+    } else {
+      static_assert(is_false_v<RecvDataType>, "unexpected type of data");
+    }
+  }
+
+  Messenger(const Messenger&) = delete;
+  ~Messenger();
+};
+
+PASTIS_INLINE
+const Messenger& messenger()
+{
+  return Messenger::getInstance();
+}
+
+PASTIS_INLINE
+const size_t& rank()
+{
+  return messenger().rank();
+}
+
+PASTIS_INLINE
+const size_t& size()
+{
+  return messenger().size();
+}
+
+PASTIS_INLINE
+void barrier()
+{
+  return messenger().barrier();
+}
+
+template <typename DataType>
+PASTIS_INLINE
+DataType allReduceMax(const DataType& data)
+{
+  return messenger().allReduceMax(data);
+}
+
+template <typename DataType>
+PASTIS_INLINE
+DataType allReduceMin(const DataType& data)
+{
+  return messenger().allReduceMin(data);
+}
+
+template <typename DataType>
+PASTIS_INLINE
+DataType allReduceSum(const DataType& data)
+{
+  return messenger().allReduceSum(data);
+}
+
+template <typename DataType>
+PASTIS_INLINE
+Array<DataType>
+allGather(const DataType& data)
+{
+  return messenger().allGather(data);
+}
+
+template <typename DataType>
+PASTIS_INLINE
+Array<std::remove_const_t<DataType>>
+allGather(const Array<DataType>& array)
+{
+  return messenger().allGather(array);
+}
+
+template <typename DataType>
+PASTIS_INLINE
+Array<std::remove_const_t<DataType>>
+allToAll(const Array<DataType>& array)
+{
+  return messenger().allToAll(array);
+}
+
+template <typename DataType>
+PASTIS_INLINE
+void broadcast(DataType& data, const size_t& root_rank)
+{
+  messenger().broadcast(data, root_rank);
+}
+
+template <typename DataType>
+PASTIS_INLINE
+void broadcast(Array<DataType>& array, const size_t& root_rank)
+{
+  messenger().broadcast(array, root_rank);
+}
+
+template <typename SendDataType,
+          typename RecvDataType>
+PASTIS_INLINE
+void exchange(const std::vector<Array<SendDataType>>& sent_array_list,
+              std::vector<Array<RecvDataType>>& recv_array_list)
+{
+  static_assert(std::is_same_v<std::remove_const_t<SendDataType>,RecvDataType>,
+                "send and receive data type do not match");
+  static_assert(not std::is_const_v<RecvDataType>,
+                "receive data type cannot be const");
+
+   messenger().exchange(sent_array_list, recv_array_list);
+}
+
+#ifdef PASTIS_HAS_MPI
+
+template<> PASTIS_INLINE MPI_Datatype
+Messenger::helper::mpiType<char>() {return MPI_CHAR; }
+
+template<> PASTIS_INLINE MPI_Datatype
+Messenger::helper::mpiType<int8_t>() {return MPI_INT8_T; }
+
+template<> PASTIS_INLINE MPI_Datatype
+Messenger::helper::mpiType<int16_t>() {return MPI_INT16_T; }
+
+template<> PASTIS_INLINE MPI_Datatype
+Messenger::helper::mpiType<int32_t>() {return MPI_INT32_T; }
+
+template<> PASTIS_INLINE MPI_Datatype
+Messenger::helper::mpiType<int64_t>() {return MPI_INT64_T; }
+
+template<> PASTIS_INLINE MPI_Datatype
+Messenger::helper::mpiType<uint8_t>() {return MPI_UINT8_T; }
+
+template<> PASTIS_INLINE MPI_Datatype
+Messenger::helper::mpiType<uint16_t>() {return MPI_UINT16_T; }
+
+template<> PASTIS_INLINE MPI_Datatype
+Messenger::helper::mpiType<uint32_t>() {return MPI_UINT32_T; }
+
+template<> PASTIS_INLINE MPI_Datatype
+Messenger::helper::mpiType<uint64_t>() {return MPI_UINT64_T; }
+
+template<> PASTIS_INLINE MPI_Datatype
+Messenger::helper::mpiType<signed long long int>() {return MPI_LONG_LONG_INT; }
+
+template<> PASTIS_INLINE MPI_Datatype
+Messenger::helper::mpiType<unsigned long long int>() {return MPI_UNSIGNED_LONG_LONG; }
+
+template<> PASTIS_INLINE MPI_Datatype
+Messenger::helper::mpiType<float>() {return MPI_FLOAT; }
+
+template<> PASTIS_INLINE MPI_Datatype
+Messenger::helper::mpiType<double>() {return MPI_DOUBLE; }
+
+template<> PASTIS_INLINE MPI_Datatype
+Messenger::helper::mpiType<long double>() {return MPI_LONG_DOUBLE; }
+
+template<> PASTIS_INLINE MPI_Datatype
+Messenger::helper::mpiType<wchar_t>() {return MPI_WCHAR; }
+
+template<> PASTIS_INLINE MPI_Datatype
+Messenger::helper::mpiType<bool>() {return MPI_CXX_BOOL; }
+
+#endif // PASTIS_HAS_MPI
+
+} // namespace parallel
+
+#endif // MESSENGER_HPP
diff --git a/src/utils/Partitioner.cpp b/src/utils/Partitioner.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..709038090a5454794bbff49ef9240f2ebd17a780
--- /dev/null
+++ b/src/utils/Partitioner.cpp
@@ -0,0 +1,91 @@
+#include <Partitioner.hpp>
+#include <Messenger.hpp>
+#include <pastis_config.hpp>
+
+#include <PastisOStream.hpp>
+
+#ifdef PASTIS_HAS_MPI
+
+#define IDXTYPEWIDTH 64
+#define REALTYPEWIDTH 64
+#include <parmetis.h>
+
+
+#include <vector>
+
+
+Array<int> Partitioner::partition(const CSRGraph& graph)
+{
+  pout() << "Partitioning graph into "
+         << rang::style::bold << parallel::size() << rang::style::reset
+         << " parts\n";
+
+  int wgtflag = 0;
+  int numflag = 0;
+  int ncon = 1;
+  int npart= parallel::size();
+  std::vector<float> tpwgts(npart, 1./npart);
+
+  std::vector<float> ubvec{1.05};
+  std::vector<int> options{1,1,0};
+  int edgecut = 0;
+  Array<int> part(0);
+
+  MPI_Group world_group;
+  MPI_Comm_group(MPI_COMM_WORLD, &world_group);
+
+  MPI_Group mesh_group;
+  std::vector<int> group_ranks
+      = [&]() {
+          Array<int> graph_node_owners
+              = parallel::allGather(static_cast<int>(graph.numberOfNodes()));
+          std::vector<int> group_ranks;
+          group_ranks.reserve(graph_node_owners.size());
+          for (size_t i=0; i<graph_node_owners.size(); ++i) {
+            if (graph_node_owners[i] > 0) {
+              group_ranks.push_back(i);
+            }
+          }
+          return group_ranks;
+        } ();
+
+  MPI_Group_incl(world_group, group_ranks.size(), &(group_ranks[0]), &mesh_group);
+
+  MPI_Comm parmetis_comm;
+  MPI_Comm_create_group(MPI_COMM_WORLD, mesh_group, 1, &parmetis_comm);
+
+  int local_number_of_nodes = graph.numberOfNodes();
+
+  if (graph.numberOfNodes() > 0) {
+    part = Array<int>(local_number_of_nodes);
+    std::vector<int> vtxdist{0,local_number_of_nodes};
+
+    const Array<int>& entries = graph.entries();
+    const Array<int>& neighbors = graph.neighbors();
+
+    int result
+        = ParMETIS_V3_PartKway(&(vtxdist[0]), &(entries[0]), &(neighbors[0]),
+                               NULL, NULL, &wgtflag, &numflag,
+                               &ncon, &npart, &(tpwgts[0]), &(ubvec[0]),
+                               &(options[0]), &edgecut, &(part[0]), &parmetis_comm);
+    if (result == METIS_ERROR) {
+      perr() << "Metis Error\n";
+      std::exit(1);
+    }
+
+    MPI_Comm_free(&parmetis_comm);
+  }
+
+  MPI_Group_free(&mesh_group);
+
+  return part;
+}
+
+#else // PASTIS_HAS_MPI
+
+Array<int> Partitioner::partition(const CSRGraph&)
+{
+  return Array<int>(0);
+}
+
+#endif // PASTIS_HAS_MPI
diff --git a/src/utils/Partitioner.hpp b/src/utils/Partitioner.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..427d67f30d4af57f2fe096ffdb8b91736906ee0c
--- /dev/null
+++ b/src/utils/Partitioner.hpp
@@ -0,0 +1,17 @@
+#ifndef PARTITIONER_HPP
+#define PARTITIONER_HPP
+
+#include <CSRGraph.hpp>
+
+class Partitioner
+{
+ public:
+  Partitioner() = default;
+  Partitioner(const Partitioner&) = default;
+  ~Partitioner() = default;
+
+  Array<int> partition(const CSRGraph& graph);
+};
+
+
+#endif // PARTITIONER_HPP
diff --git a/src/utils/PastisAssert.hpp b/src/utils/PastisAssert.hpp
index d260ce25f293e955c0bbfbaa66861345658f9995..5093a44c21547709dce73cdea1e48015afb90e0a 100644
--- a/src/utils/PastisAssert.hpp
+++ b/src/utils/PastisAssert.hpp
@@ -1,16 +1,25 @@
 #ifndef PASTIS_ASSERT_HPP
 #define PASTIS_ASSERT_HPP
 
+#include <PastisMacros.hpp>
+
 #include <rang.hpp>
 #include <iostream>
 #include <string>
 
+template <typename ErrorType>
+void printAndThrow(const ErrorType& error)
+{
+  throw error;
+}
+
 class AssertError
 {
  private:
   const std::string m_file;
   const int m_line;
   const std::string m_function;
+  const std::string m_test;
   const std::string m_message;
 
  public:
@@ -20,11 +29,16 @@ class AssertError
   {
     os << '\n'
        << rang::style::bold
-       << "*** Assertion error ***\n"
+       << "---------- Assertion error -----------\n"
        << " at " << assert_error.m_file << ':' << assert_error.m_line << '\n'
        << " in " << assert_error.m_function << '\n'
-       << "*** " << rang::fgB::red << assert_error.m_message << rang::fg::reset
-       << rang::style::reset << '\n';
+       << " assertion (" << rang::fgB::red << assert_error.m_test << rang::fg::reset
+       << ") failed!\n";
+    if (not assert_error.m_message.empty()) {
+      os << ' ' << rang::fgB::yellow << assert_error.m_message
+         << rang::fg::reset << '\n';
+    }
+    os << "--------------------------------------" << rang::style::reset << '\n';
 
     return os;
   }
@@ -33,10 +47,12 @@ class AssertError
   AssertError(std::string file,
               int line,
               std::string function,
-              std::string message)
+              std::string test,
+              std::string message="")
       : m_file(file),
         m_line(line),
         m_function(function),
+        m_test(test),
         m_message(message)
   {
     ;
@@ -45,29 +61,44 @@ class AssertError
   ~AssertError() = default;
 };
 
-#pragma GCC diagnostic ignored "-Wattributes"
+PRAGMA_DIAGNOSTIC_IGNORED_WATTRIBUTES
 inline bool
 __attribute__((analyzer_noreturn))
 _pastis_assert(const bool& assert)
 {
   return assert;
 }
-#pragma GCC diagnostic pop
+PRAGMA_DIAGNOSTIC_POP
 
 #ifdef NDEBUG
 
 // Useless test is there to check syntax even in optimized mode. Costs nothing.
-#define Assert(assertion)                       \
-  if (not _pastis_assert(assertion)) {}
+#define Assert(assertion,...)                                   \
+  if (not _pastis_assert(assertion)) {                          \
+    using vargs_t = decltype(std::make_tuple(__VA_ARGS__));     \
+    static_assert(std::tuple_size_v<vargs_t> <= 1,              \
+                  "too many arguments");                        \
+  }
 
 #else // NDEBUG
 
-#define Assert(assertion)                       \
-  if (not _pastis_assert(assertion)) {          \
-    throw AssertError(__FILE__,                 \
-                      __LINE__,                 \
-                      __PRETTY_FUNCTION__,      \
-                      (#assertion));            \
+#define Assert(assertion,...)                                   \
+  if (not _pastis_assert(assertion)) {                          \
+    using vargs_t = decltype(std::make_tuple(__VA_ARGS__));     \
+    static_assert(std::tuple_size_v<vargs_t> <= 1,              \
+                  "too many arguments");                        \
+    if constexpr(std::tuple_size_v<vargs_t> == 0) {             \
+      printAndThrow(AssertError(__FILE__,                       \
+                                __LINE__,                       \
+                                __PRETTY_FUNCTION__,            \
+                                #assertion));                   \
+    } else {                                                    \
+      printAndThrow(AssertError(__FILE__,                       \
+                                __LINE__,                       \
+                                __PRETTY_FUNCTION__,            \
+                                #assertion,                     \
+                                #__VA_ARGS__));                 \
+    }                                                           \
   }
 
 #endif // NDEBUG
diff --git a/src/utils/PastisMacros.hpp b/src/utils/PastisMacros.hpp
index 59d2a579512a67a637f8c47f5745db89efea0a24..27f686688d90fd9ac640e2f73fba25258e56cd4c 100644
--- a/src/utils/PastisMacros.hpp
+++ b/src/utils/PastisMacros.hpp
@@ -10,4 +10,20 @@
 
 #define PASTIS_LAMBDA KOKKOS_LAMBDA
 
+// Sets macro to ignore unknown pragma
+
+#if !defined(__clang__) and defined(__GNUC__)
+
+#define PRAGMA_DIAGNOSTIC_IGNORED_WATTRIBUTES           \
+  _Pragma ("GCC diagnostic ignored \"-Wattributes\"")
+#define PRAGMA_DIAGNOSTIC_POP                   \
+  _Pragma ("GCC diagnostic pop")
+
+#else // !defined(__clang__) and defined(__GNUC__)
+
+#define PRAGMA_DIAGNOSTIC_IGNORED_WATTRIBUTES
+#define PRAGMA_DIAGNOSTIC_POP
+
+#endif
+
 #endif // PASTIS_MACROS_HPP
diff --git a/src/utils/PastisOStream.cpp b/src/utils/PastisOStream.cpp
index 19277e562e1274e537ffdee71b8642f145a806fc..af188ac6b8132c613f24443726c2eaf976af7d00 100644
--- a/src/utils/PastisOStream.cpp
+++ b/src/utils/PastisOStream.cpp
@@ -7,6 +7,8 @@ PastisOStream pout(std::cout);
 PastisOStream perr(std::cerr);
 
 std::stringstream null_stream;
-const PastisOStream _null_stream_initializer(*[](std::stringstream& null_stream){
-                                                null_stream.setstate(std::ios::badbit);
-                                                return &null_stream;}(null_stream));
+const PastisOStream _null_stream_initializer([]()-> std::stringstream&
+                                             {
+                                               null_stream.setstate(std::ios::badbit);
+                                               return null_stream;
+                                             }());
diff --git a/src/utils/PastisTraits.hpp b/src/utils/PastisTraits.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..871e30fd1c471ec67841b156ab89a624a41194b6
--- /dev/null
+++ b/src/utils/PastisTraits.hpp
@@ -0,0 +1,25 @@
+#ifndef PASTIS_TRAITS_HPP
+#define PASTIS_TRAITS_HPP
+
+#include <type_traits>
+
+template <size_t N, typename T> class TinyVector;
+template <size_t N, typename T> class TinyMatrix;
+
+template <typename T>
+inline constexpr bool is_trivially_castable = std::is_trivial_v<T>;
+
+template <size_t N, typename T>
+inline constexpr bool is_trivially_castable<TinyVector<N,T>> = is_trivially_castable<T>;
+template <size_t N, typename T>
+inline constexpr bool is_trivially_castable<const TinyVector<N,T>> = is_trivially_castable<T>;
+
+template <size_t N, typename T>
+inline constexpr bool is_trivially_castable<TinyMatrix<N,T>> = is_trivially_castable<T>;
+template <size_t N, typename T>
+inline constexpr bool is_trivially_castable<const TinyMatrix<N,T>> = is_trivially_castable<T>;
+
+template <typename T>
+inline constexpr bool is_false_v = false;
+
+#endif // PASTIS_TRAITS_HPP
diff --git a/src/utils/PastisUtils.cpp b/src/utils/PastisUtils.cpp
index b7250c194bd17c1a9915cd41323a7b3dd587fc11..4a22dec2bf165a73259f7aa519e7f8b3f18a9eb1 100644
--- a/src/utils/PastisUtils.cpp
+++ b/src/utils/PastisUtils.cpp
@@ -6,6 +6,8 @@
 #include <RevisionInfo.hpp>
 #include <BuildInfo.hpp>
 
+#include <Messenger.hpp>
+
 #include <rang.hpp>
 
 #include <FPEManager.hpp>
@@ -16,6 +18,8 @@
 
 std::string initialize(int& argc, char* argv[])
 {
+  parallel::Messenger::create(argc, argv);
+
   long unsigned number = 10;
   std::string filename;
 
@@ -46,7 +50,8 @@ std::string initialize(int& argc, char* argv[])
             << '\n';
   pout() << "type:     " << rang::style::bold << BuildInfo::type() << rang::style::reset << '\n';
   pout() << "compiler: " << rang::style::bold << BuildInfo::compiler() << rang::style::reset << '\n';
-  pout() << "devices:  " << rang::style::bold << BuildInfo::kokkosDevices() << rang::style::reset << '\n';
+  pout() << "kokkos:   " << rang::style::bold << BuildInfo::kokkosDevices() << rang::style::reset << '\n';
+  pout() << "mpi:      " << rang::style::bold << BuildInfo::mpiLibrary() << rang::style::reset << '\n';
   pout() << "-------------------------------------------------------\n";
   {
     CLI::App app{"Pastis help"};
@@ -73,7 +78,8 @@ std::string initialize(int& argc, char* argv[])
     try {
       app.parse(argc, argv);
     } catch (const CLI::ParseError &e) {
-      std::exit(app.exit(e));
+      parallel::Messenger::destroy();
+      std::exit(app.exit(e, pout(), perr()));
     }
 
     ConsoleManager::init(colorize);
@@ -101,4 +107,5 @@ std::string initialize(int& argc, char* argv[])
 void finalize()
 {
   Kokkos::finalize();
+  parallel::Messenger::destroy();
 }
diff --git a/src/utils/SignalManager.cpp b/src/utils/SignalManager.cpp
index 09a1c5ab8f9a6b56f58e61cd645ba3da86db8a5d..525db43c8d14a0191f8c9d18790def38051c9761 100644
--- a/src/utils/SignalManager.cpp
+++ b/src/utils/SignalManager.cpp
@@ -1,5 +1,6 @@
 #include <SignalManager.hpp>
 
+#include <PastisAssert.hpp>
 #include <PastisOStream.hpp>
 
 #include <BacktraceManager.hpp>
@@ -11,6 +12,8 @@
 #include <pastis_config.hpp>
 #include <rang.hpp>
 
+#include <Messenger.hpp>
+
 std::string SignalManager::s_pause_on_error = "auto";
 void SignalManager::setPauseForDebug(const std::string& pause_on_error)
 {
@@ -32,23 +35,25 @@ std::string SignalManager::signalName(const int& signal)
 
 void SignalManager::pauseForDebug(const int& signal)
 {
-
-  pout() << "\n======================================\n";
-  pout() << rang::style::reset
-       << rang::fg::reset
-       << rang::style::bold;
-  pout() << "to attach gdb to this process run\n";
-  pout() << "\tgdb -pid "
-       << rang::fg::red
-       << getpid()
-       << rang::fg::reset
-       << '\n';
-  pout() << "else press Control-C to exit\n";
-  pout() << rang::style::reset;
-  pout() << "======================================\n";
-  pout() << std::flush;
-
-  pause();
+  if (std::string(PASTIS_BUILD_TYPE) != "Release") {
+    if (s_pause_on_error == "yes") {
+      std::cerr << "\n======================================\n"
+		<< rang::style::reset
+		<< rang::fg::reset
+		<< rang::style::bold
+		<< "to attach gdb to this process run\n"
+		<< "\tgdb -pid "
+		<< rang::fg::red
+		<< getpid()
+		<< rang::fg::reset
+		<< '\n'
+		<< "else press Control-C to exit\n"
+		<< rang::style::reset
+		<< "======================================\n"
+		<< std::flush;
+      pause();
+    }
+  }
   std::exit(signal);
 }
 
@@ -60,11 +65,27 @@ void SignalManager::handler(int signal)
   std::signal(SIGINT,  SIG_DFL);
   std::signal(SIGABRT, SIG_DFL);
 
-  perr() << "\n *** "
+  BacktraceManager bm;
+  std::cerr << bm << '\n';
+
+  std::exception_ptr eptr = std::current_exception();
+  try {
+    if (eptr) {
+      std::rethrow_exception(eptr);
+    }
+  }
+  catch(const AssertError& assert_error) {
+    std::cerr << assert_error << '\n';
+  }
+  catch(...) {
+    std::cerr << "Unknown exception!\n";
+  }
+
+  std::cerr << "\n *** "
 	    << rang::style::reset
 	    << rang::fg::reset
-	    << rang::style::bold;
-  perr() << "Signal "
+	    << rang::style::bold
+	    << "Signal "
     	    << rang::fgB::red
 	    << signalName(signal)
     	    << rang::fg::reset
@@ -72,16 +93,7 @@ void SignalManager::handler(int signal)
 	    << rang::style::reset
 	    << " ***\n";
 
-  BacktraceManager bm;
-  perr() << bm << '\n';
-
-  if ((ConsoleManager::isTerminal(pout()) and
-       (s_pause_on_error == "auto")) or
-      (s_pause_on_error == "yes")) {
-    SignalManager::pauseForDebug(signal);
-  } else {
-    std::exit(signal);
-  }
+  SignalManager::pauseForDebug(signal);
  }
 
 void SignalManager::init(const bool& enable)
diff --git a/src/utils/pastis_build_info.hpp.in b/src/utils/pastis_build_info.hpp.in
index cd8756fc6bebedb8277b4d05bbbae280343bc686..86a2eff3dfe794b34a24c8722239c23bf4c21dce 100644
--- a/src/utils/pastis_build_info.hpp.in
+++ b/src/utils/pastis_build_info.hpp.in
@@ -3,6 +3,7 @@
 
 #define PASTIS_BUILD_TYPE "@CMAKE_BUILD_TYPE@"
 #define PASTIS_BUILD_COMPILER "@CMAKE_CXX_COMPILER@"
+#define PASTIS_BUILD_COMPILER_VERSION "@CMAKE_CXX_COMPILER_VERSION@"
 #define PASTIS_BUILD_KOKKOS_DEVICES "@KOKKOS_GMAKE_DEVICES@"
 
 #endif // PASTIS_BUILD_INFO_HPP
diff --git a/src/utils/pastis_config.hpp.in b/src/utils/pastis_config.hpp.in
index b27b12f70b2a5c397bb029d6f793dd8389ac78ad..9cd9e145b0bbf7b9a0075f8ec5a22fdf9cf61fb7 100644
--- a/src/utils/pastis_config.hpp.in
+++ b/src/utils/pastis_config.hpp.in
@@ -2,6 +2,8 @@
 #define PASTIS_CONFIG_HPP
 
 #cmakedefine PASTIS_HAS_FENV_H
+#cmakedefine PASTIS_HAS_MPI
+
 #cmakedefine SYSTEM_IS_LINUX
 #cmakedefine SYSTEM_IS_DARWIN
 #cmakedefine SYSTEM_IS_WINDOWS
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 94118a24d6104092883f9e6280cfd08c5c4ba5d9..394e2c339c952c745e90aead7587631b2d3404f3 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -13,6 +13,11 @@ add_executable (unit_tests
   test_TinyVector.cpp
   )
 
+add_executable (mpi_unit_tests
+  mpi_test_main.cpp
+  mpi_test_Messenger.cpp
+  )
+
 target_include_directories(Catch2 INTERFACE ${CATCH_INCLUDE_DIR})
 
 target_link_libraries (unit_tests
@@ -21,7 +26,21 @@ target_link_libraries (unit_tests
   Catch2
   )
 
+target_link_libraries (mpi_unit_tests
+  PastisUtils
+  kokkos
+  ${PARMETIS_LIBRARIES}
+  ${MPI_CXX_LINK_FLAGS} ${MPI_CXX_LIBRARIES}
+  Catch2
+  )
+
 enable_testing()
 
 #parse catch tests
 ParseAndAddCatchTests(unit_tests)
+
+if(${PASTIS_HAS_MPI})
+set(OptionalCatchTestLauncher ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} 3 --oversubscribe --path ${PASTIS_BINARY_DIR})
+endif()
+ParseAndAddCatchTests(mpi_unit_tests)
+unset(OptionalCatchTestLauncher)
diff --git a/tests/mpi_test_Messenger.cpp b/tests/mpi_test_Messenger.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f6ad79c210efd1f541f75f4332989e0a34d87162
--- /dev/null
+++ b/tests/mpi_test_Messenger.cpp
@@ -0,0 +1,437 @@
+#include <catch.hpp>
+
+#include <Messenger.hpp>
+#include <Array.hpp>
+
+#include <pastis_config.hpp>
+#include <fstream>
+
+#ifdef PASTIS_HAS_MPI
+#include <mpi.h>
+#define IF_MPI(INSTRUCTION) INSTRUCTION
+#else
+#define IF_MPI(INSTRUCTION)
+#endif // PASTIS_HAS_MPI
+
+namespace mpi_check
+{
+struct integer
+{
+  int m_int;
+  operator int&() {return m_int;}
+  operator const int&() const {return m_int;}
+  integer& operator=(const int& i) {m_int = i; return *this;}
+};
+
+struct tri_int
+{
+  int m_int_0;
+  int m_int_1;
+  int m_int_2;
+  bool operator==(const tri_int& t) const {
+    return ((m_int_0 == t.m_int_0) and
+            (m_int_1 == t.m_int_1) and
+            (m_int_2 == t.m_int_2));
+  }
+};
+
+
+template <typename T>
+void test_allToAll()
+{
+  Array<T> data_array(parallel::size());
+  for (size_t i=0; i< data_array.size(); ++i) {
+    data_array[i] = parallel::rank();
+  }
+  auto exchanged_array = parallel::allToAll(data_array);
+
+  for (size_t i=0; i< data_array.size(); ++i) {
+    const size_t value = exchanged_array[i];
+    REQUIRE(value == i);
+  }
+}
+
+template <>
+void test_allToAll<bool>()
+{
+  Array<bool> data_array(parallel::size());
+  for (size_t i=0; i< data_array.size(); ++i) {
+    data_array[i] = ((parallel::rank()%2)==0);
+  }
+  auto exchanged_array = parallel::allToAll(data_array);
+
+  for (size_t i=0; i< data_array.size(); ++i) {
+    REQUIRE(exchanged_array[i] == ((i%2)==0));
+  }
+}
+
+template <>
+void test_allToAll<tri_int>()
+{
+  Array<tri_int> data_array(parallel::size());
+  for (size_t i=0; i< data_array.size(); ++i) {
+    const int val = 1+parallel::rank();
+    data_array[i] = tri_int{val, 2*val, val+3 };
+  }
+  auto exchanged_array = parallel::allToAll(data_array);
+
+  for (size_t i=0; i< data_array.size(); ++i) {
+    const int val = 1+i;
+    REQUIRE(exchanged_array[i] == tri_int{val, 2*val, val+3 });
+  }
+}
+
+}
+
+TEST_CASE("Messenger", "[mpi]") {
+
+  SECTION("communication info") {
+    int rank=0;
+    IF_MPI(MPI_Comm_rank(MPI_COMM_WORLD, &rank));
+    REQUIRE(rank == parallel::rank());
+
+    int size=1;
+    IF_MPI(MPI_Comm_size(MPI_COMM_WORLD, &size));
+    REQUIRE(size == parallel::size());
+  }
+
+  SECTION("reduction") {
+    const int min_value = parallel::allReduceMin(parallel::rank()+3);
+    REQUIRE(min_value ==3);
+
+    const int max_value = parallel::allReduceMax(parallel::rank()+3);
+    REQUIRE(max_value == ((parallel::size()-1) + 3));
+  }
+
+  SECTION("all to all") {
+    // chars
+    mpi_check::test_allToAll<char>();
+    mpi_check::test_allToAll<wchar_t>();
+
+    // integers
+    mpi_check::test_allToAll<int8_t>();
+    mpi_check::test_allToAll<int16_t>();
+    mpi_check::test_allToAll<int32_t>();
+    mpi_check::test_allToAll<int64_t>();
+    mpi_check::test_allToAll<uint8_t>();
+    mpi_check::test_allToAll<uint16_t>();
+    mpi_check::test_allToAll<uint32_t>();
+    mpi_check::test_allToAll<uint64_t>();
+    mpi_check::test_allToAll<signed long long int>();
+    mpi_check::test_allToAll<unsigned long long int>();
+
+    // floats
+    mpi_check::test_allToAll<float>();
+    mpi_check::test_allToAll<double>();
+    mpi_check::test_allToAll<long double>();
+
+    // bools
+    mpi_check::test_allToAll<bool>();
+
+    // trivial simple type
+    mpi_check::test_allToAll<mpi_check::integer>();
+
+    // compound trivial type
+    mpi_check::test_allToAll<mpi_check::tri_int>();
+
+#ifndef NDEBUG
+    SECTION("checking invalid all to all") {
+      if (parallel::size() > 1) {
+        Array<int> invalid_all_to_all(parallel::size()+1);
+        REQUIRE_THROWS_AS(parallel::allToAll(invalid_all_to_all), AssertError);
+
+        Array<int> different_size_all_to_all(parallel::size()*(parallel::rank()+1));
+        REQUIRE_THROWS_AS(parallel::allToAll(different_size_all_to_all), AssertError);
+      }
+    }
+#endif // NDEBUG
+  }
+
+  SECTION("broadcast value") {
+    {
+      // simple type
+      size_t value{(3+parallel::rank())*2};
+      parallel::broadcast(value, 0);
+      REQUIRE(value == 6);
+    }
+
+    {
+      // trivial simple type
+      mpi_check::integer value{static_cast<int>((3+parallel::rank())*2)};
+      parallel::broadcast(value, 0);
+      REQUIRE((value == 6));
+    }
+
+    {
+      // compound trivial type
+      mpi_check::tri_int value{static_cast<int>((3+parallel::rank())*2),
+                               static_cast<int>(2+parallel::rank()),
+                               static_cast<int>(4-parallel::rank())};
+      parallel::broadcast(value, 0);
+      REQUIRE((value == mpi_check::tri_int{6,2,4}));
+    }
+  }
+
+  SECTION("broadcast array") {
+    {
+      // simple type
+      Array<size_t> array(3);
+      array[0] = (3+parallel::rank())*2;
+      array[1] = 2+parallel::rank();
+      array[2] = 4-parallel::rank();
+      parallel::broadcast(array, 0);
+      REQUIRE(((array[0]==6)  and (array[1]==2) and (array[2]==4)));
+    }
+
+    {
+      // trivial simple type
+      Array<mpi_check::integer> array(3);
+      array[0] = static_cast<int>((3+parallel::rank())*2);
+      array[1] = static_cast<int>(2+parallel::rank());
+      array[2] = static_cast<int>(4-parallel::rank());
+      parallel::broadcast(array, 0);
+      REQUIRE(((array[0]==6)  and (array[1]==2) and (array[2]==4)));
+    }
+
+    {
+      // compound trivial type
+      Array<mpi_check::tri_int> array(3);
+      array[0] = mpi_check::tri_int{static_cast<int>((3+parallel::rank())*2),
+                                    static_cast<int>(2+parallel::rank()),
+                                    static_cast<int>(4-parallel::rank())};
+      array[1] = mpi_check::tri_int{static_cast<int>((2+parallel::rank())*4),
+                                    static_cast<int>(3+parallel::rank()),
+                                    static_cast<int>(1-parallel::rank())};
+      array[2] = mpi_check::tri_int{static_cast<int>((5+parallel::rank())),
+                                    static_cast<int>(-3+parallel::rank()),
+                                    static_cast<int>(parallel::rank())};
+      parallel::broadcast(array, 0);
+      REQUIRE(((array[0] == mpi_check::tri_int{6, 2,4}) and
+               (array[1] == mpi_check::tri_int{8, 3,1}) and
+               (array[2] == mpi_check::tri_int{5,-3,0})));
+    }
+  }
+
+  SECTION("all gather value") {
+    {
+      // simple type
+      size_t value{(3+parallel::rank())*2};
+      Array<size_t> gather_array = parallel::allGather(value);
+      REQUIRE(gather_array.size() == parallel::size());
+
+      for (size_t i=0; i<gather_array.size(); ++i) {
+        REQUIRE((gather_array[i] == (3+i)*2));
+      }
+    }
+
+    {
+      // trivial simple type
+      mpi_check::integer value{static_cast<int>((3+parallel::rank())*2)};
+      Array<mpi_check::integer> gather_array = parallel::allGather(value);
+      REQUIRE(gather_array.size() == parallel::size());
+
+      for (size_t i=0; i<gather_array.size(); ++i) {
+        const int expected_value = (3+i)*2;
+        REQUIRE(gather_array[i] == expected_value);
+      }
+    }
+
+    {
+      // compound trivial type
+      mpi_check::tri_int value{static_cast<int>((3+parallel::rank())*2),
+                               static_cast<int>(2+parallel::rank()),
+                               static_cast<int>(4-parallel::rank())};
+      Array<mpi_check::tri_int> gather_array
+          = parallel::allGather(value);
+
+      REQUIRE(gather_array.size() == parallel::size());
+      for (size_t i=0; i<gather_array.size(); ++i) {
+        mpi_check::tri_int expected_value{static_cast<int>((3+i)*2),
+                                          static_cast<int>(2+i),
+                                          static_cast<int>(4-i)};
+        REQUIRE((gather_array[i] == expected_value));
+      }
+    }
+  }
+
+  SECTION("all gather array") {
+    {
+      // simple type
+      Array<int> array(3);
+      for (size_t i=0; i<array.size(); ++i) {
+        array[i] = (3+parallel::rank())*2+i;
+      }
+      Array<int> gather_array = parallel::allGather(array);
+      REQUIRE(gather_array.size() == array.size()*parallel::size());
+
+      for (size_t i=0; i<gather_array.size(); ++i) {
+        const int expected_value = (3+i/array.size())*2+(i%array.size());
+        REQUIRE((gather_array[i] == expected_value));
+      }
+    }
+
+    {
+      // trivial simple type
+      Array<mpi_check::integer> array(3);
+      for (size_t i=0; i<array.size(); ++i) {
+        array[i] = (3+parallel::rank())*2+i;
+      }
+      Array<mpi_check::integer> gather_array = parallel::allGather(array);
+      REQUIRE(gather_array.size() == array.size()*parallel::size());
+
+      for (size_t i=0; i<gather_array.size(); ++i) {
+        const int expected_value = (3+i/array.size())*2+(i%array.size());
+        REQUIRE((gather_array[i] == expected_value));
+      }
+    }
+
+    {
+      // compound trivial type
+      Array<mpi_check::tri_int> array(3);
+      for (size_t i=0; i<array.size(); ++i) {
+        array[i] = mpi_check::tri_int{static_cast<int>((3+parallel::rank())*2),
+                                      static_cast<int>(2+parallel::rank()+i),
+                                      static_cast<int>(4-parallel::rank()-i)};
+      }
+      Array<mpi_check::tri_int> gather_array
+          = parallel::allGather(array);
+
+      REQUIRE(gather_array.size() == array.size()*parallel::size());
+      for (size_t i=0; i<gather_array.size(); ++i) {
+        mpi_check::tri_int expected_value{static_cast<int>((3+i/array.size())*2),
+                                          static_cast<int>(2+i/array.size()+(i%array.size())),
+                                          static_cast<int>(4-i/array.size()-(i%array.size()))};
+        REQUIRE((gather_array[i] == expected_value));
+      }
+    }
+  }
+
+  SECTION("all array exchanges") {
+    { // simple type
+      std::vector<Array<const int>> send_array_list(parallel::size());
+      for (size_t i=0; i<send_array_list.size(); ++i) {
+        Array<int> send_array(i+1);
+        for (size_t j=0; j<send_array.size(); ++j) {
+          send_array[j] = (parallel::rank()+1)*j;
+        }
+        send_array_list[i] = send_array;
+      }
+
+      std::vector<Array<int>> recv_array_list(parallel::size());
+      for (size_t i=0; i<recv_array_list.size(); ++i) {
+        recv_array_list[i] = Array<int>(parallel::rank()+1);
+      }
+      parallel::exchange(send_array_list, recv_array_list);
+
+      for (size_t i=0; i<parallel::size(); ++i) {
+        const Array<const int> recv_array = recv_array_list[i];
+        for (size_t j=0; j<recv_array.size(); ++j) {
+          REQUIRE(recv_array[j] == (i+1)*j);
+        }
+      }
+    }
+
+    { //  trivial simple type
+      std::vector<Array<mpi_check::integer>> send_array_list(parallel::size());
+      for (size_t i=0; i<send_array_list.size(); ++i) {
+        Array<mpi_check::integer> send_array(i+1);
+        for (size_t j=0; j<send_array.size(); ++j) {
+          send_array[j] = static_cast<int>((parallel::rank()+1)*j);
+        }
+        send_array_list[i] = send_array;
+      }
+
+      std::vector<Array<mpi_check::integer>> recv_array_list(parallel::size());
+      for (size_t i=0; i<recv_array_list.size(); ++i) {
+        recv_array_list[i] = Array<mpi_check::integer>(parallel::rank()+1);
+      }
+      parallel::exchange(send_array_list, recv_array_list);
+
+      for (size_t i=0; i<parallel::size(); ++i) {
+        const Array<const mpi_check::integer> recv_array = recv_array_list[i];
+        for (size_t j=0; j<recv_array.size(); ++j) {
+          REQUIRE(recv_array[j] == (i+1)*j);
+        }
+      }
+    }
+
+    {
+      // compound trivial type
+      std::vector<Array<mpi_check::tri_int>> send_array_list(parallel::size());
+      for (size_t i=0; i<send_array_list.size(); ++i) {
+        Array<mpi_check::tri_int> send_array(i+1);
+        for (size_t j=0; j<send_array.size(); ++j) {
+          send_array[j] = mpi_check::tri_int{static_cast<int>((parallel::rank()+1)*j),
+                                             static_cast<int>(parallel::rank()),
+                                             static_cast<int>(j)};
+        }
+        send_array_list[i] = send_array;
+      }
+
+      std::vector<Array<mpi_check::tri_int>> recv_array_list(parallel::size());
+      for (size_t i=0; i<recv_array_list.size(); ++i) {
+        recv_array_list[i] = Array<mpi_check::tri_int>(parallel::rank()+1);
+      }
+      parallel::exchange(send_array_list, recv_array_list);
+
+      for (size_t i=0; i<parallel::size(); ++i) {
+        const Array<const mpi_check::tri_int> recv_array = recv_array_list[i];
+        for (size_t j=0; j<recv_array.size(); ++j) {
+          mpi_check::tri_int expected_value{static_cast<int>((i+1)*j),
+                                            static_cast<int>(i),
+                                            static_cast<int>(j)};
+          REQUIRE((recv_array[j] == expected_value));
+        }
+      }
+    }
+  }
+
+#ifndef NDEBUG
+  SECTION("checking all array exchange invalid sizes") {
+    std::vector<Array<const int>> send_array_list(parallel::size());
+    for (size_t i=0; i<send_array_list.size(); ++i) {
+      Array<int> send_array(i+1);
+      send_array.fill(parallel::rank());
+      send_array_list[i] = send_array;
+    }
+
+    std::vector<Array<int>> recv_array_list(parallel::size());
+    REQUIRE_THROWS_AS(parallel::exchange(send_array_list, recv_array_list), AssertError);
+  }
+#endif // NDEBUG
+
+
+  SECTION("checking barrier") {
+    for (size_t i=0; i<parallel::size(); ++i) {
+      if (i==parallel::rank()) {
+        std::ofstream file;
+        if (i==0) {
+          file.open("barrier_test", std::ios_base::out);
+        } else {
+          file.open("barrier_test", std::ios_base::app);
+        }
+        file << i << "\n" << std::flush;
+      }
+      parallel::barrier();
+    }
+
+    { // reading produced file
+      std::ifstream file("barrier_test");
+      std::vector<size_t> number_list;
+      while (file) {
+        size_t value;
+        file >> value;
+        if (file) {
+          number_list.push_back(value);
+        }
+      }
+      REQUIRE(number_list.size() == parallel::size());
+      for (size_t i=0; i<number_list.size(); ++i) {
+        REQUIRE(number_list[i] == i);
+      }
+    }
+    parallel::barrier();
+
+    std::remove("barrier_test");
+  }
+}
diff --git a/tests/mpi_test_main.cpp b/tests/mpi_test_main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c3b8593f365b621da12155bc1c2f717d93a90a19
--- /dev/null
+++ b/tests/mpi_test_main.cpp
@@ -0,0 +1,24 @@
+#define CATCH_CONFIG_RUNNER
+#include <catch.hpp>
+
+#include <Kokkos_Core.hpp>
+#include <Messenger.hpp>
+
+#include <cstdlib>
+
+int main( int argc, char* argv[] )
+{
+  parallel::Messenger::create(argc,  argv);
+  Kokkos::initialize({4,-1,-1,true});
+
+  if (parallel::rank() != 0) {
+    setenv("GCOV_PREFIX", "/dev/null", 1);
+  }
+
+  int result = Catch::Session().run( argc, argv );
+
+  Kokkos::finalize();
+  parallel::Messenger::destroy();
+
+  return result;
+}
diff --git a/tests/test_Array.cpp b/tests/test_Array.cpp
index 6cd02146c8775dca7a061672b8efed1fbdcad236..8bac68679ce4bd245fc1ff846adbfaf4a691e82a 100644
--- a/tests/test_Array.cpp
+++ b/tests/test_Array.cpp
@@ -4,6 +4,13 @@
 #include <Array.hpp>
 #include <Types.hpp>
 
+#include <vector>
+#include <set>
+#include <list>
+#include <deque>
+#include <valarray>
+#include <unordered_set>
+
 // Instantiate to ensure full coverage is performed
 template class Array<int>;
 
@@ -50,7 +57,19 @@ TEST_CASE("Array", "[utils]") {
 
   }
 
-  SECTION("checking for affectations") {
+  SECTION("checking for fill") {
+    Array<int> b(10);
+    b.fill(3);
+
+    REQUIRE(((b[0] == 3) and (b[1] == 3) and
+             (b[2] == 3) and (b[3] == 3) and
+             (b[4] == 3) and (b[5] == 3) and
+             (b[6] == 3) and (b[7] == 3) and
+             (b[8] == 3) and (b[9] == 3)));
+
+  }
+
+  SECTION("checking for affectations (shallow copy)") {
     Array<const int> b;
     b = a;
 
@@ -78,7 +97,154 @@ TEST_CASE("Array", "[utils]") {
              (d[4] == 8) and (d[5] ==10) and
              (d[6] ==12) and (d[7] ==14) and
              (d[8] ==16) and (d[9] ==18)));
+
+  }
+
+  SECTION("checking for affectations (deep copy)") {
+    Array<int> b(copy(a));
+
+    REQUIRE(((b[0] == 0) and (b[1] == 2) and
+             (b[2] == 4) and (b[3] == 6) and
+             (b[4] == 8) and (b[5] ==10) and
+             (b[6] ==12) and (b[7] ==14) and
+             (b[8] ==16) and (b[9] ==18)));
+
+    b.fill(2);
+
+    REQUIRE(((a[0] == 0) and (a[1] == 2) and
+             (a[2] == 4) and (a[3] == 6) and
+             (a[4] == 8) and (a[5] ==10) and
+             (a[6] ==12) and (a[7] ==14) and
+             (a[8] ==16) and (a[9] ==18)));
+
+    REQUIRE(((b[0] == 2) and (b[1] == 2) and
+             (b[2] == 2) and (b[3] == 2) and
+             (b[4] == 2) and (b[5] == 2) and
+             (b[6] == 2) and (b[7] == 2) and
+             (b[8] == 2) and (b[9] == 2)));
+
+    Array<int> c;
+    c = a;
+
+    REQUIRE(((c[0] == 0) and (c[1] == 2) and
+             (c[2] == 4) and (c[3] == 6) and
+             (c[4] == 8) and (c[5] ==10) and
+             (c[6] ==12) and (c[7] ==14) and
+             (c[8] ==16) and (c[9] ==18)));
+
+    c = copy(b);
+
+    REQUIRE(((a[0] == 0) and (a[1] == 2) and
+             (a[2] == 4) and (a[3] == 6) and
+             (a[4] == 8) and (a[5] ==10) and
+             (a[6] ==12) and (a[7] ==14) and
+             (a[8] ==16) and (a[9] ==18)));
+
+    REQUIRE(((c[0] == 2) and (c[1] == 2) and
+             (c[2] == 2) and (c[3] == 2) and
+             (c[4] == 2) and (c[5] == 2) and
+             (c[6] == 2) and (c[7] == 2) and
+             (c[8] == 2) and (c[9] == 2)));
+  }
+
+  SECTION("checking for std container conversion") {
+    {
+      std::vector<int> v{1,2,5,3};
+      {
+        Array<int> v_array = convert_to_array(v);
+
+        REQUIRE(v_array.size() == v.size());
+        REQUIRE(((v_array[0] == 1) and (v_array[1] == 2) and
+                 (v_array[2] == 5) and (v_array[3] == 3)));
+      }
+
+      {
+        Array<const int> v_array = convert_to_array(v);
+
+        REQUIRE(v_array.size() == v.size());
+        REQUIRE(((v_array[0] == 1) and (v_array[1] == 2) and
+                 (v_array[2] == 5) and (v_array[3] == 3)));
+      }
+    }
+
+    {
+      std::vector<int> w;
+      {
+        Array<int> w_array =  convert_to_array(w);
+        REQUIRE(w_array.size() == 0);
+      }
+      {
+        Array<const int> w_array =  convert_to_array(w);
+        REQUIRE(w_array.size() == 0);
+      }
+    }
+
+    {
+      std::valarray<int> v{1,2,5,3};
+      Array<int> v_array = convert_to_array(v);
+
+      REQUIRE(v_array.size() == v.size());
+      REQUIRE(((v_array[0] == 1) and (v_array[1] == 2) and
+               (v_array[2] == 5) and (v_array[3] == 3)));
+    }
+
+    {
+      std::set<int> s{4,2,5,3,1,3,2};
+      Array<int> s_array = convert_to_array(s);
+
+      REQUIRE(s_array.size() == s.size());
+      REQUIRE(((s_array[0] == 1) and (s_array[1] == 2) and
+               (s_array[2] == 3) and (s_array[3] == 4) and
+               (s_array[4] == 5)));
+    }
+
+    {
+      std::unordered_set<int> us{4,2,5,3,1,3,2};
+      Array<int> us_array = convert_to_array(us);
+
+      REQUIRE(us_array.size() == us.size());
+
+      std::set<int> s;
+      for (size_t i=0; i<us_array.size(); ++i) {
+        REQUIRE((us.find(us_array[i]) != us.end()));
+        s.insert(us_array[i]);
+      }
+      REQUIRE(s.size() == us_array.size());
+    }
+
+    {
+      std::multiset<int> ms{4,2,5,3,1,3,2};
+      Array<int> ms_array = convert_to_array(ms);
+
+      REQUIRE(ms_array.size() == ms.size());
+      REQUIRE(((ms_array[0] == 1) and (ms_array[1] == 2) and
+               (ms_array[2] == 2) and (ms_array[3] == 3) and
+               (ms_array[4] == 3) and (ms_array[5] == 4) and
+               (ms_array[6] == 5)));
+    }
+
+    {
+      std::list<int> l{1,3,5,6,2};
+      Array<int> l_array = convert_to_array(l);
+
+      REQUIRE(l_array.size() == l.size());
+      REQUIRE(((l_array[0] == 1) and (l_array[1] == 3) and
+               (l_array[2] == 5) and (l_array[3] == 6) and
+               (l_array[4] == 2)));
+    }
+
+    {
+      std::deque<int> q{1,3,5,6,2};
+      q.push_front(2);
+      Array<int> q_array = convert_to_array(q);
+
+      REQUIRE(q_array.size() == q.size());
+      REQUIRE(((q_array[0] == 2) and (q_array[1] == 1) and
+               (q_array[2] == 3) and (q_array[3] == 5) and
+               (q_array[4] == 6) and (q_array[5] == 2)));
+    }
   }
+
 #ifndef NDEBUG
   SECTION("checking for bounds violation") {
     REQUIRE_THROWS_AS(a[10], AssertError);
diff --git a/tests/test_ArrayUtils.cpp b/tests/test_ArrayUtils.cpp
index 8183e7157a2c39d3f27b3fbd703917fcdf567ff1..9d56667850c6ca727a154c6ef20fa40b6ba01ac1 100644
--- a/tests/test_ArrayUtils.cpp
+++ b/tests/test_ArrayUtils.cpp
@@ -4,15 +4,15 @@
 #include <Array.hpp>
 #include <ArrayUtils.hpp>
 
+#include <TinyVector.hpp>
+#include <TinyMatrix.hpp>
+
 // Instantiate to ensure full coverage is performed
 template class Array<int>;
 
 TEST_CASE("ArrayUtils", "[utils]") {
 
-  SECTION("checking for reductions") {
-    using ArrayType = Array<int>;
-    using data_type = ArrayType::data_type;
-
+  SECTION("checking for Array reductions") {
     Array<int> a(10);
     a[0] =13;
     a[1] = 1;
@@ -25,72 +25,51 @@ TEST_CASE("ArrayUtils", "[utils]") {
     a[8] =12;
     a[9] = 9;
 
-    SECTION("ReduceMin") {
-      ReduceMin r_min(a);
-      data_type data;
-
-      r_min.init(data);
-      REQUIRE(data == std::numeric_limits<data_type>::max());
-
-      r_min(2,data);
-      REQUIRE(data == 8);
-
-      r_min(9,data);
-      REQUIRE(data == 8);
-
-      r_min(5,data);
-      REQUIRE(data == -1);
-
-      {
-        data = 0;
-        data_type r_value{3};
-        r_min.join(data, r_value);
-        REQUIRE(data == 0);
-      }
-
-      {
-        data = 0;
-        data_type r_value{-5};
-        r_min.join(data, r_value);
-        REQUIRE(data == -5);
-      }
-
-      REQUIRE((r_min == -3));
-
-      REQUIRE((ReduceMin(a) == -3));
+    SECTION("Min") {
+      REQUIRE((min(a) == -3));
     }
 
-    SECTION("ReduceMax") {
-      ReduceMax r_max(a);
-      data_type data;
-
-      r_max.init(data);
-      REQUIRE(data == -std::numeric_limits<data_type>::max());
-
-      r_max(3,data);
-      REQUIRE(data == -3);
-      r_max(5,data);
-      REQUIRE(data == -1);
-      r_max(8,data);
-      REQUIRE(data == 12);
-
-      {
-        data = 0;
-        data_type r_value{3};
-        r_max.join(data, r_value);
-        REQUIRE(data == 3);
-      }
+    SECTION("Max") {
+      REQUIRE((max(a) == 23));
+    }
 
-      {
-        data = 0;
-        data_type r_value{-5};
-        r_max.join(data, r_value);
-        REQUIRE(data == 0);
-      }
+    SECTION("Sum") {
+      REQUIRE((sum(a) == 75));
+    }
 
-      REQUIRE((r_max == 23));
+    SECTION("TinyVector Sum") {
+      using N2 = TinyVector<2,int>;
+      Array<N2> b(10);
+      b[0] ={13, 2};
+      b[1] = {1, 3};
+      b[2] = {8, -2};
+      b[3] ={-3, 2};
+      b[4] ={23, 4};
+      b[5] ={-1, -3};
+      b[6] ={13, 17};
+      b[7] = {0, 9};
+      b[8] ={12, 13};
+      b[9] = {9, -17};
+
+      REQUIRE((sum(b) == N2{75, 28}));
+    }
 
-      REQUIRE((ReduceMax(a) == 23));
+    SECTION("TinyMatrix Sum") {
+      using N22 = TinyMatrix<2,int>;
+      Array<N22> b(10);
+      b[0] ={13,  2,  0,  1};
+      b[1] = {1,  3,  6,  3};
+      b[2] = {8, -2, -1, 21};
+      b[3] ={-3,  2,  5, 12};
+      b[4] ={23,  4,  7,  1};
+      b[5] ={-1, -3, 33, 11};
+      b[6] ={13, 17, 12, 13};
+      b[7] = {0,  9,  1, 14};
+      b[8] ={12, 13, -3,-71};
+      b[9] = {9,-17,  0, 16};
+
+      REQUIRE((sum(b) == N22{75, 28, 60, 21}));
     }
+
   }
 }
diff --git a/tests/test_PastisAssert.cpp b/tests/test_PastisAssert.cpp
index d55ad8d826d75ac10e81b5b110495e74d131e921..90995c30f9782baa8f2c0468909983ac166e3a9c 100644
--- a/tests/test_PastisAssert.cpp
+++ b/tests/test_PastisAssert.cpp
@@ -8,10 +8,22 @@ TEST_CASE("PastisAssert", "[utils]") {
     const std::string filename = "filename";
     const int line = 10;
     const std::string function = "function";
+    const std::string test = "test";
+
+    AssertError assert_error(filename, line, function, test);
+
+    REQUIRE(Catch::Detail::stringify(assert_error) == "\n---------- Assertion error -----------\n at filename:10\n in function\n assertion (test) failed!\n--------------------------------------\n");
+  }
+
+  SECTION("checking for assert error with message") {
+    const std::string filename = "filename";
+    const int line = 10;
+    const std::string function = "function";
+    const std::string test = "test";
     const std::string message = "message";
 
-    AssertError assert_error(filename, line, function, message);
+    AssertError assert_error(filename, line, function, test, message);
 
-    REQUIRE(Catch::Detail::stringify(assert_error) == "\n*** Assertion error ***\n at filename:10\n in function\n*** message\n");
+    REQUIRE(Catch::Detail::stringify(assert_error) == "\n---------- Assertion error -----------\n at filename:10\n in function\n assertion (test) failed!\n message\n--------------------------------------\n");
   }
 }