diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8047520391cb75d3f46acad5c87a720414f3c040..8f484b0393a938d9995e19666903f3fc8f20b254 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -22,12 +22,20 @@ if("${PUGS_SHORT_VERSION}" STREQUAL "")
   message(FATAL_ERROR "Unable to compute short version from PUGS_VERSION=${PUGS_VERSION}")
 endif()
 
-
-set(CMAKE_CONFIGURATION_TYPES "Release;Debug;Coverage" CACHE STRING INTERNAL FORCE )
-
 # set project version as PUGS_SHORT_VERSION
 project (Pugs VERSION ${PUGS_SHORT_VERSION})
 
+# -----------------------------------------------------
+# dynamic libraries
+
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+link_libraries("-rdynamic")
+set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
+
+#------------------------------------------------------
+
+set(CMAKE_CONFIGURATION_TYPES "Release;Debug;Coverage" CACHE STRING INTERNAL FORCE )
+
 #------------------------------------------------------
 
 set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
@@ -133,8 +141,16 @@ endif()
 #------------------------------------------------------
 # Search for ParMETIS
 
+find_package(ParMETIS)
+if(PARMETIS_LIBRARIES)
+  add_library(PkgConfig::ParMETIS STATIC IMPORTED)
+  set_property(TARGET PkgConfig::ParMETIS PROPERTY
+    IMPORTED_LOCATION "${PARMETIS_LIBRARIES}")
+
+  set(PARMETIS_TARGET PkgConfig::ParMETIS)
+endif()
+
 if(${MPI_FOUND})
-  find_package(ParMETIS)
   if (NOT PARMETIS_LIBRARIES)
     if(PUGS_ENABLE_MPI MATCHES "^AUTO$")
       message(STATUS "MPI support deactivated: ParMETIS cannot be found!")
@@ -160,7 +176,16 @@ set(PUGS_ENABLE_PETSC AUTO CACHE STRING
 if (PUGS_ENABLE_PETSC MATCHES "^(AUTO|ON)$")
   if (MPI_FOUND)
     # PETSc support is deactivated if MPI is not found
-    pkg_check_modules(PETSC PETSc)
+    pkg_check_modules(PETSC IMPORTED_TARGET GLOBAL PETSc)
+
+    set_property(TARGET PkgConfig::PETSC PROPERTY
+      IMPORTED_LOCATION "${PETSC_LIBRARIES}"
+    )
+    set_property(TARGET PkgConfig::PETSC PROPERTY
+      INTERFACE_INCLUDE_DIRECTORIES "${PETSC_INCLUDE_DIRS}"
+    )
+
+    set(PETSC_TARGET PkgConfig::PETSC)
   else()
     message(STATUS "PETSc support is deactivated since pugs will not be build with MPI support")
     set(PETSC_FOUND FALSE)
@@ -188,7 +213,16 @@ set(PUGS_ENABLE_SLEPC AUTO CACHE STRING
 if (PUGS_ENABLE_SLEPC MATCHES "^(AUTO|ON)$")
   if (PETSC_FOUND)
     # SLEPc support is deactivated if PETSc is not found
-    pkg_check_modules(SLEPC SLEPc)
+    pkg_check_modules(SLEPC IMPORTED_TARGET GLOBAL SLEPc)
+
+    set_property(TARGET PkgConfig::SLEPC PROPERTY
+      IMPORTED_LOCATION "${SLEPC_LIBRARIES}"
+    )
+    set_property(TARGET PkgConfig::SLEPC PROPERTY
+      INTERFACE_INCLUDE_DIRECTORIES "${SLEPC_INCLUDE_DIRS}"
+    )
+
+    set(SLEPC_TARGET PkgConfig::SLEPC)
   else()
     message(STATUS "SLEPc support is deactivated since pugs will not be build with PETSc support")
     set(SLEPC_FOUND FALSE)
@@ -225,22 +259,37 @@ set(PUGS_ENABLE_HDF5 AUTO CACHE STRING
   "Choose one of: AUTO ON OFF")
 
 if (PUGS_ENABLE_HDF5 MATCHES "^(AUTO|ON)$")
-  # May be risky. (make to show pugs build options)
-  set(HDF5_PREFER_PARALLEL TRUE)
-  find_package(HDF5)
-  if (HDF5_FOUND)
-    # HighFive
-    set(HIGHFIVE_USE_BOOST  OFF)   # no Boost
-    set(HIGHFIVE_BUILD_DOCS OFF)   # no doc
-    set(HIGHFIVE_UNIT_TESTS OFF)   # no unit tests
-    set(HIGHFIVE_UNIT_TESTS OFF)   # no unit tests
-    set(HIGHFIVE_EXAMPLES OFF)     # no examples
-    add_subdirectory(${PUGS_SOURCE_DIR}/packages/HighFive/)
-    set(HIGHFIVE_TARGET HighFive)
+  if (MPI_FOUND)
+    # May be risky. (make to show pugs build options)
+    set(HDF5_PREFER_PARALLEL TRUE)
+    find_package(HDF5)
+    if (HDF5_FOUND)
+      # HighFive
+      set(HIGHFIVE_USE_BOOST  OFF)   # no Boost
+      set(HIGHFIVE_BUILD_DOCS OFF)   # no doc
+      set(HIGHFIVE_UNIT_TESTS OFF)   # no unit tests
+      set(HIGHFIVE_UNIT_TESTS OFF)   # no unit tests
+      set(HIGHFIVE_EXAMPLES OFF)     # no examples
+      add_subdirectory(${PUGS_SOURCE_DIR}/packages/HighFive/)
+      set(HIGHFIVE_TARGET HighFive::HighFive)
+
+      add_library(PkgConfig::HDF5 STATIC IMPORTED)
+      set_property(TARGET PkgConfig::HDF5 PROPERTY
+	IMPORTED_LOCATION "${HDF5_LIBRARIES}")
+
+      set_property(TARGET PkgConfig::HDF5 PROPERTY
+	INTERFACE_INCLUDE_DIRECTORIES "${HDF5_INCLUDE_DIRS}")
+
+      set(HDF5_TARGET PkgConfig::HDF5)
+    endif()
+  else()
+    message(STATUS "HDF5 support is deactivated since pugs will not be build with MPI support")
+    set(HDF5_FOUND FALSE)
   endif()
   set(PUGS_HAS_HDF5 ${HDF5_FOUND})
 else()
   unset(HIGHFIVE_TARGET)
+  unset(HDF5_TARGET PkgConfig::HDF5)
   unset(PUGS_HAS_HDF5)
 endif()
 
@@ -251,7 +300,14 @@ find_package(Slurm)
 
 set(PUGS_HAS_SLURM ${SLURM_FOUND})
 
-if (${SLURM_FOUND})
+if (SLURM_FOUND)
+  add_library(PkgConfig::Slurm STATIC IMPORTED)
+  set_property(TARGET PkgConfig::Slurm PROPERTY
+    IMPORTED_LOCATION "${SLURM_LIBRARY}")
+  set_property(TARGET PkgConfig::Slurm PROPERTY
+    INTERFACE_INCLUDE_DIRECTORIES "${SLURM_INCLUDE_DIR}")
+
+  set(SLURM_TARGET PkgConfig::Slurm)
   include_directories(SYSTEM "${SLURM_INCLUDE_DIR}")
 else()
   set(SLURM_LIBRARY "")
@@ -364,6 +420,10 @@ endif()
 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${PUGS_CXX_FLAGS}")
 set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${PUGS_CXX_FLAGS}")
 
+
+message(STATUS "CMAKE_CXX_FLAGS = ${CMAKE_CXX_FLAGS}")
+
+
 # Add debug mode for Standard C++ library (not for AppleClang since it is broken)
 if (NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
   set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_GLIBCXX_DEBUG -D_LIBCPP_DEBUG=1")
@@ -372,12 +432,14 @@ endif()
 #------------------------------------------------------
 
 # Rang (colors? Useless thus necessary!)
+add_subdirectory(${PUGS_SOURCE_DIR}/packages/rang/)
 include_directories(SYSTEM "${PUGS_SOURCE_DIR}/packages/rang/include")
 
 # CLI11
 include_directories(SYSTEM "${PUGS_SOURCE_DIR}/packages/CLI11/include")
 
 # PEGTL
+add_subdirectory(${PUGS_SOURCE_DIR}/packages/PEGTL/)
 include_directories(SYSTEM "${PUGS_SOURCE_DIR}/packages/PEGTL/include/tao")
 
 # Pugs src
@@ -400,7 +462,7 @@ if(${PUGS_HAS_MPI})
   endif()
   set(MPIEXEC_NUMPROC 3)
   set(MPIEXEC_PATH_FLAG --path)
-  set(MPI_LAUNCHER ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MPIEXEC_NUMPROC} ${MPIEXEC_PATH_FLAG} ${PUGS_BINARY_DIR} ${MPIEXEC_OPTION_FLAGS})
+  set(MPI_LAUNCHER ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MPIEXEC_NUMPROC} ${MPIEXEC_OPTION_FLAGS})
 endif()
 
 add_custom_target(all_unit_tests
@@ -427,11 +489,11 @@ endif()
 
 add_custom_target(run_mpi_unit_tests
   COMMAND ${MPI_LAUNCHER} "${PUGS_BINARY_DIR}/mpi_unit_tests"
+  WORKING_DIRECTORY ${PUGS_BINARY_DIR}
   DEPENDS run_unit_tests
   COMMENT ${RUN_MPI_UNIT_TESTS_COMMENT}
   )
 
-
 add_custom_target(run_unit_tests
   COMMAND "${PUGS_BINARY_DIR}/unit_tests"
   DEPENDS all_unit_tests
@@ -594,10 +656,6 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Coverage")
   endif()
 endif()
 
-# -----------------------------------------------------
-
-link_libraries("-rdynamic")
-
 # ------------------- Source files --------------------
 # Pugs binary
 add_executable(
@@ -625,17 +683,20 @@ target_link_libraries(
   PugsLanguageUtils
   PugsCheckpointing
   Kokkos::kokkos
-  ${PETSC_LIBRARIES}
-  ${SLEPC_LIBRARIES}
-  ${PARMETIS_LIBRARIES}
+  ${PETSC_TARGET}
+  ${SLEPC_TARGET}
+  ${PARMETIS_TARGET}
   ${MPI_CXX_LINK_FLAGS} ${MPI_CXX_LIBRARIES}
   ${KOKKOS_CXX_FLAGS}
   ${OPENMP_LINK_FLAGS}
   ${PUGS_STD_LINK_FLAGS}
   ${HIGHFIVE_TARGET}
-  ${SLURM_LIBRARY}
+  ${SLURM_TARGET}
   stdc++fs
-  )
+)
+
+target_include_directories(pugs PUBLIC ${PETSC_INCLUDE_DIRS})
+target_include_directories(pugs PUBLIC ${HDF5_INCLUDE_DIRS})
 
 # Checkpoint management tool
 add_executable(
@@ -660,19 +721,18 @@ target_link_libraries(
   PugsMesh
   PugsOutput
   Kokkos::kokkos
-  ${PETSC_LIBRARIES}
-  ${SLEPC_LIBRARIES}
-  ${PARMETIS_LIBRARIES}
+  ${PETSC_TARGET}
+  ${SLEPC_TARGET}
+  ${PARMETIS_TARGET}
   ${MPI_CXX_LINK_FLAGS} ${MPI_CXX_LIBRARIES}
   ${KOKKOS_CXX_FLAGS}
   ${OPENMP_LINK_FLAGS}
   ${PUGS_STD_LINK_FLAGS}
   ${HIGHFIVE_TARGET}
-  ${SLURM_LIBRARY}
+  ${SLURM_TARGET}
   stdc++fs
   )
 
-
 # -------------------- Documentation --------------------
 
 include(PugsDoc)
@@ -685,7 +745,6 @@ include(PugsDoxygen)
 install(TARGETS
   pugs
   pugs_checkpoint
-  PugsMesh
   PugsAlgebra
   PugsAnalysis
   PugsCheckpointing
@@ -694,18 +753,33 @@ install(TARGETS
   PugsLanguage
   PugsLanguageAST
   PugsLanguageModules
-  PugsLanguageAlgorithms
+  PugsLanguageUtils
   PugsMesh
-  PugsAlgebra
-  PugsScheme
-  PugsUtils
   PugsOutput
-  PugsLanguageUtils
+  PugsScheme
   kokkos
+  Catch2
+
+  EXPORT "${PROJECT_NAME}Targets"
 
   RUNTIME DESTINATION bin
   LIBRARY DESTINATION lib
-  ARCHIVE DESTINATION lib)
+  ARCHIVE DESTINATION lib
+)
+
+include(CMakePackageConfigHelpers)
+
+write_basic_package_version_file(
+  PugsConfigVersion.cmake
+  VERSION ${PACKAGE_VERSION}
+  COMPATIBILITY AnyNewerVersion
+)
+
+install(EXPORT PugsTargets
+  FILE PugsTargets.cmake
+  NAMESPACE Pugs::
+  DESTINATION lib/cmake/pugs
+)
 
 # ------------------- Build options -------------------
 message("")
@@ -840,3 +914,24 @@ endif()
 
 message("================================")
 message("")
+
+configure_file(
+  ${PUGS_SOURCE_DIR}/cmake/PugsCompileFlags.cmake.in
+  ${PUGS_BINARY_DIR}/cmake/PugsCompileFlags.cmake
+  @ONLY
+)
+
+install(
+  FILES ${PUGS_BINARY_DIR}/cmake/PugsCompileFlags.cmake
+  DESTINATION lib/cmake/pugs
+)
+
+# Ugly patch to install user headers for Catch2 (for plugins)
+install(
+  DIRECTORY
+  "${PUGS_BINARY_DIR}/packages/Catch2/generated-includes/catch2" # Also install the generated header
+  DESTINATION
+  "include"
+  FILES_MATCHING
+  PATTERN "*.hpp"
+  )
diff --git a/README.md b/README.md
index 36e460a851a53d40b4ab442460594091fa22b20f..c859bc694043fc05a83ed47dae3f515eca0bbb42 100644
--- a/README.md
+++ b/README.md
@@ -11,10 +11,10 @@ that are assembled together by a user friendly language.
 ## Requirements
 
 For the most basic build, `pugs` only requires
-- a C++-17 compiler.
+- a C++-20 compiler.
   - g++-10 or higher versions
-  - clang-10 or higher versions
-- `CMake` (at least 3.16)
+  - clang-11 or higher versions
+- `CMake` (at least 3.19)
 
 > **Warning**:<br>
 > Building `pugs` in its source directory is **forbidden**. Trying to
diff --git a/cmake/FindParMETIS.cmake b/cmake/FindParMETIS.cmake
index d9b91d33d92617f3282d4eac2184549b62f62418..5fb1bb961c53897ab8f9cd33a2bf6bedfa41a783 100644
--- a/cmake/FindParMETIS.cmake
+++ b/cmake/FindParMETIS.cmake
@@ -1,6 +1,5 @@
 # Looking for ParMETIS
 
-
 find_path(PARMETIS_INCLUDE_DIR parmetis.h
   PATH_SUFFIX include parmetis $ENV{PARMETIS_INCDIR})
 
@@ -17,7 +16,8 @@ if(EXISTS "${PARMETIS_INCLUDE_DIR}/parmetis.h")
   find_path(METIS_INCLUDE_DIR metis.h $ENV{METIS_INCDIR})
   if(EXISTS "${METIS_INCLUDE_DIR}/metis.h")
     message(STATUS "Found metis.h in ${METIS_INCLUDE_DIR}")
-    set(PARMETIS_LIBRARIES ${LIB_PARMETIS} ${LIB_METIS})
+    set(PARMETIS_LIBRARIES ${LIB_PARMETIS})
+    set(METIS_LIBRARIES  ${LIB_METIS})
     message(STATUS "Found parmetis/metis libraries ${PARMETIS_LIBRARIES}")
     else()
       message(WARNING "** Could not find metis.h.\n** Is METIS_INCDIR correctly set (Actual: \"$ENV{METIS_INCDIR}\")?")
@@ -27,3 +27,4 @@ else()
 endif()
 
 mark_as_advanced(PARMETIS_INCLUDE_DIR PARMETIS_LIBRARIES)
+mark_as_advanced(METIS_INCLUDE_DIR METIS_LIBRARIES)
diff --git a/cmake/PugsCompileFlags.cmake.in b/cmake/PugsCompileFlags.cmake.in
new file mode 100644
index 0000000000000000000000000000000000000000..83ecd3e6036a82c3e8696d2b75de2f35d724051c
--- /dev/null
+++ b/cmake/PugsCompileFlags.cmake.in
@@ -0,0 +1,10 @@
+@PACKAGE_INIT@
+
+set(PUGS_CMAKE_CXX_FLAGS "@CMAKE_CXX_FLAGS@")
+set(PUGS_CMAKE_CXX_STANDARD "@CMAKE_CXX_STANDARD@")
+
+set(PUGS_CMAKE_BUILD_TYPE "@CMAKE_BUILD_TYPE@")
+set(PUGS_CMAKE_CXX_COMPILER "@CMAKE_CXX_COMPILER@")
+set(PUGS_CMAKE_C_COMPILER "@CMAKE_C_COMPILER@")
+
+set(PUGS_HAS_MPI "@PUGS_HAS_MPI@")
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index eebccac12c3c1c7b80b6b1e13ff81d0e802a8def..30d920bb0c111049ae655f67e3951424134dbb01 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -3,6 +3,21 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
 
 #------------------------------------------------------
 
+install(
+  DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/"
+  DESTINATION "include"
+  FILES_MATCHING
+  PATTERN "*.hpp"
+)
+
+install(
+  DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/"
+  DESTINATION "include"
+  FILES_MATCHING
+  PATTERN "*.hpp"
+  PATTERN "CMakeFiles" EXCLUDE
+)
+
 # Pugs utils
 add_subdirectory(utils)
 
diff --git a/src/algebra/CMakeLists.txt b/src/algebra/CMakeLists.txt
index 74f40290a2f9250cc2296ec62358b44c366ffc85..d215b09b50d9933f84aab02855388bb4bd25dced 100644
--- a/src/algebra/CMakeLists.txt
+++ b/src/algebra/CMakeLists.txt
@@ -9,7 +9,10 @@ add_library(
 
 target_link_libraries(
   PugsAlgebra
-  ${PETSC_LIBRARIES}
-  ${SLEPC_LIBRARIES}
+  ${PETSC_TARGET}
+  ${SLEPC_TARGET}
   ${HIGHFIVE_TARGET}
 )
+
+target_include_directories(PugsAlgebra PUBLIC ${PETSC_INCLUDE_DIRS})
+target_include_directories(PugsAlgebra PUBLIC ${SLEPC_INCLUDE_DIRS})
diff --git a/src/algebra/ShrinkVectorView.hpp b/src/algebra/ShrinkVectorView.hpp
index cdf786c5a30970b747b0f540642d5637059198b8..83aed7cb2295ee4d20a044f7ba1039f34e7b5ffa 100644
--- a/src/algebra/ShrinkVectorView.hpp
+++ b/src/algebra/ShrinkVectorView.hpp
@@ -23,10 +23,10 @@ class ShrinkVectorView
   friend std::ostream&
   operator<<(std::ostream& os, const ShrinkVectorView& x)
   {
-    if (x.size() > 0) {
+    if (x.dimension() > 0) {
       os << 0 << ':' << NaNHelper(x[0]);
     }
-    for (size_t i = 1; i < x.size(); ++i) {
+    for (size_t i = 1; i < x.dimension(); ++i) {
       os << ' ' << i << ':' << NaNHelper(x[i]);
     }
     return os;
diff --git a/src/language/ast/ASTModulesImporter.cpp b/src/language/ast/ASTModulesImporter.cpp
index 4cae0a921c139b3ae68a3decb0ce4f83876c4701..e1dc0043e689b506776540bb57e4851dc10883ae 100644
--- a/src/language/ast/ASTModulesImporter.cpp
+++ b/src/language/ast/ASTModulesImporter.cpp
@@ -26,10 +26,10 @@ ASTModulesImporter::_importModule(ASTNode& import_node)
     std::cout << " * importing '" << rang::fgB::green << module_name << rang::style::reset << "' module\n";
   }
 
-  m_module_repository.populateSymbolTable(module_name_node, m_symbol_table);
-  m_module_repository.registerOperators(module_name);
+  ModuleRepository::getInstance().populateSymbolTable(module_name_node, m_symbol_table);
+  ModuleRepository::getInstance().registerOperators(module_name);
   if (CheckpointResumeRepository::isCreated()) {
-    m_module_repository.registerCheckpointResume(module_name);
+    ModuleRepository::getInstance().registerCheckpointResume(module_name);
   }
 }
 
@@ -49,7 +49,7 @@ ASTModulesImporter::ASTModulesImporter(ASTNode& root_node) : m_symbol_table{*roo
 {
   Assert(root_node.is_root());
   OperatorRepository::instance().reset();
-  m_module_repository.populateMandatoryData(root_node, m_symbol_table);
+  ModuleRepository::getInstance().populateMandatoryData(root_node, m_symbol_table);
 
   this->_importAllModules(root_node);
 
diff --git a/src/language/ast/ASTModulesImporter.hpp b/src/language/ast/ASTModulesImporter.hpp
index 566155c4a19ecd00f0b049a29030c0036f212d8a..15ba8a7dc6cecbbb39bfb11c4d202d39200061a4 100644
--- a/src/language/ast/ASTModulesImporter.hpp
+++ b/src/language/ast/ASTModulesImporter.hpp
@@ -14,8 +14,6 @@ class ASTModulesImporter
   std::set<std::string> m_imported_modules;
   SymbolTable& m_symbol_table;
 
-  ModuleRepository m_module_repository;
-
   void _importModule(ASTNode& import_node);
   void _importAllModules(ASTNode& node);
 
@@ -23,7 +21,7 @@ class ASTModulesImporter
   const ModuleRepository&
   moduleRepository() const
   {
-    return m_module_repository;
+    return ModuleRepository::getInstance();
   }
 
   ASTModulesImporter(ASTNode& root_node);
diff --git a/src/language/modules/BinaryOperatorRegisterForVh.cpp b/src/language/modules/BinaryOperatorRegisterForVh.cpp
index 3eb83537323d91bbf6266cbefebf4d004d74c2be..d1c20cb78718568a3535e30f0b5c9ce255be1db6 100644
--- a/src/language/modules/BinaryOperatorRegisterForVh.cpp
+++ b/src/language/modules/BinaryOperatorRegisterForVh.cpp
@@ -1,6 +1,6 @@
 #include <language/modules/BinaryOperatorRegisterForVh.hpp>
 
-#include <language/modules/SchemeModule.hpp>
+#include <language/modules/SchemeModuleTypes.hpp>
 #include <language/utils/BinaryOperatorProcessorBuilder.hpp>
 #include <language/utils/DataHandler.hpp>
 #include <language/utils/DataVariant.hpp>
diff --git a/src/language/modules/CoreModule.cpp b/src/language/modules/CoreModule.cpp
index d7b331a1dc606a83cf77dfe405b184b9ff143308..47e0183256413b7579165b26364dff68f0614a74 100644
--- a/src/language/modules/CoreModule.cpp
+++ b/src/language/modules/CoreModule.cpp
@@ -42,7 +42,7 @@
 #include <utils/checkpointing/ResumingManager.hpp>
 #include <utils/checkpointing/WriteOStream.hpp>
 
-#include <random>
+#include <language/modules/ModuleRepository.hpp>
 
 CoreModule::CoreModule() : BuiltinModule(true)
 {
@@ -219,3 +219,5 @@ CoreModule::registerCheckpointResume() const
 
 #endif   // PUGS_HAS_HDF5
 }
+
+ModuleRepository::Subscribe<CoreModule> core_module;
diff --git a/src/language/modules/DevUtilsModule.cpp b/src/language/modules/DevUtilsModule.cpp
index 361b4357eda0ebbfe4350cc6c64bbe72cfaa0829..4595ea6c9d9a45431b303c00b4de6defdda38b7b 100644
--- a/src/language/modules/DevUtilsModule.cpp
+++ b/src/language/modules/DevUtilsModule.cpp
@@ -1,5 +1,7 @@
 #include <language/modules/DevUtilsModule.hpp>
 
+#include <language/modules/ModuleRepository.hpp>
+
 #include <dev/ParallelChecker.hpp>
 #include <language/modules/MeshModuleTypes.hpp>
 #include <language/modules/SchemeModuleTypes.hpp>
@@ -126,3 +128,5 @@ DevUtilsModule::registerOperators() const
 void
 DevUtilsModule::registerCheckpointResume() const
 {}
+
+ModuleRepository::Subscribe<DevUtilsModule> dev_utils_module;
diff --git a/src/language/modules/LinearSolverModule.cpp b/src/language/modules/LinearSolverModule.cpp
index aef6470d2455909f5ad2d307aa4e5cb859b8bcbb..d3e2de0faeb52c213f372e3a751f075ea2d735d7 100644
--- a/src/language/modules/LinearSolverModule.cpp
+++ b/src/language/modules/LinearSolverModule.cpp
@@ -1,5 +1,7 @@
 #include <language/modules/LinearSolverModule.hpp>
 
+#include <language/modules/ModuleRepository.hpp>
+
 #include <algebra/LinearSolver.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 #include <language/utils/TypeDescriptor.hpp>
@@ -98,3 +100,5 @@ LinearSolverModule::registerOperators() const
 void
 LinearSolverModule::registerCheckpointResume() const
 {}
+
+ModuleRepository::Subscribe<LinearSolverModule> linear_solver_module;
diff --git a/src/language/modules/MathFunctionRegisterForVh.cpp b/src/language/modules/MathFunctionRegisterForVh.cpp
index b31fe47d0eba87ff2e7f0823a530bf170e410d6c..d9a30e86996a9cd6b99af63d64a8ab33264c7fe3 100644
--- a/src/language/modules/MathFunctionRegisterForVh.cpp
+++ b/src/language/modules/MathFunctionRegisterForVh.cpp
@@ -1,6 +1,7 @@
 #include <language/modules/MathFunctionRegisterForVh.hpp>
 
 #include <language/modules/SchemeModule.hpp>
+#include <language/modules/SchemeModuleTypes.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 #include <language/utils/EmbeddedDiscreteFunctionMathFunctions.hpp>
 #include <scheme/DiscreteFunctionVariant.hpp>
diff --git a/src/language/modules/MathModule.cpp b/src/language/modules/MathModule.cpp
index 19a35a1529b1b65310c3c51d69fe4c5d02dc4d8a..1caea452428125e7a1c16aa88cb8278a5685fb35 100644
--- a/src/language/modules/MathModule.cpp
+++ b/src/language/modules/MathModule.cpp
@@ -1,5 +1,6 @@
 #include <language/modules/MathModule.hpp>
 
+#include <language/modules/ModuleRepository.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 
 #include <cmath>
@@ -136,3 +137,5 @@ MathModule::registerOperators() const
 void
 MathModule::registerCheckpointResume() const
 {}
+
+ModuleRepository::Subscribe<MathModule> math_module;
diff --git a/src/language/modules/MeshModule.cpp b/src/language/modules/MeshModule.cpp
index c76260ce58c75c130e4638054fc394458fedbf63..c060da50d6da17915937edda6f5403a9e604355e 100644
--- a/src/language/modules/MeshModule.cpp
+++ b/src/language/modules/MeshModule.cpp
@@ -1,5 +1,7 @@
 #include <language/modules/MeshModule.hpp>
 
+#include <language/modules/ModuleRepository.hpp>
+
 #include <algebra/TinyVector.hpp>
 #include <language/node_processor/ExecutionPolicy.hpp>
 #include <language/utils/BinaryOperatorProcessorBuilder.hpp>
@@ -160,27 +162,28 @@ MeshModule::MeshModule()
 
                                           ));
 
-  this
-    ->_addBuiltinFunction("interpolate",
-                          std::function(
+  this->_addBuiltinFunction("interpolate",
+                            std::function(
 
-                            [](std::shared_ptr<const MeshVariant> mesh_v, std::shared_ptr<const ItemType> item_type,
-                               const FunctionSymbolId& function_id) -> std::shared_ptr<const ItemValueVariant> {
-                              return ItemValueVariantFunctionInterpoler{mesh_v, *item_type, function_id}.interpolate();
-                            }
+                              [](std::shared_ptr<const MeshVariant> mesh_v, std::shared_ptr<const ItemType> item_type,
+                                 const FunctionSymbolId& function_id) -> std::shared_ptr<const ItemValueVariant> {
+                                return ItemValueVariantFunctionInterpoler{mesh_v, *item_type, function_id}
+                                  .interpolate();
+                              }
 
-                            ));
+                              ));
 
-  this->_addBuiltinFunction(
-    "interpolate_array",
-    std::function(
+  this->_addBuiltinFunction("interpolate_array",
+                            std::function(
 
-      [](std::shared_ptr<const MeshVariant> mesh_v, std::shared_ptr<const ItemType> item_type,
-         const std::vector<FunctionSymbolId>& function_id_list) -> std::shared_ptr<const ItemArrayVariant> {
-        return ItemArrayVariantFunctionInterpoler{mesh_v, *item_type, function_id_list}.interpolate();
-      }
+                              [](std::shared_ptr<const MeshVariant> mesh_v, std::shared_ptr<const ItemType> item_type,
+                                 const std::vector<FunctionSymbolId>& function_id_list)
+                                -> std::shared_ptr<const ItemArrayVariant> {
+                                return ItemArrayVariantFunctionInterpoler{mesh_v, *item_type, function_id_list}
+                                  .interpolate();
+                              }
 
-      ));
+                              ));
 
   this->_addBuiltinFunction("transform",
                             std::function(
@@ -442,3 +445,5 @@ MeshModule::registerCheckpointResume() const
 
 #endif   // PUGS_HAS_HDF5
 }
+
+ModuleRepository::Subscribe<MeshModule> mesh_module;
diff --git a/src/language/modules/ModuleRepository.cpp b/src/language/modules/ModuleRepository.cpp
index 59689fb6fee512e45cd76c7c9d3cd5c11d543ee8..c9c487b67b5fdbf63d2b307baf1497519666a5d8 100644
--- a/src/language/modules/ModuleRepository.cpp
+++ b/src/language/modules/ModuleRepository.cpp
@@ -1,14 +1,6 @@
 #include <language/modules/ModuleRepository.hpp>
 
 #include <language/ast/ASTNode.hpp>
-#include <language/modules/CoreModule.hpp>
-#include <language/modules/DevUtilsModule.hpp>
-#include <language/modules/LinearSolverModule.hpp>
-#include <language/modules/MathModule.hpp>
-#include <language/modules/MeshModule.hpp>
-#include <language/modules/SchemeModule.hpp>
-#include <language/modules/SocketModule.hpp>
-#include <language/modules/WriterModule.hpp>
 #include <language/utils/BasicAffectationRegistrerFor.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 #include <language/utils/CheckpointResumeRepository.hpp>
@@ -19,9 +11,29 @@
 
 #include <utils/PugsAssert.hpp>
 
-#include <algorithm>
 #include <set>
 
+ModuleRepository* ModuleRepository::m_instance = nullptr;
+
+ModuleRepository&
+ModuleRepository::getInstance()
+{
+  if (m_instance == nullptr) {
+    m_instance = new ModuleRepository;
+  }
+
+  return *m_instance;
+}
+
+void
+ModuleRepository::destroy()
+{
+  if (m_instance != nullptr) {
+    delete m_instance;
+    m_instance = nullptr;
+  }
+}
+
 void
 ModuleRepository::_subscribe(std::unique_ptr<IModule> m)
 {
@@ -52,18 +64,6 @@ ModuleRepository::_subscribe(std::unique_ptr<IModule> m)
   Assert(success, "module has already been subscribed");
 }
 
-ModuleRepository::ModuleRepository()
-{
-  this->_subscribe(std::make_unique<CoreModule>());
-  this->_subscribe(std::make_unique<LinearSolverModule>());
-  this->_subscribe(std::make_unique<MathModule>());
-  this->_subscribe(std::make_unique<MeshModule>());
-  this->_subscribe(std::make_unique<SchemeModule>());
-  this->_subscribe(std::make_unique<SocketModule>());
-  this->_subscribe(std::make_unique<DevUtilsModule>());
-  this->_subscribe(std::make_unique<WriterModule>());
-}
-
 template <typename NameEmbedderMapT, typename EmbedderTableT>
 void
 ModuleRepository::_populateEmbedderTableT(const ASTNode& module_node,
diff --git a/src/language/modules/ModuleRepository.hpp b/src/language/modules/ModuleRepository.hpp
index 407d7b55bd9b7c8454159a315b9c2f7a256eecb6..35bf6f7aa6a7704f2804125c488ac0bf115ddf39 100644
--- a/src/language/modules/ModuleRepository.hpp
+++ b/src/language/modules/ModuleRepository.hpp
@@ -31,7 +31,23 @@ class ModuleRepository
                             const IModule::NameValueMap& name_value_map,
                             SymbolTable& symbol_table);
 
+  static ModuleRepository* m_instance;
+
  public:
+  static ModuleRepository& getInstance();
+
+  static void destroy();
+
+  template <typename ModuleT>
+  struct Subscribe
+  {
+    Subscribe()
+    {
+      static_assert(std::is_base_of_v<IModule, ModuleT>, "module must inherit IModule interface");
+      ModuleRepository::getInstance()._subscribe(std::make_unique<ModuleT>());
+    }
+  };
+
   void populateSymbolTable(const ASTNode& module_name_node, SymbolTable& symbol_table);
   void populateMandatoryData(const ASTNode& root_node, SymbolTable& symbol_table);
   void registerOperators(const std::string& module_name);
@@ -46,7 +62,10 @@ class ModuleRepository
   ModuleRepository(const ModuleRepository&) = delete;
   ModuleRepository(ModuleRepository&&)      = delete;
 
-  ModuleRepository();
+ private:
+  ModuleRepository() = default;
+
+ public:
   ~ModuleRepository() = default;
 };
 
diff --git a/src/language/modules/SchemeModule.cpp b/src/language/modules/SchemeModule.cpp
index e1729dace44e70d88cd35aca7034fde3936c523b..11cb53bc1ee9d79d10edab6a1d3d8ca21c07e0a3 100644
--- a/src/language/modules/SchemeModule.cpp
+++ b/src/language/modules/SchemeModule.cpp
@@ -1,4 +1,7 @@
 #include <language/modules/SchemeModule.hpp>
+#include <language/modules/SchemeModuleTypes.hpp>
+
+#include <language/modules/ModuleRepository.hpp>
 
 #include <analysis/GaussLegendreQuadratureDescriptor.hpp>
 #include <analysis/GaussLobattoQuadratureDescriptor.hpp>
@@ -1716,3 +1719,5 @@ SchemeModule::registerCheckpointResume() const
 
 #endif   // PUGS_HAS_HDF5
 }
+
+ModuleRepository::Subscribe<SchemeModule> scheme_module;
diff --git a/src/language/modules/SchemeModule.hpp b/src/language/modules/SchemeModule.hpp
index c61b3084406dbd8ae9889c79b4172195daaa6fe8..cf169175c4b46f7364799e16bdb4b84633d1f61c 100644
--- a/src/language/modules/SchemeModule.hpp
+++ b/src/language/modules/SchemeModule.hpp
@@ -2,7 +2,6 @@
 #define SCHEME_MODULE_HPP
 
 #include <language/modules/BuiltinModule.hpp>
-#include <language/modules/SchemeModuleTypes.hpp>
 
 class SchemeModule : public BuiltinModule
 {
diff --git a/src/language/modules/SocketModule.cpp b/src/language/modules/SocketModule.cpp
index 79321d088f960e0bb7450c82820cf6568e79d174..9c2ecdbe1b40730d0181d7f4ee6f3bf396f70a34 100644
--- a/src/language/modules/SocketModule.cpp
+++ b/src/language/modules/SocketModule.cpp
@@ -1,5 +1,7 @@
 #include <language/modules/SocketModule.hpp>
 
+#include <language/modules/ModuleRepository.hpp>
+
 #include <language/utils/BinaryOperatorProcessorBuilder.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 #include <language/utils/CheckpointResumeRepository.hpp>
@@ -271,3 +273,5 @@ SocketModule::registerCheckpointResume() const
                            throw NotImplementedError("checkpoint/resume with sockets");
                          }));
 }
+
+ModuleRepository::Subscribe<SocketModule> socket_module;
diff --git a/src/language/modules/UnaryOperatorRegisterForVh.cpp b/src/language/modules/UnaryOperatorRegisterForVh.cpp
index e2e9c2db9b72a9a687806d24dee836cfd37a22e8..3d52ab54687eabcd6162536d88325b3285d45e1f 100644
--- a/src/language/modules/UnaryOperatorRegisterForVh.cpp
+++ b/src/language/modules/UnaryOperatorRegisterForVh.cpp
@@ -1,6 +1,6 @@
 #include <language/modules/UnaryOperatorRegisterForVh.hpp>
 
-#include <language/modules/SchemeModule.hpp>
+#include <language/modules/SchemeModuleTypes.hpp>
 #include <language/utils/DataHandler.hpp>
 #include <language/utils/DataVariant.hpp>
 #include <language/utils/EmbeddedDiscreteFunctionOperators.hpp>
diff --git a/src/language/modules/WriterModule.cpp b/src/language/modules/WriterModule.cpp
index 6d84506501de277f1970c13a7c24cd587ed79f20..e524a885639ac9a2a2da6330422565447bcb347d 100644
--- a/src/language/modules/WriterModule.cpp
+++ b/src/language/modules/WriterModule.cpp
@@ -1,7 +1,9 @@
 #include <language/modules/WriterModule.hpp>
 
-#include <language/modules/MeshModule.hpp>
-#include <language/modules/SchemeModule.hpp>
+#include <language/modules/ModuleRepository.hpp>
+
+#include <language/modules/MeshModuleTypes.hpp>
+#include <language/modules/SchemeModuleTypes.hpp>
 #include <language/utils/BuiltinFunctionEmbedder.hpp>
 #include <language/utils/CheckpointResumeRepository.hpp>
 #include <language/utils/TypeDescriptor.hpp>
@@ -242,3 +244,5 @@ WriterModule::registerCheckpointResume() const
 
 #endif   // PUGS_HAS_HDF5
 }
+
+ModuleRepository::Subscribe<WriterModule> writer_module{};
diff --git a/src/main.cpp b/src/main.cpp
index c1eeef6a633d5074fef20545a2348a77e5a6888a..cbaf6277e5ee0c1c7d689403e8d78cae54c3ed5f 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,6 +1,7 @@
 #include <analysis/QuadratureManager.hpp>
 #include <dev/ParallelChecker.hpp>
 #include <language/PugsParser.hpp>
+#include <language/modules/ModuleRepository.hpp>
 #include <mesh/DualConnectivityManager.hpp>
 #include <mesh/DualMeshManager.hpp>
 #include <mesh/MeshDataManager.hpp>
@@ -12,6 +13,8 @@
 #include <utils/RandomEngine.hpp>
 #include <utils/checkpointing/ResumingManager.hpp>
 
+#include <utils/PluginsLoader.hpp>
+
 int
 main(int argc, char* argv[])
 {
@@ -23,6 +26,8 @@ main(int argc, char* argv[])
 
   std::string filename = initialize(argc, argv);
 
+  PluginsLoader plugins_loader;
+
   SynchronizerManager::create();
   RandomEngine::create();
   QuadratureManager::create();
@@ -34,6 +39,8 @@ main(int argc, char* argv[])
   parser(filename);
   ExecutionStatManager::printInfo();
 
+  ModuleRepository::destroy();
+
   StencilManager::destroy();
   DualMeshManager::destroy();
   DualConnectivityManager::destroy();
diff --git a/src/mesh/CMakeLists.txt b/src/mesh/CMakeLists.txt
index b4d70359a5a4a7f8a4d2e352ab702ecc26761dbc..781b7e583595eab942d2f924de705cf332841fad 100644
--- a/src/mesh/CMakeLists.txt
+++ b/src/mesh/CMakeLists.txt
@@ -50,5 +50,6 @@ add_library(
 
 target_link_libraries(
   PugsMesh
+  ${PARMETIS_TARGET}
   ${HIGHFIVE_TARGET}
 )
diff --git a/src/mesh/StencilArray.hpp b/src/mesh/StencilArray.hpp
index 9571c346bae6e4e345a16b09188dce15ab73b456..ffe8638bf0d8fdb472780150603c7fab21ca1eb9 100644
--- a/src/mesh/StencilArray.hpp
+++ b/src/mesh/StencilArray.hpp
@@ -12,6 +12,9 @@ class StencilArray
  public:
   using ItemToItemMatrixT = ItemToItemMatrix<source_item_type, target_item_type>;
 
+  using SourceItemId = ItemIdT<source_item_type>;
+  using TargetItemId = ItemIdT<target_item_type>;
+
   class BoundaryDescriptorStencilArray
   {
    private:
@@ -71,9 +74,9 @@ class StencilArray
 
   PUGS_INLINE
   auto
-  operator[](CellId cell_id) const
+  operator[](SourceItemId source_item_id) const
   {
-    return m_stencil_array[cell_id];
+    return m_stencil_array[source_item_id];
   }
 
   StencilArray(const ConnectivityMatrix& connectivity_matrix,
diff --git a/src/mesh/StencilBuilder.cpp b/src/mesh/StencilBuilder.cpp
index 31db762a1002e3c633afeaabd4f06d6a98dff634..20fc3bf61dd6a59ef82ff7bdb25dc5e32c7eef24 100644
--- a/src/mesh/StencilBuilder.cpp
+++ b/src/mesh/StencilBuilder.cpp
@@ -5,16 +5,23 @@
 #include <utils/GlobalVariableManager.hpp>
 #include <utils/Messenger.hpp>
 
-#include <set>
-
 template <ItemType item_type>
 class StencilBuilder::Layer
 {
   using ItemId = ItemIdT<item_type>;
+  ItemValue<const int, item_type> m_number_of;
+
   std::vector<ItemId> m_item_id_vector;
   std::vector<int> m_item_number_vector;
 
  public:
+  void
+  clear()
+  {
+    m_item_id_vector.clear();
+    m_item_number_vector.clear();
+  }
+
   size_t
   size() const
   {
@@ -22,9 +29,17 @@ class StencilBuilder::Layer
     return m_item_id_vector.size();
   }
 
+  const std::vector<ItemId>&
+  itemIdList() const
+  {
+    return m_item_id_vector;
+  }
+
   bool
-  hasItemNumber(const int item_number) const
+  hasItemNumber(const ItemId item_id) const
   {
+    const int item_number = m_number_of[item_id];
+
     ssize_t begin = 0;
     ssize_t end   = m_item_number_vector.size();
 
@@ -37,26 +52,22 @@ class StencilBuilder::Layer
         if (begin == mid) {
           break;
         }
-        begin = mid - 1;
+        begin = mid;
       } else {
         if (end == mid) {
           break;
         }
-        end = mid + 1;
+        end = mid;
       }
     }
     return false;
   }
 
-  const std::vector<ItemId>&
-  itemIdList() const
-  {
-    return m_item_id_vector;
-  }
-
   void
-  add(const ItemId item_id, const int item_number)
+  add(const ItemId item_id)
   {
+    const int item_number = m_number_of[item_id];
+
     ssize_t begin = 0;
     ssize_t end   = m_item_number_vector.size();
 
@@ -97,7 +108,14 @@ class StencilBuilder::Layer
     }
   }
 
-  Layer() = default;
+  Layer& operator=(const Layer&) = default;
+  Layer& operator=(Layer&&)      = default;
+
+  template <size_t Dimension>
+  Layer(const Connectivity<Dimension>& connectivity) : m_number_of{connectivity.template number<item_type>()}
+  {}
+
+  //  Layer() = default;
 
   Layer(const Layer&) = default;
   Layer(Layer&&)      = default;
@@ -117,27 +135,25 @@ StencilBuilder::_buildSymmetryConnectingItemList(const ConnectivityType& connect
                                                                       symmetry_boundary_descriptor_list.size()};
   symmetry_connecting_item_list.fill(false);
 
-  if constexpr (ConnectivityType::Dimension > 1) {
-    auto face_to_connecting_item_matrix =
-      connectivity.template getItemToItemMatrix<ItemType::face, connecting_item_type>();
+  if constexpr (ItemTypeId<ConnectivityType::Dimension>::itemTId(connecting_item_type) ==
+                ItemTypeId<ConnectivityType::Dimension>::itemTId(ItemType::face)) {
     size_t i_symmetry_boundary = 0;
     for (auto p_boundary_descriptor : symmetry_boundary_descriptor_list) {
       const IBoundaryDescriptor& boundary_descriptor = *p_boundary_descriptor;
 
       bool found = false;
-      for (size_t i_ref_face_list = 0; i_ref_face_list < connectivity.template numberOfRefItemList<ItemType::face>();
-           ++i_ref_face_list) {
-        const auto& ref_face_list = connectivity.template refItemList<ItemType::face>(i_ref_face_list);
-        if (ref_face_list.refId() == boundary_descriptor) {
-          found = true;
-          for (size_t i_face = 0; i_face < ref_face_list.list().size(); ++i_face) {
-            const FaceId face_id      = ref_face_list.list()[i_face];
-            auto connecting_item_list = face_to_connecting_item_matrix[face_id];
-            for (size_t i_connecting_item = 0; i_connecting_item < connecting_item_list.size(); ++i_connecting_item) {
-              const ConnectingItemId connecting_item_id = connecting_item_list[i_connecting_item];
+      for (size_t i_ref_connecting_item_list = 0;
+           i_ref_connecting_item_list < connectivity.template numberOfRefItemList<connecting_item_type>();
+           ++i_ref_connecting_item_list) {
+        const auto& ref_connecting_item_list =
+          connectivity.template refItemList<connecting_item_type>(i_ref_connecting_item_list);
+        if (ref_connecting_item_list.refId() == boundary_descriptor) {
+          found                     = true;
+          auto connecting_item_list = ref_connecting_item_list.list();
+          for (size_t i_connecting_item = 0; i_connecting_item < connecting_item_list.size(); ++i_connecting_item) {
+            const ConnectingItemId connecting_item_id = connecting_item_list[i_connecting_item];
 
-              symmetry_connecting_item_list[connecting_item_id][i_symmetry_boundary] = true;
-            }
+            symmetry_connecting_item_list[connecting_item_id][i_symmetry_boundary] = true;
           }
           break;
         }
@@ -149,26 +165,27 @@ StencilBuilder::_buildSymmetryConnectingItemList(const ConnectivityType& connect
         throw NormalError(error_msg.str());
       }
     }
-
-    return symmetry_connecting_item_list;
   } else {
+    auto face_to_connecting_item_matrix =
+      connectivity.template getItemToItemMatrix<ItemType::face, connecting_item_type>();
     size_t i_symmetry_boundary = 0;
     for (auto p_boundary_descriptor : symmetry_boundary_descriptor_list) {
       const IBoundaryDescriptor& boundary_descriptor = *p_boundary_descriptor;
 
       bool found = false;
-      for (size_t i_ref_connecting_item_list = 0;
-           i_ref_connecting_item_list < connectivity.template numberOfRefItemList<connecting_item_type>();
-           ++i_ref_connecting_item_list) {
-        const auto& ref_connecting_item_list =
-          connectivity.template refItemList<connecting_item_type>(i_ref_connecting_item_list);
-        if (ref_connecting_item_list.refId() == boundary_descriptor) {
-          found                     = true;
-          auto connecting_item_list = ref_connecting_item_list.list();
-          for (size_t i_connecting_item = 0; i_connecting_item < connecting_item_list.size(); ++i_connecting_item) {
-            const ConnectingItemId connecting_item_id = connecting_item_list[i_connecting_item];
+      for (size_t i_ref_face_list = 0; i_ref_face_list < connectivity.template numberOfRefItemList<ItemType::face>();
+           ++i_ref_face_list) {
+        const auto& ref_face_list = connectivity.template refItemList<ItemType::face>(i_ref_face_list);
+        if (ref_face_list.refId() == boundary_descriptor) {
+          found = true;
+          for (size_t i_face = 0; i_face < ref_face_list.list().size(); ++i_face) {
+            const FaceId face_id      = ref_face_list.list()[i_face];
+            auto connecting_item_list = face_to_connecting_item_matrix[face_id];
+            for (size_t i_connecting_item = 0; i_connecting_item < connecting_item_list.size(); ++i_connecting_item) {
+              const ConnectingItemId connecting_item_id = connecting_item_list[i_connecting_item];
 
-            symmetry_connecting_item_list[connecting_item_id][i_symmetry_boundary] = true;
+              symmetry_connecting_item_list[connecting_item_id][i_symmetry_boundary] = true;
+            }
           }
           break;
         }
@@ -180,357 +197,572 @@ StencilBuilder::_buildSymmetryConnectingItemList(const ConnectivityType& connect
         throw NormalError(error_msg.str());
       }
     }
-
-    return symmetry_connecting_item_list;
   }
+
+  return symmetry_connecting_item_list;
 }
 
-template <typename ConnectivityType>
-Array<const uint32_t>
-StencilBuilder::_getRowMap(const ConnectivityType& connectivity) const
+template <ItemType item_type, ItemType connecting_item_type, typename ConnectivityType>
+StencilArray<item_type, item_type>
+StencilBuilder::_build_for_same_source_and_target(const ConnectivityType& connectivity,
+                                                  const size_t& number_of_layers,
+                                                  const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const
 {
-  auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
-  auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+  if (number_of_layers == 0) {
+    throw NormalError("number of layers must be greater than 0 to build stencils");
+  }
+
+  auto item_to_connecting_item_matrix = connectivity.template getItemToItemMatrix<item_type, connecting_item_type>();
+  auto connecting_item_to_item_matrix = connectivity.template getItemToItemMatrix<connecting_item_type, item_type>();
+
+  auto item_is_owned          = connectivity.template isOwned<item_type>();
+  auto item_number            = connectivity.template number<item_type>();
+  auto connecting_item_number = connectivity.template number<connecting_item_type>();
+
+  using ItemId           = ItemIdT<item_type>;
+  using ConnectingItemId = ItemIdT<connecting_item_type>;
 
-  auto cell_is_owned = connectivity.cellIsOwned();
+  ItemArray<bool, connecting_item_type> symmetry_item_list =
+    this->_buildSymmetryConnectingItemList<connecting_item_type>(connectivity, symmetry_boundary_descriptor_list);
 
-  Array<uint32_t> row_map{connectivity.numberOfCells() + 1};
+  Array<uint32_t> row_map{connectivity.template numberOf<item_type>() + 1};
   row_map[0] = 0;
-  std::vector<CellId> neighbors;
-  for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-    neighbors.resize(0);
-    // The stencil is not built for ghost cells
-    if (cell_is_owned[cell_id]) {
-      auto cell_nodes = cell_to_node_matrix[cell_id];
-      for (size_t i_node = 0; i_node < cell_nodes.size(); ++i_node) {
-        const NodeId node_id = cell_nodes[i_node];
-        auto node_cells      = node_to_cell_matrix[node_id];
-        for (size_t i_node_cell = 0; i_node_cell < node_cells.size(); ++i_node_cell) {
-          const CellId node_cell_id = node_cells[i_node_cell];
-          if (node_cell_id != cell_id) {
-            neighbors.push_back(node_cells[i_node_cell]);
+  std::vector<Array<uint32_t>> symmetry_row_map_list(symmetry_boundary_descriptor_list.size());
+  for (auto&& symmetry_row_map : symmetry_row_map_list) {
+    symmetry_row_map    = Array<uint32_t>{connectivity.template numberOf<item_type>() + 1};
+    symmetry_row_map[0] = 0;
+  }
+
+  std::vector<ItemId> column_indices_vector;
+  std::vector<std::vector<uint32_t>> symmetry_column_indices_vector(symmetry_boundary_descriptor_list.size());
+
+  std::vector<Layer<item_type>> item_layer_list;
+  std::vector<Layer<connecting_item_type>> connecting_layer_list;
+
+  std::vector<Layer<item_type>> symmetry_item_layer_list;
+  std::vector<Layer<connecting_item_type>> symmetry_connecting_layer_list;
+
+  for (size_t i = 0; i < number_of_layers; ++i) {
+    item_layer_list.emplace_back(Layer<item_type>{connectivity});
+    connecting_layer_list.emplace_back(Layer<connecting_item_type>{connectivity});
+
+    if (symmetry_boundary_descriptor_list.size() > 0) {
+      symmetry_item_layer_list.emplace_back(Layer<item_type>{connectivity});
+      symmetry_connecting_layer_list.emplace_back(Layer<connecting_item_type>{connectivity});
+    }
+  }
+
+  for (ItemId item_id = 0; item_id < connectivity.template numberOf<item_type>(); ++item_id) {
+    if (item_is_owned[item_id]) {
+      for (auto&& item_layer : item_layer_list) {
+        item_layer.clear();
+      }
+      for (auto&& connecting_layer : connecting_layer_list) {
+        connecting_layer.clear();
+      }
+
+      // First layer is a special case
+      {
+        auto& item_layer       = item_layer_list[0];
+        auto& connecting_layer = connecting_layer_list[0];
+
+        for (size_t i_connecting_item = 0; i_connecting_item < item_to_connecting_item_matrix[item_id].size();
+             ++i_connecting_item) {
+          const ConnectingItemId connecting_item_id = item_to_connecting_item_matrix[item_id][i_connecting_item];
+
+          connecting_layer.add(connecting_item_id);
+        }
+
+        for (auto connecting_item_id : connecting_layer.itemIdList()) {
+          for (size_t i_item = 0; i_item < connecting_item_to_item_matrix[connecting_item_id].size(); ++i_item) {
+            const ItemId layer_item_id = connecting_item_to_item_matrix[connecting_item_id][i_item];
+            if (layer_item_id != item_id) {
+              item_layer.add(layer_item_id);
+            }
           }
         }
       }
-      std::sort(neighbors.begin(), neighbors.end());
-      neighbors.erase(std::unique(neighbors.begin(), neighbors.end()), neighbors.end());
-    }
-    // The cell itself is not counted
-    row_map[cell_id + 1] = row_map[cell_id] + neighbors.size();
-  }
 
-  return row_map;
-}
+      for (size_t i_layer = 1; i_layer < number_of_layers; ++i_layer) {
+        auto& connecting_layer = connecting_layer_list[i_layer];
 
-template <typename ConnectivityType>
-Array<const uint32_t>
-StencilBuilder::_getColumnIndices(const ConnectivityType& connectivity, const Array<const uint32_t>& row_map) const
-{
-  auto cell_number = connectivity.cellNumber();
+        auto has_connecting_item = [&i_layer, &connecting_layer_list](ConnectingItemId connecting_item_id) -> bool {
+          for (ssize_t i = i_layer - 1; i >= 0; --i) {
+            if (connecting_layer_list[i].hasItemNumber(connecting_item_id)) {
+              return true;
+            }
+          }
 
-  Array<uint32_t> max_index(row_map.size() - 1);
-  parallel_for(
-    max_index.size(), PUGS_LAMBDA(size_t i) { max_index[i] = row_map[i]; });
-
-  auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
-  auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
-
-  auto cell_is_owned = connectivity.cellIsOwned();
-
-  Array<uint32_t> column_indices(row_map[row_map.size() - 1]);
-  column_indices.fill(std::numeric_limits<uint32_t>::max());
-
-  for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-    // The stencil is not built for ghost cells
-    if (cell_is_owned[cell_id]) {
-      auto cell_nodes = cell_to_node_matrix[cell_id];
-      for (size_t i_node = 0; i_node < cell_nodes.size(); ++i_node) {
-        const NodeId node_id = cell_nodes[i_node];
-        auto node_cells      = node_to_cell_matrix[node_id];
-        for (size_t i_node_cell = 0; i_node_cell < node_cells.size(); ++i_node_cell) {
-          const CellId node_cell_id = node_cells[i_node_cell];
-          if (node_cell_id != cell_id) {
-            bool found = false;
-            for (size_t i_index = row_map[cell_id]; i_index < max_index[cell_id]; ++i_index) {
-              if (column_indices[i_index] == node_cell_id) {
-                found = true;
-                break;
+          return false;
+        };
+
+        for (auto&& previous_layer_item_id_list : item_layer_list[i_layer - 1].itemIdList()) {
+          const auto item_to_connecting_item_list = item_to_connecting_item_matrix[previous_layer_item_id_list];
+          for (size_t i_connecting_item = 0; i_connecting_item < item_to_connecting_item_list.size();
+               ++i_connecting_item) {
+            const ConnectingItemId connecting_item_id = item_to_connecting_item_list[i_connecting_item];
+
+            if (not has_connecting_item(connecting_item_id)) {
+              connecting_layer.add(connecting_item_id);
+            }
+          }
+        }
+
+        auto& item_layer = item_layer_list[i_layer];
+
+        auto has_layer_item = [&i_layer, &item_layer_list](ItemId layer_item_id) -> bool {
+          for (ssize_t i = i_layer - 1; i >= 0; --i) {
+            if (item_layer_list[i].hasItemNumber(layer_item_id)) {
+              return true;
+            }
+          }
+
+          return false;
+        };
+
+        for (auto connecting_item_id : connecting_layer.itemIdList()) {
+          const auto& connecting_item_to_item_list = connecting_item_to_item_matrix[connecting_item_id];
+          for (size_t i_item = 0; i_item < connecting_item_to_item_list.size(); ++i_item) {
+            const ItemId layer_item_id = connecting_item_to_item_list[i_item];
+            if ((layer_item_id != item_id) and (not has_layer_item(layer_item_id))) {
+              item_layer.add(layer_item_id);
+            }
+          }
+        }
+      }
+
+      for (size_t i_symmetry = 0; i_symmetry < symmetry_boundary_descriptor_list.size(); ++i_symmetry) {
+        for (auto&& symmetry_item_layer : symmetry_item_layer_list) {
+          symmetry_item_layer.clear();
+        }
+        for (auto&& symmetry_connecting_layer : symmetry_connecting_layer_list) {
+          symmetry_connecting_layer.clear();
+        }
+
+        // First layer is a special case
+        for (auto&& connecting_item_id : connecting_layer_list[0].itemIdList()) {
+          if (symmetry_item_list[connecting_item_id][i_symmetry]) {
+            symmetry_connecting_layer_list[0].add(connecting_item_id);
+          }
+        }
+
+        for (auto&& connecting_item_id : symmetry_connecting_layer_list[0].itemIdList()) {
+          auto item_of_connecting_item_list = connecting_item_to_item_matrix[connecting_item_id];
+          for (size_t i_item_of_connecting_item = 0; i_item_of_connecting_item < item_of_connecting_item_list.size();
+               ++i_item_of_connecting_item) {
+            const ItemId item_id_of_connecting_item = item_of_connecting_item_list[i_item_of_connecting_item];
+            symmetry_item_layer_list[0].add(item_id_of_connecting_item);
+          }
+        }
+
+        for (size_t i_layer = 1; i_layer < number_of_layers; ++i_layer) {
+          auto has_connecting_item = [&i_layer,
+                                      &symmetry_connecting_layer_list](ConnectingItemId connecting_item_id) -> bool {
+            for (ssize_t i = i_layer - 1; i >= 0; --i) {
+              if (symmetry_connecting_layer_list[i].hasItemNumber(connecting_item_id)) {
+                return true;
               }
             }
-            if (not found) {
-              const auto node_cell_number = cell_number[node_cell_id];
-              size_t i_index              = row_map[cell_id];
-              // search for position for index
-              while ((i_index < max_index[cell_id])) {
-                if (node_cell_number > cell_number[CellId(column_indices[i_index])]) {
-                  ++i_index;
-                } else {
-                  break;
-                }
+
+            return false;
+          };
+
+          for (auto&& symmetry_connecting_item_id : connecting_layer_list[i_layer].itemIdList()) {
+            if (symmetry_item_list[symmetry_connecting_item_id][i_symmetry]) {
+              if (not has_connecting_item(symmetry_connecting_item_id)) {
+                symmetry_connecting_layer_list[i_layer].add(symmetry_connecting_item_id);
               }
+            }
+          }
 
-              for (size_t i_destination = max_index[cell_id]; i_destination > i_index; --i_destination) {
-                const size_t i_source = i_destination - 1;
+          for (auto&& previous_layer_item_id_list : symmetry_item_layer_list[i_layer - 1].itemIdList()) {
+            const auto item_to_connecting_item_list = item_to_connecting_item_matrix[previous_layer_item_id_list];
+            for (size_t i_connecting_item = 0; i_connecting_item < item_to_connecting_item_list.size();
+                 ++i_connecting_item) {
+              const ConnectingItemId connecting_item_id = item_to_connecting_item_list[i_connecting_item];
 
-                column_indices[i_destination] = column_indices[i_source];
+              if (not has_connecting_item(connecting_item_id)) {
+                symmetry_connecting_layer_list[i_layer].add(connecting_item_id);
+              }
+            }
+          }
+
+          auto has_symmetry_layer_item = [&i_layer, &symmetry_item_layer_list](ItemId layer_item_id) -> bool {
+            for (ssize_t i = i_layer - 1; i >= 0; --i) {
+              if (symmetry_item_layer_list[i].hasItemNumber(layer_item_id)) {
+                return true;
+              }
+            }
+
+            return false;
+          };
+
+          for (auto&& connecting_item_id : symmetry_connecting_layer_list[i_layer].itemIdList()) {
+            auto item_of_connecting_item_list = connecting_item_to_item_matrix[connecting_item_id];
+            for (size_t i_item_of_connecting_item = 0; i_item_of_connecting_item < item_of_connecting_item_list.size();
+                 ++i_item_of_connecting_item) {
+              const ItemId item_id_of_connecting_item = item_of_connecting_item_list[i_item_of_connecting_item];
+              if (not has_symmetry_layer_item(item_id_of_connecting_item)) {
+                symmetry_item_layer_list[i_layer].add(item_id_of_connecting_item);
               }
-              ++max_index[cell_id];
-              column_indices[i_index] = node_cell_id;
             }
           }
         }
+
+        for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) {
+          for (auto symmetry_layer_item_id : symmetry_item_layer_list[i_layer].itemIdList()) {
+            symmetry_column_indices_vector[i_symmetry].push_back(symmetry_layer_item_id);
+          }
+        }
+
+        {
+          size_t stencil_size = 0;
+          for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) {
+            auto& item_layer = symmetry_item_layer_list[i_layer];
+            stencil_size += item_layer.size();
+          }
+
+          symmetry_row_map_list[i_symmetry][item_id + 1] = symmetry_row_map_list[i_symmetry][item_id] + stencil_size;
+        }
+      }
+
+      {
+        size_t stencil_size = 0;
+        for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) {
+          auto& item_layer = item_layer_list[i_layer];
+          for (auto stencil_item_id : item_layer.itemIdList()) {
+            column_indices_vector.push_back(stencil_item_id);
+          }
+          stencil_size += item_layer.size();
+        }
+        row_map[item_id + 1] = row_map[item_id] + stencil_size;
+      }
+
+    } else {
+      row_map[item_id + 1] = row_map[item_id];
+      for (size_t i = 0; i < symmetry_row_map_list.size(); ++i) {
+        symmetry_row_map_list[i][item_id + 1] = symmetry_row_map_list[i][item_id];
       }
     }
   }
 
-  return column_indices;
+  Array<uint32_t> column_indices{column_indices_vector.size()};
+  parallel_for(
+    column_indices_vector.size(), PUGS_LAMBDA(size_t i) { column_indices[i] = column_indices_vector[i]; });
+
+  ConnectivityMatrix primal_stencil{row_map, column_indices};
+
+  typename StencilArray<item_type, item_type>::BoundaryDescriptorStencilArrayList symmetry_boundary_stencil_list;
+  {
+    size_t i = 0;
+    for (auto&& p_boundary_descriptor : symmetry_boundary_descriptor_list) {
+      symmetry_boundary_stencil_list.emplace_back(
+        typename StencilArray<item_type, item_type>::
+          BoundaryDescriptorStencilArray{p_boundary_descriptor,
+                                         ConnectivityMatrix{symmetry_row_map_list[i],
+                                                            convert_to_array(symmetry_column_indices_vector[i])}});
+      ++i;
+    }
+  }
+
+  return {{primal_stencil}, {symmetry_boundary_stencil_list}};
 }
 
-template <ItemType item_type, ItemType connecting_item_type, typename ConnectivityType>
-StencilArray<item_type, item_type>
-StencilBuilder::_build_for_same_source_and_target(const ConnectivityType& connectivity,
-                                                  const size_t& number_of_layers,
-                                                  const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const
+template <ItemType source_item_type,
+          ItemType connecting_item_type,
+          ItemType target_item_type,
+          typename ConnectivityType>
+StencilArray<source_item_type, target_item_type>
+StencilBuilder::_build_for_different_source_and_target(
+  const ConnectivityType& connectivity,
+  const size_t& number_of_layers,
+  const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const
 {
+  constexpr size_t Dimension = ConnectivityType::Dimension;
+
   if (number_of_layers == 0) {
     throw NormalError("number of layers must be greater than 0 to build stencils");
   }
-  if (number_of_layers > 2) {
-    throw NotImplementedError("number of layers too large");
-  }
-  auto item_to_connecting_item_matrix = connectivity.template getItemToItemMatrix<item_type, connecting_item_type>();
-  auto connecting_item_to_item_matrix = connectivity.template getItemToItemMatrix<connecting_item_type, item_type>();
 
-  auto item_is_owned          = connectivity.template isOwned<item_type>();
-  auto item_number            = connectivity.template number<item_type>();
+  auto connecting_item_to_target_item_matrix =
+    connectivity.template getItemToItemMatrix<connecting_item_type, target_item_type>();
+  auto target_item_to_connecting_item_matrix =
+    connectivity.template getItemToItemMatrix<target_item_type, connecting_item_type>();
+
+  auto source_item_to_connecting_item_matrix =
+    [](const auto& given_connectivity) -> ItemToItemMatrix<source_item_type, connecting_item_type> {
+    if constexpr (ItemTypeId<Dimension>::itemTId(source_item_type) ==
+                  ItemTypeId<Dimension>::itemTId(connecting_item_type)) {
+      return {ConnectivityMatrix{}};
+    } else {
+      return given_connectivity.template getItemToItemMatrix<source_item_type, connecting_item_type>();
+    }
+  }(connectivity);
+
+  auto source_item_is_owned   = connectivity.template isOwned<source_item_type>();
+  auto target_item_number     = connectivity.template number<target_item_type>();
   auto connecting_item_number = connectivity.template number<connecting_item_type>();
 
-  using ItemId           = ItemIdT<item_type>;
+  using SourceItemId     = ItemIdT<source_item_type>;
+  using TargetItemId     = ItemIdT<target_item_type>;
   using ConnectingItemId = ItemIdT<connecting_item_type>;
 
-  if (symmetry_boundary_descriptor_list.size() == 0) {
-    if (number_of_layers == 1) {
-      Array<uint32_t> row_map{connectivity.template numberOf<item_type>() + 1};
-      row_map[0] = 0;
+  ItemArray<bool, connecting_item_type> symmetry_item_list =
+    this->_buildSymmetryConnectingItemList<connecting_item_type>(connectivity, symmetry_boundary_descriptor_list);
+
+  Array<uint32_t> row_map{connectivity.template numberOf<source_item_type>() + 1};
+  row_map[0] = 0;
+  std::vector<Array<uint32_t>> symmetry_row_map_list(symmetry_boundary_descriptor_list.size());
+  for (auto&& symmetry_row_map : symmetry_row_map_list) {
+    symmetry_row_map    = Array<uint32_t>{connectivity.template numberOf<source_item_type>() + 1};
+    symmetry_row_map[0] = 0;
+  }
+
+  std::vector<TargetItemId> column_indices_vector;
+  std::vector<std::vector<uint32_t>> symmetry_column_indices_vector(symmetry_boundary_descriptor_list.size());
 
-      std::vector<ItemId> column_indices_vector;
+  std::vector<Layer<target_item_type>> target_item_layer_list;
+  std::vector<Layer<connecting_item_type>> connecting_layer_list;
 
-      for (ItemId item_id = 0; item_id < connectivity.template numberOf<item_type>(); ++item_id) {
-        // First layer is a special case
+  std::vector<Layer<target_item_type>> symmetry_target_item_layer_list;
+  std::vector<Layer<connecting_item_type>> symmetry_connecting_layer_list;
 
-        Layer<item_type> item_layer;
-        Layer<connecting_item_type> connecting_layer;
+  for (size_t i = 0; i < number_of_layers; ++i) {
+    target_item_layer_list.emplace_back(Layer<target_item_type>{connectivity});
+    connecting_layer_list.emplace_back(Layer<connecting_item_type>{connectivity});
 
-        if (item_is_owned[item_id]) {
-          for (size_t i_connecting_item_1 = 0; i_connecting_item_1 < item_to_connecting_item_matrix[item_id].size();
-               ++i_connecting_item_1) {
-            const ConnectingItemId layer_1_connecting_item_id =
-              item_to_connecting_item_matrix[item_id][i_connecting_item_1];
-            connecting_layer.add(layer_1_connecting_item_id, connecting_item_number[layer_1_connecting_item_id]);
-          }
+    if (symmetry_boundary_descriptor_list.size() > 0) {
+      symmetry_target_item_layer_list.emplace_back(Layer<target_item_type>{connectivity});
+      symmetry_connecting_layer_list.emplace_back(Layer<connecting_item_type>{connectivity});
+    }
+  }
 
-          for (auto connecting_item_id : connecting_layer.itemIdList()) {
-            for (size_t i_item_1 = 0; i_item_1 < connecting_item_to_item_matrix[connecting_item_id].size();
-                 ++i_item_1) {
-              const ItemId layer_1_item_id = connecting_item_to_item_matrix[connecting_item_id][i_item_1];
-              if (layer_1_item_id != item_id) {
-                item_layer.add(layer_1_item_id, item_number[layer_1_item_id]);
-              }
-            }
+  for (SourceItemId source_item_id = 0; source_item_id < connectivity.template numberOf<source_item_type>();
+       ++source_item_id) {
+    if (source_item_is_owned[source_item_id]) {
+      for (auto&& target_item_layer : target_item_layer_list) {
+        target_item_layer.clear();
+      }
+      for (auto&& connecting_layer : connecting_layer_list) {
+        connecting_layer.clear();
+      }
+
+      // First layer is a special case
+      {
+        auto& target_item_layer = target_item_layer_list[0];
+        auto& connecting_layer  = connecting_layer_list[0];
+
+        if constexpr (ItemTypeId<Dimension>::itemTId(source_item_type) ==
+                      ItemTypeId<Dimension>::itemTId(connecting_item_type)) {
+          connecting_layer.add(ConnectingItemId{static_cast<typename ConnectingItemId::base_type>(source_item_id)});
+        } else {
+          for (size_t i_connecting_item = 0;
+               i_connecting_item < source_item_to_connecting_item_matrix[source_item_id].size(); ++i_connecting_item) {
+            const ConnectingItemId connecting_item_id =
+              source_item_to_connecting_item_matrix[source_item_id][i_connecting_item];
+
+            connecting_layer.add(connecting_item_id);
           }
         }
 
-        for (auto layer_item_id : item_layer.itemIdList()) {
-          column_indices_vector.push_back(layer_item_id);
+        for (auto connecting_item_id : connecting_layer.itemIdList()) {
+          for (size_t i_item = 0; i_item < connecting_item_to_target_item_matrix[connecting_item_id].size(); ++i_item) {
+            const TargetItemId layer_item_id = connecting_item_to_target_item_matrix[connecting_item_id][i_item];
+            target_item_layer.add(layer_item_id);
+          }
         }
-        row_map[item_id + 1] = row_map[item_id] + item_layer.itemIdList().size();
       }
 
-      if (row_map[row_map.size() - 1] != column_indices_vector.size()) {
-        throw UnexpectedError("incorrect stencil size");
-      }
-      Array<uint32_t> column_indices(row_map[row_map.size() - 1]);
-      column_indices.fill(std::numeric_limits<uint32_t>::max());
-
-      for (size_t i = 0; i < column_indices.size(); ++i) {
-        column_indices[i] = column_indices_vector[i];
-      }
+      for (size_t i_layer = 1; i_layer < number_of_layers; ++i_layer) {
+        auto& connecting_layer = connecting_layer_list[i_layer];
 
-      return {ConnectivityMatrix{row_map, column_indices}, {}};
-    } else {
-      Array<uint32_t> row_map{connectivity.template numberOf<item_type>() + 1};
-      row_map[0] = 0;
-
-      std::vector<ItemId> column_indices_vector;
-
-      for (ItemId item_id = 0; item_id < connectivity.template numberOf<item_type>(); ++item_id) {
-        if (item_is_owned[item_id]) {
-          std::set<ItemId, std::function<bool(ItemId, ItemId)>> item_set(
-            [=](ItemId item_0, ItemId item_1) { return item_number[item_0] < item_number[item_1]; });
-
-          for (size_t i_connecting_item_1 = 0; i_connecting_item_1 < item_to_connecting_item_matrix[item_id].size();
-               ++i_connecting_item_1) {
-            const ConnectingItemId layer_1_connecting_item_id =
-              item_to_connecting_item_matrix[item_id][i_connecting_item_1];
-
-            for (size_t i_item_1 = 0; i_item_1 < connecting_item_to_item_matrix[layer_1_connecting_item_id].size();
-                 ++i_item_1) {
-              ItemId item_1_id = connecting_item_to_item_matrix[layer_1_connecting_item_id][i_item_1];
-
-              for (size_t i_connecting_item_2 = 0;
-                   i_connecting_item_2 < item_to_connecting_item_matrix[item_1_id].size(); ++i_connecting_item_2) {
-                const ConnectingItemId layer_2_connecting_item_id =
-                  item_to_connecting_item_matrix[item_1_id][i_connecting_item_2];
-
-                for (size_t i_item_2 = 0; i_item_2 < connecting_item_to_item_matrix[layer_2_connecting_item_id].size();
-                     ++i_item_2) {
-                  ItemId item_2_id = connecting_item_to_item_matrix[layer_2_connecting_item_id][i_item_2];
-
-                  if (item_2_id != item_id) {
-                    item_set.insert(item_2_id);
-                  }
-                }
-              }
+        auto has_connecting_item = [&i_layer, &connecting_layer_list](ConnectingItemId connecting_item_id) -> bool {
+          for (ssize_t i = i_layer - 1; i >= 0; --i) {
+            if (connecting_layer_list[i].hasItemNumber(connecting_item_id)) {
+              return true;
             }
           }
 
-          for (auto stencil_item_id : item_set) {
-            column_indices_vector.push_back(stencil_item_id);
+          return false;
+        };
+
+        for (auto&& previous_layer_item_id_list : target_item_layer_list[i_layer - 1].itemIdList()) {
+          const auto target_item_to_connecting_item_list =
+            target_item_to_connecting_item_matrix[previous_layer_item_id_list];
+          for (size_t i_connecting_item = 0; i_connecting_item < target_item_to_connecting_item_list.size();
+               ++i_connecting_item) {
+            const ConnectingItemId connecting_item_id = target_item_to_connecting_item_list[i_connecting_item];
+
+            if (not has_connecting_item(connecting_item_id)) {
+              connecting_layer.add(connecting_item_id);
+            }
           }
-          row_map[item_id + 1] = row_map[item_id] + item_set.size();
         }
-      }
 
-      if (row_map[row_map.size() - 1] != column_indices_vector.size()) {
-        throw UnexpectedError("incorrect stencil size");
-      }
+        auto& target_item_layer = target_item_layer_list[i_layer];
 
-      Array<uint32_t> column_indices(row_map[row_map.size() - 1]);
-      column_indices.fill(std::numeric_limits<uint32_t>::max());
+        auto has_layer_item = [&i_layer, &target_item_layer_list](TargetItemId layer_item_id) -> bool {
+          for (ssize_t i = i_layer - 1; i >= 0; --i) {
+            if (target_item_layer_list[i].hasItemNumber(layer_item_id)) {
+              return true;
+            }
+          }
 
-      for (size_t i = 0; i < column_indices.size(); ++i) {
-        column_indices[i] = column_indices_vector[i];
-      }
-      ConnectivityMatrix primal_stencil{row_map, column_indices};
+          return false;
+        };
 
-      return {primal_stencil, {}};
-    }
-  } else {
-    ItemArray<bool, connecting_item_type> symmetry_item_list =
-      this->_buildSymmetryConnectingItemList<connecting_item_type>(connectivity, symmetry_boundary_descriptor_list);
-
-    Array<uint32_t> row_map{connectivity.template numberOf<item_type>() + 1};
-    row_map[0] = 0;
-    std::vector<Array<uint32_t>> symmetry_row_map_list(symmetry_boundary_descriptor_list.size());
-    for (auto&& symmetry_row_map : symmetry_row_map_list) {
-      symmetry_row_map    = Array<uint32_t>{connectivity.template numberOf<item_type>() + 1};
-      symmetry_row_map[0] = 0;
-    }
+        for (auto connecting_item_id : connecting_layer.itemIdList()) {
+          const auto& connecting_item_to_target_item_list = connecting_item_to_target_item_matrix[connecting_item_id];
+          for (size_t i_item = 0; i_item < connecting_item_to_target_item_list.size(); ++i_item) {
+            const TargetItemId layer_item_id = connecting_item_to_target_item_list[i_item];
+            if (not has_layer_item(layer_item_id)) {
+              target_item_layer.add(layer_item_id);
+            }
+          }
+        }
+      }
 
-    std::vector<uint32_t> column_indices_vector;
-    std::vector<std::vector<uint32_t>> symmetry_column_indices_vector(symmetry_boundary_descriptor_list.size());
+      for (size_t i_symmetry = 0; i_symmetry < symmetry_boundary_descriptor_list.size(); ++i_symmetry) {
+        for (auto&& symmetry_target_item_layer : symmetry_target_item_layer_list) {
+          symmetry_target_item_layer.clear();
+        }
+        for (auto&& symmetry_connecting_layer : symmetry_connecting_layer_list) {
+          symmetry_connecting_layer.clear();
+        }
 
-    for (ItemId item_id = 0; item_id < connectivity.template numberOf<item_type>(); ++item_id) {
-      std::set<ItemId> item_set;
-      std::vector<std::set<ItemId>> by_boundary_symmetry_item(symmetry_boundary_descriptor_list.size());
+        // First layer is a special case
+        for (auto&& connecting_item_id : connecting_layer_list[0].itemIdList()) {
+          if (symmetry_item_list[connecting_item_id][i_symmetry]) {
+            symmetry_connecting_layer_list[0].add(connecting_item_id);
+          }
+        }
 
-      if (item_is_owned[item_id]) {
-        auto item_to_connecting_item_list = item_to_connecting_item_matrix[item_id];
-        for (size_t i_connecting_item_of_item = 0; i_connecting_item_of_item < item_to_connecting_item_list.size();
-             ++i_connecting_item_of_item) {
-          const ConnectingItemId connecting_item_id_of_item = item_to_connecting_item_list[i_connecting_item_of_item];
-          auto connecting_item_to_item_list = connecting_item_to_item_matrix[connecting_item_id_of_item];
-          for (size_t i_item_of_connecting_item = 0; i_item_of_connecting_item < connecting_item_to_item_list.size();
+        for (auto&& connecting_item_id : symmetry_connecting_layer_list[0].itemIdList()) {
+          auto item_of_connecting_item_list = connecting_item_to_target_item_matrix[connecting_item_id];
+          for (size_t i_item_of_connecting_item = 0; i_item_of_connecting_item < item_of_connecting_item_list.size();
                ++i_item_of_connecting_item) {
-            const ItemId item_id_of_connecting_item = connecting_item_to_item_list[i_item_of_connecting_item];
-            if (item_id != item_id_of_connecting_item) {
-              item_set.insert(item_id_of_connecting_item);
-            }
+            const TargetItemId item_id_of_connecting_item = item_of_connecting_item_list[i_item_of_connecting_item];
+            symmetry_target_item_layer_list[0].add(item_id_of_connecting_item);
           }
         }
 
-        {
-          std::vector<ItemId> item_vector;
-          for (auto&& set_item_id : item_set) {
-            item_vector.push_back(set_item_id);
+        for (size_t i_layer = 1; i_layer < number_of_layers; ++i_layer) {
+          auto has_connecting_item = [&i_layer,
+                                      &symmetry_connecting_layer_list](ConnectingItemId connecting_item_id) -> bool {
+            for (ssize_t i = i_layer - 1; i >= 0; --i) {
+              if (symmetry_connecting_layer_list[i].hasItemNumber(connecting_item_id)) {
+                return true;
+              }
+            }
+
+            return false;
+          };
+
+          for (auto&& symmetry_connecting_item_id : connecting_layer_list[i_layer].itemIdList()) {
+            if (symmetry_item_list[symmetry_connecting_item_id][i_symmetry]) {
+              if (not has_connecting_item(symmetry_connecting_item_id)) {
+                symmetry_connecting_layer_list[i_layer].add(symmetry_connecting_item_id);
+              }
+            }
           }
-          std::sort(item_vector.begin(), item_vector.end(),
-                    [&item_number](const ItemId& item0_id, const ItemId& item1_id) {
-                      return item_number[item0_id] < item_number[item1_id];
-                    });
 
-          for (auto&& vector_item_id : item_vector) {
-            column_indices_vector.push_back(vector_item_id);
+          for (auto&& previous_layer_target_item_id_list : symmetry_target_item_layer_list[i_layer - 1].itemIdList()) {
+            const auto item_to_connecting_item_list =
+              target_item_to_connecting_item_matrix[previous_layer_target_item_id_list];
+            for (size_t i_connecting_item = 0; i_connecting_item < item_to_connecting_item_list.size();
+                 ++i_connecting_item) {
+              const ConnectingItemId connecting_item_id = item_to_connecting_item_list[i_connecting_item];
+
+              if (not has_connecting_item(connecting_item_id)) {
+                symmetry_connecting_layer_list[i_layer].add(connecting_item_id);
+              }
+            }
           }
-        }
 
-        for (size_t i = 0; i < symmetry_boundary_descriptor_list.size(); ++i) {
-          std::set<ItemId> symmetry_item_set;
-          for (size_t i_connecting_item_of_item = 0; i_connecting_item_of_item < item_to_connecting_item_list.size();
-               ++i_connecting_item_of_item) {
-            const ConnectingItemId connecting_item_id_of_item = item_to_connecting_item_list[i_connecting_item_of_item];
-            if (symmetry_item_list[connecting_item_id_of_item][i]) {
-              auto item_of_connecting_item_list = connecting_item_to_item_matrix[connecting_item_id_of_item];
-              for (size_t i_item_of_connecting_item = 0;
-                   i_item_of_connecting_item < item_of_connecting_item_list.size(); ++i_item_of_connecting_item) {
-                const ItemId item_id_of_connecting_item = item_of_connecting_item_list[i_item_of_connecting_item];
-                symmetry_item_set.insert(item_id_of_connecting_item);
+          auto has_symmetry_layer_item = [&i_layer,
+                                          &symmetry_target_item_layer_list](TargetItemId layer_target_item_id) -> bool {
+            for (ssize_t i = i_layer - 1; i >= 0; --i) {
+              if (symmetry_target_item_layer_list[i].hasItemNumber(layer_target_item_id)) {
+                return true;
+              }
+            }
+
+            return false;
+          };
+
+          for (auto&& connecting_item_id : symmetry_connecting_layer_list[i_layer].itemIdList()) {
+            auto item_of_connecting_item_list = connecting_item_to_target_item_matrix[connecting_item_id];
+            for (size_t i_target_item_of_connecting_item = 0;
+                 i_target_item_of_connecting_item < item_of_connecting_item_list.size();
+                 ++i_target_item_of_connecting_item) {
+              const TargetItemId target_item_id_of_connecting_item =
+                item_of_connecting_item_list[i_target_item_of_connecting_item];
+              if (not has_symmetry_layer_item(target_item_id_of_connecting_item)) {
+                symmetry_target_item_layer_list[i_layer].add(target_item_id_of_connecting_item);
               }
             }
           }
-          by_boundary_symmetry_item[i] = symmetry_item_set;
+        }
 
-          std::vector<ItemId> item_vector;
-          for (auto&& set_item_id : symmetry_item_set) {
-            item_vector.push_back(set_item_id);
+        for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) {
+          for (auto symmetry_layer_target_item_id : symmetry_target_item_layer_list[i_layer].itemIdList()) {
+            symmetry_column_indices_vector[i_symmetry].push_back(symmetry_layer_target_item_id);
           }
-          std::sort(item_vector.begin(), item_vector.end(),
-                    [&item_number](const ItemId& item0_id, const ItemId& item1_id) {
-                      return item_number[item0_id] < item_number[item1_id];
-                    });
+        }
 
-          for (auto&& vector_item_id : item_vector) {
-            symmetry_column_indices_vector[i].push_back(vector_item_id);
+        {
+          size_t stencil_size = 0;
+          for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) {
+            auto& target_item_layer = symmetry_target_item_layer_list[i_layer];
+            stencil_size += target_item_layer.size();
           }
+
+          symmetry_row_map_list[i_symmetry][source_item_id + 1] =
+            symmetry_row_map_list[i_symmetry][source_item_id] + stencil_size;
         }
       }
-      row_map[item_id + 1] = row_map[item_id] + item_set.size();
 
-      for (size_t i = 0; i < symmetry_row_map_list.size(); ++i) {
-        symmetry_row_map_list[i][item_id + 1] = symmetry_row_map_list[i][item_id] + by_boundary_symmetry_item[i].size();
+      {
+        size_t stencil_size = 0;
+        for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) {
+          auto& item_layer = target_item_layer_list[i_layer];
+          for (auto stencil_item_id : item_layer.itemIdList()) {
+            column_indices_vector.push_back(stencil_item_id);
+          }
+          stencil_size += item_layer.size();
+        }
+        row_map[source_item_id + 1] = row_map[source_item_id] + stencil_size;
       }
-    }
-    ConnectivityMatrix primal_stencil{row_map, convert_to_array(column_indices_vector)};
-
-    typename StencilArray<item_type, item_type>::BoundaryDescriptorStencilArrayList symmetry_boundary_stencil_list;
-    {
-      size_t i = 0;
-      for (auto&& p_boundary_descriptor : symmetry_boundary_descriptor_list) {
-        symmetry_boundary_stencil_list.emplace_back(
-          typename StencilArray<item_type, item_type>::
-            BoundaryDescriptorStencilArray{p_boundary_descriptor,
-                                           ConnectivityMatrix{symmetry_row_map_list[i],
-                                                              convert_to_array(symmetry_column_indices_vector[i])}});
-        ++i;
+
+    } else {
+      row_map[source_item_id + 1] = row_map[source_item_id];
+      for (size_t i = 0; i < symmetry_row_map_list.size(); ++i) {
+        symmetry_row_map_list[i][source_item_id + 1] = symmetry_row_map_list[i][source_item_id];
       }
     }
+  }
+
+  Array<uint32_t> column_indices{column_indices_vector.size()};
+  parallel_for(
+    column_indices_vector.size(), PUGS_LAMBDA(size_t i) { column_indices[i] = column_indices_vector[i]; });
+
+  ConnectivityMatrix primal_stencil{row_map, column_indices};
 
-    return {{primal_stencil}, {symmetry_boundary_stencil_list}};
+  typename StencilArray<source_item_type, target_item_type>::BoundaryDescriptorStencilArrayList
+    symmetry_boundary_stencil_list;
+  {
+    size_t i = 0;
+    for (auto&& p_boundary_descriptor : symmetry_boundary_descriptor_list) {
+      symmetry_boundary_stencil_list.emplace_back(
+        typename StencilArray<source_item_type, target_item_type>::
+          BoundaryDescriptorStencilArray{p_boundary_descriptor,
+                                         ConnectivityMatrix{symmetry_row_map_list[i],
+                                                            convert_to_array(symmetry_column_indices_vector[i])}});
+      ++i;
+    }
   }
-}
 
-template <ItemType source_item_type,
-          ItemType connecting_item_type,
-          ItemType target_item_type,
-          typename ConnectivityType>
-StencilArray<source_item_type, target_item_type>
-StencilBuilder::_build_for_different_source_and_target(
-  const ConnectivityType& connectivity,
-  const size_t& number_of_layers,
-  const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const
-{
-  static_assert(source_item_type != target_item_type);
-  throw NotImplementedError("different source target");
+  return {{primal_stencil}, {symmetry_boundary_stencil_list}};
 }
 
 template <ItemType source_item_type,
@@ -631,7 +863,35 @@ StencilBuilder::buildC2C(const IConnectivity& connectivity,
 }
 
 NodeToCellStencilArray
-StencilBuilder::buildN2C(const IConnectivity&, const StencilDescriptor&, const BoundaryDescriptorList&) const
+StencilBuilder::buildN2C(const IConnectivity& connectivity,
+                         const StencilDescriptor& stencil_descriptor,
+                         const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const
 {
-  throw NotImplementedError("node to cell stencil");
+  if ((parallel::size() > 1) and
+      (stencil_descriptor.numberOfLayers() > GlobalVariableManager::instance().getNumberOfGhostLayers())) {
+    std::ostringstream error_msg;
+    error_msg << "Stencil builder requires" << rang::fgB::yellow << stencil_descriptor.numberOfLayers()
+              << rang::fg::reset << " layers while parallel number of ghost layer is "
+              << GlobalVariableManager::instance().getNumberOfGhostLayers() << ".\n";
+    error_msg << "Increase the number of ghost layers (using the '--number-of-ghost-layers' option).";
+    throw NormalError(error_msg.str());
+  }
+
+  switch (connectivity.dimension()) {
+  case 1: {
+    return this->_build<ItemType::node, ItemType::cell>(dynamic_cast<const Connectivity<1>&>(connectivity),
+                                                        stencil_descriptor, symmetry_boundary_descriptor_list);
+  }
+  case 2: {
+    return this->_build<ItemType::node, ItemType::cell>(dynamic_cast<const Connectivity<2>&>(connectivity),
+                                                        stencil_descriptor, symmetry_boundary_descriptor_list);
+  }
+  case 3: {
+    return this->_build<ItemType::node, ItemType::cell>(dynamic_cast<const Connectivity<3>&>(connectivity),
+                                                        stencil_descriptor, symmetry_boundary_descriptor_list);
+  }
+  default: {
+    throw UnexpectedError("invalid connectivity dimension");
+  }
+  }
 }
diff --git a/src/mesh/StencilBuilder.hpp b/src/mesh/StencilBuilder.hpp
index 4f7ead00b618db999dd5386258ac058b664c65a7..2ed1d3a6c45fdac651ce62ba05690532c506cf26 100644
--- a/src/mesh/StencilBuilder.hpp
+++ b/src/mesh/StencilBuilder.hpp
@@ -19,13 +19,6 @@ class StencilBuilder
   template <ItemType item_type>
   class Layer;
 
-  template <typename ConnectivityType>
-  Array<const uint32_t> _getRowMap(const ConnectivityType& connectivity) const;
-
-  template <typename ConnectivityType>
-  Array<const uint32_t> _getColumnIndices(const ConnectivityType& connectivity,
-                                          const Array<const uint32_t>& row_map) const;
-
   template <ItemType connecting_item, typename ConnectivityType>
   auto _buildSymmetryConnectingItemList(const ConnectivityType& connectivity,
                                         const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const;
@@ -74,11 +67,12 @@ class StencilBuilder
                                   const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const;
 
   friend class StencilManager;
+
   template <ItemType source_item_type, ItemType target_item_type>
   StencilArray<source_item_type, target_item_type>
-  build(const IConnectivity& connectivity,
-        const StencilDescriptor& stencil_descriptor,
-        const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const
+  _build(const IConnectivity& connectivity,
+         const StencilDescriptor& stencil_descriptor,
+         const BoundaryDescriptorList& symmetry_boundary_descriptor_list) const
   {
     if constexpr ((source_item_type == ItemType::cell) and (target_item_type == ItemType::cell)) {
       return buildC2C(connectivity, stencil_descriptor, symmetry_boundary_descriptor_list);
diff --git a/src/mesh/StencilManager.cpp b/src/mesh/StencilManager.cpp
index 842f8858f9d1a571e28d3f0397f70e97682da0af..04a8e32a1d50d872d95896e8e7a58e76e32e2189 100644
--- a/src/mesh/StencilManager.cpp
+++ b/src/mesh/StencilManager.cpp
@@ -49,8 +49,8 @@ StencilManager::_getStencilArray(
         Key{connectivity.id(), stencil_descriptor, symmetry_boundary_descriptor_list})) {
     stored_source_to_target_stencil_map[Key{connectivity.id(), stencil_descriptor, symmetry_boundary_descriptor_list}] =
       std::make_shared<StencilArray<source_item_type, target_item_type>>(
-        StencilBuilder{}.template build<source_item_type, target_item_type>(connectivity, stencil_descriptor,
-                                                                            symmetry_boundary_descriptor_list));
+        StencilBuilder{}.template _build<source_item_type, target_item_type>(connectivity, stencil_descriptor,
+                                                                             symmetry_boundary_descriptor_list));
   }
 
   return *stored_source_to_target_stencil_map.at(
diff --git a/src/scheme/DirichletVectorBoundaryConditionDescriptor.hpp b/src/scheme/DirichletVectorBoundaryConditionDescriptor.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c1f1dcc3fadeced1dbed1f560a6e72c86eebf0ed
--- /dev/null
+++ b/src/scheme/DirichletVectorBoundaryConditionDescriptor.hpp
@@ -0,0 +1,58 @@
+#ifndef DIRICHLET_VECTOR_BOUNDARY_CONDITION_DESCRIPTOR_HPP
+#define DIRICHLET_VECTOR_BOUNDARY_CONDITION_DESCRIPTOR_HPP
+
+#include <language/utils/FunctionSymbolId.hpp>
+#include <mesh/IBoundaryDescriptor.hpp>
+#include <scheme/BoundaryConditionDescriptorBase.hpp>
+
+#include <memory>
+#include <vector>
+
+class DirichletVectorBoundaryConditionDescriptor : public BoundaryConditionDescriptorBase
+{
+ private:
+  std::ostream&
+  _write(std::ostream& os) const final
+  {
+    os << "dirichlet_vector(" << m_name << ',' << *m_boundary_descriptor << ")";
+    return os;
+  }
+
+  const std::string m_name;
+
+  const std::vector<FunctionSymbolId> m_rhs_symbol_id_list;
+
+ public:
+  const std::string&
+  name() const
+  {
+    return m_name;
+  }
+
+  const std::vector<FunctionSymbolId>&
+  rhsSymbolIdList() const
+  {
+    return m_rhs_symbol_id_list;
+  }
+
+  Type
+  type() const final
+  {
+    return Type::dirichlet_vector;
+  }
+
+  DirichletVectorBoundaryConditionDescriptor(const std::string_view name,
+                                             std::shared_ptr<const IBoundaryDescriptor> boundary_descriptor,
+                                             const std::vector<FunctionSymbolId>& rhs_symbol_id_list)
+    : BoundaryConditionDescriptorBase{boundary_descriptor}, m_name{name}, m_rhs_symbol_id_list{rhs_symbol_id_list}
+  {
+    ;
+  }
+
+  DirichletVectorBoundaryConditionDescriptor(const DirichletVectorBoundaryConditionDescriptor&) = delete;
+  DirichletVectorBoundaryConditionDescriptor(DirichletVectorBoundaryConditionDescriptor&&)      = delete;
+
+  ~DirichletVectorBoundaryConditionDescriptor() = default;
+};
+
+#endif   // DIRICHLET_VECTOR_BOUNDARY_CONDITION_DESCRIPTOR_HPP
diff --git a/src/scheme/IBoundaryConditionDescriptor.hpp b/src/scheme/IBoundaryConditionDescriptor.hpp
index fc9ff381b97758e79642647a36680364093c193b..63f698e16eb611f9cb03bc82e6d737372924d36d 100644
--- a/src/scheme/IBoundaryConditionDescriptor.hpp
+++ b/src/scheme/IBoundaryConditionDescriptor.hpp
@@ -13,6 +13,7 @@ class IBoundaryConditionDescriptor
   {
     axis,
     dirichlet,
+    dirichlet_vector,
     external,
     fourier,
     fixed,
diff --git a/src/scheme/PolynomialReconstruction.cpp b/src/scheme/PolynomialReconstruction.cpp
index 0e0b2a5afe28330dad7ad0a82b7dc9b277f027c8..0bc65fc85c5e8f9a650a212c0517a2c0d21b8e6b 100644
--- a/src/scheme/PolynomialReconstruction.cpp
+++ b/src/scheme/PolynomialReconstruction.cpp
@@ -32,6 +32,14 @@ symmetrize_vector(const TinyVector<Dimension>& normal, const TinyVector<Dimensio
   return u - 2 * dot(u, normal) * normal;
 }
 
+template <size_t Dimension>
+PUGS_INLINE auto
+symmetrize_matrix(const TinyVector<Dimension>& normal, const TinyMatrix<Dimension>& A)
+{
+  const TinyMatrix S = TinyMatrix<Dimension>{identity} - 2 * tensorProduct(normal, normal);
+  return S * A * S;
+}
+
 template <size_t Dimension>
 PUGS_INLINE auto
 symmetrize_coordinates(const TinyVector<Dimension>& origin,
@@ -790,6 +798,44 @@ PolynomialReconstruction::_createMutableDiscreteFunctionDPKVariantList(
   return mutable_discrete_function_dpk_variant_list;
 }
 
+template <MeshConcept MeshType>
+void
+PolynomialReconstruction::_checkDataAndSymmetriesCompatibility(
+  const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const
+{
+  for (auto&& discrete_function_variant : discrete_function_variant_list) {
+    std::visit(
+      [&](auto&& discrete_function) {
+        using DiscreteFunctionT = std::decay_t<decltype(discrete_function)>;
+        if constexpr (is_discrete_function_P0_v<DiscreteFunctionT> or
+                      is_discrete_function_P0_vector_v<DiscreteFunctionT>) {
+          using DataType = std::decay_t<typename DiscreteFunctionT::data_type>;
+          if constexpr (is_tiny_vector_v<DataType>) {
+            if constexpr (DataType::Dimension != MeshType::Dimension) {
+              std::stringstream error_msg;
+              error_msg << "cannot symmetrize vectors of dimension " << DataType::Dimension
+                        << " using a mesh of dimension " << MeshType::Dimension;
+              throw NormalError(error_msg.str());
+            }
+          } else if constexpr (is_tiny_matrix_v<DataType>) {
+            if constexpr ((DataType::NumberOfRows != MeshType::Dimension) or
+                          (DataType::NumberOfColumns != MeshType::Dimension)) {
+              std::stringstream error_msg;
+              error_msg << "cannot symmetrize matrices of dimensions " << DataType::NumberOfRows << 'x'
+                        << DataType::NumberOfColumns << " using a mesh of dimension " << MeshType::Dimension;
+              throw NormalError(error_msg.str());
+            }
+          }
+        } else {
+          // LCOV_EXCL_START
+          throw UnexpectedError("invalid discrete function type");
+          // LCOV_EXCL_STOP
+        }
+      },
+      discrete_function_variant->discreteFunction());
+  }
+}
+
 template <MeshConcept MeshType>
 [[nodiscard]] std::vector<std::shared_ptr<const DiscreteFunctionDPkVariant>>
 PolynomialReconstruction::_build(
@@ -800,6 +846,10 @@ PolynomialReconstruction::_build(
 
   using Rd = TinyVector<MeshType::Dimension>;
 
+  if (m_descriptor.symmetryBoundaryDescriptorList().size() > 0) {
+    this->_checkDataAndSymmetriesCompatibility<MeshType>(discrete_function_variant_list);
+  }
+
   const size_t number_of_columns = this->_getNumberOfColumns(discrete_function_variant_list);
 
   const size_t basis_dimension =
@@ -897,7 +947,7 @@ PolynomialReconstruction::_build(
   }
 
   parallel_for(
-    mesh.numberOfCells(), PUGS_LAMBDA(const CellId cell_j_id) {
+    mesh.numberOfCells(), PUGS_CLASS_LAMBDA(const CellId cell_j_id) {
       if (cell_is_owned[cell_j_id]) {
         const int32_t t = tokens.acquire();
 
@@ -954,21 +1004,32 @@ PolynomialReconstruction::_build(
                           B(index, kB) = qi_qj[k];
                         }
                       } else {
-                        const DataType& qi_qj = discrete_function[cell_i_id] - qj;
-                        for (size_t kB = column_begin, k = 0; k < DataType::Dimension; ++k, ++kB) {
-                          B(index, kB) = qi_qj[k];
-                        }
+                        // LCOV_EXCL_START
+                        std::stringstream error_msg;
+                        error_msg << "cannot symmetrize vectors of dimension " << DataType::Dimension
+                                  << " using a mesh of dimension " << MeshType::Dimension;
+                        throw UnexpectedError(error_msg.str());
+                        // LCOV_EXCL_STOP
                       }
                     } else if constexpr (is_tiny_matrix_v<DataType>) {
                       if constexpr ((DataType::NumberOfColumns == DataType::NumberOfRows) and
                                     (DataType::NumberOfColumns == MeshType::Dimension)) {
-                        throw NotImplementedError("TinyMatrix symmetries for reconstruction of DiscreteFunctionP0");
-                      }
-                      const DataType& qi_qj = discrete_function[cell_i_id] - qj;
-                      for (size_t k = 0; k < DataType::NumberOfRows; ++k) {
-                        for (size_t l = 0; l < DataType::NumberOfColumns; ++l) {
-                          B(index, column_begin + k * DataType::NumberOfColumns + l) = qi_qj(k, l);
+                        const Rd& normal = symmetry_normal_list[i_symmetry];
+
+                        const DataType& qi    = discrete_function[cell_i_id];
+                        const DataType& qi_qj = symmetrize_matrix(normal, qi) - qj;
+                        for (size_t k = 0; k < DataType::NumberOfRows; ++k) {
+                          for (size_t l = 0; l < DataType::NumberOfColumns; ++l) {
+                            B(index, column_begin + k * DataType::NumberOfColumns + l) = qi_qj(k, l);
+                          }
                         }
+                      } else {
+                        // LCOV_EXCL_START
+                        std::stringstream error_msg;
+                        error_msg << "cannot symmetrize matrices of dimensions " << DataType::NumberOfRows << 'x'
+                                  << DataType::NumberOfColumns << " using a mesh of dimension " << MeshType::Dimension;
+                        throw UnexpectedError(error_msg.str());
+                        // LCOV_EXCL_STOP
                       }
                     }
                   }
@@ -1031,8 +1092,6 @@ PolynomialReconstruction::_build(
                     }
                   }
                 } else if constexpr (is_tiny_matrix_v<DataType>) {
-                  throw NotImplementedError("NIY TinyMatrix data");
-
                   for (size_t i_symmetry = 0; i_symmetry < stencil_array.symmetryBoundaryStencilArrayList().size();
                        ++i_symmetry) {
                     auto& ghost_stencil  = stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry].stencilArray();
diff --git a/src/scheme/PolynomialReconstruction.hpp b/src/scheme/PolynomialReconstruction.hpp
index b2b855271d1ed4140ebb51d1b21314602caa0dc6..bf6013639f732fd52f9c145ba4982701c0e4bcfc 100644
--- a/src/scheme/PolynomialReconstruction.hpp
+++ b/src/scheme/PolynomialReconstruction.hpp
@@ -17,6 +17,10 @@ class PolynomialReconstruction
   size_t _getNumberOfColumns(
     const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const;
 
+  template <MeshConcept MeshType>
+  void _checkDataAndSymmetriesCompatibility(
+    const std::vector<std::shared_ptr<const DiscreteFunctionVariant>>& discrete_function_variant_list) const;
+
   template <MeshConcept MeshType>
   std::vector<MutableDiscreteFunctionDPkVariant> _createMutableDiscreteFunctionDPKVariantList(
     const std::shared_ptr<const MeshType>& p_mesh,
diff --git a/src/utils/CMakeLists.txt b/src/utils/CMakeLists.txt
index 13b0db80862c2b28e80dc18ff22951b08157db28..4d74c5e195fe0361819315cd3732661ba7589e56 100644
--- a/src/utils/CMakeLists.txt
+++ b/src/utils/CMakeLists.txt
@@ -16,6 +16,7 @@ add_library(
   Messenger.cpp
   Partitioner.cpp
   PETScWrapper.cpp
+  PluginsLoader.cpp
   PugsUtils.cpp
   RandomEngine.cpp
   ReproducibleSumManager.cpp
@@ -32,11 +33,15 @@ endif()
 
 target_link_libraries(
   PugsUtils
-  ${PETSC_LIBRARIES}
-  ${SLEPC_LIBRARIES}
+  ${PETSC_TARGET}
+  ${SLEPC_TARGET}
+  ${SLURM_TARGET}
   ${HIGHFIVE_TARGET}
 )
 
+target_include_directories(PugsUtils PUBLIC ${PETSC_INCLUDE_DIRS})
+target_include_directories(PugsUtils PUBLIC ${SLURM_INCLUDE_DIRS})
+
 # --------------- get git revision info ---------------
 
 # Generates revision header file
@@ -58,12 +63,11 @@ set_source_files_properties(
 add_custom_command(TARGET PugsGitRevison
   COMMAND ${CMAKE_COMMAND} -E remove -f
   ${CMAKE_CURRENT_BINARY_DIR}/pugs_git_revision
-  DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/pugs_git_revision.hpp
+  POST_BUILD
   COMMENT ""
   )
 
 add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/pugs_git_revision.hpp
-  PRE_BUILD
   COMMAND ${CMAKE_COMMAND} -E copy_if_different
   ${CMAKE_CURRENT_BINARY_DIR}/pugs_git_revision
   ${CMAKE_CURRENT_BINARY_DIR}/pugs_git_revision.hpp
@@ -73,7 +77,6 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/pugs_git_revision.hpp
   )
 
 add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/pugs_git_revision
-  PRE_BUILD
   COMMAND ${CMAKE_COMMAND} -DPUGS_VERSION=${PUGS_VERSION} -DPUGS_SOURCE_DIR=${PUGS_SOURCE_DIR} -P ${PUGS_SOURCE_DIR}/cmake/GetPugsGitRevision.cmake
   COMMENT "Check pugs git status"
   VERBATIM
diff --git a/src/utils/ConsoleManager.hpp b/src/utils/ConsoleManager.hpp
index a2afe46fbc0ad16e263d1f5a9b3f8a84d5202d94..693219e30285d1b0acae26d4b43d310882069784 100644
--- a/src/utils/ConsoleManager.hpp
+++ b/src/utils/ConsoleManager.hpp
@@ -1,7 +1,7 @@
 #ifndef CONSOLE_MANAGER_HPP
 #define CONSOLE_MANAGER_HPP
 
-#include <string>
+#include <ostream>
 
 class ConsoleManager
 {
diff --git a/src/utils/PluginsLoader.cpp b/src/utils/PluginsLoader.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..35e24605e8a01b4d350aadb18bcacda313e9004c
--- /dev/null
+++ b/src/utils/PluginsLoader.cpp
@@ -0,0 +1,104 @@
+#include <utils/PluginsLoader.hpp>
+
+#include <utils/ConsoleManager.hpp>
+
+#include <dlfcn.h>
+
+#include <rang.hpp>
+
+#include <filesystem>
+#include <iostream>
+#include <sstream>
+#include <unistd.h>
+#include <vector>
+
+std::vector<std::string>
+split(const std::string& full_string)
+{
+  std::vector<std::string> split_string;
+
+  std::stringstream is(full_string);
+
+  std::string segment;
+  while (std::getline(is, segment, ';')) {
+    if (segment.size() > 0) {
+      split_string.push_back(segment);
+    }
+  }
+  return split_string;
+}
+
+void
+PluginsLoader::_open(const std::string& plugin)
+{
+  auto handle = dlopen(plugin.c_str(), RTLD_NOW);
+  if (handle != nullptr) {
+    m_dl_handler_stack.push(handle);
+    if (ConsoleManager::showPreamble()) {
+      std::cout << " * \"" << rang::fgB::green << plugin << rang::fg::reset << "\"\n";
+    }
+  } else {
+    std::cerr << "   " << rang::fgB::red << "cannot load " << rang::fg::reset << '\"' << rang::fgB::yellow << plugin
+              << rang::fg::reset << "\"\n";
+  }
+}
+
+PluginsLoader::PluginsLoader()
+{
+  std::vector<std::string> plugin_vector;
+
+  {
+    char* env = getenv("PUGS_PLUGIN");
+    if (env != nullptr) {
+      std::string plugins = env;
+      plugin_vector       = split(plugins);
+    }
+  }
+
+  {
+    char* env = getenv("PUGS_PLUGIN_DIR");
+    if (env != nullptr) {
+      std::string paths                    = env;
+      std::vector<std::string> path_vector = split(paths);
+      for (auto&& path : path_vector) {
+        if (access(path.c_str(), R_OK) == -1) {
+          std::cerr << ' ' << rang::fgB::red << 'X' << rang::fg::reset << " cannot access plugin dir \""
+                    << rang::fgB::yellow << path << rang::fg::reset << "\"\n";
+        } else {
+          for (auto&& entry :
+               std::filesystem::directory_iterator(path,
+                                                   (std::filesystem::directory_options::follow_directory_symlink |
+                                                    std::filesystem::directory_options::skip_permission_denied))) {
+            if (entry.path().extension() == ".so") {
+              plugin_vector.push_back(entry.path().string());
+            }
+          }
+        }
+      }
+    }
+  }
+
+  // keep unique entries
+  std::sort(plugin_vector.begin(), plugin_vector.end());
+  plugin_vector.resize(std::distance(plugin_vector.begin(), std::unique(plugin_vector.begin(), plugin_vector.end())));
+
+  if (plugin_vector.size() > 0) {
+    if (ConsoleManager::showPreamble()) {
+      std::cout << rang::style::bold << "Loading plugins" << rang::style::reset << '\n';
+    }
+    for (auto&& plugin : plugin_vector) {
+      this->_open(plugin);
+    }
+    if (ConsoleManager::showPreamble()) {
+      std::cout << "-------------------------------------------------------\n";
+    }
+  }
+}
+
+PluginsLoader::~PluginsLoader()
+{
+  while (not m_dl_handler_stack.empty()) {
+    dlclose(m_dl_handler_stack.top());
+    m_dl_handler_stack.pop();
+  }
+}
diff --git a/src/utils/PluginsLoader.hpp b/src/utils/PluginsLoader.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..76be25c6934c5f336360e938d9a40d75f075e6d3
--- /dev/null
+++ b/src/utils/PluginsLoader.hpp
@@ -0,0 +1,23 @@
+#ifndef PLUGINS_LOADER_HPP
+#define PLUGINS_LOADER_HPP
+
+#include <stack>
+#include <string>
+
+class PluginsLoader
+{
+ private:
+  std::stack<void*> m_dl_handler_stack;
+
+  void _open(const std::string& filename);
+
+ public:
+  PluginsLoader();
+
+  PluginsLoader(const PluginsLoader&) = delete;
+  PluginsLoader(PluginsLoader&&)      = delete;
+
+  ~PluginsLoader();
+};
+
+#endif   // PLUGINS_LOADER_HPP
diff --git a/src/utils/checkpointing/CMakeLists.txt b/src/utils/checkpointing/CMakeLists.txt
index b051975e8538df40a2b9b8a8d7f0486874cda938..06d34a349ab63a6e0de206908e50a82d418a857a 100644
--- a/src/utils/checkpointing/CMakeLists.txt
+++ b/src/utils/checkpointing/CMakeLists.txt
@@ -66,4 +66,8 @@ add_dependencies(PugsCheckpointing
 target_link_libraries(
   PugsCheckpointing
   ${HIGHFIVE_TARGET}
+#  ${HDF5_TARGET}
 )
+
+#target_include_directories(PugsCheckpointing PUBLIC ${HDF5_INCLUDE_DIRS})
+#target_include_directories(PugsAlgebra PUBLIC ${SLEPC_INCLUDE_DIRS})
diff --git a/src/utils/checkpointing/IBoundaryConditionDescriptorHFType.hpp b/src/utils/checkpointing/IBoundaryConditionDescriptorHFType.hpp
index 210b521164075f275829865397ea1771a460c207..4a6e5c92cded1a614d43a17ace96f211a3f48e2d 100644
--- a/src/utils/checkpointing/IBoundaryConditionDescriptorHFType.hpp
+++ b/src/utils/checkpointing/IBoundaryConditionDescriptorHFType.hpp
@@ -10,6 +10,7 @@ create_enum_i_boundary_condition_descriptor_type()
 {
   return {{"axis", IBoundaryConditionDescriptor::Type::axis},
           {"dirichlet", IBoundaryConditionDescriptor::Type::dirichlet},
+          {"dirichlet_vector", IBoundaryConditionDescriptor::Type::dirichlet_vector},
           {"external", IBoundaryConditionDescriptor::Type::external},
           {"fixed", IBoundaryConditionDescriptor::Type::fixed},
           {"fourier", IBoundaryConditionDescriptor::Type::fourier},
diff --git a/src/utils/checkpointing/ReadIBoundaryConditionDescriptor.cpp b/src/utils/checkpointing/ReadIBoundaryConditionDescriptor.cpp
index 55b43c9f04bf1b3b585df208bd7a3ea86a3d2850..1f405b4d8a72a8cd15e7799732a16fc4a71ac12b 100644
--- a/src/utils/checkpointing/ReadIBoundaryConditionDescriptor.cpp
+++ b/src/utils/checkpointing/ReadIBoundaryConditionDescriptor.cpp
@@ -5,6 +5,7 @@
 #include <language/utils/EmbeddedData.hpp>
 #include <scheme/AxisBoundaryConditionDescriptor.hpp>
 #include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/DirichletVectorBoundaryConditionDescriptor.hpp>
 #include <scheme/ExternalBoundaryConditionDescriptor.hpp>
 #include <scheme/FixedBoundaryConditionDescriptor.hpp>
 #include <scheme/FourierBoundaryConditionDescriptor.hpp>
@@ -48,6 +49,21 @@ readIBoundaryConditionDescriptor(const HighFive::Group& iboundaryconditiondecrip
       std::make_shared<const DirichletBoundaryConditionDescriptor>(name, i_boundary_descriptor,
                                                                    *ResumingData::instance().functionSymbolId(rhs_id));
     break;
+  }
+  case IBoundaryConditionDescriptor::Type::dirichlet_vector: {
+    const std::string name = iboundaryconditiondecriptor_group.getAttribute("name").read<std::string>();
+
+    const std::vector function_id_list =
+      iboundaryconditiondecriptor_group.getAttribute("function_id_list").read<std::vector<size_t>>();
+
+    std::vector<FunctionSymbolId> function_symbol_id_list;
+    for (auto function_id : function_id_list) {
+      function_symbol_id_list.push_back(*ResumingData::instance().functionSymbolId(function_id));
+    }
+
+    bc_descriptor = std::make_shared<const DirichletVectorBoundaryConditionDescriptor>(name, i_boundary_descriptor,
+                                                                                       function_symbol_id_list);
+    break;
   }
     // LCOV_EXCL_START
   case IBoundaryConditionDescriptor::Type::external: {
diff --git a/src/utils/checkpointing/WriteIBoundaryConditionDescriptor.cpp b/src/utils/checkpointing/WriteIBoundaryConditionDescriptor.cpp
index 66a8670583ae63442313da42c847bbeee758a2d5..8a3c5dd062aca586bd800a19f9b0e27f4fc30e93 100644
--- a/src/utils/checkpointing/WriteIBoundaryConditionDescriptor.cpp
+++ b/src/utils/checkpointing/WriteIBoundaryConditionDescriptor.cpp
@@ -5,6 +5,7 @@
 #include <language/utils/DataHandler.hpp>
 #include <scheme/AxisBoundaryConditionDescriptor.hpp>
 #include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/DirichletVectorBoundaryConditionDescriptor.hpp>
 #include <scheme/FixedBoundaryConditionDescriptor.hpp>
 #include <scheme/FourierBoundaryConditionDescriptor.hpp>
 #include <scheme/FreeBoundaryConditionDescriptor.hpp>
@@ -47,6 +48,18 @@ writeIBoundaryConditionDescriptor(HighFive::Group& variable_group,
     variable_group.createAttribute("name", dirichlet_bc_descriptor.name());
     variable_group.createAttribute("rhs_function_id", dirichlet_bc_descriptor.rhsSymbolId().id());
     break;
+  }
+  case IBoundaryConditionDescriptor::Type::dirichlet_vector: {
+    const DirichletVectorBoundaryConditionDescriptor& dirichlet_vector_bc_descriptor =
+      dynamic_cast<const DirichletVectorBoundaryConditionDescriptor&>(iboundary_condition_descriptor);
+    variable_group.createAttribute("name", dirichlet_vector_bc_descriptor.name());
+    writeIBoundaryDescriptor(boundary_group, dirichlet_vector_bc_descriptor.boundaryDescriptor());
+    std::vector<size_t> function_id_list;
+    for (auto&& function_symbol_id : dirichlet_vector_bc_descriptor.rhsSymbolIdList()) {
+      function_id_list.push_back(function_symbol_id.id());
+    }
+    variable_group.createAttribute("function_id_list", function_id_list);
+    break;
   }
     // LCOV_EXCL_START
   case IBoundaryConditionDescriptor::Type::external: {
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 874df648b6b6719d8f5b1e79cfcd6a46683e16ea..690030bb8aaafde19d6a7f5129a5d676f4d599c7 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -4,6 +4,20 @@ include_directories(${PUGS_SOURCE_DIR}/src)
 include_directories(${PUGS_BINARY_DIR}/src)
 include_directories(${PUGS_SOURCE_DIR}/tests)
 
+install(
+  DIRECTORY "${PUGS_SOURCE_DIR}/packages/Catch2/src/catch2"
+  DESTINATION "include"
+  FILES_MATCHING
+  PATTERN "*.hpp"
+)
+
+install(
+  DIRECTORY "${PUGS_BINARY_DIR}/generated-includes/catch2"
+  DESTINATION "include"
+  FILES_MATCHING
+  PATTERN "*.hpp"
+)
+
 set(checkpointing_sequential_TESTS
   # this one should enventually integrate parallel tests
   test_checkpointing_Checkpoint_sequential.cpp
@@ -264,7 +278,8 @@ add_executable (mpi_unit_tests
   test_PolynomialReconstruction.cpp
   test_PolynomialReconstructionDescriptor.cpp
   test_RandomEngine.cpp
-  test_StencilBuilder.cpp
+  test_StencilBuilder_cell2cell.cpp
+  test_StencilBuilder_node2cell.cpp
   test_SubItemArrayPerItemVariant.cpp
   test_SubItemValuePerItem.cpp
   test_SubItemValuePerItemVariant.cpp
diff --git a/tests/NbGhostLayersTester.hpp b/tests/NbGhostLayersTester.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..0468fe18305367834c9cbd0959ba5b3f443fe870
--- /dev/null
+++ b/tests/NbGhostLayersTester.hpp
@@ -0,0 +1,27 @@
+#ifndef NB_GHOST_LAYERS_TESTER_HPP
+#define NB_GHOST_LAYERS_TESTER_HPP
+
+#include <cstddef>
+#include <utils/GlobalVariableManager.hpp>
+
+class NbGhostLayersTester
+{
+ private:
+  const size_t m_original_number_of_ghost_layers;
+
+ public:
+  PUGS_INLINE
+  NbGhostLayersTester(const size_t number_of_ghost_layers)
+    : m_original_number_of_ghost_layers{GlobalVariableManager::instance().getNumberOfGhostLayers()}
+  {
+    GlobalVariableManager::instance().m_number_of_ghost_layers = number_of_ghost_layers;
+  }
+
+  PUGS_INLINE
+  ~NbGhostLayersTester()
+  {
+    GlobalVariableManager::instance().m_number_of_ghost_layers = m_original_number_of_ghost_layers;
+  }
+};
+
+#endif   // NB_GHOST_LAYERS_TESTER_HPP
diff --git a/tests/test_ConnectivityDispatcher.cpp b/tests/test_ConnectivityDispatcher.cpp
index abe07cc21e21a1b34ae3f20dfe13ff7f52ac83b9..09ec9982e10e08d84c808f157fe4e11add3d756a 100644
--- a/tests/test_ConnectivityDispatcher.cpp
+++ b/tests/test_ConnectivityDispatcher.cpp
@@ -9,29 +9,12 @@
 #include <utils/Messenger.hpp>
 
 #include <MeshDataBaseForTests.hpp>
+#include <NbGhostLayersTester.hpp>
 
 #include <filesystem>
 
 // clazy:excludeall=non-pod-global-static
 
-class NbGhostLayersTester
-{
- private:
-  const size_t m_original_number_of_ghost_layers;
-
- public:
-  NbGhostLayersTester(const size_t number_of_ghost_layers)
-    : m_original_number_of_ghost_layers{GlobalVariableManager::instance().getNumberOfGhostLayers()}
-  {
-    GlobalVariableManager::instance().m_number_of_ghost_layers = number_of_ghost_layers;
-  }
-
-  ~NbGhostLayersTester()
-  {
-    GlobalVariableManager::instance().m_number_of_ghost_layers = m_original_number_of_ghost_layers;
-  }
-};
-
 TEST_CASE("ConnectivityDispatcher", "[mesh]")
 {
   auto check_number_of_ghost_layers = [](const auto& connectivity, const size_t number_of_layers) {
diff --git a/tests/test_StencilBuilder.cpp b/tests/test_StencilBuilder.cpp
deleted file mode 100644
index b99ba218aa1e3eb5d8ec884e974cfad2f4a157db..0000000000000000000000000000000000000000
--- a/tests/test_StencilBuilder.cpp
+++ /dev/null
@@ -1,349 +0,0 @@
-#include <catch2/catch_test_macros.hpp>
-#include <catch2/matchers/catch_matchers_all.hpp>
-
-#include <MeshDataBaseForTests.hpp>
-#include <mesh/Connectivity.hpp>
-#include <mesh/ConnectivityUtils.hpp>
-#include <mesh/ItemValue.hpp>
-#include <mesh/ItemValueUtils.hpp>
-#include <mesh/Mesh.hpp>
-#include <mesh/MeshFlatNodeBoundary.hpp>
-#include <mesh/MeshVariant.hpp>
-#include <mesh/NamedBoundaryDescriptor.hpp>
-#include <mesh/StencilManager.hpp>
-#include <utils/Messenger.hpp>
-
-// clazy:excludeall=non-pod-global-static
-
-TEST_CASE("StencilBuilder", "[mesh]")
-{
-  SECTION("inner stencil")
-  {
-    auto is_valid = [](const auto& connectivity, const auto& stencil_array) {
-      auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
-      auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
-
-      auto cell_is_owned = connectivity.cellIsOwned();
-      auto cell_number   = connectivity.cellNumber();
-
-      for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-        if (cell_is_owned[cell_id]) {
-          std::set<CellId, std::function<bool(CellId, CellId)>> cell_set(
-            [=](CellId cell_0, CellId cell_1) { return cell_number[cell_0] < cell_number[cell_1]; });
-          auto cell_nodes = cell_to_node_matrix[cell_id];
-          for (size_t i_node = 0; i_node < cell_nodes.size(); ++i_node) {
-            const NodeId node_id = cell_nodes[i_node];
-            auto node_cells      = node_to_cell_matrix[node_id];
-            for (size_t i_node_cell = 0; i_node_cell < node_cells.size(); ++i_node_cell) {
-              const CellId node_cell_id = node_cells[i_node_cell];
-              if (node_cell_id != cell_id) {
-                cell_set.insert(node_cell_id);
-              }
-            }
-          }
-
-          auto cell_stencil = stencil_array[cell_id];
-
-          auto i_set_cell = cell_set.begin();
-          for (size_t index = 0; index < cell_stencil.size(); ++index, ++i_set_cell) {
-            if (*i_set_cell != cell_stencil[index]) {
-              return false;
-            }
-          }
-        }
-      }
-      return true;
-    };
-
-    SECTION("1D")
-    {
-      SECTION("cartesian")
-      {
-        const auto& mesh = *MeshDataBaseForTests::get().cartesian1DMesh()->get<Mesh<1>>();
-
-        const Connectivity<1>& connectivity = mesh.connectivity();
-
-        auto stencil_array =
-          StencilManager::instance()
-            .getCellToCellStencilArray(connectivity, StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes});
-
-        REQUIRE(is_valid(connectivity, stencil_array));
-      }
-
-      SECTION("unordered")
-      {
-        const auto& mesh = *MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
-
-        const Connectivity<1>& connectivity = mesh.connectivity();
-        auto stencil_array =
-          StencilManager::instance()
-            .getCellToCellStencilArray(connectivity, StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes});
-
-        REQUIRE(is_valid(connectivity, stencil_array));
-      }
-    }
-
-    SECTION("2D")
-    {
-      SECTION("cartesian")
-      {
-        const auto& mesh = *MeshDataBaseForTests::get().cartesian2DMesh()->get<Mesh<2>>();
-
-        const Connectivity<2>& connectivity = mesh.connectivity();
-        REQUIRE(
-          is_valid(connectivity,
-                   StencilManager::instance()
-                     .getCellToCellStencilArray(connectivity,
-                                                StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes})));
-      }
-
-      SECTION("hybrid")
-      {
-        const auto& mesh = *MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
-
-        const Connectivity<2>& connectivity = mesh.connectivity();
-        REQUIRE(
-          is_valid(connectivity,
-                   StencilManager::instance()
-                     .getCellToCellStencilArray(connectivity,
-                                                StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes})));
-      }
-    }
-
-    SECTION("3D")
-    {
-      SECTION("carteian")
-      {
-        const auto& mesh = *MeshDataBaseForTests::get().cartesian3DMesh()->get<Mesh<3>>();
-
-        const Connectivity<3>& connectivity = mesh.connectivity();
-        REQUIRE(
-          is_valid(connectivity,
-                   StencilManager::instance()
-                     .getCellToCellStencilArray(connectivity,
-                                                StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes})));
-      }
-
-      SECTION("hybrid")
-      {
-        const auto& mesh = *MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
-
-        const Connectivity<3>& connectivity = mesh.connectivity();
-        REQUIRE(
-          is_valid(connectivity,
-                   StencilManager::instance()
-                     .getCellToCellStencilArray(connectivity,
-                                                StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes})));
-      }
-    }
-  }
-
-  SECTION("Stencil using symmetries")
-  {
-    auto check_ghost_cells_have_empty_stencils = [](const auto& stencil_array, const auto& connecticity) {
-      bool is_empty = true;
-
-      auto cell_is_owned = connecticity.cellIsOwned();
-
-      for (CellId cell_id = 0; cell_id < connecticity.numberOfCells(); ++cell_id) {
-        if (not cell_is_owned[cell_id]) {
-          is_empty &= (stencil_array[cell_id].size() == 0);
-          for (size_t i_symmetry_stencil = 0;
-               i_symmetry_stencil < stencil_array.symmetryBoundaryStencilArrayList().size(); ++i_symmetry_stencil) {
-            is_empty &=
-              (stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry_stencil].stencilArray()[cell_id].size() ==
-               0);
-          }
-        }
-      }
-
-      return is_empty;
-    };
-
-    auto symmetry_stencils_are_valid = [](const auto& stencil_array, const auto& mesh) {
-      bool is_valid = true;
-
-      auto node_to_cell_matrix = mesh.connectivity().nodeToCellMatrix();
-      auto cell_to_node_matrix = mesh.connectivity().cellToNodeMatrix();
-      auto cell_is_owned       = mesh.connectivity().cellIsOwned();
-      auto cell_number         = mesh.connectivity().cellNumber();
-
-      for (size_t i_symmetry_stencil = 0; i_symmetry_stencil < stencil_array.symmetryBoundaryStencilArrayList().size();
-           ++i_symmetry_stencil) {
-        const IBoundaryDescriptor& boundary_descriptor =
-          stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry_stencil].boundaryDescriptor();
-
-        auto boundary_stencil   = stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry_stencil].stencilArray();
-        auto boundary_node_list = getMeshFlatNodeBoundary(mesh, boundary_descriptor);
-
-        CellValue<bool> boundary_cell{mesh.connectivity()};
-        boundary_cell.fill(false);
-        auto node_list = boundary_node_list.nodeList();
-        for (size_t i_node = 0; i_node < node_list.size(); ++i_node) {
-          const NodeId node_id = node_list[i_node];
-          auto node_cell_list  = node_to_cell_matrix[node_id];
-          for (size_t i_cell = 0; i_cell < node_cell_list.size(); ++i_cell) {
-            const CellId cell_id   = node_cell_list[i_cell];
-            boundary_cell[cell_id] = true;
-          }
-        }
-
-        std::set<NodeId> symmetry_node;
-        for (size_t i_node = 0; i_node < node_list.size(); ++i_node) {
-          const NodeId node_id = node_list[i_node];
-          symmetry_node.insert(node_id);
-        }
-
-        for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
-          if ((not boundary_cell[cell_id]) or (not cell_is_owned[cell_id])) {
-            is_valid &= (boundary_stencil[cell_id].size() == 0);
-          } else {
-            auto cell_node_list = cell_to_node_matrix[cell_id];
-            std::set<CellId, std::function<bool(CellId, CellId)>> cell_set(
-              [&](CellId cell0_id, CellId cell1_id) { return cell_number[cell0_id] < cell_number[cell1_id]; });
-            for (size_t i_node = 0; i_node < cell_node_list.size(); ++i_node) {
-              const NodeId node_id = cell_node_list[i_node];
-              if (symmetry_node.contains(node_id)) {
-                auto node_cell_list = node_to_cell_matrix[node_id];
-                for (size_t i_node_cell = 0; i_node_cell < node_cell_list.size(); ++i_node_cell) {
-                  const CellId node_cell_id = node_cell_list[i_node_cell];
-                  cell_set.insert(node_cell_id);
-                }
-              }
-            }
-
-            if (cell_set.size() == boundary_stencil[cell_id].size()) {
-              size_t i = 0;
-              for (auto&& id : cell_set) {
-                is_valid &= (id == boundary_stencil[cell_id][i++]);
-              }
-            } else {
-              is_valid = false;
-            }
-          }
-        }
-      }
-
-      return is_valid;
-    };
-
-    SECTION("1D")
-    {
-      StencilManager::BoundaryDescriptorList list;
-      list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN"));
-      list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX"));
-
-      SECTION("cartesian")
-      {
-        const auto& mesh = *MeshDataBaseForTests::get().cartesian1DMesh()->get<Mesh<1>>();
-
-        const Connectivity<1>& connectivity = mesh.connectivity();
-
-        auto stencil_array =
-          StencilManager::instance()
-            .getCellToCellStencilArray(connectivity, StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes},
-                                       list);
-
-        REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
-        REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
-        REQUIRE(symmetry_stencils_are_valid(stencil_array, mesh));
-      }
-
-      SECTION("hybrid")
-      {
-        const auto& mesh = *MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
-
-        const Connectivity<1>& connectivity = mesh.connectivity();
-        auto stencil =
-          StencilManager::instance()
-            .getCellToCellStencilArray(connectivity, StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes},
-                                       list);
-
-        REQUIRE(stencil.symmetryBoundaryStencilArrayList().size() == 2);
-        REQUIRE(check_ghost_cells_have_empty_stencils(stencil, connectivity));
-        REQUIRE(symmetry_stencils_are_valid(stencil, mesh));
-      }
-    }
-
-    SECTION("2D")
-    {
-      StencilManager::BoundaryDescriptorList list;
-      list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN"));
-      list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX"));
-      list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN"));
-      list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX"));
-
-      SECTION("cartesian")
-      {
-        const auto& mesh = *MeshDataBaseForTests::get().cartesian2DMesh()->get<Mesh<2>>();
-
-        const Connectivity<2>& connectivity = mesh.connectivity();
-
-        auto stencil_array =
-          StencilManager::instance()
-            .getCellToCellStencilArray(connectivity, StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes},
-                                       list);
-
-        REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
-        REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
-        REQUIRE(symmetry_stencils_are_valid(stencil_array, mesh));
-      }
-
-      SECTION("hybrid")
-      {
-        const auto& mesh = *MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
-
-        const Connectivity<2>& connectivity = mesh.connectivity();
-        auto stencil =
-          StencilManager::instance()
-            .getCellToCellStencilArray(connectivity, StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes},
-                                       list);
-
-        REQUIRE(stencil.symmetryBoundaryStencilArrayList().size() == 4);
-        REQUIRE(check_ghost_cells_have_empty_stencils(stencil, connectivity));
-        REQUIRE(symmetry_stencils_are_valid(stencil, mesh));
-      }
-    }
-
-    SECTION("3D")
-    {
-      StencilManager::BoundaryDescriptorList list;
-      list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN"));
-      list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX"));
-      list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN"));
-      list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX"));
-      list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMIN"));
-      list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMAX"));
-
-      SECTION("cartesian")
-      {
-        const auto& mesh = *MeshDataBaseForTests::get().cartesian3DMesh()->get<Mesh<3>>();
-
-        const Connectivity<3>& connectivity = mesh.connectivity();
-        auto stencil =
-          StencilManager::instance()
-            .getCellToCellStencilArray(connectivity, StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes},
-                                       list);
-
-        REQUIRE(stencil.symmetryBoundaryStencilArrayList().size() == 6);
-        REQUIRE(check_ghost_cells_have_empty_stencils(stencil, connectivity));
-        REQUIRE(symmetry_stencils_are_valid(stencil, mesh));
-      }
-
-      SECTION("hybrid")
-      {
-        const auto& mesh = *MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
-
-        const Connectivity<3>& connectivity = mesh.connectivity();
-        auto stencil =
-          StencilManager::instance()
-            .getCellToCellStencilArray(connectivity, StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes},
-                                       list);
-
-        REQUIRE(stencil.symmetryBoundaryStencilArrayList().size() == 6);
-        REQUIRE(check_ghost_cells_have_empty_stencils(stencil, connectivity));
-        REQUIRE(symmetry_stencils_are_valid(stencil, mesh));
-      }
-    }
-  }
-}
diff --git a/tests/test_StencilBuilder_cell2cell.cpp b/tests/test_StencilBuilder_cell2cell.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..42ceb6a5da583a9869035db09f5239d008c6f780
--- /dev/null
+++ b/tests/test_StencilBuilder_cell2cell.cpp
@@ -0,0 +1,1081 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <mesh/CartesianMeshBuilder.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/ConnectivityUtils.hpp>
+#include <mesh/ItemValue.hpp>
+#include <mesh/ItemValueUtils.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshFaceBoundary.hpp>
+#include <mesh/MeshVariant.hpp>
+#include <mesh/NamedBoundaryDescriptor.hpp>
+#include <mesh/StencilManager.hpp>
+#include <utils/Messenger.hpp>
+
+#include <NbGhostLayersTester.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("StencilBuilder cell2cell", "[mesh]")
+{
+  auto is_valid = []<ItemType connecting_item_type>(const auto& connectivity, const auto& stencil_array,
+                                                    const size_t number_of_layers) {
+    auto cell_to_connecting_item_matrix =
+      connectivity.template getItemToItemMatrix<ItemType::cell, connecting_item_type>();
+    auto connecting_to_cell_matrix = connectivity.template getItemToItemMatrix<connecting_item_type, ItemType::cell>();
+    auto cell_is_owned             = connectivity.cellIsOwned();
+    auto cell_number               = connectivity.cellNumber();
+
+    using ConnectingItemId = ItemIdT<connecting_item_type>;
+
+    for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+      if (cell_is_owned[cell_id]) {
+        std::vector<CellId> expected_stencil;
+
+        std::set<CellId> marked_cell_set;
+        marked_cell_set.insert(cell_id);
+
+        std::set<ConnectingItemId> marked_connecting_item_set;
+        std::set<ConnectingItemId> layer_connecting_item_set;
+        {
+          auto cell_to_connecting_item_list = cell_to_connecting_item_matrix[cell_id];
+          for (size_t i_connecting_item = 0; i_connecting_item < cell_to_connecting_item_list.size();
+               ++i_connecting_item) {
+            const ConnectingItemId connecting_item_id = cell_to_connecting_item_list[i_connecting_item];
+            layer_connecting_item_set.insert(connecting_item_id);
+            marked_connecting_item_set.insert(connecting_item_id);
+          }
+        }
+
+        for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) {
+          std::set<CellId, std::function<bool(CellId, CellId)>> cell_set(
+            [=](CellId cell_0, CellId cell_1) { return cell_number[cell_0] < cell_number[cell_1]; });
+
+          for (auto&& connecting_item_id : layer_connecting_item_set) {
+            auto connecting_item_to_cell_list = connecting_to_cell_matrix[connecting_item_id];
+            for (size_t i_connecting_item_of_cell = 0; i_connecting_item_of_cell < connecting_item_to_cell_list.size();
+                 ++i_connecting_item_of_cell) {
+              const CellId connecting_item_id_of_cell = connecting_item_to_cell_list[i_connecting_item_of_cell];
+              if (not marked_cell_set.contains(connecting_item_id_of_cell)) {
+                cell_set.insert(connecting_item_id_of_cell);
+                marked_cell_set.insert(connecting_item_id_of_cell);
+              }
+            }
+          }
+
+          layer_connecting_item_set.clear();
+          for (auto layer_cell_id : cell_set) {
+            auto cell_to_connecting_item_list = cell_to_connecting_item_matrix[layer_cell_id];
+            for (size_t i_connecting_item = 0; i_connecting_item < cell_to_connecting_item_list.size();
+                 ++i_connecting_item) {
+              const ConnectingItemId connecting_item_id = cell_to_connecting_item_list[i_connecting_item];
+              if (not marked_connecting_item_set.contains(connecting_item_id)) {
+                layer_connecting_item_set.insert(connecting_item_id);
+                marked_connecting_item_set.insert(connecting_item_id);
+              }
+            }
+          }
+
+          for (auto&& set_cell_id : cell_set) {
+            expected_stencil.push_back(set_cell_id);
+          }
+        }
+
+        auto cell_stencil = stencil_array[cell_id];
+
+        auto i_set_cell = expected_stencil.begin();
+        if (cell_stencil.size() != expected_stencil.size()) {
+          return false;
+        }
+
+        for (size_t index = 0; index < cell_stencil.size(); ++index, ++i_set_cell) {
+          if (*i_set_cell != cell_stencil[index]) {
+            return false;
+          }
+        }
+      }
+    }
+    return true;
+  };
+
+  SECTION("inner stencil")
+  {
+    SECTION("1 layer")
+    {
+      SECTION("1D")
+      {
+        SECTION("cartesian")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+          const Connectivity<1>& connectivity = mesh.connectivity();
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::edge>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::face>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       1));
+        }
+
+        SECTION("unordered")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+          const Connectivity<1>& connectivity = mesh.connectivity();
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::edge>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::face>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       1));
+        }
+      }
+
+      SECTION("2D")
+      {
+        SECTION("cartesian")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().cartesian2DMesh()->get<Mesh<2>>();
+
+          const Connectivity<2>& connectivity = mesh.connectivity();
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::edge>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::face>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       1));
+        }
+
+        SECTION("hybrid")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+
+          const Connectivity<2>& connectivity = mesh.connectivity();
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::edge>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::face>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       1));
+        }
+      }
+
+      SECTION("3D")
+      {
+        SECTION("carteian")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().cartesian3DMesh()->get<Mesh<3>>();
+
+          const Connectivity<3>& connectivity = mesh.connectivity();
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::edge>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::face>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       1));
+        }
+
+        SECTION("hybrid")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
+
+          const Connectivity<3>& connectivity = mesh.connectivity();
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::edge>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::face>(connectivity,
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       1));
+        }
+      }
+    }
+
+    SECTION("2 layers")
+    {
+      NbGhostLayersTester nb_ghost_layers_tester(2);
+
+      SECTION("1D")
+      {
+        SECTION("cartesian")
+        {
+          auto mesh_v = CartesianMeshBuilder(TinyVector<1>{0}, TinyVector<1>{1}, TinyVector<1, uint64_t>(20)).mesh();
+
+          const auto& mesh = *mesh_v->get<Mesh<1>>();
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       2));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::edge>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       2));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::face>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       2));
+        }
+      }
+
+      SECTION("2D")
+      {
+        SECTION("cartesian")
+        {
+          auto mesh_v =
+            CartesianMeshBuilder(TinyVector<2>{0, 0}, TinyVector<2>{1, 2}, TinyVector<2, uint64_t>(5, 7)).mesh();
+          const auto& mesh = *mesh_v->get<Mesh<2>>();
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       2));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::edge>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       2));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::face>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       2));
+        }
+      }
+
+      SECTION("3D")
+      {
+        SECTION("carteian")
+        {
+          auto mesh_v =
+            CartesianMeshBuilder(TinyVector<3>{0, 0, 0}, TinyVector<3>{1, 1, 2}, TinyVector<3, uint64_t>(3, 4, 5))
+              .mesh();
+          const auto& mesh = *mesh_v->get<Mesh<3>>();
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       2));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::edge>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       2));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::face>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getCellToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       2));
+        }
+      }
+    }
+  }
+
+  SECTION("Stencil using symmetries")
+  {
+    auto check_ghost_cells_have_empty_stencils = [](const auto& stencil_array, const auto& connecticity) {
+      bool is_empty = true;
+
+      auto cell_is_owned = connecticity.cellIsOwned();
+
+      for (CellId cell_id = 0; cell_id < connecticity.numberOfCells(); ++cell_id) {
+        if (not cell_is_owned[cell_id]) {
+          is_empty &= (stencil_array[cell_id].size() == 0);
+          for (size_t i_symmetry_stencil = 0;
+               i_symmetry_stencil < stencil_array.symmetryBoundaryStencilArrayList().size(); ++i_symmetry_stencil) {
+            is_empty &=
+              (stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry_stencil].stencilArray()[cell_id].size() ==
+               0);
+          }
+        }
+      }
+
+      return is_empty;
+    };
+
+    auto are_symmetry_stencils_valid = []<ItemType connecting_item_type>(const auto& stencil_array, const auto& mesh,
+                                                                         const size_t number_of_layers) {
+      bool are_valid_symmetries = true;
+
+      auto connecting_to_cell_matrix =
+        mesh.connectivity().template getItemToItemMatrix<connecting_item_type, ItemType::cell>();
+      auto cell_to_connecting_item_matrix =
+        mesh.connectivity().template getItemToItemMatrix<ItemType::cell, connecting_item_type>();
+      auto cell_is_owned = mesh.connectivity().cellIsOwned();
+      auto cell_number   = mesh.connectivity().cellNumber();
+
+      auto connecting_number = mesh.connectivity().template number<connecting_item_type>();
+
+      using ConnectingItemId = ItemIdT<connecting_item_type>;
+      using MeshType         = std::decay_t<decltype(mesh)>;
+
+      for (size_t i_symmetry_stencil = 0; i_symmetry_stencil < stencil_array.symmetryBoundaryStencilArrayList().size();
+           ++i_symmetry_stencil) {
+        const IBoundaryDescriptor& boundary_descriptor =
+          stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry_stencil].boundaryDescriptor();
+
+        auto boundary_stencil   = stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry_stencil].stencilArray();
+        auto boundary_face_list = getMeshFaceBoundary(mesh, boundary_descriptor);
+
+        std::set<ConnectingItemId> sym_connecting_item_set;
+
+        if constexpr (ItemTypeId<MeshType::Dimension>::itemTId(connecting_item_type) ==
+                      ItemTypeId<MeshType::Dimension>::itemTId(ItemType::face)) {
+          for (size_t i_face = 0; i_face < boundary_face_list.faceList().size(); ++i_face) {
+            const FaceId face_id = boundary_face_list.faceList()[i_face];
+            sym_connecting_item_set.insert(ConnectingItemId{FaceId::base_type{face_id}});
+          }
+
+        } else {
+          auto face_to_connecting_item_matrix =
+            mesh.connectivity().template getItemToItemMatrix<ItemType::face, connecting_item_type>();
+
+          for (size_t i_face = 0; i_face < boundary_face_list.faceList().size(); ++i_face) {
+            const FaceId face_id      = boundary_face_list.faceList()[i_face];
+            auto connecting_item_list = face_to_connecting_item_matrix[face_id];
+            for (size_t i_connecting_item = 0; i_connecting_item < connecting_item_list.size(); ++i_connecting_item) {
+              sym_connecting_item_set.insert(connecting_item_list[i_connecting_item]);
+            }
+          }
+        }
+
+        for (CellId cell_id = 0; cell_id < mesh.numberOfCells(); ++cell_id) {
+          if (not cell_is_owned[cell_id]) {
+            are_valid_symmetries &= (boundary_stencil[cell_id].size() == 0);
+          } else {
+            std::vector<CellId> expected_stencil;
+
+            std::set<CellId> marked_cell_set;
+            marked_cell_set.insert(cell_id);
+
+            std::set<ConnectingItemId> marked_connecting_item_set;
+            std::set<ConnectingItemId> layer_connecting_item_set;
+            {
+              auto cell_to_connecting_item_list = cell_to_connecting_item_matrix[cell_id];
+              for (size_t i_connecting_item = 0; i_connecting_item < cell_to_connecting_item_list.size();
+                   ++i_connecting_item) {
+                const ConnectingItemId connecting_item_id = cell_to_connecting_item_list[i_connecting_item];
+                layer_connecting_item_set.insert(connecting_item_id);
+                marked_connecting_item_set.insert(connecting_item_id);
+              }
+            }
+
+            std::set<ConnectingItemId> marked_sym_connecting_item_set;
+            std::set<ConnectingItemId> layer_sym_connecting_item_set;
+
+            for (auto&& connecting_item_id : marked_connecting_item_set) {
+              if (sym_connecting_item_set.contains(connecting_item_id)) {
+                marked_sym_connecting_item_set.insert(connecting_item_id);
+                layer_sym_connecting_item_set.insert(connecting_item_id);
+              }
+            }
+
+            std::set<CellId> marked_sym_cell_set;
+
+            for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) {
+              std::set<CellId, std::function<bool(CellId, CellId)>> cell_set(
+                [=](CellId cell_0, CellId cell_1) { return cell_number[cell_0] < cell_number[cell_1]; });
+
+              for (auto&& connecting_item_id : layer_connecting_item_set) {
+                auto connecting_item_to_cell_list = connecting_to_cell_matrix[connecting_item_id];
+                for (size_t i_connecting_item_of_cell = 0;
+                     i_connecting_item_of_cell < connecting_item_to_cell_list.size(); ++i_connecting_item_of_cell) {
+                  const CellId connecting_item_id_of_cell = connecting_item_to_cell_list[i_connecting_item_of_cell];
+                  if (not marked_cell_set.contains(connecting_item_id_of_cell)) {
+                    cell_set.insert(connecting_item_id_of_cell);
+                    marked_cell_set.insert(connecting_item_id_of_cell);
+                  }
+                }
+              }
+
+              std::set<CellId, std::function<bool(CellId, CellId)>> sym_cell_set(
+                [=](CellId cell_0, CellId cell_1) { return cell_number[cell_0] < cell_number[cell_1]; });
+
+              for (auto&& connecting_item_id : layer_sym_connecting_item_set) {
+                auto connecting_item_to_cell_list = connecting_to_cell_matrix[connecting_item_id];
+                for (size_t i_connecting_item_of_cell = 0;
+                     i_connecting_item_of_cell < connecting_item_to_cell_list.size(); ++i_connecting_item_of_cell) {
+                  const CellId connecting_item_id_of_cell = connecting_item_to_cell_list[i_connecting_item_of_cell];
+                  if (not marked_sym_cell_set.contains(connecting_item_id_of_cell)) {
+                    sym_cell_set.insert(connecting_item_id_of_cell);
+                    marked_sym_cell_set.insert(connecting_item_id_of_cell);
+                  }
+                }
+              }
+
+              for (auto&& set_sym_cell_id : sym_cell_set) {
+                expected_stencil.push_back(set_sym_cell_id);
+              }
+
+              layer_connecting_item_set.clear();
+              for (auto&& layer_cell_id : cell_set) {
+                auto cell_to_connecting_item_list = cell_to_connecting_item_matrix[layer_cell_id];
+                for (size_t i_connecting_item = 0; i_connecting_item < cell_to_connecting_item_list.size();
+                     ++i_connecting_item) {
+                  const ConnectingItemId connecting_item_id = cell_to_connecting_item_list[i_connecting_item];
+                  if (not marked_connecting_item_set.contains(connecting_item_id)) {
+                    layer_connecting_item_set.insert(connecting_item_id);
+                    marked_connecting_item_set.insert(connecting_item_id);
+                  }
+                }
+              }
+
+              layer_sym_connecting_item_set.clear();
+
+              for (auto&& connecting_item_id : layer_connecting_item_set) {
+                if (sym_connecting_item_set.contains(connecting_item_id)) {
+                  if (not marked_sym_connecting_item_set.contains(connecting_item_id)) {
+                    marked_sym_connecting_item_set.insert(connecting_item_id);
+                    layer_sym_connecting_item_set.insert(connecting_item_id);
+                  }
+                }
+              }
+
+              for (auto layer_sym_cell_id : sym_cell_set) {
+                auto cell_to_connecting_item_list = cell_to_connecting_item_matrix[layer_sym_cell_id];
+                for (size_t i_connecting_item = 0; i_connecting_item < cell_to_connecting_item_list.size();
+                     ++i_connecting_item) {
+                  const ConnectingItemId connecting_item_id = cell_to_connecting_item_list[i_connecting_item];
+                  if (not marked_sym_connecting_item_set.contains(connecting_item_id)) {
+                    marked_sym_connecting_item_set.insert(connecting_item_id);
+                    layer_sym_connecting_item_set.insert(connecting_item_id);
+                  }
+                }
+              }
+            }
+
+            auto cell_stencil = boundary_stencil[cell_id];
+
+            if (cell_stencil.size() != expected_stencil.size()) {
+              are_valid_symmetries = false;
+            }
+
+            auto i_set_cell = expected_stencil.begin();
+            for (size_t index = 0; index < cell_stencil.size(); ++index, ++i_set_cell) {
+              if (*i_set_cell != cell_stencil[index]) {
+                are_valid_symmetries = false;
+              }
+            }
+          }
+        }
+      }
+
+      return are_valid_symmetries;
+    };
+
+    SECTION("1 layer")
+    {
+      SECTION("1D")
+      {
+        StencilManager::BoundaryDescriptorList list;
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX"));
+
+        SECTION("cartesian")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().cartesian1DMesh()->get<Mesh<1>>();
+
+          const Connectivity<1>& connectivity = mesh.connectivity();
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1));
+          }
+        }
+
+        SECTION("unordered")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+          const Connectivity<1>& connectivity = mesh.connectivity();
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1));
+          }
+        }
+      }
+
+      SECTION("2D")
+      {
+        StencilManager::BoundaryDescriptorList list;
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX"));
+
+        SECTION("cartesian")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().cartesian2DMesh()->get<Mesh<2>>();
+
+          const Connectivity<2>& connectivity = mesh.connectivity();
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::edge>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 1));
+            REQUIRE(not(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1)));
+            REQUIRE(is_valid.template operator()<ItemType::face>(connectivity, stencil_array, 1));
+          }
+        }
+
+        SECTION("hybrid")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+
+          const Connectivity<2>& connectivity = mesh.connectivity();
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::edge>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 1));
+            REQUIRE(not(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1)));
+            REQUIRE(is_valid.template operator()<ItemType::face>(connectivity, stencil_array, 1));
+          }
+        }
+      }
+
+      SECTION("3D")
+      {
+        StencilManager::BoundaryDescriptorList list;
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMAX"));
+
+        SECTION("cartesian")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().cartesian3DMesh()->get<Mesh<3>>();
+
+          const Connectivity<3>& connectivity = mesh.connectivity();
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::edge>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::face>(connectivity, stencil_array, 1));
+          }
+        }
+
+        SECTION("hybrid")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
+
+          const Connectivity<3>& connectivity = mesh.connectivity();
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::edge>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::face>(connectivity, stencil_array, 1));
+          }
+        }
+      }
+    }
+
+    SECTION("2 layers")
+    {
+      NbGhostLayersTester nb_ghost_layers_tester(2);
+
+      SECTION("1D")
+      {
+        StencilManager::BoundaryDescriptorList list;
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX"));
+
+        SECTION("cartesian")
+        {
+          auto mesh_v = CartesianMeshBuilder(TinyVector<1>{0}, TinyVector<1>{1}, TinyVector<1, uint64_t>(20)).mesh();
+          const auto& mesh = *mesh_v->get<Mesh<1>>();
+
+          const Connectivity<1>& connectivity = mesh.connectivity();
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 2));
+            REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 2));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 2));
+            REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 2));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 2));
+            REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 2));
+          }
+        }
+      }
+
+      SECTION("2D")
+      {
+        StencilManager::BoundaryDescriptorList list;
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX"));
+
+        SECTION("cartesian")
+        {
+          auto mesh_v =
+            CartesianMeshBuilder(TinyVector<2>{0, 0}, TinyVector<2>{1, 2}, TinyVector<2, uint64_t>(5, 7)).mesh();
+          const auto& mesh = *mesh_v->get<Mesh<2>>();
+
+          const Connectivity<2>& connectivity = mesh.connectivity();
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 2));
+            REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 2));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 2));
+            REQUIRE(is_valid.template operator()<ItemType::edge>(connectivity, stencil_array, 2));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 2));
+            REQUIRE(not(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 2)));
+            REQUIRE(is_valid.template operator()<ItemType::face>(connectivity, stencil_array, 2));
+          }
+        }
+      }
+
+      SECTION("3D")
+      {
+        StencilManager::BoundaryDescriptorList list;
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMAX"));
+
+        SECTION("cartesian")
+        {
+          auto mesh_v =
+            CartesianMeshBuilder(TinyVector<3>{0, 0, 0}, TinyVector<3>{1, 1, 2}, TinyVector<3, uint64_t>(3, 4, 5))
+              .mesh();
+          const auto& mesh = *mesh_v->get<Mesh<3>>();
+
+          const Connectivity<3>& connectivity = mesh.connectivity();
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::node>(stencil_array, mesh, 2));
+            REQUIRE(is_valid.template operator()<ItemType::node>(connectivity, stencil_array, 2));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::edge>(stencil_array, mesh, 2));
+            REQUIRE(is_valid.template operator()<ItemType::edge>(connectivity, stencil_array, 2));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getCellToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_cells_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(are_symmetry_stencils_valid.template operator()<ItemType::face>(stencil_array, mesh, 2));
+            REQUIRE(is_valid.template operator()<ItemType::face>(connectivity, stencil_array, 2));
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/tests/test_StencilBuilder_node2cell.cpp b/tests/test_StencilBuilder_node2cell.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6d9c429c6d4ba903185c90a1e6c37d63bf9c9feb
--- /dev/null
+++ b/tests/test_StencilBuilder_node2cell.cpp
@@ -0,0 +1,1184 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <mesh/CartesianMeshBuilder.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/ConnectivityUtils.hpp>
+#include <mesh/ItemValue.hpp>
+#include <mesh/ItemValueUtils.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/MeshFaceBoundary.hpp>
+#include <mesh/MeshVariant.hpp>
+#include <mesh/NamedBoundaryDescriptor.hpp>
+#include <mesh/StencilManager.hpp>
+#include <utils/Messenger.hpp>
+
+#include <NbGhostLayersTester.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("StencilBuilder node2cell", "[mesh]")
+{
+  auto is_valid = []<ItemType source_item_type, ItemType connecting_item_type>(const auto& connectivity,
+                                                                               const auto& stencil_array,
+                                                                               const size_t number_of_layers) {
+    constexpr size_t Dimension = std::decay_t<decltype(connectivity)>::Dimension;
+
+    auto cell_to_connecting_item_matrix =
+      connectivity.template getItemToItemMatrix<ItemType::cell, connecting_item_type>();
+
+    auto source_item_to_connecting_item_matrix =
+      [](const auto& given_connectivity) -> ItemToItemMatrix<source_item_type, connecting_item_type> {
+      if constexpr (ItemTypeId<Dimension>::itemTId(source_item_type) ==
+                    ItemTypeId<Dimension>::itemTId(connecting_item_type)) {
+        return ConnectivityMatrix{};
+      } else {
+        return given_connectivity.template getItemToItemMatrix<source_item_type, connecting_item_type>();
+      }
+    }(connectivity);
+
+    auto connecting_to_cell_matrix = connectivity.template getItemToItemMatrix<connecting_item_type, ItemType::cell>();
+    auto cell_is_owned             = connectivity.cellIsOwned();
+    auto cell_number               = connectivity.cellNumber();
+    auto source_item_is_owned      = connectivity.template isOwned<source_item_type>();
+
+    using SourceItemId     = ItemIdT<source_item_type>;
+    using ConnectingItemId = ItemIdT<connecting_item_type>;
+
+    for (SourceItemId source_item_id = 0; source_item_id < connectivity.template numberOf<source_item_type>();
+         ++source_item_id) {
+      if (source_item_is_owned[source_item_id]) {
+        std::vector<CellId> expected_stencil;
+
+        std::set<CellId> marked_cell_set;
+        if constexpr (source_item_type == ItemType::cell) {
+          marked_cell_set.insert(source_item_id);
+        }
+
+        std::set<ConnectingItemId> marked_connecting_item_set;
+        std::set<ConnectingItemId> layer_connecting_item_set;
+        {
+          if constexpr (ItemTypeId<Dimension>::itemTId(source_item_type) ==
+                        ItemTypeId<Dimension>::itemTId(connecting_item_type)) {
+            ConnectingItemId connecting_id =
+              ConnectingItemId{static_cast<typename SourceItemId::base_type>(source_item_id)};
+            layer_connecting_item_set.insert(connecting_id);
+            marked_connecting_item_set.insert(connecting_id);
+          } else {
+            auto source_item_to_connecting_item_list = source_item_to_connecting_item_matrix[source_item_id];
+            for (size_t i_connecting_item = 0; i_connecting_item < source_item_to_connecting_item_list.size();
+                 ++i_connecting_item) {
+              const ConnectingItemId connecting_item_id = source_item_to_connecting_item_list[i_connecting_item];
+              layer_connecting_item_set.insert(connecting_item_id);
+              marked_connecting_item_set.insert(connecting_item_id);
+            }
+          }
+        }
+
+        for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) {
+          std::set<CellId, std::function<bool(CellId, CellId)>> cell_set(
+            [=](CellId cell_0, CellId cell_1) { return cell_number[cell_0] < cell_number[cell_1]; });
+
+          for (auto&& connecting_item_id : layer_connecting_item_set) {
+            auto connecting_item_to_cell_list = connecting_to_cell_matrix[connecting_item_id];
+            for (size_t i_connecting_item_of_cell = 0; i_connecting_item_of_cell < connecting_item_to_cell_list.size();
+                 ++i_connecting_item_of_cell) {
+              const CellId connecting_item_id_of_cell = connecting_item_to_cell_list[i_connecting_item_of_cell];
+              if (not marked_cell_set.contains(connecting_item_id_of_cell)) {
+                cell_set.insert(connecting_item_id_of_cell);
+                marked_cell_set.insert(connecting_item_id_of_cell);
+              }
+            }
+          }
+
+          layer_connecting_item_set.clear();
+          for (auto layer_cell_id : cell_set) {
+            auto cell_to_connecting_item_list = cell_to_connecting_item_matrix[layer_cell_id];
+            for (size_t i_connecting_item = 0; i_connecting_item < cell_to_connecting_item_list.size();
+                 ++i_connecting_item) {
+              const ConnectingItemId connecting_item_id = cell_to_connecting_item_list[i_connecting_item];
+              if (not marked_connecting_item_set.contains(connecting_item_id)) {
+                layer_connecting_item_set.insert(connecting_item_id);
+                marked_connecting_item_set.insert(connecting_item_id);
+              }
+            }
+          }
+
+          for (auto&& set_cell_id : cell_set) {
+            expected_stencil.push_back(set_cell_id);
+          }
+        }
+
+        auto cell_stencil = stencil_array[source_item_id];
+
+        auto i_set_cell = expected_stencil.begin();
+        if (cell_stencil.size() != expected_stencil.size()) {
+          return false;
+        }
+
+        for (size_t index = 0; index < cell_stencil.size(); ++index, ++i_set_cell) {
+          if (*i_set_cell != cell_stencil[index]) {
+            return false;
+          }
+        }
+      }
+    }
+    return true;
+  };
+
+  SECTION("inner stencil")
+  {
+    SECTION("1 layer")
+    {
+      SECTION("1D")
+      {
+        SECTION("cartesian")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+          const Connectivity<1>& connectivity = mesh.connectivity();
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::node>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::edge>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::face>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       1));
+        }
+
+        SECTION("unordered")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+          const Connectivity<1>& connectivity = mesh.connectivity();
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::node>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::edge>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::face>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       1));
+        }
+      }
+
+      SECTION("2D")
+      {
+        SECTION("cartesian")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().cartesian2DMesh()->get<Mesh<2>>();
+
+          const Connectivity<2>& connectivity = mesh.connectivity();
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::node>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::edge>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::face>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       1));
+        }
+
+        SECTION("hybrid")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+
+          const Connectivity<2>& connectivity = mesh.connectivity();
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::node>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::edge>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::face>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       1));
+        }
+      }
+
+      SECTION("3D")
+      {
+        SECTION("carteian")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().cartesian3DMesh()->get<Mesh<3>>();
+
+          const Connectivity<3>& connectivity = mesh.connectivity();
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::node>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::edge>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::face>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       1));
+        }
+
+        SECTION("hybrid")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
+
+          const Connectivity<3>& connectivity = mesh.connectivity();
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::node>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::edge>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       1));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::face>(connectivity,
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(connectivity,
+                                                                    StencilDescriptor{1, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       1));
+        }
+      }
+    }
+
+    SECTION("2 layers")
+    {
+      NbGhostLayersTester nb_ghost_layers_tester(2);
+
+      SECTION("1D")
+      {
+        SECTION("cartesian")
+        {
+          auto mesh_v = CartesianMeshBuilder(TinyVector<1>{0}, TinyVector<1>{1}, TinyVector<1, uint64_t>(20)).mesh();
+
+          const auto& mesh = *mesh_v->get<Mesh<1>>();
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::node>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       2));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::edge>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       2));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::face>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       2));
+        }
+      }
+
+      SECTION("2D")
+      {
+        SECTION("cartesian")
+        {
+          auto mesh_v =
+            CartesianMeshBuilder(TinyVector<2>{0, 0}, TinyVector<2>{1, 2}, TinyVector<2, uint64_t>(5, 7)).mesh();
+          const auto& mesh = *mesh_v->get<Mesh<2>>();
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::node>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       2));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::edge>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       2));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::face>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       2));
+        }
+      }
+
+      SECTION("3D")
+      {
+        SECTION("carteian")
+        {
+          auto mesh_v =
+            CartesianMeshBuilder(TinyVector<3>{0, 0, 0}, TinyVector<3>{1, 1, 2}, TinyVector<3, uint64_t>(3, 4, 5))
+              .mesh();
+          const auto& mesh = *mesh_v->get<Mesh<3>>();
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::node>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_nodes}),
+                                       2));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::edge>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_edges}),
+                                       2));
+
+          REQUIRE(
+            is_valid.template
+            operator()<ItemType::node,
+                       ItemType::face>(mesh.connectivity(),
+                                       StencilManager::instance()
+                                         .getNodeToCellStencilArray(mesh.connectivity(),
+                                                                    StencilDescriptor{2, StencilDescriptor::
+                                                                                           ConnectionType::by_faces}),
+                                       2));
+        }
+      }
+    }
+  }
+
+  SECTION("Stencil using symmetries")
+  {
+    auto check_ghost_nodes_have_empty_stencils = [](const auto& stencil_array, const auto& connecticity) {
+      bool is_empty = true;
+
+      auto node_is_owned = connecticity.nodeIsOwned();
+
+      for (NodeId node_id = 0; node_id < connecticity.numberOfNodes(); ++node_id) {
+        if (not node_is_owned[node_id]) {
+          is_empty &= (stencil_array[node_id].size() == 0);
+          for (size_t i_symmetry_stencil = 0;
+               i_symmetry_stencil < stencil_array.symmetryBoundaryStencilArrayList().size(); ++i_symmetry_stencil) {
+            is_empty &=
+              (stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry_stencil].stencilArray()[node_id].size() ==
+               0);
+          }
+        }
+      }
+
+      return is_empty;
+    };
+
+    auto are_symmetry_stencils_valid =
+      []<ItemType source_item_type, ItemType connecting_item_type>(const auto& stencil_array, const auto& mesh,
+                                                                   const size_t number_of_layers) {
+        bool are_valid_symmetries = true;
+
+        constexpr const size_t Dimension = std::decay_t<decltype(mesh)>::Dimension;
+
+        auto connecting_to_cell_matrix =
+          mesh.connectivity().template getItemToItemMatrix<connecting_item_type, ItemType::cell>();
+        auto cell_to_connecting_item_matrix =
+          mesh.connectivity().template getItemToItemMatrix<ItemType::cell, connecting_item_type>();
+        //        auto source_item_is_owned        = mesh.connectivity().cellIsOwned();
+        auto cell_number          = mesh.connectivity().cellNumber();
+        auto source_item_is_owned = mesh.connectivity().template isOwned<source_item_type>();
+
+        auto connecting_number = mesh.connectivity().template number<connecting_item_type>();
+
+        auto source_item_to_connecting_item_matrix =
+          [](const auto& given_connectivity) -> ItemToItemMatrix<source_item_type, connecting_item_type> {
+          if constexpr (ItemTypeId<Dimension>::itemTId(source_item_type) ==
+                        ItemTypeId<Dimension>::itemTId(connecting_item_type)) {
+            return ConnectivityMatrix{};
+          } else {
+            return given_connectivity.template getItemToItemMatrix<source_item_type, connecting_item_type>();
+          }
+        }(mesh.connectivity());
+
+        using SourceItemId     = ItemIdT<source_item_type>;
+        using ConnectingItemId = ItemIdT<connecting_item_type>;
+        using MeshType         = std::decay_t<decltype(mesh)>;
+
+        for (size_t i_symmetry_stencil = 0;
+             i_symmetry_stencil < stencil_array.symmetryBoundaryStencilArrayList().size(); ++i_symmetry_stencil) {
+          const IBoundaryDescriptor& boundary_descriptor =
+            stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry_stencil].boundaryDescriptor();
+
+          auto boundary_stencil   = stencil_array.symmetryBoundaryStencilArrayList()[i_symmetry_stencil].stencilArray();
+          auto boundary_face_list = getMeshFaceBoundary(mesh, boundary_descriptor);
+
+          std::set<ConnectingItemId> sym_connecting_item_set;
+
+          if constexpr (ItemTypeId<MeshType::Dimension>::itemTId(connecting_item_type) ==
+                        ItemTypeId<MeshType::Dimension>::itemTId(ItemType::face)) {
+            for (size_t i_face = 0; i_face < boundary_face_list.faceList().size(); ++i_face) {
+              const FaceId face_id = boundary_face_list.faceList()[i_face];
+              sym_connecting_item_set.insert(ConnectingItemId{FaceId::base_type{face_id}});
+            }
+
+          } else {
+            auto face_to_connecting_item_matrix =
+              mesh.connectivity().template getItemToItemMatrix<ItemType::face, connecting_item_type>();
+
+            for (size_t i_face = 0; i_face < boundary_face_list.faceList().size(); ++i_face) {
+              const FaceId face_id      = boundary_face_list.faceList()[i_face];
+              auto connecting_item_list = face_to_connecting_item_matrix[face_id];
+              for (size_t i_connecting_item = 0; i_connecting_item < connecting_item_list.size(); ++i_connecting_item) {
+                sym_connecting_item_set.insert(connecting_item_list[i_connecting_item]);
+              }
+            }
+          }
+
+          for (SourceItemId source_item_id = 0; source_item_id < mesh.template numberOf<source_item_type>();
+               ++source_item_id) {
+            if (not source_item_is_owned[source_item_id]) {
+              are_valid_symmetries &= (boundary_stencil[source_item_id].size() == 0);
+            } else {
+              std::vector<CellId> expected_stencil;
+
+              std::set<CellId> marked_cell_set;
+              if constexpr (source_item_type == ItemType::cell) {
+                marked_cell_set.insert(source_item_id);
+              }
+
+              std::set<ConnectingItemId> marked_connecting_item_set;
+              std::set<ConnectingItemId> layer_connecting_item_set;
+              if constexpr (ItemTypeId<Dimension>::itemTId(source_item_type) ==
+                            ItemTypeId<Dimension>::itemTId(connecting_item_type)) {
+                const ConnectingItemId connecting_item_id =
+                  static_cast<typename SourceItemId::base_type>(source_item_id);
+                layer_connecting_item_set.insert(connecting_item_id);
+                marked_connecting_item_set.insert(connecting_item_id);
+              } else {
+                auto source_item_to_connecting_item_list = source_item_to_connecting_item_matrix[source_item_id];
+                for (size_t i_connecting_item = 0; i_connecting_item < source_item_to_connecting_item_list.size();
+                     ++i_connecting_item) {
+                  const ConnectingItemId connecting_item_id = source_item_to_connecting_item_list[i_connecting_item];
+                  layer_connecting_item_set.insert(connecting_item_id);
+                  marked_connecting_item_set.insert(connecting_item_id);
+                }
+              }
+
+              std::set<ConnectingItemId> marked_sym_connecting_item_set;
+              std::set<ConnectingItemId> layer_sym_connecting_item_set;
+
+              for (auto&& connecting_item_id : marked_connecting_item_set) {
+                if (sym_connecting_item_set.contains(connecting_item_id)) {
+                  marked_sym_connecting_item_set.insert(connecting_item_id);
+                  layer_sym_connecting_item_set.insert(connecting_item_id);
+                }
+              }
+
+              std::set<CellId> marked_sym_cell_set;
+
+              for (size_t i_layer = 0; i_layer < number_of_layers; ++i_layer) {
+                std::set<CellId, std::function<bool(CellId, CellId)>> cell_set(
+                  [=](CellId cell_0, CellId cell_1) { return cell_number[cell_0] < cell_number[cell_1]; });
+
+                for (auto&& connecting_item_id : layer_connecting_item_set) {
+                  auto connecting_item_to_cell_list = connecting_to_cell_matrix[connecting_item_id];
+                  for (size_t i_connecting_item_of_cell = 0;
+                       i_connecting_item_of_cell < connecting_item_to_cell_list.size(); ++i_connecting_item_of_cell) {
+                    const CellId connecting_item_id_of_cell = connecting_item_to_cell_list[i_connecting_item_of_cell];
+                    if (not marked_cell_set.contains(connecting_item_id_of_cell)) {
+                      cell_set.insert(connecting_item_id_of_cell);
+                      marked_cell_set.insert(connecting_item_id_of_cell);
+                    }
+                  }
+                }
+
+                std::set<CellId, std::function<bool(CellId, CellId)>> sym_cell_set(
+                  [=](CellId cell_0, CellId cell_1) { return cell_number[cell_0] < cell_number[cell_1]; });
+
+                for (auto&& connecting_item_id : layer_sym_connecting_item_set) {
+                  auto connecting_item_to_cell_list = connecting_to_cell_matrix[connecting_item_id];
+                  for (size_t i_connecting_item_of_cell = 0;
+                       i_connecting_item_of_cell < connecting_item_to_cell_list.size(); ++i_connecting_item_of_cell) {
+                    const CellId connecting_item_id_of_cell = connecting_item_to_cell_list[i_connecting_item_of_cell];
+                    if (not marked_sym_cell_set.contains(connecting_item_id_of_cell)) {
+                      sym_cell_set.insert(connecting_item_id_of_cell);
+                      marked_sym_cell_set.insert(connecting_item_id_of_cell);
+                    }
+                  }
+                }
+
+                for (auto&& set_sym_cell_id : sym_cell_set) {
+                  expected_stencil.push_back(set_sym_cell_id);
+                }
+
+                layer_connecting_item_set.clear();
+                for (auto&& layer_cell_id : cell_set) {
+                  auto cell_to_connecting_item_list = cell_to_connecting_item_matrix[layer_cell_id];
+                  for (size_t i_connecting_item = 0; i_connecting_item < cell_to_connecting_item_list.size();
+                       ++i_connecting_item) {
+                    const ConnectingItemId connecting_item_id = cell_to_connecting_item_list[i_connecting_item];
+                    if (not marked_connecting_item_set.contains(connecting_item_id)) {
+                      layer_connecting_item_set.insert(connecting_item_id);
+                      marked_connecting_item_set.insert(connecting_item_id);
+                    }
+                  }
+                }
+
+                layer_sym_connecting_item_set.clear();
+
+                for (auto&& connecting_item_id : layer_connecting_item_set) {
+                  if (sym_connecting_item_set.contains(connecting_item_id)) {
+                    if (not marked_sym_connecting_item_set.contains(connecting_item_id)) {
+                      marked_sym_connecting_item_set.insert(connecting_item_id);
+                      layer_sym_connecting_item_set.insert(connecting_item_id);
+                    }
+                  }
+                }
+
+                for (auto layer_sym_cell_id : sym_cell_set) {
+                  auto cell_to_connecting_item_list = cell_to_connecting_item_matrix[layer_sym_cell_id];
+                  for (size_t i_connecting_item = 0; i_connecting_item < cell_to_connecting_item_list.size();
+                       ++i_connecting_item) {
+                    const ConnectingItemId connecting_item_id = cell_to_connecting_item_list[i_connecting_item];
+                    if (not marked_sym_connecting_item_set.contains(connecting_item_id)) {
+                      marked_sym_connecting_item_set.insert(connecting_item_id);
+                      layer_sym_connecting_item_set.insert(connecting_item_id);
+                    }
+                  }
+                }
+              }
+
+              auto cell_stencil = boundary_stencil[source_item_id];
+
+              if (cell_stencil.size() != expected_stencil.size()) {
+                are_valid_symmetries = false;
+              }
+
+              auto i_set_cell = expected_stencil.begin();
+              for (size_t index = 0; index < cell_stencil.size(); ++index, ++i_set_cell) {
+                if (*i_set_cell != cell_stencil[index]) {
+                  are_valid_symmetries = false;
+                }
+              }
+            }
+          }
+        }
+
+        return are_valid_symmetries;
+      };
+
+    SECTION("1 layer")
+    {
+      SECTION("1D")
+      {
+        StencilManager::BoundaryDescriptorList list;
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX"));
+
+        SECTION("cartesian")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().cartesian1DMesh()->get<Mesh<1>>();
+
+          const Connectivity<1>& connectivity = mesh.connectivity();
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1));
+          }
+        }
+
+        SECTION("unordered")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().unordered1DMesh()->get<Mesh<1>>();
+
+          const Connectivity<1>& connectivity = mesh.connectivity();
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1));
+          }
+        }
+      }
+
+      SECTION("2D")
+      {
+        StencilManager::BoundaryDescriptorList list;
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX"));
+
+        SECTION("cartesian")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().cartesian2DMesh()->get<Mesh<2>>();
+
+          const Connectivity<2>& connectivity = mesh.connectivity();
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::edge>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::face>(connectivity, stencil_array, 1));
+          }
+        }
+
+        SECTION("hybrid")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().hybrid2DMesh()->get<Mesh<2>>();
+
+          const Connectivity<2>& connectivity = mesh.connectivity();
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::edge>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::face>(connectivity, stencil_array, 1));
+          }
+        }
+      }
+
+      SECTION("3D")
+      {
+        StencilManager::BoundaryDescriptorList list;
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMAX"));
+
+        SECTION("cartesian")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().cartesian3DMesh()->get<Mesh<3>>();
+
+          const Connectivity<3>& connectivity = mesh.connectivity();
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::edge>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::face>(connectivity, stencil_array, 1));
+          }
+        }
+
+        SECTION("hybrid")
+        {
+          const auto& mesh = *MeshDataBaseForTests::get().hybrid3DMesh()->get<Mesh<3>>();
+
+          const Connectivity<3>& connectivity = mesh.connectivity();
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::edge>(connectivity, stencil_array, 1));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{1, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 1));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::face>(connectivity, stencil_array, 1));
+          }
+        }
+      }
+    }
+
+    SECTION("2 layers")
+    {
+      NbGhostLayersTester nb_ghost_layers_tester(2);
+
+      SECTION("1D")
+      {
+        StencilManager::BoundaryDescriptorList list;
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX"));
+
+        SECTION("cartesian")
+        {
+          auto mesh_v = CartesianMeshBuilder(TinyVector<1>{0}, TinyVector<1>{1}, TinyVector<1, uint64_t>(20)).mesh();
+          const auto& mesh = *mesh_v->get<Mesh<1>>();
+
+          const Connectivity<1>& connectivity = mesh.connectivity();
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 2));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 2));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 2));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 2));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 2);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 2));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 2));
+          }
+        }
+      }
+
+      SECTION("2D")
+      {
+        StencilManager::BoundaryDescriptorList list;
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX"));
+
+        SECTION("cartesian")
+        {
+          auto mesh_v =
+            CartesianMeshBuilder(TinyVector<2>{0, 0}, TinyVector<2>{1, 2}, TinyVector<2, uint64_t>(5, 7)).mesh();
+          const auto& mesh = *mesh_v->get<Mesh<2>>();
+
+          const Connectivity<2>& connectivity = mesh.connectivity();
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 2));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 2));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 2));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::edge>(connectivity, stencil_array, 2));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 4);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 2));
+            REQUIRE(not(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 2)));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::face>(connectivity, stencil_array, 2));
+          }
+        }
+      }
+
+      SECTION("3D")
+      {
+        StencilManager::BoundaryDescriptorList list;
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("XMAX"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("YMAX"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMIN"));
+        list.emplace_back(std::make_shared<NamedBoundaryDescriptor>("ZMAX"));
+
+        SECTION("cartesian")
+        {
+          auto mesh_v =
+            CartesianMeshBuilder(TinyVector<3>{0, 0, 0}, TinyVector<3>{1, 1, 2}, TinyVector<3, uint64_t>(3, 4, 5))
+              .mesh();
+          const auto& mesh = *mesh_v->get<Mesh<3>>();
+
+          const Connectivity<3>& connectivity = mesh.connectivity();
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_nodes}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::node>(stencil_array, mesh, 2));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::node>(connectivity, stencil_array, 2));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_edges}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::edge>(stencil_array, mesh, 2));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::edge>(connectivity, stencil_array, 2));
+          }
+
+          {
+            auto stencil_array =
+              StencilManager::instance()
+                .getNodeToCellStencilArray(connectivity,
+                                           StencilDescriptor{2, StencilDescriptor::ConnectionType::by_faces}, list);
+
+            REQUIRE(stencil_array.symmetryBoundaryStencilArrayList().size() == 6);
+            REQUIRE(check_ghost_nodes_have_empty_stencils(stencil_array, connectivity));
+            REQUIRE(
+              are_symmetry_stencils_valid.template operator()<ItemType::node, ItemType::face>(stencil_array, mesh, 2));
+            REQUIRE(is_valid.template operator()<ItemType::node, ItemType::face>(connectivity, stencil_array, 2));
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/tests/test_checkpointing_IBoundaryConditionDescriptor.cpp b/tests/test_checkpointing_IBoundaryConditionDescriptor.cpp
index 34505db62eff529b480c912ce5ca36714c7bc17b..ab192f387231dba8c5a1802f8d503303852c7b13 100644
--- a/tests/test_checkpointing_IBoundaryConditionDescriptor.cpp
+++ b/tests/test_checkpointing_IBoundaryConditionDescriptor.cpp
@@ -9,6 +9,7 @@
 #include <mesh/NumberedBoundaryDescriptor.hpp>
 #include <scheme/AxisBoundaryConditionDescriptor.hpp>
 #include <scheme/DirichletBoundaryConditionDescriptor.hpp>
+#include <scheme/DirichletVectorBoundaryConditionDescriptor.hpp>
 #include <scheme/ExternalBoundaryConditionDescriptor.hpp>
 #include <scheme/FixedBoundaryConditionDescriptor.hpp>
 #include <scheme/FourierBoundaryConditionDescriptor.hpp>
@@ -179,6 +180,17 @@ let i: R -> R, x -> x+3;
                                                          p_dirichlet_bc_descriptor)},
                                                        file, useless_group, symbol_table_group);
 
+      const std::vector<FunctionSymbolId> dirichlet_vector_function_id_list{FunctionSymbolId{2, symbol_table},
+                                                                            FunctionSymbolId{3, symbol_table}};
+      auto p_dirichlet_vector_bc_descriptor =
+        std::make_shared<const DirichletVectorBoundaryConditionDescriptor>("dirichlet_vector_name", p_boundary_1,
+                                                                           dirichlet_vector_function_id_list);
+      checkpointing::writeIBoundaryConditionDescriptor("dirichlet_vector_bc_descriptor",
+                                                       EmbeddedData{std::make_shared<
+                                                         DataHandler<const IBoundaryConditionDescriptor>>(
+                                                         p_dirichlet_vector_bc_descriptor)},
+                                                       file, useless_group, symbol_table_group);
+
       const FunctionSymbolId neumann_function_id{1, symbol_table};
       auto p_neumann_bc_descriptor =
         std::make_shared<const NeumannBoundaryConditionDescriptor>("neumann_name", p_boundary_1, neumann_function_id);
@@ -245,6 +257,9 @@ let i: R -> R, x -> x+3;
       EmbeddedData read_dirichlet_bc_descriptor =
         checkpointing::readIBoundaryConditionDescriptor("dirichlet_bc_descriptor", symbol_table_group);
 
+      EmbeddedData read_dirichlet_vector_bc_descriptor =
+        checkpointing::readIBoundaryConditionDescriptor("dirichlet_vector_bc_descriptor", symbol_table_group);
+
       EmbeddedData read_neumann_bc_descriptor =
         checkpointing::readIBoundaryConditionDescriptor("neumann_bc_descriptor", symbol_table_group);
 
@@ -268,6 +283,7 @@ let i: R -> R, x -> x+3;
       REQUIRE_NOTHROW(get_value(read_free_bc_descriptor));
       REQUIRE_NOTHROW(get_value(read_fixed_bc_descriptor));
       REQUIRE_NOTHROW(get_value(read_dirichlet_bc_descriptor));
+      REQUIRE_NOTHROW(get_value(read_dirichlet_vector_bc_descriptor));
       REQUIRE_NOTHROW(get_value(read_neumann_bc_descriptor));
       REQUIRE_NOTHROW(get_value(read_fourier_bc_descriptor));
       REQUIRE_NOTHROW(get_value(read_inflow_bc_descriptor));
@@ -280,6 +296,8 @@ let i: R -> R, x -> x+3;
       REQUIRE(get_value(read_free_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::free);
       REQUIRE(get_value(read_fixed_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::fixed);
       REQUIRE(get_value(read_dirichlet_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::dirichlet);
+      REQUIRE(get_value(read_dirichlet_vector_bc_descriptor).type() ==
+              IBoundaryConditionDescriptor::Type::dirichlet_vector);
       REQUIRE(get_value(read_neumann_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::neumann);
       REQUIRE(get_value(read_fourier_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::fourier);
       REQUIRE(get_value(read_inflow_bc_descriptor).type() == IBoundaryConditionDescriptor::Type::inflow);
@@ -293,6 +311,8 @@ let i: R -> R, x -> x+3;
       REQUIRE_NOTHROW(dynamic_cast<const FixedBoundaryConditionDescriptor&>(get_value(read_fixed_bc_descriptor)));
       REQUIRE_NOTHROW(
         dynamic_cast<const DirichletBoundaryConditionDescriptor&>(get_value(read_dirichlet_bc_descriptor)));
+      REQUIRE_NOTHROW(dynamic_cast<const DirichletVectorBoundaryConditionDescriptor&>(
+        get_value(read_dirichlet_vector_bc_descriptor)));
       REQUIRE_NOTHROW(dynamic_cast<const NeumannBoundaryConditionDescriptor&>(get_value(read_neumann_bc_descriptor)));
       REQUIRE_NOTHROW(dynamic_cast<const FourierBoundaryConditionDescriptor&>(get_value(read_fourier_bc_descriptor)));
       REQUIRE_NOTHROW(dynamic_cast<const InflowBoundaryConditionDescriptor&>(get_value(read_inflow_bc_descriptor)));
@@ -308,6 +328,8 @@ let i: R -> R, x -> x+3;
       auto& read_fixed_bc = dynamic_cast<const FixedBoundaryConditionDescriptor&>(get_value(read_fixed_bc_descriptor));
       auto& read_dirichlet_bc =
         dynamic_cast<const DirichletBoundaryConditionDescriptor&>(get_value(read_dirichlet_bc_descriptor));
+      auto& read_dirichlet_vector_bc =
+        dynamic_cast<const DirichletVectorBoundaryConditionDescriptor&>(get_value(read_dirichlet_vector_bc_descriptor));
       auto& read_neumann_bc =
         dynamic_cast<const NeumannBoundaryConditionDescriptor&>(get_value(read_neumann_bc_descriptor));
       auto& read_fourier_bc =
@@ -326,6 +348,15 @@ let i: R -> R, x -> x+3;
       REQUIRE(read_dirichlet_bc.boundaryDescriptor().type() == p_dirichlet_bc_descriptor->boundaryDescriptor().type());
       REQUIRE(read_dirichlet_bc.name() == p_dirichlet_bc_descriptor->name());
       REQUIRE(read_dirichlet_bc.rhsSymbolId().id() == p_dirichlet_bc_descriptor->rhsSymbolId().id());
+      REQUIRE(read_dirichlet_vector_bc.boundaryDescriptor().type() ==
+              p_dirichlet_vector_bc_descriptor->boundaryDescriptor().type());
+      REQUIRE(read_dirichlet_vector_bc.name() == p_dirichlet_vector_bc_descriptor->name());
+      REQUIRE(read_dirichlet_vector_bc.rhsSymbolIdList().size() ==
+              p_dirichlet_vector_bc_descriptor->rhsSymbolIdList().size());
+      for (size_t i = 0; i < read_dirichlet_vector_bc.rhsSymbolIdList().size(); ++i) {
+        REQUIRE(read_dirichlet_vector_bc.rhsSymbolIdList()[i].id() ==
+                p_dirichlet_vector_bc_descriptor->rhsSymbolIdList()[i].id());
+      }
       REQUIRE(read_neumann_bc.boundaryDescriptor().type() == p_neumann_bc_descriptor->boundaryDescriptor().type());
       REQUIRE(read_neumann_bc.name() == p_neumann_bc_descriptor->name());
       REQUIRE(read_neumann_bc.rhsSymbolId().id() == p_neumann_bc_descriptor->rhsSymbolId().id());
@@ -337,6 +368,8 @@ let i: R -> R, x -> x+3;
       REQUIRE(read_inflow_bc.functionSymbolId().id() == p_inflow_bc_descriptor->functionSymbolId().id());
       REQUIRE(read_inflow_list_bc.boundaryDescriptor().type() ==
               p_inflow_list_bc_descriptor->boundaryDescriptor().type());
+      REQUIRE(read_inflow_list_bc.functionSymbolIdList().size() ==
+              p_inflow_list_bc_descriptor->functionSymbolIdList().size());
       for (size_t i = 0; i < read_inflow_list_bc.functionSymbolIdList().size(); ++i) {
         REQUIRE(read_inflow_list_bc.functionSymbolIdList()[i].id() ==
                 p_inflow_list_bc_descriptor->functionSymbolIdList()[i].id());
diff --git a/tools/generate-plugin.sh b/tools/generate-plugin.sh
new file mode 100755
index 0000000000000000000000000000000000000000..275d1795f79753fbd259a2079119d977c89b7d82
--- /dev/null
+++ b/tools/generate-plugin.sh
@@ -0,0 +1,95 @@
+#!/bin/bash
+
+BOLD='\e[1m'
+RESET='\e[0m'
+
+GREEN='\e[92m'
+RED='\e[91m'
+YELLOW='\e[93m'
+
+echo -ne ${BOLD}
+echo -e "---------------------"
+echo -e "pugs plugin generator"
+echo -e "---------------------"
+echo -e ${RESET}
+
+CURRENT_DIR="$(pwd -P)"
+SCRIPT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
+PUGS_DIR="$(dirname ${SCRIPT_DIR})"
+
+if [[ "${CURRENT_DIR}" =~ "${PUGS_DIR}" ]]
+then
+    echo -e ${RED}"Aborting..."${RESET}
+    echo -e "run this script outside of pugs sources"
+    exit 1
+fi
+
+NAME_RE='^[A-Z][a-zA-Z0-9]*$'
+
+echo "   Plugin name must fulfill the following constrains:"
+echo "   - be a single word that starts by an upper case,"
+echo "   - contains only letters or numbers,"
+echo "   and preferably separate words with caps."
+echo
+echo "   ex.: MyFirstPlugin"
+echo
+
+while [[ ! "${PLUGIN_NAME}" =~ $NAME_RE ]]
+do
+    echo -n "Give plugin name: "
+    read -r PLUGIN_NAME
+
+    if [[ ! "${PLUGIN_NAME}" =~ $NAME_RE ]]
+    then
+	echo -e ${RED}"  invalid name!"${RESET}
+	echo
+	unset PLUGIN_NAME
+    fi
+
+done
+
+PLUGIN_UP="${PLUGIN_NAME^^}"
+PLUGIN_LOW="${PLUGIN_NAME,,}"
+echo
+echo -e "creating plugin ${YELLOW}${PLUGIN_NAME}${RESET} in directory ${YELLOW}${PLUGIN_LOW}${RESET}"
+echo
+
+if [[ -e ${PLUGIN_LOW} ]]
+then
+    echo -e ${RED}"Aborting..."${RESET}
+    echo -e "directory \"${PLUGIN_LOW}\" ${YELLOW}already exists${RESET}!"
+    exit 1
+fi
+
+function substitute()
+{
+    sed s/_PLUGIN_NAME_/${PLUGIN_NAME}/g | sed s/_PLUGIN_LOW_/${PLUGIN_LOW}/g | sed s/_PLUGIN_UP_/${PLUGIN_UP}/g
+}
+
+mkdir -p "${PLUGIN_LOW}/cmake"
+mkdir -p "${PLUGIN_LOW}/tests"
+
+cp "${PUGS_DIR}"/tests/MeshDataBaseForTests.hpp "${PLUGIN_LOW}"/tests/
+cp "${PUGS_DIR}"/tests/MeshDataBaseForTests.cpp "${PLUGIN_LOW}"/tests/
+cp "${PUGS_DIR}"/tests/ParallelCheckerTester.hpp "${PLUGIN_LOW}"/tests/
+cp "${PUGS_DIR}"/tests/ParallelCheckerTester.cpp "${PLUGIN_LOW}"/tests/
+cp "${PUGS_DIR}"/tests/test_main.cpp "${PLUGIN_LOW}"/tests/
+cp "${PUGS_DIR}"/tests/mpi_test_main.cpp "${PLUGIN_LOW}"/tests/
+
+cp "${PUGS_DIR}"/cmake/CheckNotInSources.cmake "${PLUGIN_LOW}"/cmake/
+cp "${PUGS_DIR}"/tools/plugin-template/FindPugs.cmake "${PLUGIN_LOW}"/cmake/
+cp "${PUGS_DIR}"/.gitignore "${PLUGIN_LOW}"
+cp "${PUGS_DIR}"/.clang-format "${PLUGIN_LOW}"
+
+cat "${PUGS_DIR}"/tools/plugin-template/CMakeLists.txt-template | substitute > "${PLUGIN_LOW}"/CMakeLists.txt
+cat "${PUGS_DIR}"/tools/plugin-template/Module.hpp-template | substitute > "${PLUGIN_LOW}"/${PLUGIN_NAME}Module.hpp
+cat "${PUGS_DIR}"/tools/plugin-template/Module.cpp-template | substitute > "${PLUGIN_LOW}"/${PLUGIN_NAME}Module.cpp
+cat "${PUGS_DIR}"/tools/plugin-template/README.md-template | substitute > "${PLUGIN_LOW}"/README.md
+
+cat "${PUGS_DIR}"/tools/plugin-template/tests-CMakeLists.txt-template | substitute > "${PLUGIN_LOW}"/tests/CMakeLists.txt
+
+(cd "${PLUGIN_LOW}"; git init -q)
+(cd "${PLUGIN_LOW}"; git add .)
+(cd "${PLUGIN_LOW}"; git commit -m "init" -q)
+
+echo -e ${GREEN}"Creation finished successfully!"${RESET}
diff --git a/tools/plugin-template/CMakeLists.txt-template b/tools/plugin-template/CMakeLists.txt-template
new file mode 100644
index 0000000000000000000000000000000000000000..9c6478b0711cbbd5e38a41f3eaa6c4736a300e67
--- /dev/null
+++ b/tools/plugin-template/CMakeLists.txt-template
@@ -0,0 +1,152 @@
+cmake_minimum_required (VERSION 3.19)
+
+project("_PLUGIN_LOW_")
+
+set(PUGS_PREFIX_PATH "" CACHE STRING "pugs intall dir")
+
+string(COMPARE EQUAL "${PUGS_PREFIX_PATH}" "" pugs_prefix_undefined)
+
+if (pugs_prefix_undefined)
+  message(FATAL_ERROR "PUGS_PREFIX_PATH must be defined")
+endif()
+
+# CMake utils
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+
+# Forbids in-source builds
+include(CheckNotInSources)
+
+# use PkgConfig to find packages
+find_package(PkgConfig REQUIRED)
+find_package(Pugs REQUIRED)
+
+list(APPEND CMAKE_MODULE_PATH "${PUGS_PREFIX_PATH}/lib/cmake/Kokkos")
+include(KokkosConfig)
+
+set(HDF5_PREFER_PARALLEL TRUE)
+list(APPEND CMAKE_MODULE_PATH "${PUGS_PREFIX_PATH}/lib/cmake/HighFive")
+include(HighFiveConfig)
+
+list(APPEND CMAKE_MODULE_PATH "${PUGS_PREFIX_PATH}/lib/cmake/pugs")
+include(PugsTargets)
+include(PugsCompileFlags)
+
+#------------------------------------------------------
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${PUGS_CMAKE_CXX_FLAGS}")
+set(CMAKE_CXX_STANDARD "${PUGS_CMAKE_CXX_STANDARD}")
+
+# -----------------------------------------------------
+# dynamic libraries
+
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+link_libraries("-rdynamic")
+set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib")
+
+#------------------------------------------------------
+
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+#------------------------------------------------------
+
+set(_PLUGIN_UP__SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
+set(_PLUGIN_UP__BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}")
+
+#------------------------------------------------------
+# use pugs compilation settings
+
+set(CMAKE_BUILD_TYPE "${PUGS_CMAKE_BUILD_TYPE}" CACHE STRING "" FORCE)
+set(CMAKE_CXX_COMPILER "${PUGS_CMAKE_CXX_COMPILER}" CACHE STRING "" FORCE)
+set(CMAKE_C_COMPILER "${PUGS_CMAKE_C_COMPILER}" CACHE STRING "" FORCE)
+
+mark_as_advanced(CMAKE_BUILD_TYPE CMAKE_CXX_COMPILER CMAKE_C_COMPILER)
+
+#------------------------------------------------------
+# default build shared libraries
+set(BUILD_SHARED_LIBS ON CACHE STRING "" FORCE)
+
+#------------------------------------------------------
+
+# Checks if compiler version is compatible with Pugs sources
+set(GNU_CXX_MIN_VERSION "10.0.0")
+set(CLANG_CXX_MIN_VERSION "11.0.0")
+
+#------------------------------------------------------
+
+include_directories("${CMAKE_CURRENT_SOURCE_DIR}")
+include_directories(SYSTEM "${PUGS_PREFIX_PATH}/include")
+include_directories(SYSTEM "${PUGS_PREFIX_PATH}/include/kokkos")
+include_directories(SYSTEM "${PUGS_PREFIX_PATH}/include/tao/")
+include_directories(SYSTEM "${MPI_CXX_INCLUDE_DIRS}")
+
+get_target_property(_prop Pugs::PugsAlgebra INTERFACE_INCLUDE_DIRECTORIES)
+set(PUGS_INC_DIR "${PUGS_INC_DIR};${_prop}")
+get_target_property(_prop Pugs::PugsUtils INTERFACE_INCLUDE_DIRECTORIES)
+set(PUGS_INC_DIR "${PUGS_INC_DIR};${_prop}")
+get_target_property(_prop Pugs::pugs INTERFACE_INCLUDE_DIRECTORIES)
+set(PUGS_INC_DIR "${PUGS_INC_DIR};${_prop}")
+
+include_directories(SYSTEM ${PUGS_INC_DIR})
+link_directories(${PUGS_PREFIX_PATH}/lib)
+
+#------------------------------------------------------
+
+if(${PUGS_HAS_MPI})
+  set(MPIEXEC_OPTION_FLAGS --oversubscribe)
+  if (NOT "$ENV{GITLAB_CI}" STREQUAL "")
+    set(MPIEXEC_OPTION_FLAGS ${MPIEXEC_OPTION_FLAGS} --allow-run-as-root)
+  endif()
+  set(MPIEXEC_NUMPROC 3)
+  set(MPIEXEC_PATH_FLAG --path)
+  set(MPI_LAUNCHER ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${MPIEXEC_NUMPROC} ${MPIEXEC_OPTION_FLAGS})
+endif()
+
+add_custom_target(all_unit_tests
+  DEPENDS unit_tests mpi_unit_tests
+)
+
+add_custom_target(check
+  DEPENDS test
+  )
+
+add_custom_target(test
+  DEPENDS run_all_unit_tests
+  )
+
+add_custom_target(run_all_unit_tests
+  DEPENDS run_mpi_unit_tests
+  )
+
+if(PUGS_HAS_MPI)
+  set(RUN_MPI_UNIT_TESTS_COMMENT "Running mpi_unit_tests [using ${MPIEXEC_NUMPROC} procs]")
+else()
+  set(RUN_MPI_UNIT_TESTS_COMMENT "Running mpi_unit_tests [sequentially]")
+endif()
+
+add_custom_target(run_mpi_unit_tests
+  COMMAND ${MPI_LAUNCHER} "${_PLUGIN_UP__BINARY_DIR}/mpi_unit_tests" --allow-running-no-tests
+  WORKING_DIRECTORY ${_PLUGIN_UP__BINARY_DIR}
+  DEPENDS run_unit_tests
+  COMMENT ${RUN_MPI_UNIT_TESTS_COMMENT}
+  )
+
+add_custom_target(run_unit_tests
+  COMMAND "${_PLUGIN_UP__BINARY_DIR}/unit_tests" --allow-running-no-tests
+  DEPENDS all_unit_tests
+  COMMENT "Running unit_tests"
+  )
+
+#------------------------------------------------------
+
+add_library(_PLUGIN_NAME_
+  _PLUGIN_NAME_Module.cpp
+  # add cpp sources files here
+)
+
+#------------------------------------------------------
+
+add_subdirectory(tests)
+
+#------------------------------------------------------
+
+install(TARGETS _PLUGIN_NAME_)
diff --git a/tools/plugin-template/FindPugs.cmake b/tools/plugin-template/FindPugs.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..6269bed8a9055339894cdd9fa9e63b2b479c9b70
--- /dev/null
+++ b/tools/plugin-template/FindPugs.cmake
@@ -0,0 +1,10 @@
+# Finds for the pugs installation directory
+
+find_path(PUGS_PREFIX_PATH include/utils/pugs_version.hpp
+  HINTS
+  $ENV{PUGS_INSTALL_DIR}
+  /usr/local/pugs
+  NO_DEFAULT_PATH
+)
+
+find_package_handle_standard_args(Pugs REQUIRED_VARS PUGS_PREFIX_PATH )
diff --git a/tools/plugin-template/Module.cpp-template b/tools/plugin-template/Module.cpp-template
new file mode 100644
index 0000000000000000000000000000000000000000..dd264e03631590e3cf0db152779e2118df86d474
--- /dev/null
+++ b/tools/plugin-template/Module.cpp-template
@@ -0,0 +1,28 @@
+#include <_PLUGIN_NAME_Module.hpp>
+
+#include <language/modules/ModuleRepository.hpp>
+#include <language/utils/BuiltinFunctionEmbedder.hpp>
+
+_PLUGIN_NAME_Module::_PLUGIN_NAME_Module() : BuiltinModule(false)
+{
+  // Simple hello world example
+  this->_addBuiltinFunction("_PLUGIN_LOW__hello", std::function(
+
+                                                    []() -> void { std::cout << "_PLUGIN_NAME_: Hello world\n"; }
+
+                                                    ));
+}
+
+void
+_PLUGIN_NAME_Module::registerOperators() const
+{
+  // Kept empty for basic use
+}
+
+void
+_PLUGIN_NAME_Module::registerCheckpointResume() const
+{
+  // kept empty for basic use
+}
+
+ModuleRepository::Subscribe<_PLUGIN_NAME_Module> _PLUGIN_LOW__module;
diff --git a/tools/plugin-template/Module.hpp-template b/tools/plugin-template/Module.hpp-template
new file mode 100644
index 0000000000000000000000000000000000000000..757e3b5e650390251d15515cfdcf26a9c970a102
--- /dev/null
+++ b/tools/plugin-template/Module.hpp-template
@@ -0,0 +1,22 @@
+#ifndef _PLUGIN_UP__MODULE_HPP
+#define _PLUGIN_UP__MODULE_HPP
+
+#include <language/modules/BuiltinModule.hpp>
+
+class _PLUGIN_NAME_Module : public BuiltinModule
+{
+ public:
+  std::string_view
+  name() const final
+  {
+    return "_PLUGIN_LOW_";
+  }
+
+  void registerOperators() const final;
+  void registerCheckpointResume() const final;
+
+  _PLUGIN_NAME_Module();
+  ~_PLUGIN_NAME_Module() = default;
+};
+
+#endif   // _PLUGIN_UP__MODULE_HPP
diff --git a/tools/plugin-template/README.md-template b/tools/plugin-template/README.md-template
new file mode 100644
index 0000000000000000000000000000000000000000..4db1071714e56642f3114dad4bc3451bbb274b56
--- /dev/null
+++ b/tools/plugin-template/README.md-template
@@ -0,0 +1,67 @@
+`pugs`'s plugin `_PLUGIN_NAME_`
+===============================
+
+# Building `_PLUGIN_NAME_`
+
+## `pugs` installation
+
+Building this plugin requires an **installed** version of `pugs`.
+`pugs` follows standard `cmake` installation recipes.
+
+Before building `pugs` one should define its installation directory.
+In the `pugs` compilation directory one should execute
+```shell
+cmake -DCMAKE_INSTALL_PREFIX=pugs_install_dir ...
+```
+where `pugs_install_dir` is the chosen installation directory.
+
+Then one simply runs
+```shell
+make install
+```
+
+## building the plugin `_PLUGIN_NAME_`
+
+> **Warning**:<br>
+> Building `_PLUGIN_NAME_` in its source directory is
+> **forbidden**. Trying to do so will result in a failure. However it
+> generally leaves some garbage files in the source directory, namely
+> the `CMakeCache.txt` and the `CMakeFiles` directory. `CMake` itself
+> is not able to remove them, to avoid the risk of compilation issues,
+> one has to dot it manually...
+
+In the build directory one runs
+```shell
+cmake -DPUGS_PREFIX_PATH=pugs_install_dir _PLUGIN_LOW__dir
+```
+where `pugs_install_dir` has the same value as above and `_PLUGIN_LOW__dir`
+is the directory that contains this `README.md` file.
+
+Then to build the plugin, one runs
+```shell
+make
+```
+
+If anything runs fine, the dynamic library `lib_PLUGIN_NAME_.so` is
+built.
+
+# Using `_PLUGIN_NAME_`
+
+In order to use the created plugin, one simply has to give the
+location of `lib_PLUGIN_NAME_.so` to `pugs`. This is done by means of
+environment variables. There are two possibilities:
+- `PUGS_PLUGIN` contains a semicolumn separated list of plugin
+  libraries,
+- `PUGS_PLUGIN_DIR` contains a semicolumn separated list of path to
+  plugin libraries.
+
+Example
+```shell
+export PUGS_PLUGIN="/pathtoplugin/lib_PLUGIN_NAME_.so"
+```
+or
+```shell
+export PUGS_PLUGIN_DIR="/pathtoplugin1;/pathtoplugin2"
+```
+
+Then one launches `pugs` classically.
diff --git a/tools/plugin-template/tests-CMakeLists.txt-template b/tools/plugin-template/tests-CMakeLists.txt-template
new file mode 100644
index 0000000000000000000000000000000000000000..026ff2c2962ed988d35e2680b8b97919801c7a4b
--- /dev/null
+++ b/tools/plugin-template/tests-CMakeLists.txt-template
@@ -0,0 +1,95 @@
+set(EXECUTABLE_OUTPUT_PATH ${_PLUGIN_UP__BINARY_DIR})
+
+include_directories(${_PLUGIN_UP__SOURCE_DIR}/tests)
+
+add_executable (unit_tests
+  test_main.cpp
+
+  # add unit tests here
+  )
+
+  set(_PLUGIN_UP__checkpointing_TESTS
+  )
+
+if(PUGS_HAS_HDF5)
+  list(APPEND _PLUGIN_UP__checkpointing_TESTS
+  )
+endif(PUGS_HAS_HDF5)
+
+add_executable (mpi_unit_tests
+  mpi_test_main.cpp
+  ${_PLUGIN_UP__checkpointing_TESTS}
+
+  # add mpi unit tests here
+)
+
+add_library(test_MeshDataBase
+  MeshDataBaseForTests.cpp)
+
+add_library(test_ParallelCheckerTester
+  ParallelCheckerTester.cpp)
+
+target_link_libraries (test_ParallelCheckerTester
+  ${HIGHFIVE_TARGET})
+
+target_link_libraries (unit_tests
+  test_MeshDataBase
+  test_ParallelCheckerTester
+  PugsAlgebra
+  PugsAnalysis
+  PugsUtils
+  PugsLanguage
+  PugsLanguageAST
+  PugsLanguageModules
+  PugsMesh
+  PugsAlgebra
+  PugsUtils
+  PugsLanguageUtils
+  PugsScheme
+  PugsOutput
+  PugsUtils
+  PugsCheckpointing
+  PugsDev
+  PugsAlgebra
+  PugsMesh
+  Kokkos::kokkos
+  ${PARMETIS_LIBRARIES}
+  ${MPI_CXX_LINK_FLAGS} ${MPI_CXX_LIBRARIES}
+  ${PETSC_LIBRARIES}
+  Catch2
+  ${PUGS_STD_LINK_FLAGS}
+  ${HIGHFIVE_TARGET}
+  ${SLURM_LIBRARY}
+  stdc++fs
+  )
+
+target_link_libraries (mpi_unit_tests
+  test_MeshDataBase
+  test_ParallelCheckerTester
+  PugsAlgebra
+  PugsAnalysis
+  PugsUtils
+  PugsLanguage
+  PugsLanguageAST
+  PugsLanguageModules
+  PugsMesh
+  PugsAlgebra
+  PugsUtils
+  PugsLanguageUtils
+  PugsScheme
+  PugsOutput
+  PugsUtils
+  PugsCheckpointing
+  PugsDev
+  PugsAlgebra
+  PugsMesh
+  Kokkos::kokkos
+  ${PARMETIS_LIBRARIES}
+  ${MPI_CXX_LINK_FLAGS} ${MPI_CXX_LIBRARIES}
+  ${PETSC_LIBRARIES}
+  Catch2
+  ${PUGS_STD_LINK_FLAGS}
+  ${HIGHFIVE_TARGET}
+  ${SLURM_LIBRARY}
+  stdc++fs
+  )