diff --git a/src/mesh/ConnectivityDispatcher.cpp b/src/mesh/ConnectivityDispatcher.cpp
index 1502bed705b252d9be19e1783ab7719488451c8c..769eb23bcfc75c1118fe5b19e4428dd1e36ede33 100644
--- a/src/mesh/ConnectivityDispatcher.cpp
+++ b/src/mesh/ConnectivityDispatcher.cpp
@@ -183,7 +183,7 @@ ConnectivityDispatcher<Dimension>::_gatherFrom(
     std::vector<MutableDataType> data_by_item_vector;
     for (size_t j = 0; j < item_list_to_send_by_proc[i_rank].size(); ++j) {
       const ItemId& item_id = item_list_to_send_by_proc[i_rank][j];
-      const auto& item_data = data_to_gather.itemValues(item_id);
+      const auto& item_data = data_to_gather.itemArray(item_id);
       for (size_t l = 0; l < item_data.size(); ++l) {
         data_by_item_vector.push_back(item_data[l]);
       }
diff --git a/src/mesh/ItemArrayUtils.hpp b/src/mesh/ItemArrayUtils.hpp
index 6025bd0f3b17763be3f4b6646ab6730be0d6de18..ca339de9a10809d939c8924844c507442c83b8ef 100644
--- a/src/mesh/ItemArrayUtils.hpp
+++ b/src/mesh/ItemArrayUtils.hpp
@@ -10,6 +10,302 @@
 
 #include <iostream>
 
+template <typename DataType, ItemType item_type, typename ConnectivityPtr>
+std::remove_const_t<DataType>
+min(const ItemArray<DataType, item_type, ConnectivityPtr>& item_value)
+{
+  using ItemArrayType   = ItemArray<DataType, item_type, ConnectivityPtr>;
+  using ItemIsOwnedType = ItemValue<const bool, item_type>;
+  using data_type       = std::remove_const_t<typename ItemArrayType::data_type>;
+  using index_type      = typename ItemArrayType::index_type;
+
+  static_assert(std::is_arithmetic_v<data_type>, "min cannot be called on non-arithmetic data");
+  static_assert(not std::is_same_v<data_type, bool>, "min cannot be called on boolean data");
+
+  class ItemArrayMin
+  {
+   private:
+    const ItemArrayType& m_item_value;
+    const ItemIsOwnedType m_is_owned;
+
+   public:
+    PUGS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_item_value.numberOfItems(), *this, reduced_value);
+      return reduced_value;
+    }
+
+    PUGS_INLINE
+    void
+    operator()(const index_type& i_item, data_type& data) const
+    {
+      if (m_is_owned[i_item]) {
+        Array array = m_item_value[i_item];
+        for (size_t i = 0; i < m_item_value.sizeOfArrays(); ++i) {
+          if (array[i] < data) {
+            data = array[i];
+          }
+        }
+      }
+    }
+
+    PUGS_INLINE
+    void
+    join(volatile data_type& dst, const volatile data_type& src) const
+    {
+      if (src < dst) {
+        // cannot be reached if initial value is the min
+        dst = src;   // LCOV_EXCL_LINE
+      }
+    }
+
+    PUGS_INLINE
+    void
+    init(data_type& value) const
+    {
+      value = std::numeric_limits<data_type>::max();
+    }
+
+    PUGS_INLINE
+    ItemArrayMin(const ItemArrayType& item_value)
+      : m_item_value(item_value), m_is_owned([&](const IConnectivity& connectivity) {
+          Assert((connectivity.dimension() > 0) and (connectivity.dimension() <= 3),
+                 "unexpected connectivity dimension");
+
+          switch (connectivity.dimension()) {
+          case 1: {
+            const auto& connectivity_1d = static_cast<const Connectivity1D&>(connectivity);
+            return connectivity_1d.isOwned<item_type>();
+            break;
+          }
+          case 2: {
+            const auto& connectivity_2d = static_cast<const Connectivity2D&>(connectivity);
+            return connectivity_2d.isOwned<item_type>();
+            break;
+          }
+          case 3: {
+            const auto& connectivity_3d = static_cast<const Connectivity3D&>(connectivity);
+            return connectivity_3d.isOwned<item_type>();
+            break;
+          }
+            // LCOV_EXCL_START
+          default: {
+            throw UnexpectedError("unexpected dimension");
+          }
+            // LCOV_EXCL_STOP
+          }
+        }(*item_value.connectivity_ptr()))
+    {
+      ;
+    }
+
+    PUGS_INLINE
+    ~ItemArrayMin() = default;
+  };
+
+  const DataType local_min = ItemArrayMin{item_value};
+  return parallel::allReduceMin(local_min);
+}
+
+template <typename DataType, ItemType item_type, typename ConnectivityPtr>
+std::remove_const_t<DataType>
+max(const ItemArray<DataType, item_type, ConnectivityPtr>& item_value)
+{
+  using ItemArrayType   = ItemArray<DataType, item_type, ConnectivityPtr>;
+  using ItemIsOwnedType = ItemValue<const bool, item_type>;
+  using data_type       = std::remove_const_t<typename ItemArrayType::data_type>;
+  using index_type      = typename ItemArrayType::index_type;
+
+  static_assert(std::is_arithmetic_v<data_type>, "max cannot be called on non-arithmetic data");
+  static_assert(not std::is_same_v<data_type, bool>, "max cannot be called on boolean data");
+
+  class ItemArrayMax
+  {
+   private:
+    const ItemArrayType& m_item_value;
+    const ItemIsOwnedType m_is_owned;
+
+   public:
+    PUGS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_item_value.numberOfItems(), *this, reduced_value);
+      return reduced_value;
+    }
+
+    PUGS_INLINE
+    void
+    operator()(const index_type& i_item, data_type& data) const
+    {
+      if (m_is_owned[i_item]) {
+        Array array = m_item_value[i_item];
+        for (size_t i = 0; i < m_item_value.sizeOfArrays(); ++i) {
+          if (array[i] > data) {
+            data = array[i];
+          }
+        }
+      }
+    }
+
+    PUGS_INLINE
+    void
+    join(volatile data_type& dst, const volatile data_type& src) const
+    {
+      if (src > dst) {
+        // cannot be reached if initial value is the max
+        dst = src;   // LCOV_EXCL_LINE
+      }
+    }
+
+    PUGS_INLINE
+    void
+    init(data_type& value) const
+    {
+      value = std::numeric_limits<data_type>::min();
+    }
+
+    PUGS_INLINE
+    ItemArrayMax(const ItemArrayType& item_value)
+      : m_item_value(item_value), m_is_owned([&](const IConnectivity& connectivity) {
+          Assert((connectivity.dimension() > 0) and (connectivity.dimension() <= 3),
+                 "unexpected connectivity dimension");
+
+          switch (connectivity.dimension()) {
+          case 1: {
+            const auto& connectivity_1d = static_cast<const Connectivity1D&>(connectivity);
+            return connectivity_1d.isOwned<item_type>();
+            break;
+          }
+          case 2: {
+            const auto& connectivity_2d = static_cast<const Connectivity2D&>(connectivity);
+            return connectivity_2d.isOwned<item_type>();
+            break;
+          }
+          case 3: {
+            const auto& connectivity_3d = static_cast<const Connectivity3D&>(connectivity);
+            return connectivity_3d.isOwned<item_type>();
+            break;
+          }
+            // LCOV_EXCL_START
+          default: {
+            throw UnexpectedError("unexpected dimension");
+          }
+            // LCOV_EXCL_STOP
+          }
+        }(*item_value.connectivity_ptr()))
+    {
+      ;
+    }
+
+    PUGS_INLINE
+    ~ItemArrayMax() = default;
+  };
+
+  const DataType local_max = ItemArrayMax{item_value};
+  return parallel::allReduceMax(local_max);
+}
+
+template <typename DataType, ItemType item_type, typename ConnectivityPtr>
+std::remove_const_t<DataType>
+sum(const ItemArray<DataType, item_type, ConnectivityPtr>& item_value)
+{
+  using ItemArrayType   = ItemArray<DataType, item_type, ConnectivityPtr>;
+  using ItemIsOwnedType = ItemValue<const bool, item_type>;
+  using data_type       = std::remove_const_t<typename ItemArrayType::data_type>;
+  using index_type      = typename ItemArrayType::index_type;
+
+  static_assert(not std::is_same_v<data_type, bool>, "sum cannot be called on boolean data");
+
+  class ItemArraySum
+  {
+   private:
+    const ItemArrayType& m_item_value;
+    const ItemIsOwnedType m_is_owned;
+
+   public:
+    PUGS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_item_value.numberOfItems(), *this, reduced_value);
+      return reduced_value;
+    }
+
+    PUGS_INLINE
+    void
+    operator()(const index_type& i_item, data_type& data) const
+    {
+      if (m_is_owned[i_item]) {
+        Array array = m_item_value[i_item];
+        for (size_t i = 0; i < m_item_value.sizeOfArrays(); ++i) {
+          data += array[i];
+        }
+      }
+    }
+
+    PUGS_INLINE
+    void
+    join(volatile data_type& dst, const volatile data_type& src) const
+    {
+      dst += src;
+    }
+
+    PUGS_INLINE
+    void
+    init(data_type& value) const
+    {
+      if constexpr (std::is_arithmetic_v<data_type>) {
+        value = 0;
+      } else {
+        static_assert(is_tiny_vector_v<data_type> or is_tiny_matrix_v<data_type>, "invalid data type");
+        value = zero;
+      }
+    }
+
+    PUGS_INLINE
+    ItemArraySum(const ItemArrayType& item_value)
+      : m_item_value(item_value), m_is_owned([&](const IConnectivity& connectivity) {
+          Assert((connectivity.dimension() > 0) and (connectivity.dimension() <= 3),
+                 "unexpected connectivity dimension");
+
+          switch (connectivity.dimension()) {
+          case 1: {
+            const auto& connectivity_1d = static_cast<const Connectivity1D&>(connectivity);
+            return connectivity_1d.isOwned<item_type>();
+            break;
+          }
+          case 2: {
+            const auto& connectivity_2d = static_cast<const Connectivity2D&>(connectivity);
+            return connectivity_2d.isOwned<item_type>();
+            break;
+          }
+          case 3: {
+            const auto& connectivity_3d = static_cast<const Connectivity3D&>(connectivity);
+            return connectivity_3d.isOwned<item_type>();
+            break;
+          }
+            // LCOV_EXCL_START
+          default: {
+            throw UnexpectedError("unexpected dimension");
+          }
+            // LCOV_EXCL_STOP
+          }
+        }(*item_value.connectivity_ptr()))
+    {
+      ;
+    }
+
+    PUGS_INLINE
+    ~ItemArraySum() = default;
+  };
+
+  const DataType local_sum = ItemArraySum{item_value};
+  return parallel::allReduceSum(local_sum);
+}
+
 template <typename DataType, ItemType item_type, typename ConnectivityPtr>
 void
 synchronize(ItemArray<DataType, item_type, ConnectivityPtr>& item_array)
diff --git a/src/mesh/ItemType.hpp b/src/mesh/ItemType.hpp
index c2f2da131e417e931cce8d3c40d91fd83ad19d45..285806fe0599f66d5f70b55e0e4e2de3e5d9d919 100644
--- a/src/mesh/ItemType.hpp
+++ b/src/mesh/ItemType.hpp
@@ -59,8 +59,8 @@ struct ItemTypeId<1>
       i = 0;
       break;
     }
-    case ItemType::edge:
     case ItemType::face:
+    case ItemType::edge:
     case ItemType::node: {
       // in 1d, faces, edges and nodes are the same
       i = 1;
@@ -69,6 +69,13 @@ struct ItemTypeId<1>
     }
     return i;
   }
+
+  PUGS_INLINE
+  static constexpr size_t
+  dimension(ItemType item_type)
+  {
+    return 1 - itemTId(item_type);
+  }
 };
 
 template <ItemType item_type>
@@ -87,8 +94,8 @@ struct ItemTypeId<2>
       i = 0;
       break;
     }
-    case ItemType::edge:
-    case ItemType::face: {
+    case ItemType::face:
+    case ItemType::edge: {
       // in 2d, faces and edges are the same
       i = 1;
       break;
@@ -100,6 +107,13 @@ struct ItemTypeId<2>
     }
     return i;
   }
+
+  PUGS_INLINE
+  static constexpr size_t
+  dimension(ItemType item_type)
+  {
+    return 2 - itemTId(item_type);
+  }
 };
 
 template <ItemType item_type>
@@ -118,11 +132,11 @@ struct ItemTypeId<3>
       i = 0;
       break;
     }
-    case ItemType::edge: {
+    case ItemType::face: {
       i = 1;
       break;
     }
-    case ItemType::face: {
+    case ItemType::edge: {
       i = 2;
       break;
     }
@@ -133,6 +147,13 @@ struct ItemTypeId<3>
     }
     return i;
   }
+
+  PUGS_INLINE
+  static constexpr size_t
+  dimension(ItemType item_type)
+  {
+    return 3 - itemTId(item_type);
+  }
 };
 
 template <ItemType item_type>
diff --git a/src/mesh/ItemValueUtils.hpp b/src/mesh/ItemValueUtils.hpp
index b5151953c8bff1501124b027ef82c259c7bc17b7..ef99f59e44f9f71a01267ca0c424220241573f60 100644
--- a/src/mesh/ItemValueUtils.hpp
+++ b/src/mesh/ItemValueUtils.hpp
@@ -20,7 +20,8 @@ min(const ItemValue<DataType, item_type, ConnectivityPtr>& item_value)
   using data_type       = std::remove_const_t<typename ItemValueType::data_type>;
   using index_type      = typename ItemValueType::index_type;
 
-  static_assert(not std::is_same_v<data_type, bool>, "min cannot be called on boolean arrays");
+  static_assert(std::is_arithmetic_v<data_type>, "min cannot be called on non-arithmetic data");
+  static_assert(not std::is_same_v<data_type, bool>, "min cannot be called on boolean data");
 
   class ItemValueMin
   {
@@ -113,7 +114,9 @@ max(const ItemValue<DataType, item_type, ConnectivityPtr>& item_value)
   using data_type       = std::remove_const_t<typename ItemValueType::data_type>;
   using index_type      = typename ItemValueType::index_type;
 
-  static_assert(not std::is_same_v<data_type, bool>, "min cannot be called on boolean arrays");
+  static_assert(std::is_arithmetic_v<data_type>, "max cannot be called on non arithmetic data");
+  static_assert(not std::is_same_v<data_type, bool>, "max cannot be called on boolean data");
+
   class ItemValueMax
   {
    private:
@@ -196,16 +199,16 @@ max(const ItemValue<DataType, item_type, ConnectivityPtr>& item_value)
   return parallel::allReduceMax(local_max);
 }
 
-template <typename DataType, ItemType item_type>
+template <typename DataType, ItemType item_type, typename ConnectivityPtr>
 std::remove_const_t<DataType>
-sum(const ItemValue<DataType, item_type>& item_value)
+sum(const ItemValue<DataType, item_type, ConnectivityPtr>& item_value)
 {
-  using ItemValueType   = ItemValue<DataType, item_type>;
+  using ItemValueType   = ItemValue<DataType, item_type, ConnectivityPtr>;
   using ItemIsOwnedType = ItemValue<const bool, item_type>;
   using data_type       = std::remove_const_t<typename ItemValueType::data_type>;
   using index_type      = typename ItemValueType::index_type;
 
-  static_assert(not std::is_same_v<data_type, bool>, "sum cannot be called on boolean arrays");
+  static_assert(not std::is_same_v<data_type, bool>, "sum cannot be called on boolean data");
 
   class ItemValueSum
   {
diff --git a/src/mesh/MeshData.hpp b/src/mesh/MeshData.hpp
index 596ab38bd3713686e3b69b96e7b854b170bca5e0..8842fd8ac604221d869e046a7d5e9e0975916ad8 100644
--- a/src/mesh/MeshData.hpp
+++ b/src/mesh/MeshData.hpp
@@ -401,7 +401,7 @@ class MeshData : public IMeshData
           const auto& cell_nodes = cell_to_node_matrix[j];
 
           const auto& cell_faces       = cell_to_face_matrix[j];
-          const auto& face_is_reversed = cell_face_is_reversed.itemValues(j);
+          const auto& face_is_reversed = cell_face_is_reversed.itemArray(j);
 
           for (size_t L = 0; L < cell_faces.size(); ++L) {
             const FaceId& l        = cell_faces[L];
diff --git a/src/mesh/SubItemArrayPerItem.hpp b/src/mesh/SubItemArrayPerItem.hpp
index f2103789e9a49c92b921fe2f8b24b3bdaf8f69ec..7b204d2193aa6cdb450f7e8d8b23f91d0a47e2c3 100644
--- a/src/mesh/SubItemArrayPerItem.hpp
+++ b/src/mesh/SubItemArrayPerItem.hpp
@@ -50,7 +50,7 @@ class SubItemArrayPerItem
 
  public:
   friend PUGS_INLINE SubItemArrayPerItem<std::remove_const_t<DataType>, ItemOfItem, ConnectivityPtr>
-  copy(SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& source)
+  copy(const SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& source)
   {
     SubItemArrayPerItem<std::remove_const_t<DataType>, ItemOfItem, ConnectivityPtr> image;
 
diff --git a/src/mesh/SubItemArrayPerItemUtils.hpp b/src/mesh/SubItemArrayPerItemUtils.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..5e4f122e3d8dc7a06858091a2e8f8baa402c95e4
--- /dev/null
+++ b/src/mesh/SubItemArrayPerItemUtils.hpp
@@ -0,0 +1,359 @@
+#ifndef SUB_ITEM_ARRAY_PER_ITEM_UTILS_HPP
+#define SUB_ITEM_ARRAY_PER_ITEM_UTILS_HPP
+
+#include <utils/Messenger.hpp>
+
+#include <mesh/Connectivity.hpp>
+#include <mesh/SubItemArrayPerItem.hpp>
+#include <mesh/Synchronizer.hpp>
+#include <mesh/SynchronizerManager.hpp>
+#include <utils/PugsTraits.hpp>
+
+#include <iostream>
+
+template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+std::remove_const_t<DataType>
+min(const SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& sub_item_array_per_item)
+{
+  using SubItemArrayPerItemType = SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>;
+  constexpr ItemType item_type  = ItemOfItem::item_type;
+  using ItemIsOwnedType         = ItemValue<const bool, item_type>;
+  using data_type               = std::remove_const_t<typename SubItemArrayPerItemType::data_type>;
+  using index_type              = typename SubItemArrayPerItemType::index_type;
+
+  static_assert(std::is_arithmetic_v<data_type>, "min cannot be called on non-arithmetic data");
+  static_assert(not std::is_same_v<data_type, bool>, "min cannot be called on boolean data");
+
+  class SubItemArrayPerItemMin
+  {
+   private:
+    const SubItemArrayPerItemType& m_sub_item_array_per_item;
+    const ItemIsOwnedType m_is_owned;
+
+   public:
+    PUGS_INLINE
+    operator data_type()
+    {
+      data_type reduced_array;
+      parallel_reduce(m_sub_item_array_per_item.numberOfItems(), *this, reduced_array);
+      return reduced_array;
+    }
+
+    PUGS_INLINE
+    void
+    operator()(const index_type& item_id, data_type& data) const
+    {
+      if (m_is_owned[item_id]) {
+        Table sub_item_table = m_sub_item_array_per_item.itemTable(item_id);
+        for (size_t i = 0; i < sub_item_table.numberOfRows(); ++i) {
+          for (size_t j = 0; j < sub_item_table.numberOfColumns(); ++j) {
+            if (sub_item_table(i, j) < data) {
+              data = sub_item_table(i, j);
+            }
+          }
+        }
+      }
+    }
+
+    PUGS_INLINE
+    void
+    join(volatile data_type& dst, const volatile data_type& src) const
+    {
+      if (src < dst) {
+        // cannot be reached if initial array is the min
+        dst = src;   // LCOV_EXCL_LINE
+      }
+    }
+
+    PUGS_INLINE
+    void
+    init(data_type& array) const
+    {
+      array = std::numeric_limits<data_type>::max();
+    }
+
+    PUGS_INLINE
+    SubItemArrayPerItemMin(const SubItemArrayPerItemType& item_array)
+      : m_sub_item_array_per_item(item_array), m_is_owned([&](const IConnectivity& connectivity) {
+          Assert((connectivity.dimension() > 0) and (connectivity.dimension() <= 3),
+                 "unexpected connectivity dimension");
+
+          switch (connectivity.dimension()) {
+          case 1: {
+            const auto& connectivity_1d = static_cast<const Connectivity1D&>(connectivity);
+            return connectivity_1d.isOwned<item_type>();
+            break;
+          }
+          case 2: {
+            const auto& connectivity_2d = static_cast<const Connectivity2D&>(connectivity);
+            return connectivity_2d.isOwned<item_type>();
+            break;
+          }
+          case 3: {
+            const auto& connectivity_3d = static_cast<const Connectivity3D&>(connectivity);
+            return connectivity_3d.isOwned<item_type>();
+            break;
+          }
+            // LCOV_EXCL_START
+          default: {
+            throw UnexpectedError("unexpected dimension");
+          }
+            // LCOV_EXCL_STOP
+          }
+        }(*item_array.connectivity_ptr()))
+    {
+      ;
+    }
+
+    PUGS_INLINE
+    ~SubItemArrayPerItemMin() = default;
+  };
+
+  const DataType local_min = SubItemArrayPerItemMin{sub_item_array_per_item};
+  return parallel::allReduceMin(local_min);
+}
+
+template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+std::remove_const_t<DataType>
+max(const SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& sub_item_array_per_item)
+{
+  using SubItemArrayPerItemType = SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>;
+  constexpr ItemType item_type  = ItemOfItem::item_type;
+  using ItemIsOwnedType         = ItemValue<const bool, item_type>;
+  using data_type               = std::remove_const_t<typename SubItemArrayPerItemType::data_type>;
+  using index_type              = typename SubItemArrayPerItemType::index_type;
+
+  static_assert(std::is_arithmetic_v<data_type>, "min cannot be called on non-arithmetic data");
+  static_assert(not std::is_same_v<data_type, bool>, "max cannot be called on boolean data");
+
+  class SubItemArrayPerItemMax
+  {
+   private:
+    const SubItemArrayPerItemType& m_sub_item_array_per_item;
+    const ItemIsOwnedType m_is_owned;
+
+   public:
+    PUGS_INLINE
+    operator data_type()
+    {
+      data_type reduced_array;
+      parallel_reduce(m_sub_item_array_per_item.numberOfItems(), *this, reduced_array);
+      return reduced_array;
+    }
+
+    PUGS_INLINE
+    void
+    operator()(const index_type& item_id, data_type& data) const
+    {
+      if (m_is_owned[item_id]) {
+        Table sub_item_table = m_sub_item_array_per_item.itemTable(item_id);
+        for (size_t i = 0; i < sub_item_table.numberOfRows(); ++i) {
+          for (size_t j = 0; j < sub_item_table.numberOfColumns(); ++j) {
+            if (sub_item_table(i, j) > data) {
+              data = sub_item_table(i, j);
+            }
+          }
+        }
+      }
+    }
+
+    PUGS_INLINE
+    void
+    join(volatile data_type& dst, const volatile data_type& src) const
+    {
+      if (src > dst) {
+        // cannot be reached if initial array is the max
+        dst = src;   // LCOV_EXCL_LINE
+      }
+    }
+
+    PUGS_INLINE
+    void
+    init(data_type& array) const
+    {
+      array = std::numeric_limits<data_type>::min();
+    }
+
+    PUGS_INLINE
+    SubItemArrayPerItemMax(const SubItemArrayPerItemType& item_array)
+      : m_sub_item_array_per_item(item_array), m_is_owned([&](const IConnectivity& connectivity) {
+          Assert((connectivity.dimension() > 0) and (connectivity.dimension() <= 3),
+                 "unexpected connectivity dimension");
+
+          switch (connectivity.dimension()) {
+          case 1: {
+            const auto& connectivity_1d = static_cast<const Connectivity1D&>(connectivity);
+            return connectivity_1d.isOwned<item_type>();
+            break;
+          }
+          case 2: {
+            const auto& connectivity_2d = static_cast<const Connectivity2D&>(connectivity);
+            return connectivity_2d.isOwned<item_type>();
+            break;
+          }
+          case 3: {
+            const auto& connectivity_3d = static_cast<const Connectivity3D&>(connectivity);
+            return connectivity_3d.isOwned<item_type>();
+            break;
+          }
+            // LCOV_EXCL_START
+          default: {
+            throw UnexpectedError("unexpected dimension");
+          }
+            // LCOV_EXCL_STOP
+          }
+        }(*item_array.connectivity_ptr()))
+    {
+      ;
+    }
+
+    PUGS_INLINE
+    ~SubItemArrayPerItemMax() = default;
+  };
+
+  const DataType local_max = SubItemArrayPerItemMax{sub_item_array_per_item};
+  return parallel::allReduceMax(local_max);
+}
+
+template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+std::remove_const_t<DataType>
+sum(const SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& item_array)
+{
+  using SubItemArrayPerItemType = SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>;
+  constexpr ItemType item_type  = ItemOfItem::item_type;
+  using ItemIsOwnedType         = ItemValue<const bool, item_type>;
+  using data_type               = std::remove_const_t<typename SubItemArrayPerItemType::data_type>;
+  using index_type              = typename SubItemArrayPerItemType::index_type;
+
+  static_assert(not std::is_same_v<data_type, bool>, "sum cannot be called on boolean data");
+
+  class SubItemArrayPerItemSum
+  {
+   private:
+    const SubItemArrayPerItemType& m_sub_item_array_per_item;
+    const ItemIsOwnedType m_is_owned;
+
+   public:
+    PUGS_INLINE
+    operator data_type()
+    {
+      data_type reduced_array;
+      parallel_reduce(m_sub_item_array_per_item.numberOfItems(), *this, reduced_array);
+      return reduced_array;
+    }
+
+    PUGS_INLINE
+    void
+    operator()(const index_type& item_id, data_type& data) const
+    {
+      if (m_is_owned[item_id]) {
+        Table sub_item_table = m_sub_item_array_per_item.itemTable(item_id);
+        for (size_t i = 0; i < sub_item_table.numberOfRows(); ++i) {
+          for (size_t j = 0; j < sub_item_table.numberOfColumns(); ++j) {
+            data += sub_item_table(i, j);
+          }
+        }
+      }
+    }
+
+    PUGS_INLINE
+    void
+    join(volatile data_type& dst, const volatile data_type& src) const
+    {
+      dst += src;
+    }
+
+    PUGS_INLINE
+    void
+    init(data_type& array) const
+    {
+      if constexpr (std::is_arithmetic_v<data_type>) {
+        array = 0;
+      } else {
+        static_assert(is_tiny_vector_v<data_type> or is_tiny_matrix_v<data_type>, "invalid data type");
+        array = zero;
+      }
+    }
+
+    PUGS_INLINE
+    SubItemArrayPerItemSum(const SubItemArrayPerItemType& item_array)
+      : m_sub_item_array_per_item(item_array), m_is_owned([&](const IConnectivity& connectivity) {
+          Assert((connectivity.dimension() > 0) and (connectivity.dimension() <= 3),
+                 "unexpected connectivity dimension");
+
+          switch (connectivity.dimension()) {
+          case 1: {
+            const auto& connectivity_1d = static_cast<const Connectivity1D&>(connectivity);
+            return connectivity_1d.isOwned<item_type>();
+            break;
+          }
+          case 2: {
+            const auto& connectivity_2d = static_cast<const Connectivity2D&>(connectivity);
+            return connectivity_2d.isOwned<item_type>();
+            break;
+          }
+          case 3: {
+            const auto& connectivity_3d = static_cast<const Connectivity3D&>(connectivity);
+            return connectivity_3d.isOwned<item_type>();
+            break;
+          }
+            // LCOV_EXCL_START
+          default: {
+            throw UnexpectedError("unexpected dimension");
+          }
+            // LCOV_EXCL_STOP
+          }
+        }(*item_array.connectivity_ptr()))
+    {
+      ;
+    }
+
+    PUGS_INLINE
+    ~SubItemArrayPerItemSum() = default;
+  };
+
+  const DataType local_sum = SubItemArrayPerItemSum{item_array};
+  return parallel::allReduceSum(local_sum);
+}
+
+template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+void
+synchronize(SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& sub_item_array_per_item)
+{
+  static_assert(not std::is_const_v<DataType>, "cannot synchronize SubItemArrayPerItem of const data");
+  if (parallel::size() > 1) {
+    auto& manager                     = SynchronizerManager::instance();
+    const IConnectivity* connectivity = sub_item_array_per_item.connectivity_ptr().get();
+    Synchronizer& synchronizer        = manager.getConnectivitySynchronizer(connectivity);
+    synchronizer.synchronize(sub_item_array_per_item);
+  }
+}
+
+template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+bool
+isSynchronized(const SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& sub_item_array_per_item)
+{
+  bool is_synchronized = true;
+  if (parallel::size() > 1) {
+    SubItemArrayPerItem<std::remove_const_t<DataType>, ItemOfItem> sub_item_array_per_item_copy =
+      copy(sub_item_array_per_item);
+
+    synchronize(sub_item_array_per_item_copy);
+
+    for (size_t i = 0; i < sub_item_array_per_item.numberOfArrays(); ++i) {
+      Array sub_item_array      = sub_item_array_per_item[i];
+      Array sub_item_array_copy = sub_item_array_per_item_copy[i];
+      for (size_t j = 0; j < sub_item_array.size(); ++j) {
+        if (sub_item_array_copy[j] != sub_item_array[j]) {
+          is_synchronized = false;
+        }
+      }
+    }
+
+    is_synchronized = parallel::allReduceAnd(is_synchronized);
+  }
+
+  return is_synchronized;
+}
+
+#endif   // SUB_ITEM_ARRAY_PER_ITEM_UTILS_HPP
diff --git a/src/mesh/SubItemValuePerItem.hpp b/src/mesh/SubItemValuePerItem.hpp
index defdc8fc9711c6be265e022e0bb0c710d078226d..ea0e900681279b306ee82a9615e843650cfb1ea7 100644
--- a/src/mesh/SubItemValuePerItem.hpp
+++ b/src/mesh/SubItemValuePerItem.hpp
@@ -49,7 +49,7 @@ class SubItemValuePerItem
 
  public:
   friend PUGS_INLINE SubItemValuePerItem<std::remove_const_t<DataType>, ItemOfItem, ConnectivityPtr>
-  copy(SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& source)
+  copy(const SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& source)
   {
     SubItemValuePerItem<std::remove_const_t<DataType>, ItemOfItem, ConnectivityPtr> image;
 
@@ -137,7 +137,7 @@ class SubItemValuePerItem
 
   template <typename IndexType>
   PUGS_INLINE Array<DataType>
-  itemValues(IndexType item_id) noexcept(NO_ASSERT)
+  itemArray(IndexType item_id) noexcept(NO_ASSERT)
   {
     static_assert(std::is_same_v<IndexType, ItemId>, "index must be an ItemId");
     Assert(this->isBuilt());
@@ -151,7 +151,7 @@ class SubItemValuePerItem
   // changes in data
   template <typename IndexType>
   PUGS_INLINE Array<DataType>
-  itemValues(IndexType item_id) const noexcept(NO_ASSERT)
+  itemArray(IndexType item_id) const noexcept(NO_ASSERT)
   {
     static_assert(std::is_same_v<IndexType, ItemId>, "index must be an ItemId");
     Assert(this->isBuilt());
diff --git a/src/mesh/SubItemValuePerItemUtils.hpp b/src/mesh/SubItemValuePerItemUtils.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..88f36d1cc15704a947d0990f0a2190c9c375c53c
--- /dev/null
+++ b/src/mesh/SubItemValuePerItemUtils.hpp
@@ -0,0 +1,350 @@
+#ifndef SUB_ITEM_VALUE_PER_ITEM_UTILS_HPP
+#define SUB_ITEM_VALUE_PER_ITEM_UTILS_HPP
+
+#include <utils/Messenger.hpp>
+
+#include <mesh/Connectivity.hpp>
+#include <mesh/SubItemValuePerItem.hpp>
+#include <mesh/Synchronizer.hpp>
+#include <mesh/SynchronizerManager.hpp>
+#include <utils/PugsTraits.hpp>
+
+#include <iostream>
+
+template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+std::remove_const_t<DataType>
+min(const SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& sub_item_value_per_item)
+{
+  using SubItemValuePerItemType = SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>;
+  constexpr ItemType item_type  = ItemOfItem::item_type;
+  using ItemIsOwnedType         = ItemValue<const bool, item_type>;
+  using data_type               = std::remove_const_t<typename SubItemValuePerItemType::data_type>;
+  using index_type              = typename SubItemValuePerItemType::index_type;
+
+  static_assert(std::is_arithmetic_v<data_type>, "min cannot be called on non-arithmetic data");
+  static_assert(not std::is_same_v<data_type, bool>, "min cannot be called on boolean data");
+
+  class SubItemValuePerItemMin
+  {
+   private:
+    const SubItemValuePerItemType& m_sub_item_value_per_item;
+    const ItemIsOwnedType m_is_owned;
+
+   public:
+    PUGS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_sub_item_value_per_item.numberOfItems(), *this, reduced_value);
+      return reduced_value;
+    }
+
+    PUGS_INLINE
+    void
+    operator()(const index_type& item_id, data_type& data) const
+    {
+      if (m_is_owned[item_id]) {
+        Array sub_item_array = m_sub_item_value_per_item.itemArray(item_id);
+        for (size_t i = 0; i < sub_item_array.size(); ++i) {
+          if (sub_item_array[i] < data) {
+            data = sub_item_array[i];
+          }
+        }
+      }
+    }
+
+    PUGS_INLINE
+    void
+    join(volatile data_type& dst, const volatile data_type& src) const
+    {
+      if (src < dst) {
+        // cannot be reached if initial value is the min
+        dst = src;   // LCOV_EXCL_LINE
+      }
+    }
+
+    PUGS_INLINE
+    void
+    init(data_type& value) const
+    {
+      value = std::numeric_limits<data_type>::max();
+    }
+
+    PUGS_INLINE
+    SubItemValuePerItemMin(const SubItemValuePerItemType& item_value)
+      : m_sub_item_value_per_item(item_value), m_is_owned([&](const IConnectivity& connectivity) {
+          Assert((connectivity.dimension() > 0) and (connectivity.dimension() <= 3),
+                 "unexpected connectivity dimension");
+
+          switch (connectivity.dimension()) {
+          case 1: {
+            const auto& connectivity_1d = static_cast<const Connectivity1D&>(connectivity);
+            return connectivity_1d.isOwned<item_type>();
+            break;
+          }
+          case 2: {
+            const auto& connectivity_2d = static_cast<const Connectivity2D&>(connectivity);
+            return connectivity_2d.isOwned<item_type>();
+            break;
+          }
+          case 3: {
+            const auto& connectivity_3d = static_cast<const Connectivity3D&>(connectivity);
+            return connectivity_3d.isOwned<item_type>();
+            break;
+          }
+            // LCOV_EXCL_START
+          default: {
+            throw UnexpectedError("unexpected dimension");
+          }
+            // LCOV_EXCL_STOP
+          }
+        }(*item_value.connectivity_ptr()))
+    {
+      ;
+    }
+
+    PUGS_INLINE
+    ~SubItemValuePerItemMin() = default;
+  };
+
+  const DataType local_min = SubItemValuePerItemMin{sub_item_value_per_item};
+  return parallel::allReduceMin(local_min);
+}
+
+template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+std::remove_const_t<DataType>
+max(const SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& sub_item_value_per_item)
+{
+  using SubItemValuePerItemType = SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>;
+  constexpr ItemType item_type  = ItemOfItem::item_type;
+  using ItemIsOwnedType         = ItemValue<const bool, item_type>;
+  using data_type               = std::remove_const_t<typename SubItemValuePerItemType::data_type>;
+  using index_type              = typename SubItemValuePerItemType::index_type;
+
+  static_assert(std::is_arithmetic_v<data_type>, "min cannot be called on non-arithmetic data");
+  static_assert(not std::is_same_v<data_type, bool>, "max cannot be called on boolean data");
+
+  class SubItemValuePerItemMax
+  {
+   private:
+    const SubItemValuePerItemType& m_sub_item_value_per_item;
+    const ItemIsOwnedType m_is_owned;
+
+   public:
+    PUGS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_sub_item_value_per_item.numberOfItems(), *this, reduced_value);
+      return reduced_value;
+    }
+
+    PUGS_INLINE
+    void
+    operator()(const index_type& item_id, data_type& data) const
+    {
+      if (m_is_owned[item_id]) {
+        Array sub_item_array = m_sub_item_value_per_item.itemArray(item_id);
+        for (size_t i = 0; i < sub_item_array.size(); ++i) {
+          if (sub_item_array[i] > data) {
+            data = sub_item_array[i];
+          }
+        }
+      }
+    }
+
+    PUGS_INLINE
+    void
+    join(volatile data_type& dst, const volatile data_type& src) const
+    {
+      if (src > dst) {
+        // cannot be reached if initial value is the max
+        dst = src;   // LCOV_EXCL_LINE
+      }
+    }
+
+    PUGS_INLINE
+    void
+    init(data_type& value) const
+    {
+      value = std::numeric_limits<data_type>::min();
+    }
+
+    PUGS_INLINE
+    SubItemValuePerItemMax(const SubItemValuePerItemType& item_value)
+      : m_sub_item_value_per_item(item_value), m_is_owned([&](const IConnectivity& connectivity) {
+          Assert((connectivity.dimension() > 0) and (connectivity.dimension() <= 3),
+                 "unexpected connectivity dimension");
+
+          switch (connectivity.dimension()) {
+          case 1: {
+            const auto& connectivity_1d = static_cast<const Connectivity1D&>(connectivity);
+            return connectivity_1d.isOwned<item_type>();
+            break;
+          }
+          case 2: {
+            const auto& connectivity_2d = static_cast<const Connectivity2D&>(connectivity);
+            return connectivity_2d.isOwned<item_type>();
+            break;
+          }
+          case 3: {
+            const auto& connectivity_3d = static_cast<const Connectivity3D&>(connectivity);
+            return connectivity_3d.isOwned<item_type>();
+            break;
+          }
+            // LCOV_EXCL_START
+          default: {
+            throw UnexpectedError("unexpected dimension");
+          }
+            // LCOV_EXCL_STOP
+          }
+        }(*item_value.connectivity_ptr()))
+    {
+      ;
+    }
+
+    PUGS_INLINE
+    ~SubItemValuePerItemMax() = default;
+  };
+
+  const DataType local_max = SubItemValuePerItemMax{sub_item_value_per_item};
+  return parallel::allReduceMax(local_max);
+}
+
+template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+std::remove_const_t<DataType>
+sum(const SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& item_value)
+{
+  using SubItemValuePerItemType = SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>;
+  constexpr ItemType item_type  = ItemOfItem::item_type;
+  using ItemIsOwnedType         = ItemValue<const bool, item_type>;
+  using data_type               = std::remove_const_t<typename SubItemValuePerItemType::data_type>;
+  using index_type              = typename SubItemValuePerItemType::index_type;
+
+  static_assert(not std::is_same_v<data_type, bool>, "sum cannot be called on boolean data");
+
+  class SubItemValuePerItemSum
+  {
+   private:
+    const SubItemValuePerItemType& m_sub_item_value_per_item;
+    const ItemIsOwnedType m_is_owned;
+
+   public:
+    PUGS_INLINE
+    operator data_type()
+    {
+      data_type reduced_value;
+      parallel_reduce(m_sub_item_value_per_item.numberOfItems(), *this, reduced_value);
+      return reduced_value;
+    }
+
+    PUGS_INLINE
+    void
+    operator()(const index_type& item_id, data_type& data) const
+    {
+      if (m_is_owned[item_id]) {
+        Array sub_item_array = m_sub_item_value_per_item.itemArray(item_id);
+        for (size_t i = 0; i < sub_item_array.size(); ++i) {
+          data += sub_item_array[i];
+        }
+      }
+    }
+
+    PUGS_INLINE
+    void
+    join(volatile data_type& dst, const volatile data_type& src) const
+    {
+      dst += src;
+    }
+
+    PUGS_INLINE
+    void
+    init(data_type& value) const
+    {
+      if constexpr (std::is_arithmetic_v<data_type>) {
+        value = 0;
+      } else {
+        static_assert(is_tiny_vector_v<data_type> or is_tiny_matrix_v<data_type>, "invalid data type");
+        value = zero;
+      }
+    }
+
+    PUGS_INLINE
+    SubItemValuePerItemSum(const SubItemValuePerItemType& item_value)
+      : m_sub_item_value_per_item(item_value), m_is_owned([&](const IConnectivity& connectivity) {
+          Assert((connectivity.dimension() > 0) and (connectivity.dimension() <= 3),
+                 "unexpected connectivity dimension");
+
+          switch (connectivity.dimension()) {
+          case 1: {
+            const auto& connectivity_1d = static_cast<const Connectivity1D&>(connectivity);
+            return connectivity_1d.isOwned<item_type>();
+            break;
+          }
+          case 2: {
+            const auto& connectivity_2d = static_cast<const Connectivity2D&>(connectivity);
+            return connectivity_2d.isOwned<item_type>();
+            break;
+          }
+          case 3: {
+            const auto& connectivity_3d = static_cast<const Connectivity3D&>(connectivity);
+            return connectivity_3d.isOwned<item_type>();
+            break;
+          }
+            // LCOV_EXCL_START
+          default: {
+            throw UnexpectedError("unexpected dimension");
+          }
+            // LCOV_EXCL_STOP
+          }
+        }(*item_value.connectivity_ptr()))
+    {
+      ;
+    }
+
+    PUGS_INLINE
+    ~SubItemValuePerItemSum() = default;
+  };
+
+  const DataType local_sum = SubItemValuePerItemSum{item_value};
+  return parallel::allReduceSum(local_sum);
+}
+
+template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+void
+synchronize(SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& sub_item_value_per_item)
+{
+  static_assert(not std::is_const_v<DataType>, "cannot synchronize SubItemValuePerItem of const data");
+  if (parallel::size() > 1) {
+    auto& manager                     = SynchronizerManager::instance();
+    const IConnectivity* connectivity = sub_item_value_per_item.connectivity_ptr().get();
+    Synchronizer& synchronizer        = manager.getConnectivitySynchronizer(connectivity);
+    synchronizer.synchronize(sub_item_value_per_item);
+  }
+}
+
+template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+bool
+isSynchronized(const SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& sub_item_value_per_item)
+{
+  bool is_synchronized = true;
+  if (parallel::size() > 1) {
+    SubItemValuePerItem<std::remove_const_t<DataType>, ItemOfItem> sub_item_value_per_item_copy =
+      copy(sub_item_value_per_item);
+
+    synchronize(sub_item_value_per_item_copy);
+
+    for (size_t i = 0; i < sub_item_value_per_item.numberOfValues(); ++i) {
+      if (sub_item_value_per_item_copy[i] != sub_item_value_per_item[i]) {
+        is_synchronized = false;
+        break;
+      }
+    }
+
+    is_synchronized = parallel::allReduceAnd(is_synchronized);
+  }
+
+  return is_synchronized;
+}
+
+#endif   // SUB_ITEM_VALUE_PER_ITEM_UTILS_HPP
diff --git a/src/mesh/Synchronizer.hpp b/src/mesh/Synchronizer.hpp
index dc0773ace208cc83e863df0ffada54e1db33f936..63e4363ef7dc1b360ca75626ef01e31dcb0728b7 100644
--- a/src/mesh/Synchronizer.hpp
+++ b/src/mesh/Synchronizer.hpp
@@ -4,6 +4,8 @@
 #include <mesh/Connectivity.hpp>
 #include <mesh/ItemArray.hpp>
 #include <mesh/ItemValue.hpp>
+#include <mesh/SubItemArrayPerItem.hpp>
+#include <mesh/SubItemValuePerItem.hpp>
 #include <utils/Exceptions.hpp>
 #include <utils/Messenger.hpp>
 
@@ -11,25 +13,32 @@
 
 #include <iostream>
 #include <map>
+#include <memory>
 
 #ifdef PUGS_HAS_MPI
 
 class Synchronizer
 {
+ private:
   template <ItemType item_type>
   using ExchangeItemTypeInfo = std::vector<Array<const ItemIdT<item_type>>>;
 
-  ExchangeItemTypeInfo<ItemType::cell> m_requested_cell_info;
-  ExchangeItemTypeInfo<ItemType::cell> m_provided_cell_info;
+  std::unique_ptr<ExchangeItemTypeInfo<ItemType::cell>> m_requested_cell_info;
+  std::unique_ptr<ExchangeItemTypeInfo<ItemType::cell>> m_provided_cell_info;
 
-  ExchangeItemTypeInfo<ItemType::face> m_requested_face_info;
-  ExchangeItemTypeInfo<ItemType::face> m_provided_face_info;
+  std::unique_ptr<ExchangeItemTypeInfo<ItemType::face>> m_requested_face_info;
+  std::unique_ptr<ExchangeItemTypeInfo<ItemType::face>> m_provided_face_info;
 
-  ExchangeItemTypeInfo<ItemType::edge> m_requested_edge_info;
-  ExchangeItemTypeInfo<ItemType::edge> m_provided_edge_info;
+  std::unique_ptr<ExchangeItemTypeInfo<ItemType::edge>> m_requested_edge_info;
+  std::unique_ptr<ExchangeItemTypeInfo<ItemType::edge>> m_provided_edge_info;
 
-  ExchangeItemTypeInfo<ItemType::node> m_requested_node_info;
-  ExchangeItemTypeInfo<ItemType::node> m_provided_node_info;
+  std::unique_ptr<ExchangeItemTypeInfo<ItemType::node>> m_requested_node_info;
+  std::unique_ptr<ExchangeItemTypeInfo<ItemType::node>> m_provided_node_info;
+
+  using ExchangeSubItemPerItemSize = std::vector<std::map<std::pair<ItemType, ItemType>, size_t>>;
+
+  ExchangeSubItemPerItemSize m_sub_item_per_item_requested_size_list;
+  ExchangeSubItemPerItemSize m_sub_item_per_item_provided_size_list;
 
   template <ItemType item_type>
   PUGS_INLINE constexpr auto&
@@ -68,22 +77,24 @@ class Synchronizer
     const auto& item_owner = connectivity.template owner<item_type>();
     using ItemId           = ItemIdT<item_type>;
 
-    auto& requested_item_info = this->_getRequestedItemInfo<item_type>();
-    requested_item_info       = [&]() {
+    auto& p_requested_item_info = this->_getRequestedItemInfo<item_type>();
+    p_requested_item_info       = [&]() {
       std::vector<std::vector<ItemId>> requested_item_vector_info(parallel::size());
       for (ItemId item_id = 0; item_id < item_owner.numberOfItems(); ++item_id) {
         if (const size_t owner = item_owner[item_id]; owner != parallel::rank()) {
           requested_item_vector_info[owner].emplace_back(item_id);
         }
       }
-      std::vector<Array<const ItemId>> requested_item_info(parallel::size());
+      ExchangeItemTypeInfo<item_type> requested_item_info(parallel::size());
       for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
         const auto& requested_item_vector = requested_item_vector_info[i_rank];
         requested_item_info[i_rank]       = convert_to_array(requested_item_vector);
       }
-      return requested_item_info;
+      return std::make_unique<ExchangeItemTypeInfo<item_type>>(std::move(requested_item_info));
     }();
 
+    auto& requested_item_info = *p_requested_item_info;
+
     Array<unsigned int> local_number_of_requested_values(parallel::size());
     for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
       local_number_of_requested_values[i_rank] = requested_item_info[i_rank].size();
@@ -114,9 +125,9 @@ class Synchronizer
       item_number_to_id_correspondance[item_number[item_id]] = item_id;
     }
 
-    auto& provided_item_info = this->_getProvidedItemInfo<item_type>();
-    provided_item_info       = [&]() {
-      std::vector<Array<const ItemId>> provided_item_info(parallel::size());
+    auto& p_provided_item_info = this->_getProvidedItemInfo<item_type>();
+    p_provided_item_info       = [&]() {
+      ExchangeItemTypeInfo<item_type> provided_item_info(parallel::size());
       for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
         Array<ItemId> provided_item_id_to_rank{local_number_of_values_to_send[i_rank]};
         const Array<int>& provided_item_number_to_rank = provided_item_number_list_by_rank[i_rank];
@@ -125,8 +136,69 @@ class Synchronizer
         }
         provided_item_info[i_rank] = provided_item_id_to_rank;
       }
-      return provided_item_info;
+      return std::make_unique<ExchangeItemTypeInfo<item_type>>(provided_item_info);
     }();
+
+    m_sub_item_per_item_provided_size_list.resize(parallel::size());
+    m_sub_item_per_item_requested_size_list.resize(parallel::size());
+  }
+
+  template <ItemType item_type, ItemType sub_item_type, size_t Dimension>
+  PUGS_INLINE size_t
+  _getSubItemPerItemRequestedSize(const Connectivity<Dimension>& connectivity, const size_t i_rank)
+  {
+    Assert(m_sub_item_per_item_requested_size_list.size() == parallel::size());
+
+    auto key = std::make_pair(item_type, sub_item_type);
+    if (auto i_size_list = m_sub_item_per_item_requested_size_list[i_rank].find(key);
+        i_size_list != m_sub_item_per_item_requested_size_list[i_rank].end()) {
+      return i_size_list->second;
+    } else {
+      const auto& p_requested_item_info = this->_getRequestedItemInfo<item_type>();
+
+      Assert(static_cast<bool>(p_requested_item_info) == true,
+             "this function should be called after calculation of exchange info");
+      const auto& requested_item_info_from_rank = (*p_requested_item_info)[i_rank];
+
+      const auto& item_to_item_matrix = connectivity.template getItemToItemMatrix<item_type, sub_item_type>();
+
+      size_t count = 0;
+      for (size_t i = 0; i < requested_item_info_from_rank.size(); ++i) {
+        count += item_to_item_matrix[requested_item_info_from_rank[i]].size();
+      }
+
+      m_sub_item_per_item_requested_size_list[i_rank][key] = count;
+      return count;
+    }
+  }
+
+  template <ItemType item_type, ItemType sub_item_type, size_t Dimension>
+  PUGS_INLINE size_t
+  _getSubItemPerItemProvidedSize(const Connectivity<Dimension>& connectivity, const size_t i_rank)
+  {
+    Assert(m_sub_item_per_item_provided_size_list.size() == parallel::size());
+
+    auto key = std::make_pair(item_type, sub_item_type);
+    if (auto i_size_list = m_sub_item_per_item_provided_size_list[i_rank].find(key);
+        i_size_list != m_sub_item_per_item_provided_size_list[i_rank].end()) {
+      return i_size_list->second;
+    } else {
+      const auto& p_provided_item_info = this->_getProvidedItemInfo<item_type>();
+
+      Assert(static_cast<bool>(p_provided_item_info) == true,
+             "this function should be called after calculation of exchange info");
+      const auto& provided_item_info_from_rank = (*p_provided_item_info)[i_rank];
+
+      const auto& item_to_item_matrix = connectivity.template getItemToItemMatrix<item_type, sub_item_type>();
+
+      size_t count = 0;
+      for (size_t i = 0; i < provided_item_info_from_rank.size(); ++i) {
+        count += item_to_item_matrix[provided_item_info_from_rank[i]].size();
+      }
+
+      m_sub_item_per_item_provided_size_list[i_rank][key] = count;
+      return count;
+    }
   }
 
   template <typename ConnectivityType, typename DataType, ItemType item_type, typename ConnectivityPtr>
@@ -137,15 +209,20 @@ class Synchronizer
 
     using ItemId = ItemIdT<item_type>;
 
-    const auto& provided_item_info  = this->_getProvidedItemInfo<item_type>();
-    const auto& requested_item_info = this->_getRequestedItemInfo<item_type>();
+    const auto& p_provided_item_info  = this->_getProvidedItemInfo<item_type>();
+    const auto& p_requested_item_info = this->_getRequestedItemInfo<item_type>();
 
-    Assert(requested_item_info.size() == provided_item_info.size());
+    Assert(static_cast<bool>(p_provided_item_info) == static_cast<bool>(p_requested_item_info));
 
-    if (provided_item_info.size() == 0) {
+    if (not p_provided_item_info) {
       this->_buildSynchronizeInfo<ConnectivityType, item_type>(connectivity);
     }
 
+    const auto& provided_item_info  = *p_provided_item_info;
+    const auto& requested_item_info = *p_requested_item_info;
+
+    Assert(requested_item_info.size() == provided_item_info.size());
+
     std::vector<Array<const DataType>> provided_data_list(parallel::size());
     for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
       const Array<const ItemId>& provided_item_info_to_rank = provided_item_info[i_rank];
@@ -181,15 +258,20 @@ class Synchronizer
 
     using ItemId = ItemIdT<item_type>;
 
-    const auto& provided_item_info  = this->_getProvidedItemInfo<item_type>();
-    const auto& requested_item_info = this->_getRequestedItemInfo<item_type>();
+    const auto& p_provided_item_info  = this->_getProvidedItemInfo<item_type>();
+    const auto& p_requested_item_info = this->_getRequestedItemInfo<item_type>();
 
-    Assert(requested_item_info.size() == provided_item_info.size());
+    Assert(static_cast<bool>(p_provided_item_info) == static_cast<bool>(p_requested_item_info));
 
-    if (provided_item_info.size() == 0) {
+    if (not p_provided_item_info) {
       this->_buildSynchronizeInfo<ConnectivityType, item_type>(connectivity);
     }
 
+    const auto& provided_item_info  = *p_provided_item_info;
+    const auto& requested_item_info = *p_requested_item_info;
+
+    Assert(requested_item_info.size() == provided_item_info.size());
+
     const size_t size_of_arrays = item_array.sizeOfArrays();
 
     std::vector<Array<const DataType>> provided_data_list(parallel::size());
@@ -229,6 +311,156 @@ class Synchronizer
     }
   }
 
+  template <typename ConnectivityType, typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+  PUGS_INLINE void
+  _synchronize(const ConnectivityType& connectivity,
+               SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& sub_item_value_per_item)
+  {
+    static_assert(not std::is_abstract_v<ConnectivityType>, "_synchronize must be called on a concrete connectivity");
+    if constexpr (ItemTypeId<ConnectivityType::Dimension>::dimension(ItemOfItem::item_type) >
+                  ItemTypeId<ConnectivityType::Dimension>::dimension(ItemOfItem::sub_item_type)) {
+      constexpr ItemType item_type     = ItemOfItem::item_type;
+      constexpr ItemType sub_item_type = ItemOfItem::sub_item_type;
+
+      using ItemId = ItemIdT<item_type>;
+
+      const auto& p_provided_item_info  = this->_getProvidedItemInfo<item_type>();
+      const auto& p_requested_item_info = this->_getRequestedItemInfo<item_type>();
+
+      Assert(static_cast<bool>(p_provided_item_info) == static_cast<bool>(p_requested_item_info));
+
+      if (not p_provided_item_info) {
+        this->_buildSynchronizeInfo<ConnectivityType, item_type>(connectivity);
+      }
+
+      const auto& provided_item_info  = *p_provided_item_info;
+      const auto& requested_item_info = *p_requested_item_info;
+
+      Assert(requested_item_info.size() == provided_item_info.size());
+
+      std::vector<Array<const DataType>> provided_data_list(parallel::size());
+      for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
+        const Array<const ItemId>& provided_item_info_to_rank = provided_item_info[i_rank];
+        const size_t send_size = _getSubItemPerItemProvidedSize<item_type, sub_item_type>(connectivity, i_rank);
+
+        Array<DataType> provided_data{send_size};
+        size_t index = 0;
+        for (size_t i = 0; i < provided_item_info_to_rank.size(); ++i) {
+          const ItemId item_id   = provided_item_info_to_rank[i];
+          const auto item_values = sub_item_value_per_item.itemArray(item_id);
+          for (size_t j = 0; j < item_values.size(); ++j) {
+            provided_data[index++] = item_values[j];
+          }
+        }
+        provided_data_list[i_rank] = provided_data;
+      }
+
+      std::vector<Array<DataType>> requested_data_list(parallel::size());
+      for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
+        const size_t recv_size      = _getSubItemPerItemRequestedSize<item_type, sub_item_type>(connectivity, i_rank);
+        requested_data_list[i_rank] = Array<DataType>{recv_size};
+      }
+
+      parallel::exchange(provided_data_list, requested_data_list);
+      for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
+        const auto& requested_item_info_from_rank = requested_item_info[i_rank];
+        const auto& requested_data                = requested_data_list[i_rank];
+
+        size_t index = 0;
+        for (size_t i = 0; i < requested_item_info_from_rank.size(); ++i) {
+          const ItemId item_id   = requested_item_info_from_rank[i];
+          const auto item_values = sub_item_value_per_item.itemArray(item_id);
+          for (size_t j = 0; j < item_values.size(); ++j) {
+            item_values[j] = requested_data[index++];
+          }
+        }
+      }
+    } else {
+      std::ostringstream os;
+      os << "synchronization requires sub-item type (" << itemName(ItemOfItem::sub_item_type)
+         << ") to be of lower dimension than item (" << itemName(ItemOfItem::item_type) << ")";
+      throw UnexpectedError(os.str());
+    }
+  }
+
+  template <typename ConnectivityType, typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+  PUGS_INLINE void
+  _synchronize(const ConnectivityType& connectivity,
+               SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& sub_item_array_per_item)
+  {
+    static_assert(not std::is_abstract_v<ConnectivityType>, "_synchronize must be called on a concrete connectivity");
+    if constexpr (ItemTypeId<ConnectivityType::Dimension>::dimension(ItemOfItem::item_type) >
+                  ItemTypeId<ConnectivityType::Dimension>::dimension(ItemOfItem::sub_item_type)) {
+      constexpr ItemType item_type     = ItemOfItem::item_type;
+      constexpr ItemType sub_item_type = ItemOfItem::sub_item_type;
+
+      using ItemId = ItemIdT<item_type>;
+
+      const auto& p_provided_item_info  = this->_getProvidedItemInfo<item_type>();
+      const auto& p_requested_item_info = this->_getRequestedItemInfo<item_type>();
+
+      Assert(static_cast<bool>(p_provided_item_info) == static_cast<bool>(p_requested_item_info));
+
+      if (not p_provided_item_info) {
+        this->_buildSynchronizeInfo<ConnectivityType, item_type>(connectivity);
+      }
+
+      const auto& provided_item_info  = *p_provided_item_info;
+      const auto& requested_item_info = *p_requested_item_info;
+
+      Assert(requested_item_info.size() == provided_item_info.size());
+
+      std::vector<Array<const DataType>> provided_data_list(parallel::size());
+      for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
+        const Array<const ItemId>& provided_item_info_to_rank = provided_item_info[i_rank];
+        const size_t send_size = _getSubItemPerItemProvidedSize<item_type, sub_item_type>(connectivity, i_rank);
+
+        Array<DataType> provided_data{send_size * sub_item_array_per_item.sizeOfArrays()};
+        size_t index = 0;
+        for (size_t i = 0; i < provided_item_info_to_rank.size(); ++i) {
+          const ItemId item_id  = provided_item_info_to_rank[i];
+          const auto item_table = sub_item_array_per_item.itemTable(item_id);
+          for (size_t j = 0; j < item_table.numberOfRows(); ++j) {
+            Assert(item_table.numberOfColumns() == sub_item_array_per_item.sizeOfArrays());
+            for (size_t k = 0; k < sub_item_array_per_item.sizeOfArrays(); ++k) {
+              provided_data[index++] = item_table(j, k);
+            }
+          }
+        }
+        provided_data_list[i_rank] = provided_data;
+      }
+
+      std::vector<Array<DataType>> requested_data_list(parallel::size());
+      for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
+        const size_t recv_size      = _getSubItemPerItemRequestedSize<item_type, sub_item_type>(connectivity, i_rank);
+        requested_data_list[i_rank] = Array<DataType>{recv_size * sub_item_array_per_item.sizeOfArrays()};
+      }
+
+      parallel::exchange(provided_data_list, requested_data_list);
+      for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
+        const auto& requested_item_info_from_rank = requested_item_info[i_rank];
+        const auto& requested_data                = requested_data_list[i_rank];
+
+        size_t index = 0;
+        for (size_t i = 0; i < requested_item_info_from_rank.size(); ++i) {
+          const ItemId item_id  = requested_item_info_from_rank[i];
+          const auto item_table = sub_item_array_per_item.itemTable(item_id);
+          for (size_t j = 0; j < item_table.numberOfRows(); ++j) {
+            Assert(item_table.numberOfColumns() == sub_item_array_per_item.sizeOfArrays());
+            for (size_t k = 0; k < sub_item_array_per_item.sizeOfArrays(); ++k) {
+              item_table(j, k) = requested_data[index++];
+            }
+          }
+        }
+      }
+    } else {
+      std::ostringstream os;
+      os << "synchronization requires sub-item type (" << itemName(ItemOfItem::sub_item_type)
+         << ") to be of lower dimension than item (" << itemName(ItemOfItem::item_type) << ")";
+      throw UnexpectedError(os.str());
+    }
+  }
+
  public:
   template <typename DataType, ItemType item_type, typename ConnectivityPtr>
   PUGS_INLINE void
@@ -260,22 +492,52 @@ class Synchronizer
 
   template <typename DataType, ItemType item_type, typename ConnectivityPtr>
   PUGS_INLINE void
-  synchronize(ItemArray<DataType, item_type, ConnectivityPtr>& item_value)
+  synchronize(ItemArray<DataType, item_type, ConnectivityPtr>& item_array)
   {
-    Assert(item_value.connectivity_ptr().use_count() > 0, "No connectivity is associated to this ItemValue");
-    const IConnectivity& connectivity = *item_value.connectivity_ptr();
+    Assert(item_array.connectivity_ptr().use_count() > 0, "No connectivity is associated to this ItemArray");
+    const IConnectivity& connectivity = *item_array.connectivity_ptr();
 
     switch (connectivity.dimension()) {
     case 1: {
-      this->_synchronize(static_cast<const Connectivity1D&>(connectivity), item_value);
+      this->_synchronize(static_cast<const Connectivity1D&>(connectivity), item_array);
       break;
     }
     case 2: {
-      this->_synchronize(static_cast<const Connectivity2D&>(connectivity), item_value);
+      this->_synchronize(static_cast<const Connectivity2D&>(connectivity), item_array);
       break;
     }
     case 3: {
-      this->_synchronize(static_cast<const Connectivity3D&>(connectivity), item_value);
+      this->_synchronize(static_cast<const Connectivity3D&>(connectivity), item_array);
+      break;
+    }
+      // LCOV_EXCL_START
+    default: {
+      throw UnexpectedError("unexpected dimension");
+    }
+      // LCOV_EXCL_STOP
+    }
+  }
+
+  template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+  PUGS_INLINE void
+  synchronize(SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& sub_item_value_per_item)
+  {
+    Assert(sub_item_value_per_item.connectivity_ptr().use_count() > 0,
+           "No connectivity is associated to this SubItemValuePerItem");
+
+    const IConnectivity& connectivity = *sub_item_value_per_item.connectivity_ptr();
+
+    switch (connectivity.dimension()) {
+    case 1: {
+      this->_synchronize(static_cast<const Connectivity1D&>(connectivity), sub_item_value_per_item);
+      break;
+    }
+    case 2: {
+      this->_synchronize(static_cast<const Connectivity2D&>(connectivity), sub_item_value_per_item);
+      break;
+    }
+    case 3: {
+      this->_synchronize(static_cast<const Connectivity3D&>(connectivity), sub_item_value_per_item);
       break;
     }
       // LCOV_EXCL_START
@@ -286,6 +548,42 @@ class Synchronizer
     }
   }
 
+  template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+  PUGS_INLINE void
+  synchronize(SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& sub_item_value_per_item)
+  {
+    Assert(sub_item_value_per_item.connectivity_ptr().use_count() > 0,
+           "No connectivity is associated to this SubItemValuePerItem");
+
+    const IConnectivity& connectivity = *sub_item_value_per_item.connectivity_ptr();
+
+    switch (connectivity.dimension()) {
+    case 1: {
+      this->_synchronize(static_cast<const Connectivity1D&>(connectivity), sub_item_value_per_item);
+      break;
+    }
+    case 2: {
+      this->_synchronize(static_cast<const Connectivity2D&>(connectivity), sub_item_value_per_item);
+      break;
+    }
+    case 3: {
+      this->_synchronize(static_cast<const Connectivity3D&>(connectivity), sub_item_value_per_item);
+      break;
+    }
+      // LCOV_EXCL_START
+    default: {
+      throw UnexpectedError("unexpected dimension");
+    }
+      // LCOV_EXCL_STOP
+    }
+  }
+
+  Synchronizer(const Synchronizer&) = delete;
+  Synchronizer(Synchronizer&&)      = delete;
+
+ private:
+  friend class SynchronizerManager;
+
   PUGS_INLINE
   Synchronizer()
   {
@@ -300,13 +598,119 @@ class Synchronizer
  public:
   template <typename DataType, ItemType item_type, typename ConnectivityPtr>
   PUGS_INLINE void
-  synchronize(ItemValue<DataType, item_type, ConnectivityPtr>&)
-  {}
+  synchronize(ItemValue<DataType, item_type, ConnectivityPtr>& item_value)
+  {
+    Assert(item_value.connectivity_ptr().use_count() > 0, "No connectivity is associated to this ItemValue");
+  }
 
   template <typename DataType, ItemType item_type, typename ConnectivityPtr>
   PUGS_INLINE void
-  synchronize(ItemArray<DataType, item_type, ConnectivityPtr>&)
-  {}
+  synchronize(ItemArray<DataType, item_type, ConnectivityPtr>& item_value)
+  {
+    Assert(item_value.connectivity_ptr().use_count() > 0, "No connectivity is associated to this ItemValue");
+  }
+
+  template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+  PUGS_INLINE void
+  synchronize(SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& sub_item_value_per_item)
+  {
+    Assert(sub_item_value_per_item.connectivity_ptr().use_count() > 0,
+           "No connectivity is associated to this SubItemValuePerItem");
+
+    const IConnectivity& connectivity = *sub_item_value_per_item.connectivity_ptr();
+
+    switch (connectivity.dimension()) {
+    case 1: {
+      if constexpr (ItemTypeId<1>::dimension(ItemOfItem::item_type) <=
+                    ItemTypeId<1>::dimension(ItemOfItem::sub_item_type)) {
+        std::ostringstream os;
+        os << "synchronization requires sub-item type (" << itemName(ItemOfItem::sub_item_type)
+           << ") to be of lower dimension than item (" << itemName(ItemOfItem::item_type) << ")";
+        throw UnexpectedError(os.str());
+      }
+      break;
+    }
+    case 2: {
+      if constexpr (ItemTypeId<2>::dimension(ItemOfItem::item_type) <=
+                    ItemTypeId<2>::dimension(ItemOfItem::sub_item_type)) {
+        std::ostringstream os;
+        os << "synchronization requires sub-item type (" << itemName(ItemOfItem::sub_item_type)
+           << ") to be of lower dimension than item (" << itemName(ItemOfItem::item_type) << ")";
+        throw UnexpectedError(os.str());
+      }
+      break;
+    }
+    case 3: {
+      if constexpr (ItemTypeId<3>::dimension(ItemOfItem::item_type) <=
+                    ItemTypeId<3>::dimension(ItemOfItem::sub_item_type)) {
+        std::ostringstream os;
+        os << "synchronization requires sub-item type (" << itemName(ItemOfItem::sub_item_type)
+           << ") to be of lower dimension than item (" << itemName(ItemOfItem::item_type) << ")";
+        throw UnexpectedError(os.str());
+      }
+      break;
+    }
+      // LCOV_EXCL_START
+    default: {
+      throw UnexpectedError("unexpected dimension");
+    }
+      // LCOV_EXCL_STOP
+    }
+  }
+
+  template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+  PUGS_INLINE void
+  synchronize(SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& sub_item_array_per_item)
+  {
+    Assert(sub_item_array_per_item.connectivity_ptr().use_count() > 0,
+           "No connectivity is associated to this SubItemArrayPerItem");
+
+    const IConnectivity& connectivity = *sub_item_array_per_item.connectivity_ptr();
+
+    switch (connectivity.dimension()) {
+    case 1: {
+      if constexpr (ItemTypeId<1>::dimension(ItemOfItem::item_type) <=
+                    ItemTypeId<1>::dimension(ItemOfItem::sub_item_type)) {
+        std::ostringstream os;
+        os << "synchronization requires sub-item type (" << itemName(ItemOfItem::sub_item_type)
+           << ") to be of lower dimension than item (" << itemName(ItemOfItem::item_type) << ")";
+        throw UnexpectedError(os.str());
+      }
+      break;
+    }
+    case 2: {
+      if constexpr (ItemTypeId<2>::dimension(ItemOfItem::item_type) <=
+                    ItemTypeId<2>::dimension(ItemOfItem::sub_item_type)) {
+        std::ostringstream os;
+        os << "synchronization requires sub-item type (" << itemName(ItemOfItem::sub_item_type)
+           << ") to be of lower dimension than item (" << itemName(ItemOfItem::item_type) << ")";
+        throw UnexpectedError(os.str());
+      }
+      break;
+    }
+    case 3: {
+      if constexpr (ItemTypeId<3>::dimension(ItemOfItem::item_type) <=
+                    ItemTypeId<3>::dimension(ItemOfItem::sub_item_type)) {
+        std::ostringstream os;
+        os << "synchronization requires sub-item type (" << itemName(ItemOfItem::sub_item_type)
+           << ") to be of lower dimension than item (" << itemName(ItemOfItem::item_type) << ")";
+        throw UnexpectedError(os.str());
+      }
+      break;
+    }
+      // LCOV_EXCL_START
+    default: {
+      throw UnexpectedError("unexpected dimension");
+    }
+      // LCOV_EXCL_STOP
+    }
+  }
+
+  Synchronizer(const Synchronizer&) = delete;
+  Synchronizer(Synchronizer&&)      = delete;
+
+ private:
+  friend class SynchronizerManager;
 
   PUGS_INLINE
   Synchronizer()
diff --git a/src/mesh/SynchronizerManager.cpp b/src/mesh/SynchronizerManager.cpp
index a72191fd3bfc001c38e577384115bffaa441633c..45f2d17a4630a5268f8e7ba48cda9605235e0db7 100644
--- a/src/mesh/SynchronizerManager.cpp
+++ b/src/mesh/SynchronizerManager.cpp
@@ -45,7 +45,7 @@ SynchronizerManager::getConnectivitySynchronizer(const IConnectivity* connectivi
       connectivity_synchronizer != m_connectivity_synchronizer_map.end()) {
     return (*connectivity_synchronizer->second);
   } else {
-    std::shared_ptr synchronizer                  = std::make_shared<Synchronizer>();
+    std::shared_ptr<Synchronizer> synchronizer(new Synchronizer);
     m_connectivity_synchronizer_map[connectivity] = synchronizer;
     return *synchronizer;
   }
diff --git a/src/scheme/AcousticSolver.cpp b/src/scheme/AcousticSolver.cpp
index 098b146e55efe52ffc7297b8ebb841206d9e3d43..db0a20dca4e640878a1c6e842b025bd32676a8ff 100644
--- a/src/scheme/AcousticSolver.cpp
+++ b/src/scheme/AcousticSolver.cpp
@@ -195,7 +195,7 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
       mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) {
         Rdxd sum                                   = zero;
         const auto& node_to_cell                   = node_to_cell_matrix[r];
-        const auto& node_local_number_in_its_cells = node_local_numbers_in_their_cells.itemValues(r);
+        const auto& node_local_number_in_its_cells = node_local_numbers_in_their_cells.itemArray(r);
 
         for (size_t j = 0; j < node_to_cell.size(); ++j) {
           const CellId J       = node_to_cell[j];
@@ -226,7 +226,7 @@ class AcousticSolverHandler::AcousticSolver final : public AcousticSolverHandler
     parallel_for(
       mesh.numberOfNodes(), PUGS_LAMBDA(NodeId r) {
         const auto& node_to_cell                   = node_to_cell_matrix[r];
-        const auto& node_local_number_in_its_cells = node_local_numbers_in_their_cells.itemValues(r);
+        const auto& node_local_number_in_its_cells = node_local_numbers_in_their_cells.itemArray(r);
 
         Rd br = zero;
         for (size_t j = 0; j < node_to_cell.size(); ++j) {
diff --git a/src/utils/Array.hpp b/src/utils/Array.hpp
index f78f543080edcfa614e6ad549a73349ffb66da74..ca8a6ea6b349b2918c54ccd8de354bff8e999a6f 100644
--- a/src/utils/Array.hpp
+++ b/src/utils/Array.hpp
@@ -189,6 +189,9 @@ min(const Array<DataType>& array)
   using data_type  = std::remove_const_t<typename ArrayType::data_type>;
   using index_type = typename ArrayType::index_type;
 
+  static_assert(std::is_arithmetic_v<data_type>, "min cannot be called on non-arithmetic data");
+  static_assert(not std::is_same_v<data_type, bool>, "min cannot be called on boolean data");
+
   class ArrayMin
   {
    private:
@@ -249,6 +252,9 @@ max(const Array<DataType>& array)
   using data_type  = std::remove_const_t<typename ArrayType::data_type>;
   using index_type = typename ArrayType::index_type;
 
+  static_assert(std::is_arithmetic_v<data_type>, "max cannot be called on non-arithmetic data");
+  static_assert(not std::is_same_v<data_type, bool>, "max cannot be called on boolean data");
+
   class ArrayMax
   {
    private:
@@ -309,6 +315,8 @@ sum(const Array<DataType>& array)
   using data_type  = std::remove_const_t<DataType>;
   using index_type = typename ArrayType::index_type;
 
+  static_assert(not std::is_same_v<data_type, bool>, "sum cannot be called on boolean data");
+
   class ArraySum
   {
    private:
diff --git a/src/utils/Messenger.hpp b/src/utils/Messenger.hpp
index 44772529e8539814eb30b36dad7b936e4c0269a6..416dbdf1d65e8b028b9ed39fff2a2acb4ebcb2d5 100644
--- a/src/utils/Messenger.hpp
+++ b/src/utils/Messenger.hpp
@@ -784,8 +784,9 @@ class Messenger
     for (size_t i = 0; i < m_size; ++i) {
       correct_sizes &= (recv_size[i] == recv_array_list[i].size());
     }
-    Assert(correct_sizes);   // LCOV_EXCL_LINE
-#endif                       // NDEBUG
+    Assert(correct_sizes, "incompatible send/recv messages length");   // LCOV_EXCL_LINE
+
+#endif   // NDEBUG
 
     if constexpr (std::is_arithmetic_v<DataType>) {
       _exchange(send_array_list, recv_array_list);
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index ddfa977a6ef8ca76df9f27bbdc2bda86395cc67f..cbd6b32bc1010a44fe13ed71cb55d03d10052895 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -173,7 +173,9 @@ add_executable (mpi_unit_tests
   test_Partitioner.cpp
   test_RandomEngine.cpp
   test_SubItemValuePerItem.cpp
+  test_SubItemValuePerItemUtils.cpp
   test_SubItemArrayPerItem.cpp
+  test_SubItemArrayPerItemUtils.cpp
   test_Synchronizer.cpp
   )
 
diff --git a/tests/test_ItemArrayUtils.cpp b/tests/test_ItemArrayUtils.cpp
index af91ecb70897073a4069f20457190b6e20318e72..99958839ce3ce945945d0156ff3243ae1ebd5b85 100644
--- a/tests/test_ItemArrayUtils.cpp
+++ b/tests/test_ItemArrayUtils.cpp
@@ -2,15 +2,14 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <MeshDataBaseForTests.hpp>
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
 #include <mesh/Connectivity.hpp>
 #include <mesh/ItemArray.hpp>
 #include <mesh/ItemArrayUtils.hpp>
 #include <mesh/Mesh.hpp>
 #include <utils/Messenger.hpp>
 
-// Instantiate to ensure full coverage is performed
-template class ItemArray<int, ItemType::cell>;
-
 // clazy:excludeall=non-pod-global-static
 
 TEST_CASE("ItemArrayUtils", "[mesh]")
@@ -68,6 +67,9 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_valid);
               REQUIRE(not is_synchronized);
+              if (parallel::size() > 1) {
+                REQUIRE(not isSynchronized(node_array));
+              }
             }
 
             synchronize(weak_node_array);
@@ -86,6 +88,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
               }
 
               REQUIRE(is_synchronized);
+              REQUIRE(isSynchronized(node_array));
             }
           }
 
@@ -129,6 +132,9 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_valid);
               REQUIRE(not is_synchronized);
+              if (parallel::size() > 1) {
+                REQUIRE(not isSynchronized(edge_array));
+              }
             }
 
             synchronize(weak_edge_array);
@@ -148,6 +154,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_synchronized);
             }
+            REQUIRE(isSynchronized(edge_array));
           }
 
           SECTION("face")
@@ -190,6 +197,9 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_valid);
               REQUIRE(not is_synchronized);
+              if (parallel::size() > 1) {
+                REQUIRE(not isSynchronized(face_array));
+              }
             }
 
             synchronize(weak_face_array);
@@ -209,6 +219,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_synchronized);
             }
+            REQUIRE(isSynchronized(face_array));
           }
 
           SECTION("cell")
@@ -251,6 +262,9 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_valid);
               REQUIRE(not is_synchronized);
+              if (parallel::size() > 1) {
+                REQUIRE(not isSynchronized(cell_array));
+              }
             }
 
             synchronize(weak_cell_array);
@@ -270,6 +284,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_synchronized);
             }
+            REQUIRE(isSynchronized(cell_array));
           }
         }
       }
@@ -326,6 +341,9 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_valid);
               REQUIRE(not is_synchronized);
+              if (parallel::size() > 1) {
+                REQUIRE(not isSynchronized(node_array));
+              }
             }
 
             synchronize(weak_node_array);
@@ -344,6 +362,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
               }
 
               REQUIRE(is_synchronized);
+              REQUIRE(isSynchronized(node_array));
             }
           }
 
@@ -387,6 +406,9 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_valid);
               REQUIRE(not is_synchronized);
+              if (parallel::size() > 1) {
+                REQUIRE(not isSynchronized(edge_array));
+              }
             }
 
             synchronize(weak_edge_array);
@@ -406,6 +428,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_synchronized);
             }
+            REQUIRE(isSynchronized(edge_array));
           }
 
           SECTION("face")
@@ -448,6 +471,9 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_valid);
               REQUIRE(not is_synchronized);
+              if (parallel::size() > 1) {
+                REQUIRE(not isSynchronized(face_array));
+              }
             }
 
             synchronize(weak_face_array);
@@ -467,6 +493,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_synchronized);
             }
+            REQUIRE(isSynchronized(face_array));
           }
 
           SECTION("cell")
@@ -509,6 +536,9 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_valid);
               REQUIRE(not is_synchronized);
+              if (parallel::size() > 1) {
+                REQUIRE(not isSynchronized(cell_array));
+              }
             }
 
             synchronize(weak_cell_array);
@@ -528,6 +558,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_synchronized);
             }
+            REQUIRE(isSynchronized(cell_array));
           }
         }
       }
@@ -584,6 +615,9 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_valid);
               REQUIRE(not is_synchronized);
+              if (parallel::size() > 1) {
+                REQUIRE(not isSynchronized(node_array));
+              }
             }
 
             synchronize(weak_node_array);
@@ -603,6 +637,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_synchronized);
             }
+            REQUIRE(isSynchronized(node_array));
           }
 
           SECTION("edge")
@@ -645,6 +680,9 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_valid);
               REQUIRE(not is_synchronized);
+              if (parallel::size() > 1) {
+                REQUIRE(not isSynchronized(edge_array));
+              }
             }
 
             synchronize(weak_edge_array);
@@ -664,6 +702,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_synchronized);
             }
+            REQUIRE(isSynchronized(edge_array));
           }
 
           SECTION("face")
@@ -706,6 +745,9 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_valid);
               REQUIRE(not is_synchronized);
+              if (parallel::size() > 1) {
+                REQUIRE(not isSynchronized(face_array));
+              }
             }
 
             synchronize(weak_face_array);
@@ -725,6 +767,7 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_synchronized);
             }
+            REQUIRE(isSynchronized(face_array));
           }
 
           SECTION("cell")
@@ -767,6 +810,9 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_valid);
               REQUIRE(not is_synchronized);
+              if (parallel::size() > 1) {
+                REQUIRE(not isSynchronized(cell_array));
+              }
             }
 
             synchronize(weak_cell_array);
@@ -786,9 +832,331 @@ TEST_CASE("ItemArrayUtils", "[mesh]")
 
               REQUIRE(is_synchronized);
             }
+            REQUIRE(isSynchronized(cell_array));
           }
         }
       }
     }
   }
+
+  SECTION("min")
+  {
+    SECTION("1D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+          CellArray<int> cell_array{connectivity, 3};
+          cell_array.fill(-1);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_1d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
+                  cell_array[cell_id][i] = 10 + parallel::rank() + i;
+                }
+              }
+            });
+
+          REQUIRE(min(cell_array) == 10);
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          CellArray<int> cell_array{connectivity, 3};
+          cell_array.fill(-1);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_2d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
+                  cell_array[cell_id][i] = 10 + parallel::rank() - i;
+                }
+              }
+            });
+
+          REQUIRE(min(cell_array) == 8);
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          CellArray<int> cell_array{connectivity, 3};
+          cell_array.fill(-1);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_3d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
+                  cell_array[cell_id][i] = 10 + parallel::rank() + 2 - i;
+                }
+              }
+            });
+
+          REQUIRE(min(cell_array) == 10);
+        }
+      }
+    }
+  }
+
+  SECTION("max")
+  {
+    SECTION("1D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+          CellArray<size_t> cell_array{connectivity, 3};
+          cell_array.fill(std::numeric_limits<size_t>::max());
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_1d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
+                  cell_array[cell_id][i] = parallel::rank() + 1 + i;
+                }
+              }
+            });
+
+          REQUIRE(max(cell_array) == parallel::size() + 2);
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          CellArray<size_t> cell_array{connectivity, 3};
+          cell_array.fill(std::numeric_limits<size_t>::max());
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_2d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
+                  cell_array[cell_id][i] = parallel::rank() + i;
+                }
+              }
+            });
+
+          REQUIRE(max(cell_array) == parallel::size() + 1);
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          CellArray<size_t> cell_array{connectivity, 2};
+          cell_array.fill(std::numeric_limits<size_t>::max());
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            mesh_3d->numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
+                  cell_array[cell_id][i] = parallel::rank() + 1 + i;
+                }
+              }
+            });
+
+          REQUIRE(max(cell_array) == parallel::size() + 1);
+        }
+      }
+    }
+  }
+
+  SECTION("sum")
+  {
+    SECTION("1D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+          CellArray<size_t> cell_array{connectivity, 3};
+          cell_array.fill(5);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+
+          const size_t global_number_of_cells = [&] {
+            size_t number_of_cells = 0;
+            for (CellId cell_id = 0; cell_id < cell_is_owned.numberOfItems(); ++cell_id) {
+              number_of_cells += cell_is_owned[cell_id];
+            }
+            return parallel::allReduceSum(number_of_cells);
+          }();
+
+          REQUIRE(sum(cell_array) == 5 * cell_array.sizeOfArrays() * global_number_of_cells);
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name() + " for size_t data")
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          FaceArray<size_t> face_array{connectivity, 3};
+          face_array.fill(2);
+
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          const size_t global_number_of_faces = [&] {
+            size_t number_of_faces = 0;
+            for (FaceId face_id = 0; face_id < face_is_owned.numberOfItems(); ++face_id) {
+              number_of_faces += face_is_owned[face_id];
+            }
+            return parallel::allReduceSum(number_of_faces);
+          }();
+
+          REQUIRE(sum(face_array) == 2 * face_array.sizeOfArrays() * global_number_of_faces);
+        }
+
+        SECTION(named_mesh.name() + " for N^2 data")
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          using N2 = TinyVector<2, size_t>;
+          FaceArray<N2> face_array{connectivity, 3};
+
+          const N2 data(3, 1);
+          face_array.fill(data);
+
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          const size_t global_number_of_faces = [&] {
+            size_t number_of_faces = 0;
+            for (FaceId face_id = 0; face_id < face_is_owned.numberOfItems(); ++face_id) {
+              number_of_faces += face_is_owned[face_id];
+            }
+            return parallel::allReduceSum(number_of_faces);
+          }();
+
+          REQUIRE(sum(face_array) == face_array.sizeOfArrays() * global_number_of_faces * data);
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name() + " for size_t data")
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          NodeArray<size_t> node_array{connectivity, 3};
+          node_array.fill(3);
+
+          auto node_is_owned = connectivity.nodeIsOwned();
+
+          const size_t global_number_of_nodes = [&] {
+            size_t number_of_nodes = 0;
+            for (NodeId node_id = 0; node_id < node_is_owned.numberOfItems(); ++node_id) {
+              number_of_nodes += node_is_owned[node_id];
+            }
+            return parallel::allReduceSum(number_of_nodes);
+          }();
+
+          REQUIRE(sum(node_array) == 3 * node_array.sizeOfArrays() * global_number_of_nodes);
+        }
+
+        SECTION(named_mesh.name() + " for N^3x2 data")
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          using N3x2 = TinyMatrix<3, 2, size_t>;
+
+          NodeArray<N3x2> node_array{connectivity, 3};
+
+          const N3x2 data(1, 3, 2, 4, 6, 5);
+          node_array.fill(data);
+
+          auto node_is_owned = connectivity.nodeIsOwned();
+
+          const size_t global_number_of_nodes = [&] {
+            size_t number_of_nodes = 0;
+            for (NodeId node_id = 0; node_id < node_is_owned.numberOfItems(); ++node_id) {
+              number_of_nodes += node_is_owned[node_id];
+            }
+            return parallel::allReduceSum(number_of_nodes);
+          }();
+
+          REQUIRE(sum(node_array) == node_array.sizeOfArrays() * global_number_of_nodes * data);
+        }
+      }
+    }
+  }
 }
diff --git a/tests/test_ItemType.cpp b/tests/test_ItemType.cpp
index d560a7098ee0e2cec80a1efb64ab599df5ef67ec..ba5d7fe603c4dfe148c2b7e209742da3e5c6cfe0 100644
--- a/tests/test_ItemType.cpp
+++ b/tests/test_ItemType.cpp
@@ -30,24 +30,39 @@ TEST_CASE("ItemType", "[connectivity]")
   SECTION("checking for item ids in 1d")
   {
     REQUIRE(ItemTypeId<1>::itemTId(cell_type) == 0);
-    REQUIRE(ItemTypeId<1>::itemTId(edge_type) == 1);
     REQUIRE(ItemTypeId<1>::itemTId(face_type) == 1);
+    REQUIRE(ItemTypeId<1>::itemTId(edge_type) == 1);
     REQUIRE(ItemTypeId<1>::itemTId(node_type) == 1);
+
+    REQUIRE(ItemTypeId<1>::dimension(cell_type) == 1);
+    REQUIRE(ItemTypeId<1>::dimension(face_type) == 0);
+    REQUIRE(ItemTypeId<1>::dimension(edge_type) == 0);
+    REQUIRE(ItemTypeId<1>::dimension(node_type) == 0);
   }
 
   SECTION("checking for item ids in 2d")
   {
     REQUIRE(ItemTypeId<2>::itemTId(cell_type) == 0);
-    REQUIRE(ItemTypeId<2>::itemTId(edge_type) == 1);
     REQUIRE(ItemTypeId<2>::itemTId(face_type) == 1);
+    REQUIRE(ItemTypeId<2>::itemTId(edge_type) == 1);
     REQUIRE(ItemTypeId<2>::itemTId(node_type) == 2);
+
+    REQUIRE(ItemTypeId<2>::dimension(cell_type) == 2);
+    REQUIRE(ItemTypeId<2>::dimension(face_type) == 1);
+    REQUIRE(ItemTypeId<2>::dimension(edge_type) == 1);
+    REQUIRE(ItemTypeId<2>::dimension(node_type) == 0);
   }
 
   SECTION("checking for item ids in 3d")
   {
     REQUIRE(ItemTypeId<3>::itemTId(cell_type) == 0);
-    REQUIRE(ItemTypeId<3>::itemTId(edge_type) == 1);
-    REQUIRE(ItemTypeId<3>::itemTId(face_type) == 2);
+    REQUIRE(ItemTypeId<3>::itemTId(face_type) == 1);
+    REQUIRE(ItemTypeId<3>::itemTId(edge_type) == 2);
     REQUIRE(ItemTypeId<3>::itemTId(node_type) == 3);
+
+    REQUIRE(ItemTypeId<3>::dimension(cell_type) == 3);
+    REQUIRE(ItemTypeId<3>::dimension(face_type) == 2);
+    REQUIRE(ItemTypeId<3>::dimension(edge_type) == 1);
+    REQUIRE(ItemTypeId<3>::dimension(node_type) == 0);
   }
 }
diff --git a/tests/test_ItemValueUtils.cpp b/tests/test_ItemValueUtils.cpp
index bee143e751de72affa1134998d8561a997bc7f12..42ed106fde72ca0a04c05ab25956c5807475f51c 100644
--- a/tests/test_ItemValueUtils.cpp
+++ b/tests/test_ItemValueUtils.cpp
@@ -2,15 +2,14 @@
 #include <catch2/matchers/catch_matchers_all.hpp>
 
 #include <MeshDataBaseForTests.hpp>
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
 #include <mesh/Connectivity.hpp>
 #include <mesh/ItemValue.hpp>
 #include <mesh/ItemValueUtils.hpp>
 #include <mesh/Mesh.hpp>
 #include <utils/Messenger.hpp>
 
-// Instantiate to ensure full coverage is performed
-template class ItemValue<int, ItemType::cell>;
-
 // clazy:excludeall=non-pod-global-static
 
 TEST_CASE("ItemValueUtils", "[mesh]")
@@ -47,6 +46,10 @@ TEST_CASE("ItemValueUtils", "[mesh]")
           }
         }
 
+        if (parallel::size() > 1) {
+          REQUIRE(not isSynchronized(face_value));
+        }
+
         synchronize(weak_face_value);
 
         {   // after synchronization
@@ -57,6 +60,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
             REQUIRE(face_owner[i_face] == face_value[i_face]);
           }
         }
+        REQUIRE(isSynchronized(face_value));
       }
     }
   }
@@ -265,7 +269,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
       std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
 
       for (auto named_mesh : mesh_list) {
-        SECTION(named_mesh.name())
+        SECTION(named_mesh.name() + "for size_t data")
         {
           auto mesh_2d = named_mesh.mesh();
 
@@ -286,6 +290,30 @@ TEST_CASE("ItemValueUtils", "[mesh]")
 
           REQUIRE(sum(face_value) == 2 * global_number_of_faces);
         }
+
+        SECTION(named_mesh.name() + "for N^3 data")
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          using N3 = TinyVector<3, size_t>;
+          FaceValue<N3> face_value{connectivity};
+          const N3 data(2, 1, 4);
+          face_value.fill(data);
+
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          const size_t global_number_of_faces = [&] {
+            size_t number_of_faces = 0;
+            for (FaceId face_id = 0; face_id < face_is_owned.numberOfItems(); ++face_id) {
+              number_of_faces += face_is_owned[face_id];
+            }
+            return parallel::allReduceSum(number_of_faces);
+          }();
+
+          REQUIRE(sum(face_value) == global_number_of_faces * data);
+        }
       }
     }
 
@@ -294,7 +322,7 @@ TEST_CASE("ItemValueUtils", "[mesh]")
       std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
 
       for (auto named_mesh : mesh_list) {
-        SECTION(named_mesh.name())
+        SECTION(named_mesh.name() + " for size_t data")
         {
           auto mesh_3d = named_mesh.mesh();
 
@@ -315,6 +343,32 @@ TEST_CASE("ItemValueUtils", "[mesh]")
 
           REQUIRE(sum(node_value) == 3 * global_number_of_nodes);
         }
+
+        SECTION(named_mesh.name() + " for N^3x2 data")
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          using N3x2 = TinyMatrix<3, 2, size_t>;
+
+          NodeValue<N3x2> node_value{connectivity};
+          const N3x2 data(3, 6, 1, 4, 5, 7);
+
+          node_value.fill(data);
+
+          auto node_is_owned = connectivity.nodeIsOwned();
+
+          const size_t global_number_of_nodes = [&] {
+            size_t number_of_nodes = 0;
+            for (NodeId node_id = 0; node_id < node_is_owned.numberOfItems(); ++node_id) {
+              number_of_nodes += node_is_owned[node_id];
+            }
+            return parallel::allReduceSum(number_of_nodes);
+          }();
+
+          REQUIRE(sum(node_value) == global_number_of_nodes * data);
+        }
       }
     }
   }
diff --git a/tests/test_SubItemArrayPerItemUtils.cpp b/tests/test_SubItemArrayPerItemUtils.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d3f29a3814fb88cc1fe75c06214dd463ac406525
--- /dev/null
+++ b/tests/test_SubItemArrayPerItemUtils.cpp
@@ -0,0 +1,430 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/SubItemArrayPerItem.hpp>
+#include <mesh/SubItemArrayPerItemUtils.hpp>
+#include <utils/Messenger.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("SubItemArrayPerItemUtils", "[mesh]")
+{
+  SECTION("Synchronize")
+  {
+    std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_2d = named_mesh.mesh();
+
+        const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+        NodeArrayPerFace<int> weak_node_array_per_face{connectivity, 3};
+
+        for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+          auto face_table = weak_node_array_per_face.itemTable(face_id);
+          for (size_t i_node = 0; i_node < face_table.numberOfRows(); ++i_node) {
+            for (size_t l = 0; l < face_table.numberOfColumns(); ++l) {
+              face_table(i_node, l) = parallel::rank() + 2 * i_node + l;
+            }
+          }
+        }
+
+        NodeArrayPerFace<const int> node_array_per_face{weak_node_array_per_face};
+
+        REQUIRE(node_array_per_face.connectivity_ptr() == weak_node_array_per_face.connectivity_ptr());
+
+        if (parallel::size() > 1) {
+          // before synchronization
+          auto face_owner = connectivity.faceOwner();
+
+          bool is_synchronized = true;
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            auto face_table = node_array_per_face.itemTable(face_id);
+            for (size_t i_node = 0; i_node < face_table.numberOfRows(); ++i_node) {
+              for (size_t l = 0; l < face_table.numberOfColumns(); ++l) {
+                if (face_table(i_node, l) != static_cast<int>(face_owner[face_id] + 2 * i_node + l)) {
+                  is_synchronized = false;
+                  break;
+                }
+              }
+            }
+          }
+
+          REQUIRE(not is_synchronized);
+          REQUIRE(not isSynchronized(node_array_per_face));
+        }
+
+        synchronize(weak_node_array_per_face);
+
+        {   // after synchronization
+          auto face_owner = connectivity.faceOwner();
+
+          bool is_synchronized = true;
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            auto face_table = node_array_per_face.itemTable(face_id);
+            for (size_t i_node = 0; i_node < face_table.numberOfRows(); ++i_node) {
+              for (size_t l = 0; l < face_table.numberOfColumns(); ++l) {
+                if (face_table(i_node, l) != static_cast<int>(face_owner[face_id] + 2 * i_node + l)) {
+                  is_synchronized = false;
+                }
+              }
+            }
+          }
+          REQUIRE(is_synchronized);
+        }
+        REQUIRE(isSynchronized(node_array_per_face));
+      }
+    }
+  }
+
+  SECTION("min")
+  {
+    SECTION("1D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+          NodeArrayPerCell<int> node_array_per_cell{connectivity, 3};
+          node_array_per_cell.fill(-1);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            connectivity.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                auto cell_table = node_array_per_cell.itemTable(cell_id);
+                for (size_t i = 0; i < cell_table.numberOfRows(); ++i) {
+                  for (size_t j = 0; j < cell_table.numberOfColumns(); ++j) {
+                    cell_table(i, j) = 10 + parallel::rank() + i + 2 * j;
+                  }
+                }
+              }
+            });
+
+          REQUIRE(min(node_array_per_cell) == 10);
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          FaceArrayPerCell<int> face_array_per_cell{connectivity, 3};
+          face_array_per_cell.fill(-1);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            connectivity.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                auto cell_table = face_array_per_cell.itemTable(cell_id);
+                for (size_t i = 0; i < cell_table.numberOfRows(); ++i) {
+                  for (size_t j = 0; j < cell_table.numberOfColumns(); ++j) {
+                    cell_table(i, j) = 10 + parallel::rank() + i + j;
+                  }
+                }
+              }
+            });
+
+          REQUIRE(min(face_array_per_cell) == 10);
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          EdgeArrayPerFace<int> edge_array_per_face{connectivity, 3};
+          edge_array_per_face.fill(-1);
+
+          auto face_is_owned = connectivity.faceIsOwned();
+          parallel_for(
+            connectivity.numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+              if (face_is_owned[face_id]) {
+                auto face_table = edge_array_per_face.itemTable(face_id);
+                for (size_t i = 0; i < face_table.numberOfRows(); ++i) {
+                  for (size_t j = 0; j < face_table.numberOfColumns(); ++j) {
+                    face_table(i, j) = 10 + parallel::rank() + i + j;
+                  }
+                }
+              }
+            });
+
+          REQUIRE(min(edge_array_per_face) == 10);
+        }
+      }
+    }
+  }
+
+  SECTION("max")
+  {
+    SECTION("1D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+          EdgeArrayPerCell<size_t> edge_array_per_cell{connectivity, 3};
+          edge_array_per_cell.fill(std::numeric_limits<size_t>::max());
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            connectivity.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                auto cell_table = edge_array_per_cell.itemTable(cell_id);
+                for (size_t i = 0; i < cell_table.numberOfRows(); ++i) {
+                  for (size_t j = 0; j < cell_table.numberOfColumns(); ++j) {
+                    cell_table(i, j) = 10 + parallel::rank() - i - j;
+                  }
+                }
+              }
+            });
+
+          REQUIRE(max(edge_array_per_cell) == 9 + parallel::size());
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          EdgeArrayPerCell<size_t> edge_array_per_cell{connectivity, 3};
+          edge_array_per_cell.fill(std::numeric_limits<size_t>::max());
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            connectivity.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                auto cell_table = edge_array_per_cell.itemTable(cell_id);
+                for (size_t i = 0; i < cell_table.numberOfRows(); ++i) {
+                  for (size_t j = 0; j < cell_table.numberOfColumns(); ++j) {
+                    cell_table(i, j) = 10 + parallel::rank() - i - j;
+                  }
+                }
+              }
+            });
+
+          REQUIRE(max(edge_array_per_cell) == 9 + parallel::size());
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          NodeArrayPerEdge<size_t> node_array_per_edge{connectivity, 3};
+          node_array_per_edge.fill(std::numeric_limits<size_t>::max());
+
+          auto edge_is_owned = connectivity.edgeIsOwned();
+
+          parallel_for(
+            connectivity.numberOfEdges(), PUGS_LAMBDA(EdgeId edge_id) {
+              if (edge_is_owned[edge_id]) {
+                auto edge_table = node_array_per_edge.itemTable(edge_id);
+                for (size_t i = 0; i < edge_table.numberOfRows(); ++i) {
+                  for (size_t j = 0; j < edge_table.numberOfColumns(); ++j) {
+                    edge_table(i, j) = 10 + parallel::rank() - i - j;
+                  }
+                }
+              }
+            });
+
+          REQUIRE(max(node_array_per_edge) == 9 + parallel::size());
+        }
+      }
+    }
+  }
+
+  SECTION("sum")
+  {
+    SECTION("1D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+          NodeArrayPerCell<size_t> node_array_per_cell{connectivity, 3};
+          node_array_per_cell.fill(5);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+
+          const size_t global_number_of_nodes_per_cells = [&] {
+            size_t number_of_nodes_per_cells = 0;
+            for (CellId cell_id = 0; cell_id < cell_is_owned.numberOfItems(); ++cell_id) {
+              number_of_nodes_per_cells += cell_is_owned[cell_id] * node_array_per_cell.numberOfSubArrays(cell_id);
+            }
+            return parallel::allReduceSum(number_of_nodes_per_cells);
+          }();
+
+          REQUIRE(sum(node_array_per_cell) ==
+                  5 * global_number_of_nodes_per_cells * node_array_per_cell.sizeOfArrays());
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name() + " for size_t data")
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          NodeArrayPerFace<size_t> node_array_per_face{connectivity, 3};
+          node_array_per_face.fill(2);
+
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          const size_t global_number_of_nodes_per_faces = [&] {
+            size_t number_of_nodes_per_faces = 0;
+            for (FaceId face_id = 0; face_id < face_is_owned.numberOfItems(); ++face_id) {
+              number_of_nodes_per_faces += face_is_owned[face_id] * node_array_per_face.numberOfSubArrays(face_id);
+            }
+            return parallel::allReduceSum(number_of_nodes_per_faces);
+          }();
+
+          REQUIRE(sum(node_array_per_face) ==
+                  2 * global_number_of_nodes_per_faces * node_array_per_face.sizeOfArrays());
+        }
+
+        SECTION(named_mesh.name() + " for N^2 data")
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          using N2 = TinyVector<2, size_t>;
+          NodeArrayPerFace<N2> node_array_per_face{connectivity, 3};
+
+          const N2 data(3, 2);
+          node_array_per_face.fill(data);
+
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          const size_t global_number_of_nodes_per_faces = [&] {
+            size_t number_of_nodes_per_faces = 0;
+            for (FaceId face_id = 0; face_id < face_is_owned.numberOfItems(); ++face_id) {
+              number_of_nodes_per_faces += face_is_owned[face_id] * node_array_per_face.numberOfSubArrays(face_id);
+            }
+            return parallel::allReduceSum(number_of_nodes_per_faces);
+          }();
+
+          REQUIRE(sum(node_array_per_face) ==
+                  global_number_of_nodes_per_faces * node_array_per_face.sizeOfArrays() * data);
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name() + " for size_t data")
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          EdgeArrayPerFace<size_t> edge_array_per_face{connectivity, 3};
+          edge_array_per_face.fill(3);
+
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          const size_t global_number_of_edges_per_faces = [&] {
+            size_t number_of_edges_per_faces = 0;
+            for (FaceId face_id = 0; face_id < face_is_owned.numberOfItems(); ++face_id) {
+              number_of_edges_per_faces += face_is_owned[face_id] * edge_array_per_face.numberOfSubArrays(face_id);
+            }
+            return parallel::allReduceSum(number_of_edges_per_faces);
+          }();
+
+          REQUIRE(sum(edge_array_per_face) ==
+                  3 * global_number_of_edges_per_faces * edge_array_per_face.sizeOfArrays());
+        }
+
+        SECTION(named_mesh.name() + " for N^2x3 data")
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          using N2x3 = TinyMatrix<2, 3, size_t>;
+          EdgeArrayPerFace<N2x3> edge_array_per_face{connectivity, 3};
+
+          const N2x3 data(1, 3, 4, 6, 2, 5);
+          edge_array_per_face.fill(data);
+
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          const size_t global_number_of_edges_per_faces = [&] {
+            size_t number_of_edges_per_faces = 0;
+            for (FaceId face_id = 0; face_id < face_is_owned.numberOfItems(); ++face_id) {
+              number_of_edges_per_faces += face_is_owned[face_id] * edge_array_per_face.numberOfSubArrays(face_id);
+            }
+            return parallel::allReduceSum(number_of_edges_per_faces);
+          }();
+
+          REQUIRE(sum(edge_array_per_face) ==
+                  global_number_of_edges_per_faces * edge_array_per_face.sizeOfArrays() * data);
+        }
+      }
+    }
+  }
+}
diff --git a/tests/test_SubItemValuePerItem.cpp b/tests/test_SubItemValuePerItem.cpp
index 4063133a30f71e2304d865fb1250e975470c7ed0..dda38918acdb156f8a7c5de552d1f82319463c97 100644
--- a/tests/test_SubItemValuePerItem.cpp
+++ b/tests/test_SubItemValuePerItem.cpp
@@ -85,7 +85,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
               for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
                 is_correct &=
                   (cell_to_node_matrix[cell_id].size() == node_value_per_cell.numberOfSubValues(cell_id)) and
-                  (node_value_per_cell.itemValues(cell_id).size() == node_value_per_cell.numberOfSubValues(cell_id));
+                  (node_value_per_cell.itemArray(cell_id).size() == node_value_per_cell.numberOfSubValues(cell_id));
               }
               REQUIRE(is_correct);
             }
@@ -94,7 +94,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
             {
               bool is_correct = true;
               for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
-                is_correct &= (const_node_value_per_cell.itemValues(cell_id).size() ==
+                is_correct &= (const_node_value_per_cell.itemArray(cell_id).size() ==
                                node_value_per_cell.numberOfSubValues(cell_id));
               }
               REQUIRE(is_correct);
@@ -624,7 +624,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
             size_t value = 0;
             for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
               for (size_t i_face = 0; i_face < face_values_per_node.numberOfSubValues(node_id); ++i_face) {
-                face_values_per_node.itemValues(node_id)[i_face] = value++;
+                face_values_per_node.itemArray(node_id)[i_face] = value++;
               }
             }
           }
@@ -644,7 +644,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
             size_t i     = 0;
             for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
               for (size_t i_face = 0; i_face < face_values_per_node.numberOfSubValues(node_id); ++i_face, ++i) {
-                is_same &= (face_values_per_node.itemValues(node_id)[i_face] == 3 + i * i);
+                is_same &= (face_values_per_node.itemArray(node_id)[i_face] == 3 + i * i);
               }
             }
             REQUIRE(is_same);
@@ -673,7 +673,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
             size_t value = 0;
             for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
               for (size_t i_node = 0; i_node < node_value_per_cell.numberOfSubValues(cell_id); ++i_node) {
-                node_value_per_cell.itemValues(cell_id)[i_node] = value++;
+                node_value_per_cell.itemArray(cell_id)[i_node] = value++;
               }
             }
           }
@@ -692,7 +692,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
           {
             for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
               for (size_t i_node = 0; i_node < node_value_per_cell.numberOfSubValues(cell_id); ++i_node) {
-                node_value_per_cell.itemValues(cell_id)[i_node] = i_node;
+                node_value_per_cell.itemArray(cell_id)[i_node] = i_node;
               }
             }
           }
@@ -715,7 +715,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
             size_t value = 0;
             for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
               for (size_t i_node = 0; i_node < node_value_per_cell.numberOfSubValues(cell_id); ++i_node) {
-                node_value_per_cell.itemValues(cell_id)[i_node] = value++;
+                node_value_per_cell.itemArray(cell_id)[i_node] = value++;
               }
             }
           }
@@ -735,7 +735,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
           {
             for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
               for (size_t i_node = 0; i_node < node_value_per_cell.numberOfSubValues(cell_id); ++i_node) {
-                node_value_per_cell.itemValues(cell_id)[i_node] = i_node;
+                node_value_per_cell.itemArray(cell_id)[i_node] = i_node;
               }
             }
           }
@@ -790,7 +790,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
     {
       CellValuePerNode<int> cell_value_per_node;
       REQUIRE_THROWS_AS(cell_value_per_node[0], AssertError);
-      REQUIRE_THROWS_AS(cell_value_per_node.itemValues(NodeId{0}), AssertError);
+      REQUIRE_THROWS_AS(cell_value_per_node.itemArray(NodeId{0}), AssertError);
       REQUIRE_THROWS_AS(cell_value_per_node(NodeId{0}, 0), AssertError);
       REQUIRE_THROWS_AS(cell_value_per_node.numberOfValues(), AssertError);
       REQUIRE_THROWS_AS(cell_value_per_node.numberOfItems(), AssertError);
@@ -798,7 +798,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
 
       FaceValuePerCell<int> face_value_per_cell;
       REQUIRE_THROWS_AS(face_value_per_cell[0], AssertError);
-      REQUIRE_THROWS_AS(face_value_per_cell.itemValues(CellId{0}), AssertError);
+      REQUIRE_THROWS_AS(face_value_per_cell.itemArray(CellId{0}), AssertError);
       REQUIRE_THROWS_AS(face_value_per_cell(CellId{0}, 0), AssertError);
       REQUIRE_THROWS_AS(face_value_per_cell.numberOfValues(), AssertError);
       REQUIRE_THROWS_AS(face_value_per_cell.numberOfItems(), AssertError);
@@ -806,7 +806,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
 
       CellValuePerEdge<int> cell_value_per_edge;
       REQUIRE_THROWS_AS(cell_value_per_edge[0], AssertError);
-      REQUIRE_THROWS_AS(cell_value_per_edge.itemValues(EdgeId{0}), AssertError);
+      REQUIRE_THROWS_AS(cell_value_per_edge.itemArray(EdgeId{0}), AssertError);
       REQUIRE_THROWS_AS(cell_value_per_edge(EdgeId{0}, 0), AssertError);
       REQUIRE_THROWS_AS(cell_value_per_edge.numberOfValues(), AssertError);
       REQUIRE_THROWS_AS(cell_value_per_edge.numberOfItems(), AssertError);
@@ -814,7 +814,7 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
 
       NodeValuePerFace<int> node_value_per_face;
       REQUIRE_THROWS_AS(node_value_per_face[0], AssertError);
-      REQUIRE_THROWS_AS(node_value_per_face.itemValues(FaceId{0}), AssertError);
+      REQUIRE_THROWS_AS(node_value_per_face.itemArray(FaceId{0}), AssertError);
       REQUIRE_THROWS_AS(node_value_per_face(FaceId{0}, 0), AssertError);
       REQUIRE_THROWS_AS(node_value_per_face.numberOfValues(), AssertError);
       REQUIRE_THROWS_AS(node_value_per_face.numberOfItems(), AssertError);
@@ -839,10 +839,10 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
           }
           if (connectivity.numberOfFaces() > 0) {
             FaceId face_id          = 0;
-            const auto& cell_values = cell_value_per_face.itemValues(face_id);
+            const auto& cell_values = cell_value_per_face.itemArray(face_id);
             REQUIRE_THROWS_AS(cell_value_per_face(face_id, cell_values.size()), AssertError);
             REQUIRE_THROWS_AS(cell_values[cell_values.size()], AssertError);
-            REQUIRE_THROWS_AS(cell_value_per_face.itemValues(face_id)[cell_values.size()] = 2, AssertError);
+            REQUIRE_THROWS_AS(cell_value_per_face.itemArray(face_id)[cell_values.size()] = 2, AssertError);
           }
 
           FaceValuePerNode<int> face_value_per_node{connectivity};
@@ -852,10 +852,10 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
           }
           if (connectivity.numberOfNodes() > 0) {
             NodeId node_id          = 0;
-            const auto& face_values = face_value_per_node.itemValues(node_id);
+            const auto& face_values = face_value_per_node.itemArray(node_id);
             REQUIRE_THROWS_AS(face_value_per_node(node_id, face_values.size()), AssertError);
             REQUIRE_THROWS_AS(face_values[face_values.size()], AssertError);
-            REQUIRE_THROWS_AS(face_value_per_node.itemValues(node_id)[face_values.size()] = 2, AssertError);
+            REQUIRE_THROWS_AS(face_value_per_node.itemArray(node_id)[face_values.size()] = 2, AssertError);
           }
 
           EdgeValuePerCell<int> edge_value_per_cell{connectivity};
@@ -865,10 +865,10 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
           }
           if (connectivity.numberOfCells() > 0) {
             CellId cell_id          = 0;
-            const auto& edge_values = edge_value_per_cell.itemValues(cell_id);
+            const auto& edge_values = edge_value_per_cell.itemArray(cell_id);
             REQUIRE_THROWS_AS(edge_value_per_cell(cell_id, edge_values.size()), AssertError);
             REQUIRE_THROWS_AS(edge_values[edge_values.size()], AssertError);
-            REQUIRE_THROWS_AS(edge_value_per_cell.itemValues(cell_id)[edge_values.size()] = 2, AssertError);
+            REQUIRE_THROWS_AS(edge_value_per_cell.itemArray(cell_id)[edge_values.size()] = 2, AssertError);
           }
 
           NodeValuePerEdge<int> node_value_per_edge{connectivity};
@@ -878,10 +878,10 @@ TEST_CASE("SubItemValuePerItem", "[mesh]")
           }
           if (connectivity.numberOfEdges() > 0) {
             EdgeId edge_id          = 0;
-            const auto& node_values = node_value_per_edge.itemValues(edge_id);
+            const auto& node_values = node_value_per_edge.itemArray(edge_id);
             REQUIRE_THROWS_AS(node_value_per_edge(edge_id, node_values.size()), AssertError);
             REQUIRE_THROWS_AS(node_values[node_values.size()], AssertError);
-            REQUIRE_THROWS_AS(node_value_per_edge.itemValues(edge_id)[node_values.size()] = 2, AssertError);
+            REQUIRE_THROWS_AS(node_value_per_edge.itemArray(edge_id)[node_values.size()] = 2, AssertError);
           }
         }
       }
diff --git a/tests/test_SubItemValuePerItemUtils.cpp b/tests/test_SubItemValuePerItemUtils.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ccda4722d7f5ac6c56bacb0d94c2ed0689166eec
--- /dev/null
+++ b/tests/test_SubItemValuePerItemUtils.cpp
@@ -0,0 +1,409 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <algebra/TinyMatrix.hpp>
+#include <algebra/TinyVector.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/SubItemValuePerItem.hpp>
+#include <mesh/SubItemValuePerItemUtils.hpp>
+#include <utils/Messenger.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("SubItemValuePerItemUtils", "[mesh]")
+{
+  SECTION("Synchronize")
+  {
+    std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+    for (auto named_mesh : mesh_list) {
+      SECTION(named_mesh.name())
+      {
+        auto mesh_2d = named_mesh.mesh();
+
+        const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+        NodeValuePerFace<int> weak_node_value_per_face{connectivity};
+
+        for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+          auto face_array = weak_node_value_per_face.itemArray(face_id);
+          for (size_t i_node = 0; i_node < face_array.size(); ++i_node) {
+            face_array[i_node] = parallel::rank() + 2 * i_node;
+          }
+        }
+
+        NodeValuePerFace<const int> node_value_per_face{weak_node_value_per_face};
+
+        REQUIRE(node_value_per_face.connectivity_ptr() == weak_node_value_per_face.connectivity_ptr());
+
+        if (parallel::size() > 1) {
+          // before synchronization
+          auto face_owner = connectivity.faceOwner();
+
+          bool is_synchronized = true;
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            auto face_array = node_value_per_face.itemArray(face_id);
+            for (size_t i_node = 0; i_node < face_array.size(); ++i_node) {
+              if (face_array[i_node] != static_cast<int>(face_owner[face_id] + 2 * i_node)) {
+                is_synchronized = false;
+                break;
+              }
+            }
+          }
+
+          REQUIRE(not is_synchronized);
+          REQUIRE(not isSynchronized(node_value_per_face));
+        }
+
+        synchronize(weak_node_value_per_face);
+
+        {   // after synchronization
+          auto face_owner = connectivity.faceOwner();
+
+          bool is_synchronized = true;
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            auto face_array = node_value_per_face.itemArray(face_id);
+            for (size_t i_node = 0; i_node < face_array.size(); ++i_node) {
+              if (face_array[i_node] != static_cast<int>(face_owner[face_id] + 2 * i_node)) {
+                is_synchronized = false;
+                break;
+              }
+            }
+          }
+          REQUIRE(is_synchronized);
+        }
+        REQUIRE(isSynchronized(node_value_per_face));
+      }
+    }
+  }
+
+  SECTION("min")
+  {
+    SECTION("1D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+          NodeValuePerCell<int> node_value_per_cell{connectivity};
+          node_value_per_cell.fill(-1);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            connectivity.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                auto cell_array = node_value_per_cell.itemArray(cell_id);
+                for (size_t i = 0; i < cell_array.size(); ++i) {
+                  cell_array[i] = 10 + parallel::rank() + i;
+                }
+              }
+            });
+
+          REQUIRE(min(node_value_per_cell) == 10);
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          FaceValuePerCell<int> face_value_per_cell{connectivity};
+          face_value_per_cell.fill(-1);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            connectivity.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                auto cell_array = face_value_per_cell.itemArray(cell_id);
+                for (size_t i = 0; i < cell_array.size(); ++i) {
+                  cell_array[i] = 10 + parallel::rank() + i;
+                }
+              }
+            });
+
+          REQUIRE(min(face_value_per_cell) == 10);
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          EdgeValuePerFace<int> edge_value_per_face{connectivity};
+          edge_value_per_face.fill(-1);
+
+          auto face_is_owned = connectivity.faceIsOwned();
+          parallel_for(
+            connectivity.numberOfFaces(), PUGS_LAMBDA(FaceId face_id) {
+              if (face_is_owned[face_id]) {
+                auto face_array = edge_value_per_face.itemArray(face_id);
+                for (size_t i = 0; i < face_array.size(); ++i) {
+                  face_array[i] = 10 + parallel::rank() + i;
+                }
+              }
+            });
+
+          REQUIRE(min(edge_value_per_face) == 10);
+        }
+      }
+    }
+  }
+
+  SECTION("max")
+  {
+    SECTION("1D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+          EdgeValuePerCell<size_t> edge_value_per_cell{connectivity};
+          edge_value_per_cell.fill(std::numeric_limits<size_t>::max());
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            connectivity.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                auto cell_array = edge_value_per_cell.itemArray(cell_id);
+                for (size_t i = 0; i < cell_array.size(); ++i) {
+                  cell_array[i] = 10 + parallel::rank() - i;
+                }
+              }
+            });
+
+          REQUIRE(max(edge_value_per_cell) == 9 + parallel::size());
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          EdgeValuePerCell<size_t> edge_value_per_cell{connectivity};
+          edge_value_per_cell.fill(std::numeric_limits<size_t>::max());
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+          parallel_for(
+            connectivity.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+              if (cell_is_owned[cell_id]) {
+                auto cell_array = edge_value_per_cell.itemArray(cell_id);
+                for (size_t i = 0; i < cell_array.size(); ++i) {
+                  cell_array[i] = 10 + parallel::rank() - i;
+                }
+              }
+            });
+
+          REQUIRE(max(edge_value_per_cell) == 9 + parallel::size());
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          CellValue<size_t> cell_value{connectivity};
+
+          NodeValuePerEdge<size_t> node_value_per_edge{connectivity};
+          node_value_per_edge.fill(std::numeric_limits<size_t>::max());
+
+          auto edge_is_owned = connectivity.edgeIsOwned();
+          parallel_for(
+            connectivity.numberOfEdges(), PUGS_LAMBDA(EdgeId edge_id) {
+              if (edge_is_owned[edge_id]) {
+                auto edge_array = node_value_per_edge.itemArray(edge_id);
+                for (size_t i = 0; i < edge_array.size(); ++i) {
+                  edge_array[i] = 10 + parallel::rank() - i;
+                }
+              }
+            });
+
+          REQUIRE(max(node_value_per_edge) == 9 + parallel::size());
+        }
+      }
+    }
+  }
+
+  SECTION("sum")
+  {
+    SECTION("1D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all1DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name())
+        {
+          auto mesh_1d = named_mesh.mesh();
+
+          const Connectivity<1>& connectivity = mesh_1d->connectivity();
+
+          NodeValuePerCell<size_t> node_value_per_cell{connectivity};
+          node_value_per_cell.fill(5);
+
+          auto cell_is_owned = connectivity.cellIsOwned();
+
+          const size_t global_number_of_nodes_per_cells = [&] {
+            size_t number_of_nodes_per_cells = 0;
+            for (CellId cell_id = 0; cell_id < cell_is_owned.numberOfItems(); ++cell_id) {
+              number_of_nodes_per_cells += cell_is_owned[cell_id] * node_value_per_cell.numberOfSubValues(cell_id);
+            }
+            return parallel::allReduceSum(number_of_nodes_per_cells);
+          }();
+
+          REQUIRE(sum(node_value_per_cell) == 5 * global_number_of_nodes_per_cells);
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all2DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name() + " for size_t data")
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          NodeValuePerFace<size_t> node_value_per_face{connectivity};
+          node_value_per_face.fill(2);
+
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          const size_t global_number_of_nodes_per_faces = [&] {
+            size_t number_of_nodes_per_faces = 0;
+            for (FaceId face_id = 0; face_id < face_is_owned.numberOfItems(); ++face_id) {
+              number_of_nodes_per_faces += face_is_owned[face_id] * node_value_per_face.numberOfSubValues(face_id);
+            }
+            return parallel::allReduceSum(number_of_nodes_per_faces);
+          }();
+
+          REQUIRE(sum(node_value_per_face) == 2 * global_number_of_nodes_per_faces);
+        }
+
+        SECTION(named_mesh.name() + " for N^2 data")
+        {
+          auto mesh_2d = named_mesh.mesh();
+
+          const Connectivity<2>& connectivity = mesh_2d->connectivity();
+
+          using N2 = TinyVector<2, size_t>;
+          NodeValuePerFace<N2> node_value_per_face{connectivity};
+
+          const N2 data(3, 2);
+          node_value_per_face.fill(data);
+
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          const size_t global_number_of_nodes_per_faces = [&] {
+            size_t number_of_nodes_per_faces = 0;
+            for (FaceId face_id = 0; face_id < face_is_owned.numberOfItems(); ++face_id) {
+              number_of_nodes_per_faces += face_is_owned[face_id] * node_value_per_face.numberOfSubValues(face_id);
+            }
+            return parallel::allReduceSum(number_of_nodes_per_faces);
+          }();
+
+          REQUIRE(sum(node_value_per_face) == global_number_of_nodes_per_faces * data);
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      std::array mesh_list = MeshDataBaseForTests::get().all3DMeshes();
+
+      for (auto named_mesh : mesh_list) {
+        SECTION(named_mesh.name() + " for size_t data")
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          EdgeValuePerFace<size_t> edge_value_per_face{connectivity};
+          edge_value_per_face.fill(3);
+
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          const size_t global_number_of_edges_per_faces = [&] {
+            size_t number_of_edges_per_faces = 0;
+            for (FaceId face_id = 0; face_id < face_is_owned.numberOfItems(); ++face_id) {
+              number_of_edges_per_faces += face_is_owned[face_id] * edge_value_per_face.numberOfSubValues(face_id);
+            }
+            return parallel::allReduceSum(number_of_edges_per_faces);
+          }();
+
+          REQUIRE(sum(edge_value_per_face) == 3 * global_number_of_edges_per_faces);
+        }
+
+        SECTION(named_mesh.name() + " for N^2x3 data")
+        {
+          auto mesh_3d = named_mesh.mesh();
+
+          const Connectivity<3>& connectivity = mesh_3d->connectivity();
+
+          using N2x3 = TinyMatrix<2, 3, size_t>;
+          EdgeValuePerFace<N2x3> edge_value_per_face{connectivity};
+
+          const N2x3 data(1, 3, 4, 6, 2, 5);
+          edge_value_per_face.fill(data);
+
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          const size_t global_number_of_edges_per_faces = [&] {
+            size_t number_of_edges_per_faces = 0;
+            for (FaceId face_id = 0; face_id < face_is_owned.numberOfItems(); ++face_id) {
+              number_of_edges_per_faces += face_is_owned[face_id] * edge_value_per_face.numberOfSubValues(face_id);
+            }
+            return parallel::allReduceSum(number_of_edges_per_faces);
+          }();
+
+          REQUIRE(sum(edge_value_per_face) == global_number_of_edges_per_faces * data);
+        }
+      }
+    }
+  }
+}
diff --git a/tests/test_Synchronizer.cpp b/tests/test_Synchronizer.cpp
index 6a4a9b52a6ce05a4a0b2758c868a49dc758d3e3d..54a065b475c757bdf8e108e01d1b8f859daa262d 100644
--- a/tests/test_Synchronizer.cpp
+++ b/tests/test_Synchronizer.cpp
@@ -5,446 +5,2318 @@
 #include <mesh/Connectivity.hpp>
 #include <mesh/Mesh.hpp>
 #include <mesh/Synchronizer.hpp>
+#include <mesh/SynchronizerManager.hpp>
 #include <utils/pugs_config.hpp>
 
 // clazy:excludeall=non-pod-global-static
 
 TEST_CASE("Synchronizer", "[mesh]")
 {
-  auto is_same_item_value = [](auto a, auto b) {
-    using IndexT = typename decltype(a)::index_type;
-    bool is_same = true;
-    for (IndexT i = 0; i < a.numberOfItems(); ++i) {
-      is_same &= (a[i] == b[i]);
-    }
-    return parallel::allReduceAnd(is_same);
-  };
+  SECTION("ItemValue")
+  {
+    auto is_same_item_value = [](auto a, auto b) {
+      using IndexT = typename decltype(a)::index_type;
+      bool is_same = true;
+      for (IndexT i = 0; i < a.numberOfItems(); ++i) {
+        is_same &= (a[i] == b[i]);
+      }
+      return parallel::allReduceAnd(is_same);
+    };
+
+    SECTION("1D")
+    {
+      constexpr size_t Dimension = 1;
+      using ConnectivityType     = Connectivity<Dimension>;
+
+      const ConnectivityType& connectivity = MeshDataBaseForTests::get().unordered1DMesh()->connectivity();
+
+      SECTION("synchonize NodeValue")
+      {
+        const auto node_owner  = connectivity.nodeOwner();
+        const auto node_number = connectivity.nodeNumber();
+
+        NodeValue<int> node_value_ref{connectivity};
+        parallel_for(
+          connectivity.numberOfNodes(),
+          PUGS_LAMBDA(const NodeId node_id) { node_value_ref[node_id] = node_owner[node_id] + node_number[node_id]; });
 
-  auto is_same_item_array = [](auto a, auto b) {
-    using IndexT = typename decltype(a)::index_type;
-    bool is_same = true;
-    for (IndexT i = 0; i < a.numberOfItems(); ++i) {
-      for (size_t j = 0; j < a.sizeOfArrays(); ++j) {
-        is_same &= (a[i][j] == b[i][j]);
+        NodeValue<int> node_value{connectivity};
+        parallel_for(
+          connectivity.numberOfNodes(),
+          PUGS_LAMBDA(const NodeId node_id) { node_value[node_id] = parallel::rank() + node_number[node_id]; });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(node_value, node_value_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_value);
+
+        REQUIRE(is_same_item_value(node_value, node_value_ref));
       }
-    }
-    return parallel::allReduceAnd(is_same);
-  };
 
-  SECTION("1D")
-  {
-    constexpr size_t Dimension = 1;
-    using ConnectivityType     = Connectivity<Dimension>;
+      SECTION("synchonize EdgeValue")
+      {
+        const auto edge_owner  = connectivity.edgeOwner();
+        const auto edge_number = connectivity.edgeNumber();
 
-    const ConnectivityType& connectivity = MeshDataBaseForTests::get().unordered1DMesh()->connectivity();
+        EdgeValue<int> edge_value_ref{connectivity};
+        parallel_for(
+          connectivity.numberOfEdges(),
+          PUGS_LAMBDA(const EdgeId edge_id) { edge_value_ref[edge_id] = edge_owner[edge_id] + edge_number[edge_id]; });
 
-    SECTION("synchonize NodeValue")
-    {
-      const auto node_owner  = connectivity.nodeOwner();
-      const auto node_number = connectivity.nodeNumber();
+        EdgeValue<int> edge_value{connectivity};
+        parallel_for(
+          connectivity.numberOfEdges(),
+          PUGS_LAMBDA(const EdgeId edge_id) { edge_value[edge_id] = parallel::rank() + edge_number[edge_id]; });
 
-      NodeValue<int> node_value_ref{connectivity};
-      parallel_for(
-        connectivity.numberOfNodes(),
-        PUGS_LAMBDA(const NodeId node_id) { node_value_ref[node_id] = node_owner[node_id] + node_number[node_id]; });
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(edge_value, edge_value_ref));
+        }
 
-      NodeValue<int> node_value{connectivity};
-      parallel_for(
-        connectivity.numberOfNodes(),
-        PUGS_LAMBDA(const NodeId node_id) { node_value[node_id] = parallel::rank() + node_number[node_id]; });
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(edge_value);
 
-      if (parallel::size() > 1) {
-        REQUIRE(not is_same_item_value(node_value, node_value_ref));
+        REQUIRE(is_same_item_value(edge_value, edge_value_ref));
       }
 
-      Synchronizer synchronizer;
-      synchronizer.synchronize(node_value);
+      SECTION("synchonize FaceValue")
+      {
+        const auto face_owner  = connectivity.faceOwner();
+        const auto face_number = connectivity.faceNumber();
 
-      REQUIRE(is_same_item_value(node_value, node_value_ref));
-    }
+        FaceValue<int> face_value_ref{connectivity};
+        parallel_for(
+          connectivity.numberOfFaces(),
+          PUGS_LAMBDA(const FaceId face_id) { face_value_ref[face_id] = face_owner[face_id] + face_number[face_id]; });
 
-    SECTION("synchonize EdgeValue")
-    {
-      const auto edge_owner  = connectivity.edgeOwner();
-      const auto edge_number = connectivity.edgeNumber();
+        FaceValue<int> face_value{connectivity};
+        parallel_for(
+          connectivity.numberOfFaces(),
+          PUGS_LAMBDA(const FaceId face_id) { face_value[face_id] = parallel::rank() + face_number[face_id]; });
 
-      EdgeValue<int> edge_value_ref{connectivity};
-      parallel_for(
-        connectivity.numberOfEdges(),
-        PUGS_LAMBDA(const EdgeId edge_id) { edge_value_ref[edge_id] = edge_owner[edge_id] + edge_number[edge_id]; });
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(face_value, face_value_ref));
+        }
 
-      EdgeValue<int> edge_value{connectivity};
-      parallel_for(
-        connectivity.numberOfEdges(),
-        PUGS_LAMBDA(const EdgeId edge_id) { edge_value[edge_id] = parallel::rank() + edge_number[edge_id]; });
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(face_value);
 
-      if (parallel::size() > 1) {
-        REQUIRE(not is_same_item_value(edge_value, edge_value_ref));
+        REQUIRE(is_same_item_value(face_value, face_value_ref));
       }
 
-      Synchronizer synchronizer;
-      synchronizer.synchronize(edge_value);
+      SECTION("synchonize CellValue")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
 
-      REQUIRE(is_same_item_value(edge_value, edge_value_ref));
+        CellValue<int> cell_value_ref{connectivity};
+        parallel_for(
+          connectivity.numberOfCells(),
+          PUGS_LAMBDA(const CellId cell_id) { cell_value_ref[cell_id] = cell_owner[cell_id] + cell_number[cell_id]; });
+
+        CellValue<int> cell_value{connectivity};
+        parallel_for(
+          connectivity.numberOfCells(),
+          PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = parallel::rank() + cell_number[cell_id]; });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(cell_value, cell_value_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(cell_value);
+
+        REQUIRE(is_same_item_value(cell_value, cell_value_ref));
+      }
     }
 
-    SECTION("synchonize FaceValue")
+    SECTION("2D")
     {
-      const auto face_owner  = connectivity.faceOwner();
-      const auto face_number = connectivity.faceNumber();
+      constexpr size_t Dimension = 2;
+      using ConnectivityType     = Connectivity<Dimension>;
+
+      const ConnectivityType& connectivity = MeshDataBaseForTests::get().hybrid2DMesh()->connectivity();
+
+      SECTION("synchonize NodeValue")
+      {
+        const auto node_owner  = connectivity.nodeOwner();
+        const auto node_number = connectivity.nodeNumber();
+
+        NodeValue<int> node_value_ref{connectivity};
+        parallel_for(
+          connectivity.numberOfNodes(),
+          PUGS_LAMBDA(const NodeId node_id) { node_value_ref[node_id] = node_owner[node_id] + node_number[node_id]; });
 
-      FaceValue<int> face_value_ref{connectivity};
-      parallel_for(
-        connectivity.numberOfFaces(),
-        PUGS_LAMBDA(const FaceId face_id) { face_value_ref[face_id] = face_owner[face_id] + face_number[face_id]; });
+        NodeValue<int> node_value{connectivity};
+        parallel_for(
+          connectivity.numberOfNodes(),
+          PUGS_LAMBDA(const NodeId node_id) { node_value[node_id] = parallel::rank() + node_number[node_id]; });
 
-      FaceValue<int> face_value{connectivity};
-      parallel_for(
-        connectivity.numberOfFaces(),
-        PUGS_LAMBDA(const FaceId face_id) { face_value[face_id] = parallel::rank() + face_number[face_id]; });
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(node_value, node_value_ref));
+        }
 
-      if (parallel::size() > 1) {
-        REQUIRE(not is_same_item_value(face_value, face_value_ref));
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_value);
+
+        REQUIRE(is_same_item_value(node_value, node_value_ref));
       }
 
-      Synchronizer synchronizer;
-      synchronizer.synchronize(face_value);
+      SECTION("synchonize EdgeValue")
+      {
+        const auto edge_owner  = connectivity.edgeOwner();
+        const auto edge_number = connectivity.edgeNumber();
 
-      REQUIRE(is_same_item_value(face_value, face_value_ref));
-    }
+        EdgeValue<int> edge_value_ref{connectivity};
+        parallel_for(
+          connectivity.numberOfEdges(),
+          PUGS_LAMBDA(const EdgeId edge_id) { edge_value_ref[edge_id] = edge_owner[edge_id] + edge_number[edge_id]; });
 
-    SECTION("synchonize CellValue")
-    {
-      const auto cell_owner  = connectivity.cellOwner();
-      const auto cell_number = connectivity.cellNumber();
+        EdgeValue<int> edge_value{connectivity};
+        parallel_for(
+          connectivity.numberOfEdges(),
+          PUGS_LAMBDA(const EdgeId edge_id) { edge_value[edge_id] = parallel::rank() + edge_number[edge_id]; });
 
-      CellValue<int> cell_value_ref{connectivity};
-      parallel_for(
-        connectivity.numberOfCells(),
-        PUGS_LAMBDA(const CellId cell_id) { cell_value_ref[cell_id] = cell_owner[cell_id] + cell_number[cell_id]; });
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(edge_value, edge_value_ref));
+        }
 
-      CellValue<int> cell_value{connectivity};
-      parallel_for(
-        connectivity.numberOfCells(),
-        PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = parallel::rank() + cell_number[cell_id]; });
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(edge_value);
 
-      if (parallel::size() > 1) {
-        REQUIRE(not is_same_item_value(cell_value, cell_value_ref));
+        REQUIRE(is_same_item_value(edge_value, edge_value_ref));
       }
 
-      Synchronizer synchronizer;
-      synchronizer.synchronize(cell_value);
+      SECTION("synchonize FaceValue")
+      {
+        const auto face_owner  = connectivity.faceOwner();
+        const auto face_number = connectivity.faceNumber();
 
-      REQUIRE(is_same_item_value(cell_value, cell_value_ref));
-    }
+        FaceValue<int> face_value_ref{connectivity};
+        parallel_for(
+          connectivity.numberOfFaces(),
+          PUGS_LAMBDA(const FaceId face_id) { face_value_ref[face_id] = face_owner[face_id] + face_number[face_id]; });
 
-    SECTION("synchonize CellArray")
-    {
-      const auto cell_owner  = connectivity.cellOwner();
-      const auto cell_number = connectivity.cellNumber();
-
-      CellArray<int> cell_array_ref{connectivity, 3};
-      parallel_for(
-        connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-          for (size_t i = 0; i < cell_array_ref.sizeOfArrays(); ++i) {
-            cell_array_ref[cell_id][i] = (i + 1) * cell_owner[cell_id] + i + cell_number[cell_id];
-          }
-        });
+        FaceValue<int> face_value{connectivity};
+        parallel_for(
+          connectivity.numberOfFaces(),
+          PUGS_LAMBDA(const FaceId face_id) { face_value[face_id] = parallel::rank() + face_number[face_id]; });
 
-      CellArray<int> cell_array{connectivity, 3};
-      parallel_for(
-        connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-          for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
-            cell_array[cell_id][i] = (i + 1) * parallel::rank() + i + cell_number[cell_id];
-          }
-        });
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(face_value, face_value_ref));
+        }
 
-      if (parallel::size() > 1) {
-        REQUIRE(not is_same_item_array(cell_array, cell_array_ref));
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(face_value);
+
+        REQUIRE(is_same_item_value(face_value, face_value_ref));
       }
 
-      Synchronizer synchronizer;
-      synchronizer.synchronize(cell_array);
+      SECTION("synchonize CellValue")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
 
-      REQUIRE(is_same_item_array(cell_array, cell_array_ref));
-    }
-  }
+        CellValue<int> cell_value_ref{connectivity};
+        parallel_for(
+          connectivity.numberOfCells(),
+          PUGS_LAMBDA(const CellId cell_id) { cell_value_ref[cell_id] = cell_owner[cell_id] + cell_number[cell_id]; });
 
-  SECTION("2D")
-  {
-    constexpr size_t Dimension = 2;
-    using ConnectivityType     = Connectivity<Dimension>;
+        CellValue<int> cell_value{connectivity};
+        parallel_for(
+          connectivity.numberOfCells(),
+          PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = parallel::rank() + cell_number[cell_id]; });
 
-    const ConnectivityType& connectivity = MeshDataBaseForTests::get().hybrid2DMesh()->connectivity();
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(cell_value, cell_value_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(cell_value);
+
+        REQUIRE(is_same_item_value(cell_value, cell_value_ref));
+      }
+    }
 
-    SECTION("synchonize NodeValue")
+    SECTION("3D")
     {
-      const auto node_owner  = connectivity.nodeOwner();
-      const auto node_number = connectivity.nodeNumber();
+      constexpr size_t Dimension = 3;
+      using ConnectivityType     = Connectivity<Dimension>;
 
-      NodeValue<int> node_value_ref{connectivity};
-      parallel_for(
-        connectivity.numberOfNodes(),
-        PUGS_LAMBDA(const NodeId node_id) { node_value_ref[node_id] = node_owner[node_id] + node_number[node_id]; });
+      const ConnectivityType& connectivity = MeshDataBaseForTests::get().hybrid3DMesh()->connectivity();
 
-      NodeValue<int> node_value{connectivity};
-      parallel_for(
-        connectivity.numberOfNodes(),
-        PUGS_LAMBDA(const NodeId node_id) { node_value[node_id] = parallel::rank() + node_number[node_id]; });
+      SECTION("synchonize NodeValue")
+      {
+        const auto node_owner  = connectivity.nodeOwner();
+        const auto node_number = connectivity.nodeNumber();
 
-      if (parallel::size() > 1) {
-        REQUIRE(not is_same_item_value(node_value, node_value_ref));
+        NodeValue<int> node_value_ref{connectivity};
+        parallel_for(
+          connectivity.numberOfNodes(),
+          PUGS_LAMBDA(const NodeId node_id) { node_value_ref[node_id] = node_owner[node_id] + node_number[node_id]; });
+
+        NodeValue<int> node_value{connectivity};
+        parallel_for(
+          connectivity.numberOfNodes(),
+          PUGS_LAMBDA(const NodeId node_id) { node_value[node_id] = parallel::rank() + node_number[node_id]; });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(node_value, node_value_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_value);
+
+        REQUIRE(is_same_item_value(node_value, node_value_ref));
       }
 
-      Synchronizer synchronizer;
-      synchronizer.synchronize(node_value);
+      SECTION("synchonize EdgeValue")
+      {
+        const auto edge_owner  = connectivity.edgeOwner();
+        const auto edge_number = connectivity.edgeNumber();
 
-      REQUIRE(is_same_item_value(node_value, node_value_ref));
-    }
+        EdgeValue<int> edge_value_ref{connectivity};
+        parallel_for(
+          connectivity.numberOfEdges(),
+          PUGS_LAMBDA(const EdgeId edge_id) { edge_value_ref[edge_id] = edge_owner[edge_id] + edge_number[edge_id]; });
 
-    SECTION("synchonize EdgeValue")
-    {
-      const auto edge_owner  = connectivity.edgeOwner();
-      const auto edge_number = connectivity.edgeNumber();
+        EdgeValue<int> edge_value{connectivity};
+        parallel_for(
+          connectivity.numberOfEdges(),
+          PUGS_LAMBDA(const EdgeId edge_id) { edge_value[edge_id] = parallel::rank() + edge_number[edge_id]; });
 
-      EdgeValue<int> edge_value_ref{connectivity};
-      parallel_for(
-        connectivity.numberOfEdges(),
-        PUGS_LAMBDA(const EdgeId edge_id) { edge_value_ref[edge_id] = edge_owner[edge_id] + edge_number[edge_id]; });
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(edge_value, edge_value_ref));
+        }
 
-      EdgeValue<int> edge_value{connectivity};
-      parallel_for(
-        connectivity.numberOfEdges(),
-        PUGS_LAMBDA(const EdgeId edge_id) { edge_value[edge_id] = parallel::rank() + edge_number[edge_id]; });
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(edge_value);
 
-      if (parallel::size() > 1) {
-        REQUIRE(not is_same_item_value(edge_value, edge_value_ref));
+        REQUIRE(is_same_item_value(edge_value, edge_value_ref));
       }
 
-      Synchronizer synchronizer;
-      synchronizer.synchronize(edge_value);
+      SECTION("synchonize FaceValue")
+      {
+        const auto face_owner  = connectivity.faceOwner();
+        const auto face_number = connectivity.faceNumber();
 
-      REQUIRE(is_same_item_value(edge_value, edge_value_ref));
-    }
+        FaceValue<int> face_value_ref{connectivity};
+        parallel_for(
+          connectivity.numberOfFaces(),
+          PUGS_LAMBDA(const FaceId face_id) { face_value_ref[face_id] = face_owner[face_id] + face_number[face_id]; });
 
-    SECTION("synchonize FaceValue")
-    {
-      const auto face_owner  = connectivity.faceOwner();
-      const auto face_number = connectivity.faceNumber();
+        FaceValue<int> face_value{connectivity};
+        parallel_for(
+          connectivity.numberOfFaces(),
+          PUGS_LAMBDA(const FaceId face_id) { face_value[face_id] = parallel::rank() + face_number[face_id]; });
 
-      FaceValue<int> face_value_ref{connectivity};
-      parallel_for(
-        connectivity.numberOfFaces(),
-        PUGS_LAMBDA(const FaceId face_id) { face_value_ref[face_id] = face_owner[face_id] + face_number[face_id]; });
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(face_value, face_value_ref));
+        }
 
-      FaceValue<int> face_value{connectivity};
-      parallel_for(
-        connectivity.numberOfFaces(),
-        PUGS_LAMBDA(const FaceId face_id) { face_value[face_id] = parallel::rank() + face_number[face_id]; });
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(face_value);
 
-      if (parallel::size() > 1) {
-        REQUIRE(not is_same_item_value(face_value, face_value_ref));
+        REQUIRE(is_same_item_value(face_value, face_value_ref));
       }
 
-      Synchronizer synchronizer;
-      synchronizer.synchronize(face_value);
+      SECTION("synchonize CellValue")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
 
-      REQUIRE(is_same_item_value(face_value, face_value_ref));
+        CellValue<int> cell_value_ref{connectivity};
+        parallel_for(
+          connectivity.numberOfCells(),
+          PUGS_LAMBDA(const CellId cell_id) { cell_value_ref[cell_id] = cell_owner[cell_id] + cell_number[cell_id]; });
+
+        CellValue<int> cell_value{connectivity};
+        parallel_for(
+          connectivity.numberOfCells(),
+          PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = parallel::rank() + cell_number[cell_id]; });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(cell_value, cell_value_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(cell_value);
+
+        REQUIRE(is_same_item_value(cell_value, cell_value_ref));
+      }
     }
+  }
 
-    SECTION("synchonize CellValue")
+  SECTION("ItemArray")
+  {
+    auto is_same_item_array = [](auto a, auto b) {
+      using IndexT = typename decltype(a)::index_type;
+      bool is_same = true;
+      for (IndexT i = 0; i < a.numberOfItems(); ++i) {
+        for (size_t j = 0; j < a.sizeOfArrays(); ++j) {
+          is_same &= (a[i][j] == b[i][j]);
+        }
+      }
+      return parallel::allReduceAnd(is_same);
+    };
+
+    SECTION("1D")
     {
-      const auto cell_owner  = connectivity.cellOwner();
-      const auto cell_number = connectivity.cellNumber();
+      constexpr size_t Dimension = 1;
+      using ConnectivityType     = Connectivity<Dimension>;
+
+      const ConnectivityType& connectivity = MeshDataBaseForTests::get().unordered1DMesh()->connectivity();
+
+      SECTION("synchonize NodeArray")
+      {
+        const auto node_owner  = connectivity.nodeOwner();
+        const auto node_number = connectivity.nodeNumber();
+
+        NodeArray<int> node_array_ref{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) {
+            for (size_t i = 0; i < node_array_ref.sizeOfArrays(); ++i) {
+              node_array_ref[node_id][i] = (i + 1) * node_owner[node_id] + i + node_number[node_id];
+            }
+          });
+
+        NodeArray<int> node_array{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) {
+            for (size_t i = 0; i < node_array.sizeOfArrays(); ++i) {
+              node_array[node_id][i] = (i + 1) * parallel::rank() + i + node_number[node_id];
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(node_array, node_array_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_array);
+
+        REQUIRE(is_same_item_array(node_array, node_array_ref));
+      }
+
+      SECTION("synchonize EdgeArray")
+      {
+        const auto edge_owner  = connectivity.edgeOwner();
+        const auto edge_number = connectivity.edgeNumber();
+
+        EdgeArray<int> edge_array_ref{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfEdges(), PUGS_LAMBDA(const EdgeId edge_id) {
+            for (size_t i = 0; i < edge_array_ref.sizeOfArrays(); ++i) {
+              edge_array_ref[edge_id][i] = (i + 1) * edge_owner[edge_id] + i + edge_number[edge_id];
+            }
+          });
+
+        EdgeArray<int> edge_array{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfEdges(), PUGS_LAMBDA(const EdgeId edge_id) {
+            for (size_t i = 0; i < edge_array.sizeOfArrays(); ++i) {
+              edge_array[edge_id][i] = (i + 1) * parallel::rank() + i + edge_number[edge_id];
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(edge_array, edge_array_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(edge_array);
+
+        REQUIRE(is_same_item_array(edge_array, edge_array_ref));
+      }
 
-      CellValue<int> cell_value_ref{connectivity};
-      parallel_for(
-        connectivity.numberOfCells(),
-        PUGS_LAMBDA(const CellId cell_id) { cell_value_ref[cell_id] = cell_owner[cell_id] + cell_number[cell_id]; });
+      SECTION("synchonize FaceArray")
+      {
+        const auto face_owner  = connectivity.faceOwner();
+        const auto face_number = connectivity.faceNumber();
+
+        FaceArray<int> face_array_ref{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t i = 0; i < face_array_ref.sizeOfArrays(); ++i) {
+              face_array_ref[face_id][i] = (i + 1) * face_owner[face_id] + i + face_number[face_id];
+            }
+          });
+
+        FaceArray<int> face_array{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t i = 0; i < face_array.sizeOfArrays(); ++i) {
+              face_array[face_id][i] = (i + 1) * parallel::rank() + i + face_number[face_id];
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(face_array, face_array_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(face_array);
+
+        REQUIRE(is_same_item_array(face_array, face_array_ref));
+      }
+
+      SECTION("synchonize CellArray")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        CellArray<int> cell_array_ref{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t i = 0; i < cell_array_ref.sizeOfArrays(); ++i) {
+              cell_array_ref[cell_id][i] = (i + 1) * cell_owner[cell_id] + i + cell_number[cell_id];
+            }
+          });
+
+        CellArray<int> cell_array{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
+              cell_array[cell_id][i] = (i + 1) * parallel::rank() + i + cell_number[cell_id];
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(cell_array, cell_array_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(cell_array);
+
+        REQUIRE(is_same_item_array(cell_array, cell_array_ref));
+      }
+    }
 
-      CellValue<int> cell_value{connectivity};
-      parallel_for(
-        connectivity.numberOfCells(),
-        PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = parallel::rank() + cell_number[cell_id]; });
+    SECTION("2D")
+    {
+      constexpr size_t Dimension = 2;
+      using ConnectivityType     = Connectivity<Dimension>;
+
+      const ConnectivityType& connectivity = MeshDataBaseForTests::get().hybrid2DMesh()->connectivity();
+
+      SECTION("synchonize NodeArray")
+      {
+        const auto node_owner  = connectivity.nodeOwner();
+        const auto node_number = connectivity.nodeNumber();
+
+        NodeArray<int> node_array_ref{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) {
+            for (size_t i = 0; i < node_array_ref.sizeOfArrays(); ++i) {
+              node_array_ref[node_id][i] = (i + 1) * node_owner[node_id] + i + node_number[node_id];
+            }
+          });
+
+        NodeArray<int> node_array{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) {
+            for (size_t i = 0; i < node_array.sizeOfArrays(); ++i) {
+              node_array[node_id][i] = (i + 1) * parallel::rank() + i + node_number[node_id];
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(node_array, node_array_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_array);
+
+        REQUIRE(is_same_item_array(node_array, node_array_ref));
+      }
 
-      if (parallel::size() > 1) {
-        REQUIRE(not is_same_item_value(cell_value, cell_value_ref));
+      SECTION("synchonize EdgeArray")
+      {
+        const auto edge_owner  = connectivity.edgeOwner();
+        const auto edge_number = connectivity.edgeNumber();
+
+        EdgeArray<int> edge_array_ref{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfEdges(), PUGS_LAMBDA(const EdgeId edge_id) {
+            for (size_t i = 0; i < edge_array_ref.sizeOfArrays(); ++i) {
+              edge_array_ref[edge_id][i] = (i + 1) * edge_owner[edge_id] + i + edge_number[edge_id];
+            }
+          });
+
+        EdgeArray<int> edge_array{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfEdges(), PUGS_LAMBDA(const EdgeId edge_id) {
+            for (size_t i = 0; i < edge_array.sizeOfArrays(); ++i) {
+              edge_array[edge_id][i] = (i + 1) * parallel::rank() + i + edge_number[edge_id];
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(edge_array, edge_array_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(edge_array);
+
+        REQUIRE(is_same_item_array(edge_array, edge_array_ref));
       }
 
-      Synchronizer synchronizer;
-      synchronizer.synchronize(cell_value);
+      SECTION("synchonize FaceArray")
+      {
+        const auto face_owner  = connectivity.faceOwner();
+        const auto face_number = connectivity.faceNumber();
+
+        FaceArray<int> face_array_ref{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t i = 0; i < face_array_ref.sizeOfArrays(); ++i) {
+              face_array_ref[face_id][i] = (i + 1) * face_owner[face_id] + i + face_number[face_id];
+            }
+          });
+
+        FaceArray<int> face_array{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t i = 0; i < face_array.sizeOfArrays(); ++i) {
+              face_array[face_id][i] = (i + 1) * parallel::rank() + i + face_number[face_id];
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(face_array, face_array_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(face_array);
+
+        REQUIRE(is_same_item_array(face_array, face_array_ref));
+      }
 
-      REQUIRE(is_same_item_value(cell_value, cell_value_ref));
+      SECTION("synchonize CellArray")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        CellArray<int> cell_array_ref{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t i = 0; i < cell_array_ref.sizeOfArrays(); ++i) {
+              cell_array_ref[cell_id][i] = (i + 1) * cell_owner[cell_id] + i + cell_number[cell_id];
+            }
+          });
+
+        CellArray<int> cell_array{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
+              cell_array[cell_id][i] = (i + 1) * parallel::rank() + i + cell_number[cell_id];
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(cell_array, cell_array_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(cell_array);
+
+        REQUIRE(is_same_item_array(cell_array, cell_array_ref));
+      }
     }
 
-    SECTION("synchonize CellArray")
+    SECTION("3D")
     {
-      const auto cell_owner  = connectivity.cellOwner();
-      const auto cell_number = connectivity.cellNumber();
-
-      CellArray<int> cell_array_ref{connectivity, 3};
-      parallel_for(
-        connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-          for (size_t i = 0; i < cell_array_ref.sizeOfArrays(); ++i) {
-            cell_array_ref[cell_id][i] = (i + 1) * cell_owner[cell_id] + i + cell_number[cell_id];
-          }
-        });
-
-      CellArray<int> cell_array{connectivity, 3};
-      parallel_for(
-        connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-          for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
-            cell_array[cell_id][i] = (i + 1) * parallel::rank() + i + cell_number[cell_id];
-          }
-        });
+      constexpr size_t Dimension = 3;
+      using ConnectivityType     = Connectivity<Dimension>;
+
+      const ConnectivityType& connectivity = MeshDataBaseForTests::get().hybrid3DMesh()->connectivity();
+
+      SECTION("synchonize NodeArray")
+      {
+        const auto node_owner  = connectivity.nodeOwner();
+        const auto node_number = connectivity.nodeNumber();
+
+        NodeArray<int> node_array_ref{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) {
+            for (size_t i = 0; i < node_array_ref.sizeOfArrays(); ++i) {
+              node_array_ref[node_id][i] = (i + 1) * node_owner[node_id] + i + node_number[node_id];
+            }
+          });
+
+        NodeArray<int> node_array{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfNodes(), PUGS_LAMBDA(const NodeId node_id) {
+            for (size_t i = 0; i < node_array.sizeOfArrays(); ++i) {
+              node_array[node_id][i] = (i + 1) * parallel::rank() + i + node_number[node_id];
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(node_array, node_array_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_array);
+
+        REQUIRE(is_same_item_array(node_array, node_array_ref));
+      }
 
-      if (parallel::size() > 1) {
-        REQUIRE(not is_same_item_array(cell_array, cell_array_ref));
+      SECTION("synchonize EdgeArray")
+      {
+        const auto edge_owner  = connectivity.edgeOwner();
+        const auto edge_number = connectivity.edgeNumber();
+
+        EdgeArray<int> edge_array_ref{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfEdges(), PUGS_LAMBDA(const EdgeId edge_id) {
+            for (size_t i = 0; i < edge_array_ref.sizeOfArrays(); ++i) {
+              edge_array_ref[edge_id][i] = (i + 1) * edge_owner[edge_id] + i + edge_number[edge_id];
+            }
+          });
+
+        EdgeArray<int> edge_array{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfEdges(), PUGS_LAMBDA(const EdgeId edge_id) {
+            for (size_t i = 0; i < edge_array.sizeOfArrays(); ++i) {
+              edge_array[edge_id][i] = (i + 1) * parallel::rank() + i + edge_number[edge_id];
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(edge_array, edge_array_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(edge_array);
+
+        REQUIRE(is_same_item_array(edge_array, edge_array_ref));
       }
 
-      Synchronizer synchronizer;
-      synchronizer.synchronize(cell_array);
+      SECTION("synchonize FaceArray")
+      {
+        const auto face_owner  = connectivity.faceOwner();
+        const auto face_number = connectivity.faceNumber();
+
+        FaceArray<int> face_array_ref{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t i = 0; i < face_array_ref.sizeOfArrays(); ++i) {
+              face_array_ref[face_id][i] = (i + 1) * face_owner[face_id] + i + face_number[face_id];
+            }
+          });
+
+        FaceArray<int> face_array{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t i = 0; i < face_array.sizeOfArrays(); ++i) {
+              face_array[face_id][i] = (i + 1) * parallel::rank() + i + face_number[face_id];
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(face_array, face_array_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(face_array);
+
+        REQUIRE(is_same_item_array(face_array, face_array_ref));
+      }
 
-      REQUIRE(is_same_item_array(cell_array, cell_array_ref));
+      SECTION("synchonize CellArray")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        CellArray<int> cell_array_ref{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t i = 0; i < cell_array_ref.sizeOfArrays(); ++i) {
+              cell_array_ref[cell_id][i] = (i + 1) * cell_owner[cell_id] + i + cell_number[cell_id];
+            }
+          });
+
+        CellArray<int> cell_array{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
+              cell_array[cell_id][i] = (i + 1) * parallel::rank() + i + cell_number[cell_id];
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(cell_array, cell_array_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(cell_array);
+
+        REQUIRE(is_same_item_array(cell_array, cell_array_ref));
+      }
     }
   }
 
-  SECTION("3D")
+  SECTION("SubItemValuePerItem")
   {
-    constexpr size_t Dimension = 3;
-    using ConnectivityType     = Connectivity<Dimension>;
+    auto is_same_item_value = [](auto a, auto b) {
+      using IndexT = typename decltype(a)::index_type;
+      bool is_same = true;
+      for (IndexT i_item = 0; i_item < a.numberOfItems(); ++i_item) {
+        for (size_t l = 0; l < a.numberOfSubValues(i_item); ++l) {
+          is_same &= (a(i_item, l) == b(i_item, l));
+        }
+      }
+      return parallel::allReduceAnd(is_same);
+    };
+
+    auto reset_ghost_values = [](auto sub_item_value_per_item, auto item_owner, auto value) {
+      using IndexT = typename decltype(sub_item_value_per_item)::index_type;
+      static_assert(std::is_same_v<typename decltype(sub_item_value_per_item)::index_type,
+                                   typename decltype(item_owner)::index_type>);
+      for (IndexT i_item = 0; i_item < sub_item_value_per_item.numberOfItems(); ++i_item) {
+        if (item_owner[i_item] != static_cast<int>(parallel::rank())) {
+          for (size_t l = 0; l < sub_item_value_per_item.numberOfSubValues(i_item); ++l) {
+            sub_item_value_per_item(i_item, l) = value;
+          }
+        }
+      }
+    };
 
-    const ConnectivityType& connectivity = MeshDataBaseForTests::get().hybrid3DMesh()->connectivity();
+    SECTION("1D")
+    {
+      constexpr size_t Dimension = 1;
+      using ConnectivityType     = Connectivity<Dimension>;
+
+      const ConnectivityType& connectivity = MeshDataBaseForTests::get().unordered1DMesh()->connectivity();
+
+      SECTION("synchonize NodeValuePerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        NodeValuePerCell<int> node_value_per_cell_ref{connectivity};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < node_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              node_value_per_cell_ref(cell_id, j) = cell_owner[cell_id] + cell_number[cell_id] + j;
+            }
+          });
+
+        NodeValuePerCell<int> node_value_per_cell{connectivity};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < node_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              node_value_per_cell(cell_id, j) = parallel::rank() + cell_number[cell_id] + j;
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(node_value_per_cell, node_value_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_value_per_cell);
+
+        REQUIRE(is_same_item_value(node_value_per_cell, node_value_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_values(node_value_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_value(node_value_per_cell, node_value_per_cell_ref));
+          synchronizer.synchronize(node_value_per_cell);
+          REQUIRE(is_same_item_value(node_value_per_cell, node_value_per_cell_ref));
+        }
+      }
 
-    SECTION("synchonize NodeValue")
+      SECTION("synchonize EdgeValuePerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        EdgeValuePerCell<int> edge_value_per_cell_ref{connectivity};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < edge_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              edge_value_per_cell_ref(cell_id, j) = cell_owner[cell_id] + cell_number[cell_id] + j;
+            }
+          });
+
+        EdgeValuePerCell<int> edge_value_per_cell{connectivity};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < edge_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              edge_value_per_cell(cell_id, j) = parallel::rank() + cell_number[cell_id] + j;
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(edge_value_per_cell, edge_value_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(edge_value_per_cell);
+
+        REQUIRE(is_same_item_value(edge_value_per_cell, edge_value_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_values(edge_value_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_value(edge_value_per_cell, edge_value_per_cell_ref));
+          synchronizer.synchronize(edge_value_per_cell);
+          REQUIRE(is_same_item_value(edge_value_per_cell, edge_value_per_cell_ref));
+        }
+      }
+
+      SECTION("synchonize FaceValuePerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        FaceValuePerCell<int> face_value_per_cell_ref{connectivity};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < face_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              face_value_per_cell_ref(cell_id, j) = cell_owner[cell_id] + cell_number[cell_id] + j;
+            }
+          });
+
+        FaceValuePerCell<int> face_value_per_cell{connectivity};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < face_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              face_value_per_cell(cell_id, j) = parallel::rank() + cell_number[cell_id] + j;
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(face_value_per_cell, face_value_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(face_value_per_cell);
+
+        REQUIRE(is_same_item_value(face_value_per_cell, face_value_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_values(face_value_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_value(face_value_per_cell, face_value_per_cell_ref));
+          synchronizer.synchronize(face_value_per_cell);
+          REQUIRE(is_same_item_value(face_value_per_cell, face_value_per_cell_ref));
+        }
+      }
+
+      SECTION("forbidden synchronization")
+      {
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+
+        SECTION("CellValuePerNode")
+        {
+          CellValuePerNode<int> cell_value_per_node{connectivity};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_value_per_node),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (node)");
+        }
+
+        SECTION("CellValuePerEdge")
+        {
+          CellValuePerEdge<int> cell_value_per_edge{connectivity};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_value_per_edge),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (edge)");
+        }
+
+        SECTION("CellValuePerFace")
+        {
+          CellValuePerFace<int> cell_value_per_face{connectivity};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_value_per_face),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (face)");
+        }
+      }
+    }
+
+    SECTION("2D")
     {
-      const auto node_owner  = connectivity.nodeOwner();
-      const auto node_number = connectivity.nodeNumber();
+      constexpr size_t Dimension = 2;
+      using ConnectivityType     = Connectivity<Dimension>;
+
+      const ConnectivityType& connectivity = MeshDataBaseForTests::get().hybrid2DMesh()->connectivity();
+
+      SECTION("synchonize NodeValuePerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        NodeValuePerCell<int> node_value_per_cell_ref{connectivity};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < node_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              node_value_per_cell_ref(cell_id, j) = cell_owner[cell_id] + cell_number[cell_id] + j;
+            }
+          });
+
+        NodeValuePerCell<int> node_value_per_cell{connectivity};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < node_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              node_value_per_cell(cell_id, j) = parallel::rank() + cell_number[cell_id] + j;
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(node_value_per_cell, node_value_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_value_per_cell);
+
+        REQUIRE(is_same_item_value(node_value_per_cell, node_value_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_values(node_value_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_value(node_value_per_cell, node_value_per_cell_ref));
+          synchronizer.synchronize(node_value_per_cell);
+          REQUIRE(is_same_item_value(node_value_per_cell, node_value_per_cell_ref));
+        }
+      }
 
-      NodeValue<int> node_value_ref{connectivity};
-      parallel_for(
-        connectivity.numberOfNodes(),
-        PUGS_LAMBDA(const NodeId node_id) { node_value_ref[node_id] = node_owner[node_id] + node_number[node_id]; });
+      SECTION("synchonize EdgeValuePerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        EdgeValuePerCell<int> edge_value_per_cell_ref{connectivity};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < edge_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              edge_value_per_cell_ref(cell_id, j) = cell_owner[cell_id] + cell_number[cell_id] + j;
+            }
+          });
+
+        EdgeValuePerCell<int> edge_value_per_cell{connectivity};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < edge_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              edge_value_per_cell(cell_id, j) = parallel::rank() + cell_number[cell_id] + j;
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(edge_value_per_cell, edge_value_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(edge_value_per_cell);
+
+        REQUIRE(is_same_item_value(edge_value_per_cell, edge_value_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_values(edge_value_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_value(edge_value_per_cell, edge_value_per_cell_ref));
+          synchronizer.synchronize(edge_value_per_cell);
+          REQUIRE(is_same_item_value(edge_value_per_cell, edge_value_per_cell_ref));
+        }
+      }
 
-      NodeValue<int> node_value{connectivity};
-      parallel_for(
-        connectivity.numberOfNodes(),
-        PUGS_LAMBDA(const NodeId node_id) { node_value[node_id] = parallel::rank() + node_number[node_id]; });
+      SECTION("synchonize FaceValuePerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        FaceValuePerCell<int> face_value_per_cell_ref{connectivity};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < face_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              face_value_per_cell_ref(cell_id, j) = cell_owner[cell_id] + cell_number[cell_id] + j;
+            }
+          });
+
+        FaceValuePerCell<int> face_value_per_cell{connectivity};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < face_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              face_value_per_cell(cell_id, j) = parallel::rank() + cell_number[cell_id] + j;
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(face_value_per_cell, face_value_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(face_value_per_cell);
+
+        REQUIRE(is_same_item_value(face_value_per_cell, face_value_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_values(face_value_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_value(face_value_per_cell, face_value_per_cell_ref));
+          synchronizer.synchronize(face_value_per_cell);
+          REQUIRE(is_same_item_value(face_value_per_cell, face_value_per_cell_ref));
+        }
+      }
 
-      if (parallel::size() > 1) {
-        REQUIRE(not is_same_item_value(node_value, node_value_ref));
+      SECTION("synchonize NodeValuePerFace")
+      {
+        const auto face_owner  = connectivity.faceOwner();
+        const auto face_number = connectivity.faceNumber();
+
+        NodeValuePerFace<int> node_value_per_face_ref{connectivity};
+
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t j = 0; j < node_value_per_face_ref.numberOfSubValues(face_id); ++j) {
+              node_value_per_face_ref(face_id, j) = face_owner[face_id] + face_number[face_id] + j;
+            }
+          });
+
+        NodeValuePerFace<int> node_value_per_face{connectivity};
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t j = 0; j < node_value_per_face_ref.numberOfSubValues(face_id); ++j) {
+              node_value_per_face(face_id, j) = parallel::rank() + face_number[face_id] + j;
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(node_value_per_face, node_value_per_face_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_value_per_face);
+
+        REQUIRE(is_same_item_value(node_value_per_face, node_value_per_face_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_values(node_value_per_face, face_owner, 0);
+          REQUIRE(not is_same_item_value(node_value_per_face, node_value_per_face_ref));
+          synchronizer.synchronize(node_value_per_face);
+          REQUIRE(is_same_item_value(node_value_per_face, node_value_per_face_ref));
+        }
       }
 
-      Synchronizer synchronizer;
-      synchronizer.synchronize(node_value);
+      SECTION("synchonize NodeValuePerEdge")
+      {
+        const auto edge_owner  = connectivity.edgeOwner();
+        const auto edge_number = connectivity.edgeNumber();
+
+        NodeValuePerEdge<int> node_value_per_edge_ref{connectivity};
+
+        parallel_for(
+          connectivity.numberOfEdges(), PUGS_LAMBDA(const EdgeId edge_id) {
+            for (size_t j = 0; j < node_value_per_edge_ref.numberOfSubValues(edge_id); ++j) {
+              node_value_per_edge_ref(edge_id, j) = edge_owner[edge_id] + edge_number[edge_id] + j;
+            }
+          });
+
+        NodeValuePerEdge<int> node_value_per_edge{connectivity};
+        parallel_for(
+          connectivity.numberOfEdges(), PUGS_LAMBDA(const EdgeId edge_id) {
+            for (size_t j = 0; j < node_value_per_edge_ref.numberOfSubValues(edge_id); ++j) {
+              node_value_per_edge(edge_id, j) = parallel::rank() + edge_number[edge_id] + j;
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(node_value_per_edge, node_value_per_edge_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_value_per_edge);
+
+        REQUIRE(is_same_item_value(node_value_per_edge, node_value_per_edge_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_values(node_value_per_edge, edge_owner, 0);
+          REQUIRE(not is_same_item_value(node_value_per_edge, node_value_per_edge_ref));
+          synchronizer.synchronize(node_value_per_edge);
+          REQUIRE(is_same_item_value(node_value_per_edge, node_value_per_edge_ref));
+        }
+      }
 
-      REQUIRE(is_same_item_value(node_value, node_value_ref));
+      SECTION("forbidden synchronization")
+      {
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+
+        SECTION("CellValuePerNode")
+        {
+          CellValuePerNode<int> cell_value_per_node{connectivity};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_value_per_node),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (node)");
+        }
+
+        SECTION("CellValuePerEdge")
+        {
+          CellValuePerEdge<int> cell_value_per_edge{connectivity};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_value_per_edge),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (edge)");
+        }
+
+        SECTION("CellValuePerFace")
+        {
+          CellValuePerFace<int> cell_value_per_face{connectivity};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_value_per_face),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (face)");
+        }
+
+        SECTION("FaceValuePerNode")
+        {
+          FaceValuePerNode<int> face_value_per_node{connectivity};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(face_value_per_node),
+                              "unexpected error: synchronization requires sub-item type (face) to be of lower "
+                              "dimension than item (node)");
+        }
+
+        SECTION("EdgeValuePerNode")
+        {
+          EdgeValuePerNode<int> edge_value_per_node{connectivity};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(edge_value_per_node),
+                              "unexpected error: synchronization requires sub-item type (edge) to be of lower "
+                              "dimension than item (node)");
+        }
+      }
     }
 
-    SECTION("synchonize EdgeValue")
+    SECTION("3D")
     {
-      const auto edge_owner  = connectivity.edgeOwner();
-      const auto edge_number = connectivity.edgeNumber();
+      constexpr size_t Dimension = 3;
+      using ConnectivityType     = Connectivity<Dimension>;
+
+      const ConnectivityType& connectivity = MeshDataBaseForTests::get().hybrid3DMesh()->connectivity();
+
+      SECTION("synchonize NodeValuePerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        NodeValuePerCell<int> node_value_per_cell_ref{connectivity};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < node_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              node_value_per_cell_ref(cell_id, j) = cell_owner[cell_id] + cell_number[cell_id] + j;
+            }
+          });
+
+        NodeValuePerCell<int> node_value_per_cell{connectivity};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < node_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              node_value_per_cell(cell_id, j) = parallel::rank() + cell_number[cell_id] + j;
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(node_value_per_cell, node_value_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_value_per_cell);
+
+        REQUIRE(is_same_item_value(node_value_per_cell, node_value_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_values(node_value_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_value(node_value_per_cell, node_value_per_cell_ref));
+          synchronizer.synchronize(node_value_per_cell);
+          REQUIRE(is_same_item_value(node_value_per_cell, node_value_per_cell_ref));
+        }
+      }
+
+      SECTION("synchonize EdgeValuePerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        EdgeValuePerCell<int> edge_value_per_cell_ref{connectivity};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < edge_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              edge_value_per_cell_ref(cell_id, j) = cell_owner[cell_id] + cell_number[cell_id] + j;
+            }
+          });
+
+        EdgeValuePerCell<int> edge_value_per_cell{connectivity};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < edge_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              edge_value_per_cell(cell_id, j) = parallel::rank() + cell_number[cell_id] + j;
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(edge_value_per_cell, edge_value_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(edge_value_per_cell);
+
+        REQUIRE(is_same_item_value(edge_value_per_cell, edge_value_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_values(edge_value_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_value(edge_value_per_cell, edge_value_per_cell_ref));
+          synchronizer.synchronize(edge_value_per_cell);
+          REQUIRE(is_same_item_value(edge_value_per_cell, edge_value_per_cell_ref));
+        }
+      }
 
-      EdgeValue<int> edge_value_ref{connectivity};
-      parallel_for(
-        connectivity.numberOfEdges(),
-        PUGS_LAMBDA(const EdgeId edge_id) { edge_value_ref[edge_id] = edge_owner[edge_id] + edge_number[edge_id]; });
+      SECTION("synchonize FaceValuePerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        FaceValuePerCell<int> face_value_per_cell_ref{connectivity};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < face_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              face_value_per_cell_ref(cell_id, j) = cell_owner[cell_id] + cell_number[cell_id] + j;
+            }
+          });
+
+        FaceValuePerCell<int> face_value_per_cell{connectivity};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < face_value_per_cell_ref.numberOfSubValues(cell_id); ++j) {
+              face_value_per_cell(cell_id, j) = parallel::rank() + cell_number[cell_id] + j;
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(face_value_per_cell, face_value_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(face_value_per_cell);
+
+        REQUIRE(is_same_item_value(face_value_per_cell, face_value_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_values(face_value_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_value(face_value_per_cell, face_value_per_cell_ref));
+          synchronizer.synchronize(face_value_per_cell);
+          REQUIRE(is_same_item_value(face_value_per_cell, face_value_per_cell_ref));
+        }
+      }
 
-      EdgeValue<int> edge_value{connectivity};
-      parallel_for(
-        connectivity.numberOfEdges(),
-        PUGS_LAMBDA(const EdgeId edge_id) { edge_value[edge_id] = parallel::rank() + edge_number[edge_id]; });
+      SECTION("synchonize NodeValuePerFace")
+      {
+        const auto face_owner  = connectivity.faceOwner();
+        const auto face_number = connectivity.faceNumber();
+
+        NodeValuePerFace<int> node_value_per_face_ref{connectivity};
+
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t j = 0; j < node_value_per_face_ref.numberOfSubValues(face_id); ++j) {
+              node_value_per_face_ref(face_id, j) = face_owner[face_id] + face_number[face_id] + j;
+            }
+          });
+
+        NodeValuePerFace<int> node_value_per_face{connectivity};
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t j = 0; j < node_value_per_face_ref.numberOfSubValues(face_id); ++j) {
+              node_value_per_face(face_id, j) = parallel::rank() + face_number[face_id] + j;
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(node_value_per_face, node_value_per_face_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_value_per_face);
+
+        REQUIRE(is_same_item_value(node_value_per_face, node_value_per_face_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_values(node_value_per_face, face_owner, 0);
+          REQUIRE(not is_same_item_value(node_value_per_face, node_value_per_face_ref));
+          synchronizer.synchronize(node_value_per_face);
+          REQUIRE(is_same_item_value(node_value_per_face, node_value_per_face_ref));
+        }
+      }
 
-      if (parallel::size() > 1) {
-        REQUIRE(not is_same_item_value(edge_value, edge_value_ref));
+      SECTION("synchonize EdgeValuePerFace")
+      {
+        const auto face_owner  = connectivity.faceOwner();
+        const auto face_number = connectivity.faceNumber();
+
+        EdgeValuePerFace<int> edge_value_per_face_ref{connectivity};
+
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t j = 0; j < edge_value_per_face_ref.numberOfSubValues(face_id); ++j) {
+              edge_value_per_face_ref(face_id, j) = face_owner[face_id] + face_number[face_id] + j;
+            }
+          });
+
+        EdgeValuePerFace<int> edge_value_per_face{connectivity};
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t j = 0; j < edge_value_per_face_ref.numberOfSubValues(face_id); ++j) {
+              edge_value_per_face(face_id, j) = parallel::rank() + face_number[face_id] + j;
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(edge_value_per_face, edge_value_per_face_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(edge_value_per_face);
+
+        REQUIRE(is_same_item_value(edge_value_per_face, edge_value_per_face_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_values(edge_value_per_face, face_owner, 0);
+          REQUIRE(not is_same_item_value(edge_value_per_face, edge_value_per_face_ref));
+          synchronizer.synchronize(edge_value_per_face);
+          REQUIRE(is_same_item_value(edge_value_per_face, edge_value_per_face_ref));
+        }
       }
 
-      Synchronizer synchronizer;
-      synchronizer.synchronize(edge_value);
+      SECTION("synchonize NodeValuePerEdge")
+      {
+        const auto edge_owner  = connectivity.edgeOwner();
+        const auto edge_number = connectivity.edgeNumber();
+
+        NodeValuePerEdge<int> node_value_per_edge_ref{connectivity};
+
+        parallel_for(
+          connectivity.numberOfEdges(), PUGS_LAMBDA(const EdgeId edge_id) {
+            for (size_t j = 0; j < node_value_per_edge_ref.numberOfSubValues(edge_id); ++j) {
+              node_value_per_edge_ref(edge_id, j) = edge_owner[edge_id] + edge_number[edge_id] + j;
+            }
+          });
+
+        NodeValuePerEdge<int> node_value_per_edge{connectivity};
+        parallel_for(
+          connectivity.numberOfEdges(), PUGS_LAMBDA(const EdgeId edge_id) {
+            for (size_t j = 0; j < node_value_per_edge_ref.numberOfSubValues(edge_id); ++j) {
+              node_value_per_edge(edge_id, j) = parallel::rank() + edge_number[edge_id] + j;
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_value(node_value_per_edge, node_value_per_edge_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_value_per_edge);
+
+        REQUIRE(is_same_item_value(node_value_per_edge, node_value_per_edge_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_values(node_value_per_edge, edge_owner, 0);
+          REQUIRE(not is_same_item_value(node_value_per_edge, node_value_per_edge_ref));
+          synchronizer.synchronize(node_value_per_edge);
+          REQUIRE(is_same_item_value(node_value_per_edge, node_value_per_edge_ref));
+        }
+      }
 
-      REQUIRE(is_same_item_value(edge_value, edge_value_ref));
+      SECTION("forbidden synchronization")
+      {
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+
+        SECTION("CellValuePerNode")
+        {
+          CellValuePerNode<int> cell_value_per_node{connectivity};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_value_per_node),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (node)");
+        }
+
+        SECTION("CellValuePerEdge")
+        {
+          CellValuePerEdge<int> cell_value_per_edge{connectivity};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_value_per_edge),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (edge)");
+        }
+
+        SECTION("CellValuePerFace")
+        {
+          CellValuePerFace<int> cell_value_per_face{connectivity};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_value_per_face),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (face)");
+        }
+
+        SECTION("FaceValuePerNode")
+        {
+          FaceValuePerNode<int> face_value_per_node{connectivity};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(face_value_per_node),
+                              "unexpected error: synchronization requires sub-item type (face) to be of lower "
+                              "dimension than item (node)");
+        }
+
+        SECTION("FaceValuePerEdge")
+        {
+          FaceValuePerEdge<int> face_value_per_edge{connectivity};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(face_value_per_edge),
+                              "unexpected error: synchronization requires sub-item type (face) to be of lower "
+                              "dimension than item (edge)");
+        }
+
+        SECTION("EdgeValuePerNode")
+        {
+          EdgeValuePerNode<int> edge_value_per_node{connectivity};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(edge_value_per_node),
+                              "unexpected error: synchronization requires sub-item type (edge) to be of lower "
+                              "dimension than item (node)");
+        }
+      }
     }
+  }
 
-    SECTION("synchonize FaceValue")
-    {
-      const auto face_owner  = connectivity.faceOwner();
-      const auto face_number = connectivity.faceNumber();
-
-      FaceValue<int> face_value_ref{connectivity};
-      parallel_for(
-        connectivity.numberOfFaces(),
-        PUGS_LAMBDA(const FaceId face_id) { face_value_ref[face_id] = face_owner[face_id] + face_number[face_id]; });
+  SECTION("SubItemArrayPerItem")
+  {
+    auto is_same_item_array = [](auto a, auto b) {
+      using IndexT = typename decltype(a)::index_type;
+      bool is_same = true;
+      for (IndexT i_item = 0; i_item < a.numberOfItems(); ++i_item) {
+        for (size_t l = 0; l < a.numberOfSubArrays(i_item); ++l) {
+          for (size_t k = 0; k < a.sizeOfArrays(); ++k) {
+            is_same &= (a(i_item, l)[k] == b(i_item, l)[k]);
+          }
+        }
+      }
+      return parallel::allReduceAnd(is_same);
+    };
+
+    auto reset_ghost_arrays = [](auto sub_item_array_per_item, auto item_owner, auto value) {
+      using IndexT = typename decltype(sub_item_array_per_item)::index_type;
+      static_assert(std::is_same_v<typename decltype(sub_item_array_per_item)::index_type,
+                                   typename decltype(item_owner)::index_type>);
+      for (IndexT i_item = 0; i_item < sub_item_array_per_item.numberOfItems(); ++i_item) {
+        if (item_owner[i_item] != static_cast<int>(parallel::rank())) {
+          for (size_t l = 0; l < sub_item_array_per_item.numberOfSubArrays(i_item); ++l) {
+            sub_item_array_per_item(i_item, l).fill(value);
+          }
+        }
+      }
+    };
 
-      FaceValue<int> face_value{connectivity};
-      parallel_for(
-        connectivity.numberOfFaces(),
-        PUGS_LAMBDA(const FaceId face_id) { face_value[face_id] = parallel::rank() + face_number[face_id]; });
+    SECTION("1D")
+    {
+      constexpr size_t Dimension = 1;
+      using ConnectivityType     = Connectivity<Dimension>;
+
+      const ConnectivityType& connectivity = MeshDataBaseForTests::get().unordered1DMesh()->connectivity();
+
+      SECTION("synchonize NodeArrayPerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        NodeArrayPerCell<int> node_array_per_cell_ref{connectivity, 3};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < node_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < node_array_per_cell_ref.sizeOfArrays(); ++k) {
+                node_array_per_cell_ref(cell_id, j)[k] = cell_owner[cell_id] + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        NodeArrayPerCell<int> node_array_per_cell{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < node_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < node_array_per_cell_ref.sizeOfArrays(); ++k) {
+                node_array_per_cell(cell_id, j)[k] = parallel::rank() + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(node_array_per_cell, node_array_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_array_per_cell);
+
+        REQUIRE(is_same_item_array(node_array_per_cell, node_array_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_arrays(node_array_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_array(node_array_per_cell, node_array_per_cell_ref));
+          synchronizer.synchronize(node_array_per_cell);
+          REQUIRE(is_same_item_array(node_array_per_cell, node_array_per_cell_ref));
+        }
+      }
 
-      if (parallel::size() > 1) {
-        REQUIRE(not is_same_item_value(face_value, face_value_ref));
+      SECTION("synchonize EdgeArrayPerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        EdgeArrayPerCell<int> edge_array_per_cell_ref{connectivity, 3};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < edge_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < edge_array_per_cell_ref.sizeOfArrays(); ++k) {
+                edge_array_per_cell_ref(cell_id, j)[k] = cell_owner[cell_id] + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < edge_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < edge_array_per_cell_ref.sizeOfArrays(); ++k) {
+                edge_array_per_cell(cell_id, j)[k] = parallel::rank() + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(edge_array_per_cell, edge_array_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(edge_array_per_cell);
+
+        REQUIRE(is_same_item_array(edge_array_per_cell, edge_array_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_arrays(edge_array_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_array(edge_array_per_cell, edge_array_per_cell_ref));
+          synchronizer.synchronize(edge_array_per_cell);
+          REQUIRE(is_same_item_array(edge_array_per_cell, edge_array_per_cell_ref));
+        }
       }
 
-      Synchronizer synchronizer;
-      synchronizer.synchronize(face_value);
+      SECTION("synchonize FaceArrayPerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        FaceArrayPerCell<int> face_array_per_cell_ref{connectivity, 3};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < face_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < face_array_per_cell_ref.sizeOfArrays(); ++k) {
+                face_array_per_cell_ref(cell_id, j)[k] = cell_owner[cell_id] + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        FaceArrayPerCell<int> face_array_per_cell{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < face_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < face_array_per_cell_ref.sizeOfArrays(); ++k) {
+                face_array_per_cell(cell_id, j)[k] = parallel::rank() + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(face_array_per_cell, face_array_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(face_array_per_cell);
+
+        REQUIRE(is_same_item_array(face_array_per_cell, face_array_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_arrays(face_array_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_array(face_array_per_cell, face_array_per_cell_ref));
+          synchronizer.synchronize(face_array_per_cell);
+          REQUIRE(is_same_item_array(face_array_per_cell, face_array_per_cell_ref));
+        }
+      }
 
-      REQUIRE(is_same_item_value(face_value, face_value_ref));
+      SECTION("forbidden synchronization")
+      {
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+
+        SECTION("CellArrayPerNode")
+        {
+          CellArrayPerNode<int> cell_array_per_node{connectivity, 3};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_array_per_node),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (node)");
+        }
+
+        SECTION("CellArrayPerEdge")
+        {
+          CellArrayPerEdge<int> cell_array_per_edge{connectivity, 3};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_array_per_edge),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (edge)");
+        }
+
+        SECTION("CellArrayPerFace")
+        {
+          CellArrayPerFace<int> cell_array_per_face{connectivity, 3};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_array_per_face),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (face)");
+        }
+      }
     }
 
-    SECTION("synchonize CellValue")
+    SECTION("2D")
     {
-      const auto cell_owner  = connectivity.cellOwner();
-      const auto cell_number = connectivity.cellNumber();
+      constexpr size_t Dimension = 2;
+      using ConnectivityType     = Connectivity<Dimension>;
+
+      const ConnectivityType& connectivity = MeshDataBaseForTests::get().hybrid2DMesh()->connectivity();
+
+      SECTION("synchonize NodeArrayPerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        NodeArrayPerCell<int> node_array_per_cell_ref{connectivity, 3};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < node_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < node_array_per_cell_ref.sizeOfArrays(); ++k) {
+                node_array_per_cell_ref(cell_id, j)[k] = cell_owner[cell_id] + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        NodeArrayPerCell<int> node_array_per_cell{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < node_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < node_array_per_cell_ref.sizeOfArrays(); ++k) {
+                node_array_per_cell(cell_id, j)[k] = parallel::rank() + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(node_array_per_cell, node_array_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_array_per_cell);
+
+        REQUIRE(is_same_item_array(node_array_per_cell, node_array_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_arrays(node_array_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_array(node_array_per_cell, node_array_per_cell_ref));
+          synchronizer.synchronize(node_array_per_cell);
+          REQUIRE(is_same_item_array(node_array_per_cell, node_array_per_cell_ref));
+        }
+      }
 
-      CellValue<int> cell_value_ref{connectivity};
-      parallel_for(
-        connectivity.numberOfCells(),
-        PUGS_LAMBDA(const CellId cell_id) { cell_value_ref[cell_id] = cell_owner[cell_id] + cell_number[cell_id]; });
+      SECTION("synchonize EdgeArrayPerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        EdgeArrayPerCell<int> edge_array_per_cell_ref{connectivity, 3};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < edge_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < edge_array_per_cell_ref.sizeOfArrays(); ++k) {
+                edge_array_per_cell_ref(cell_id, j)[k] = cell_owner[cell_id] + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < edge_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < edge_array_per_cell_ref.sizeOfArrays(); ++k) {
+                edge_array_per_cell(cell_id, j)[k] = parallel::rank() + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(edge_array_per_cell, edge_array_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(edge_array_per_cell);
+
+        REQUIRE(is_same_item_array(edge_array_per_cell, edge_array_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_arrays(edge_array_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_array(edge_array_per_cell, edge_array_per_cell_ref));
+          synchronizer.synchronize(edge_array_per_cell);
+          REQUIRE(is_same_item_array(edge_array_per_cell, edge_array_per_cell_ref));
+        }
+      }
 
-      CellValue<int> cell_value{connectivity};
-      parallel_for(
-        connectivity.numberOfCells(),
-        PUGS_LAMBDA(const CellId cell_id) { cell_value[cell_id] = parallel::rank() + cell_number[cell_id]; });
+      SECTION("synchonize FaceArrayPerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        FaceArrayPerCell<int> face_array_per_cell_ref{connectivity, 3};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < face_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < face_array_per_cell_ref.sizeOfArrays(); ++k) {
+                face_array_per_cell_ref(cell_id, j)[k] = cell_owner[cell_id] + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        FaceArrayPerCell<int> face_array_per_cell{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < face_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < face_array_per_cell_ref.sizeOfArrays(); ++k) {
+                face_array_per_cell(cell_id, j)[k] = parallel::rank() + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(face_array_per_cell, face_array_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(face_array_per_cell);
+
+        REQUIRE(is_same_item_array(face_array_per_cell, face_array_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_arrays(face_array_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_array(face_array_per_cell, face_array_per_cell_ref));
+          synchronizer.synchronize(face_array_per_cell);
+          REQUIRE(is_same_item_array(face_array_per_cell, face_array_per_cell_ref));
+        }
+      }
 
-      if (parallel::size() > 1) {
-        REQUIRE(not is_same_item_value(cell_value, cell_value_ref));
+      SECTION("synchonize NodeArrayPerFace")
+      {
+        const auto face_owner  = connectivity.faceOwner();
+        const auto face_number = connectivity.faceNumber();
+
+        NodeArrayPerFace<int> node_array_per_face_ref{connectivity, 3};
+
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t j = 0; j < node_array_per_face_ref.numberOfSubArrays(face_id); ++j) {
+              for (size_t k = 0; k < node_array_per_face_ref.sizeOfArrays(); ++k) {
+                node_array_per_face_ref(face_id, j)[k] = face_owner[face_id] + face_number[face_id] + j + 2 * k;
+              }
+            }
+          });
+
+        NodeArrayPerFace<int> node_array_per_face{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t j = 0; j < node_array_per_face_ref.numberOfSubArrays(face_id); ++j) {
+              for (size_t k = 0; k < node_array_per_face_ref.sizeOfArrays(); ++k) {
+                node_array_per_face(face_id, j)[k] = parallel::rank() + face_number[face_id] + j + 2 * k;
+              }
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(node_array_per_face, node_array_per_face_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_array_per_face);
+
+        REQUIRE(is_same_item_array(node_array_per_face, node_array_per_face_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_arrays(node_array_per_face, face_owner, 0);
+          REQUIRE(not is_same_item_array(node_array_per_face, node_array_per_face_ref));
+          synchronizer.synchronize(node_array_per_face);
+          REQUIRE(is_same_item_array(node_array_per_face, node_array_per_face_ref));
+        }
       }
 
-      Synchronizer synchronizer;
-      synchronizer.synchronize(cell_value);
+      SECTION("synchonize NodeArrayPerEdge")
+      {
+        const auto edge_owner  = connectivity.edgeOwner();
+        const auto edge_number = connectivity.edgeNumber();
+
+        NodeArrayPerEdge<int> node_array_per_edge_ref{connectivity, 3};
+
+        parallel_for(
+          connectivity.numberOfEdges(), PUGS_LAMBDA(const EdgeId edge_id) {
+            for (size_t j = 0; j < node_array_per_edge_ref.numberOfSubArrays(edge_id); ++j) {
+              for (size_t k = 0; k < node_array_per_edge_ref.sizeOfArrays(); ++k) {
+                node_array_per_edge_ref(edge_id, j)[k] = edge_owner[edge_id] + edge_number[edge_id] + j + 2 * k;
+              }
+            }
+          });
+
+        NodeArrayPerEdge<int> node_array_per_edge{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfEdges(), PUGS_LAMBDA(const EdgeId edge_id) {
+            for (size_t j = 0; j < node_array_per_edge_ref.numberOfSubArrays(edge_id); ++j) {
+              for (size_t k = 0; k < node_array_per_edge_ref.sizeOfArrays(); ++k) {
+                node_array_per_edge(edge_id, j)[k] = parallel::rank() + edge_number[edge_id] + j + 2 * k;
+              }
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(node_array_per_edge, node_array_per_edge_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_array_per_edge);
+
+        REQUIRE(is_same_item_array(node_array_per_edge, node_array_per_edge_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_arrays(node_array_per_edge, edge_owner, 0);
+          REQUIRE(not is_same_item_array(node_array_per_edge, node_array_per_edge_ref));
+          synchronizer.synchronize(node_array_per_edge);
+          REQUIRE(is_same_item_array(node_array_per_edge, node_array_per_edge_ref));
+        }
+      }
 
-      REQUIRE(is_same_item_value(cell_value, cell_value_ref));
+      SECTION("forbidden synchronization")
+      {
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+
+        SECTION("CellArrayPerNode")
+        {
+          CellArrayPerNode<int> cell_array_per_node{connectivity, 3};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_array_per_node),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (node)");
+        }
+
+        SECTION("CellArrayPerEdge")
+        {
+          CellArrayPerEdge<int> cell_array_per_edge{connectivity, 3};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_array_per_edge),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (edge)");
+        }
+
+        SECTION("CellArrayPerFace")
+        {
+          CellArrayPerFace<int> cell_array_per_face{connectivity, 3};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_array_per_face),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (face)");
+        }
+
+        SECTION("FaceArrayPerNode")
+        {
+          FaceArrayPerNode<int> face_array_per_node{connectivity, 3};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(face_array_per_node),
+                              "unexpected error: synchronization requires sub-item type (face) to be of lower "
+                              "dimension than item (node)");
+        }
+
+        SECTION("EdgeArrayPerNode")
+        {
+          EdgeArrayPerNode<int> edge_array_per_node{connectivity, 3};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(edge_array_per_node),
+                              "unexpected error: synchronization requires sub-item type (edge) to be of lower "
+                              "dimension than item (node)");
+        }
+      }
     }
 
-    SECTION("synchonize CellArray")
+    SECTION("3D")
     {
-      const auto cell_owner  = connectivity.cellOwner();
-      const auto cell_number = connectivity.cellNumber();
-
-      CellArray<int> cell_array_ref{connectivity, 3};
-      parallel_for(
-        connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-          for (size_t i = 0; i < cell_array_ref.sizeOfArrays(); ++i) {
-            cell_array_ref[cell_id][i] = (i + 1) * cell_owner[cell_id] + i + cell_number[cell_id];
-          }
-        });
+      constexpr size_t Dimension = 3;
+      using ConnectivityType     = Connectivity<Dimension>;
+
+      const ConnectivityType& connectivity = MeshDataBaseForTests::get().hybrid3DMesh()->connectivity();
+
+      SECTION("synchonize NodeArrayPerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        NodeArrayPerCell<int> node_array_per_cell_ref{connectivity, 3};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < node_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < node_array_per_cell_ref.sizeOfArrays(); ++k) {
+                node_array_per_cell_ref(cell_id, j)[k] = cell_owner[cell_id] + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        NodeArrayPerCell<int> node_array_per_cell{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < node_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < node_array_per_cell_ref.sizeOfArrays(); ++k) {
+                node_array_per_cell(cell_id, j)[k] = parallel::rank() + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(node_array_per_cell, node_array_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_array_per_cell);
+
+        REQUIRE(is_same_item_array(node_array_per_cell, node_array_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_arrays(node_array_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_array(node_array_per_cell, node_array_per_cell_ref));
+          synchronizer.synchronize(node_array_per_cell);
+          REQUIRE(is_same_item_array(node_array_per_cell, node_array_per_cell_ref));
+        }
+      }
 
-      CellArray<int> cell_array{connectivity, 3};
-      parallel_for(
-        connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
-          for (size_t i = 0; i < cell_array.sizeOfArrays(); ++i) {
-            cell_array[cell_id][i] = (i + 1) * parallel::rank() + i + cell_number[cell_id];
-          }
-        });
+      SECTION("synchonize EdgeArrayPerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        EdgeArrayPerCell<int> edge_array_per_cell_ref{connectivity, 3};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < edge_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < edge_array_per_cell_ref.sizeOfArrays(); ++k) {
+                edge_array_per_cell_ref(cell_id, j)[k] = cell_owner[cell_id] + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < edge_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < edge_array_per_cell_ref.sizeOfArrays(); ++k) {
+                edge_array_per_cell(cell_id, j)[k] = parallel::rank() + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(edge_array_per_cell, edge_array_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(edge_array_per_cell);
+
+        REQUIRE(is_same_item_array(edge_array_per_cell, edge_array_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_arrays(edge_array_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_array(edge_array_per_cell, edge_array_per_cell_ref));
+          synchronizer.synchronize(edge_array_per_cell);
+          REQUIRE(is_same_item_array(edge_array_per_cell, edge_array_per_cell_ref));
+        }
+      }
+
+      SECTION("synchonize FaceArrayPerCell")
+      {
+        const auto cell_owner  = connectivity.cellOwner();
+        const auto cell_number = connectivity.cellNumber();
+
+        FaceArrayPerCell<int> face_array_per_cell_ref{connectivity, 3};
+
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < face_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < face_array_per_cell_ref.sizeOfArrays(); ++k) {
+                face_array_per_cell_ref(cell_id, j)[k] = cell_owner[cell_id] + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        FaceArrayPerCell<int> face_array_per_cell{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfCells(), PUGS_LAMBDA(const CellId cell_id) {
+            for (size_t j = 0; j < face_array_per_cell_ref.numberOfSubArrays(cell_id); ++j) {
+              for (size_t k = 0; k < face_array_per_cell_ref.sizeOfArrays(); ++k) {
+                face_array_per_cell(cell_id, j)[k] = parallel::rank() + cell_number[cell_id] + j + 2 * k;
+              }
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(face_array_per_cell, face_array_per_cell_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(face_array_per_cell);
+
+        REQUIRE(is_same_item_array(face_array_per_cell, face_array_per_cell_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_arrays(face_array_per_cell, cell_owner, 0);
+          REQUIRE(not is_same_item_array(face_array_per_cell, face_array_per_cell_ref));
+          synchronizer.synchronize(face_array_per_cell);
+          REQUIRE(is_same_item_array(face_array_per_cell, face_array_per_cell_ref));
+        }
+      }
 
-      if (parallel::size() > 1) {
-        REQUIRE(not is_same_item_array(cell_array, cell_array_ref));
+      SECTION("synchonize NodeArrayPerFace")
+      {
+        const auto face_owner  = connectivity.faceOwner();
+        const auto face_number = connectivity.faceNumber();
+
+        NodeArrayPerFace<int> node_array_per_face_ref{connectivity, 3};
+
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t j = 0; j < node_array_per_face_ref.numberOfSubArrays(face_id); ++j) {
+              for (size_t k = 0; k < node_array_per_face_ref.sizeOfArrays(); ++k) {
+                node_array_per_face_ref(face_id, j)[k] = face_owner[face_id] + face_number[face_id] + j + 2 * k;
+              }
+            }
+          });
+
+        NodeArrayPerFace<int> node_array_per_face{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t j = 0; j < node_array_per_face_ref.numberOfSubArrays(face_id); ++j) {
+              for (size_t k = 0; k < node_array_per_face_ref.sizeOfArrays(); ++k) {
+                node_array_per_face(face_id, j)[k] = parallel::rank() + face_number[face_id] + j + 2 * k;
+              }
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(node_array_per_face, node_array_per_face_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_array_per_face);
+
+        REQUIRE(is_same_item_array(node_array_per_face, node_array_per_face_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_arrays(node_array_per_face, face_owner, 0);
+          REQUIRE(not is_same_item_array(node_array_per_face, node_array_per_face_ref));
+          synchronizer.synchronize(node_array_per_face);
+          REQUIRE(is_same_item_array(node_array_per_face, node_array_per_face_ref));
+        }
       }
 
-      Synchronizer synchronizer;
-      synchronizer.synchronize(cell_array);
+      SECTION("synchonize EdgeArrayPerFace")
+      {
+        const auto face_owner  = connectivity.faceOwner();
+        const auto face_number = connectivity.faceNumber();
+
+        EdgeArrayPerFace<int> edge_array_per_face_ref{connectivity, 3};
+
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t j = 0; j < edge_array_per_face_ref.numberOfSubArrays(face_id); ++j) {
+              for (size_t k = 0; k < edge_array_per_face_ref.sizeOfArrays(); ++k) {
+                edge_array_per_face_ref(face_id, j)[k] = face_owner[face_id] + face_number[face_id] + j + 2 * k;
+              }
+            }
+          });
+
+        EdgeArrayPerFace<int> edge_array_per_face{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfFaces(), PUGS_LAMBDA(const FaceId face_id) {
+            for (size_t j = 0; j < edge_array_per_face_ref.numberOfSubArrays(face_id); ++j) {
+              for (size_t k = 0; k < edge_array_per_face_ref.sizeOfArrays(); ++k) {
+                edge_array_per_face(face_id, j)[k] = parallel::rank() + face_number[face_id] + j + 2 * k;
+              }
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(edge_array_per_face, edge_array_per_face_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(edge_array_per_face);
+
+        REQUIRE(is_same_item_array(edge_array_per_face, edge_array_per_face_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_arrays(edge_array_per_face, face_owner, 0);
+          REQUIRE(not is_same_item_array(edge_array_per_face, edge_array_per_face_ref));
+          synchronizer.synchronize(edge_array_per_face);
+          REQUIRE(is_same_item_array(edge_array_per_face, edge_array_per_face_ref));
+        }
+      }
 
-      REQUIRE(is_same_item_array(cell_array, cell_array_ref));
+      SECTION("synchonize NodeArrayPerEdge")
+      {
+        const auto edge_owner  = connectivity.edgeOwner();
+        const auto edge_number = connectivity.edgeNumber();
+
+        NodeArrayPerEdge<int> node_array_per_edge_ref{connectivity, 3};
+
+        parallel_for(
+          connectivity.numberOfEdges(), PUGS_LAMBDA(const EdgeId edge_id) {
+            for (size_t j = 0; j < node_array_per_edge_ref.numberOfSubArrays(edge_id); ++j) {
+              for (size_t k = 0; k < node_array_per_edge_ref.sizeOfArrays(); ++k) {
+                node_array_per_edge_ref(edge_id, j)[k] = edge_owner[edge_id] + edge_number[edge_id] + j + 2 * k;
+              }
+            }
+          });
+
+        NodeArrayPerEdge<int> node_array_per_edge{connectivity, 3};
+        parallel_for(
+          connectivity.numberOfEdges(), PUGS_LAMBDA(const EdgeId edge_id) {
+            for (size_t j = 0; j < node_array_per_edge_ref.numberOfSubArrays(edge_id); ++j) {
+              for (size_t k = 0; k < node_array_per_edge_ref.sizeOfArrays(); ++k) {
+                node_array_per_edge(edge_id, j)[k] = parallel::rank() + edge_number[edge_id] + j + 2 * k;
+              }
+            }
+          });
+
+        if (parallel::size() > 1) {
+          REQUIRE(not is_same_item_array(node_array_per_edge, node_array_per_edge_ref));
+        }
+
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+        synchronizer.synchronize(node_array_per_edge);
+
+        REQUIRE(is_same_item_array(node_array_per_edge, node_array_per_edge_ref));
+
+        // Check that exchange sizes are correctly stored (require
+        // lines to be covered)
+        if (parallel::size() > 1) {
+          reset_ghost_arrays(node_array_per_edge, edge_owner, 0);
+          REQUIRE(not is_same_item_array(node_array_per_edge, node_array_per_edge_ref));
+          synchronizer.synchronize(node_array_per_edge);
+          REQUIRE(is_same_item_array(node_array_per_edge, node_array_per_edge_ref));
+        }
+      }
+
+      SECTION("forbidden synchronization")
+      {
+        Synchronizer& synchronizer = SynchronizerManager::instance().getConnectivitySynchronizer(&connectivity);
+
+        SECTION("CellArrayPerNode")
+        {
+          CellArrayPerNode<int> cell_array_per_node{connectivity, 3};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_array_per_node),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (node)");
+        }
+
+        SECTION("CellArrayPerEdge")
+        {
+          CellArrayPerEdge<int> cell_array_per_edge{connectivity, 3};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_array_per_edge),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (edge)");
+        }
+
+        SECTION("CellArrayPerFace")
+        {
+          CellArrayPerFace<int> cell_array_per_face{connectivity, 3};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(cell_array_per_face),
+                              "unexpected error: synchronization requires sub-item type (cell) to be of lower "
+                              "dimension than item (face)");
+        }
+
+        SECTION("FaceArrayPerNode")
+        {
+          FaceArrayPerNode<int> face_array_per_node{connectivity, 3};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(face_array_per_node),
+                              "unexpected error: synchronization requires sub-item type (face) to be of lower "
+                              "dimension than item (node)");
+        }
+
+        SECTION("FaceArrayPerEdge")
+        {
+          FaceArrayPerEdge<int> face_array_per_edge{connectivity, 3};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(face_array_per_edge),
+                              "unexpected error: synchronization requires sub-item type (face) to be of lower "
+                              "dimension than item (edge)");
+        }
+
+        SECTION("EdgeArrayPerNode")
+        {
+          EdgeArrayPerNode<int> edge_array_per_node{connectivity, 3};
+          REQUIRE_THROWS_WITH(synchronizer.synchronize(edge_array_per_node),
+                              "unexpected error: synchronization requires sub-item type (edge) to be of lower "
+                              "dimension than item (node)");
+        }
+      }
     }
   }
 }