diff --git a/src/mesh/Connectivity.cpp b/src/mesh/Connectivity.cpp
index de8be58168d312e083516e2824e5eec45a484e3c..a542c35e0a90463dd6c86af5672c0fbf45bf3244 100644
--- a/src/mesh/Connectivity.cpp
+++ b/src/mesh/Connectivity.cpp
@@ -82,7 +82,49 @@ Connectivity<Dimension>::_buildFrom(const ConnectivityDescriptor& descriptor)
   m_ref_node_list_vector = descriptor.template refItemListVector<ItemType::node>();
   m_ref_cell_list_vector = descriptor.template refItemListVector<ItemType::cell>();
 
-  if constexpr (Dimension > 1) {
+  if constexpr (Dimension == 1) {
+    // faces are similar to nodes
+    {
+      WeakFaceValue<int> face_number(*this);
+      face_number   = convert_to_array(descriptor.node_number_vector);
+      m_face_number = face_number;
+    }
+
+    {
+      WeakFaceValue<int> face_owner(*this);
+      face_owner   = convert_to_array(descriptor.node_owner_vector);
+      m_face_owner = face_owner;
+    }
+
+    {
+      const int rank = parallel::rank();
+      WeakFaceValue<bool> face_is_owned(*this);
+      parallel_for(
+        this->numberOfFaces(), PUGS_LAMBDA(FaceId l) { face_is_owned[l] = (m_face_owner[l] == rank); });
+      m_face_is_owned = face_is_owned;
+    }
+
+    // edges are similar to nodes
+    {
+      WeakEdgeValue<int> edge_number(*this);
+      edge_number   = convert_to_array(descriptor.node_number_vector);
+      m_edge_number = edge_number;
+    }
+
+    {
+      WeakEdgeValue<int> edge_owner(*this);
+      edge_owner   = convert_to_array(descriptor.node_owner_vector);
+      m_edge_owner = edge_owner;
+    }
+
+    {
+      const int rank = parallel::rank();
+      WeakEdgeValue<bool> edge_is_owned(*this);
+      parallel_for(
+        this->numberOfEdges(), PUGS_LAMBDA(EdgeId l) { edge_is_owned[l] = (m_edge_owner[l] == rank); });
+      m_edge_is_owned = edge_is_owned;
+    }
+  } else {
     m_item_to_item_matrix[itemTId(ItemType::face)][itemTId(ItemType::node)] = descriptor.face_to_node_vector;
 
     m_item_to_item_matrix[itemTId(ItemType::cell)][itemTId(ItemType::face)] = descriptor.cell_to_face_vector;
@@ -120,7 +162,29 @@ Connectivity<Dimension>::_buildFrom(const ConnectivityDescriptor& descriptor)
 
     m_ref_face_list_vector = descriptor.template refItemListVector<ItemType::face>();
 
-    if constexpr (Dimension > 2) {
+    if constexpr (Dimension == 2) {
+      // edges are similar to faces
+      {
+        WeakEdgeValue<int> edge_number(*this);
+        edge_number   = convert_to_array(descriptor.face_number_vector);
+        m_edge_number = edge_number;
+      }
+
+      {
+        WeakEdgeValue<int> edge_owner(*this);
+        edge_owner   = convert_to_array(descriptor.face_owner_vector);
+        m_edge_owner = edge_owner;
+      }
+
+      {
+        const int rank = parallel::rank();
+        WeakEdgeValue<bool> edge_is_owned(*this);
+        parallel_for(
+          this->numberOfEdges(), PUGS_LAMBDA(EdgeId l) { edge_is_owned[l] = (m_edge_owner[l] == rank); });
+        m_edge_is_owned = edge_is_owned;
+      }
+
+    } else {
       m_item_to_item_matrix[itemTId(ItemType::edge)][itemTId(ItemType::node)] = descriptor.edge_to_node_vector;
 
       m_item_to_item_matrix[itemTId(ItemType::face)][itemTId(ItemType::edge)] = descriptor.face_to_edge_vector;
diff --git a/src/mesh/Connectivity.hpp b/src/mesh/Connectivity.hpp
index 2b25c6e9bf6f772acf5e0b02b9e93e9ea02a9877..5ca484b2258adfbe64b11cb6913c126442846447 100644
--- a/src/mesh/Connectivity.hpp
+++ b/src/mesh/Connectivity.hpp
@@ -196,7 +196,6 @@ class Connectivity final : public IConnectivity
   const auto&
   edgeOwner() const
   {
-    throw NotImplementedError("edge owner not built");
     return m_edge_owner;
   }
 
@@ -242,7 +241,6 @@ class Connectivity final : public IConnectivity
   const auto&
   edgeIsOwned() const
   {
-    throw NotImplementedError("edge is owned not built");
     return m_edge_is_owned;
   }
 
@@ -589,8 +587,14 @@ class Connectivity final : public IConnectivity
   size_t
   numberOfEdges() const final
   {
-    const auto& edge_to_node_matrix = this->_getMatrix(ItemType::edge, ItemType::node);
-    return edge_to_node_matrix.numRows();
+    if constexpr (Dimension == 1) {
+      return this->numberOfNodes();
+    } else if constexpr (Dimension == 2) {
+      return this->numberOfFaces();
+    } else {
+      const auto& edge_to_node_matrix = this->_getMatrix(ItemType::edge, ItemType::node);
+      return edge_to_node_matrix.numRows();
+    }
   }
 
   PUGS_INLINE
diff --git a/src/mesh/ConnectivityDispatcher.cpp b/src/mesh/ConnectivityDispatcher.cpp
index 6b73d967d4d4fcb37e1376bf1d9306826d25fb1f..1502bed705b252d9be19e1783ab7719488451c8c 100644
--- a/src/mesh/ConnectivityDispatcher.cpp
+++ b/src/mesh/ConnectivityDispatcher.cpp
@@ -29,7 +29,7 @@ ConnectivityDispatcher<Dimension>::_buildNewOwner()
     using ItemId = ItemIdT<item_type>;
     ItemValue<int, item_type> item_new_owner(m_connectivity);
     parallel_for(
-      item_new_owner.size(), PUGS_LAMBDA(const ItemId& l) {
+      item_new_owner.numberOfItems(), PUGS_LAMBDA(const ItemId& l) {
         const auto& item_to_cell = item_to_cell_matrix[l];
         CellId Jmin              = item_to_cell[0];
 
@@ -268,7 +268,7 @@ ConnectivityDispatcher<Dimension>::_buildNumberOfSubItemPerItemToRecvByProc()
 
   using ItemId = ItemIdT<SubItemOfItemT::item_type>;
   parallel_for(
-    number_of_sub_item_per_item.size(),
+    number_of_sub_item_per_item.numberOfItems(),
     PUGS_LAMBDA(const ItemId& j) { number_of_sub_item_per_item[j] = item_to_sub_item_matrix[j].size(); });
 
   this->_dispatchedInfo<SubItemOfItemT>().m_number_of_sub_item_per_item_to_recv_by_proc =
diff --git a/src/mesh/IConnectivity.hpp b/src/mesh/IConnectivity.hpp
index 9bfb8e5043088a18880440235cd72b1677c1f519..90c49e4d818e6caa6414992e9ebaf7bdf46f15cc 100644
--- a/src/mesh/IConnectivity.hpp
+++ b/src/mesh/IConnectivity.hpp
@@ -13,6 +13,9 @@ class IConnectivity : public std::enable_shared_from_this<IConnectivity>
   template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
   friend class SubItemValuePerItem;
 
+  template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
+  friend class SubItemArrayPerItem;
+
   virtual const ConnectivityMatrix& _getMatrix(const ItemType& item_type_0, const ItemType& item_type_1) const = 0;
 
  public:
diff --git a/src/mesh/ItemArray.hpp b/src/mesh/ItemArray.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a38f950954084a3527717a2a858d452e3e3cf2ad
--- /dev/null
+++ b/src/mesh/ItemArray.hpp
@@ -0,0 +1,219 @@
+#ifndef ITEM_ARRAY_HPP
+#define ITEM_ARRAY_HPP
+
+#include <mesh/IConnectivity.hpp>
+#include <mesh/ItemId.hpp>
+#include <mesh/ItemType.hpp>
+#include <utils/Array.hpp>
+#include <utils/PugsAssert.hpp>
+#include <utils/SubArray.hpp>
+
+#include <memory>
+
+template <typename DataType, ItemType item_type, typename ConnectivityPtr = std::shared_ptr<const IConnectivity>>
+class ItemArray
+{
+ public:
+  static constexpr ItemType item_t{item_type};
+  using data_type = DataType;
+
+  using ItemId     = ItemIdT<item_type>;
+  using index_type = ItemId;
+
+ private:
+  using ConnectivitySharedPtr = std::shared_ptr<const IConnectivity>;
+  using ConnectivityWeakPtr   = std::weak_ptr<const IConnectivity>;
+
+  static_assert(std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr> or
+                std::is_same_v<ConnectivityPtr, ConnectivityWeakPtr>);
+
+  ConnectivityPtr m_connectivity_ptr;
+
+  Array<DataType> m_arrays_values;
+
+  size_t m_size_of_arrays;
+
+  // Allow const std:shared_ptr version to access our data
+  friend ItemArray<std::add_const_t<DataType>, item_type, ConnectivitySharedPtr>;
+
+  // Allow const std:weak_ptr version to access our data
+  friend ItemArray<std::add_const_t<DataType>, item_type, ConnectivityWeakPtr>;
+
+ public:
+  friend PUGS_INLINE ItemArray<std::remove_const_t<DataType>, item_type, ConnectivityPtr>
+  copy(const ItemArray<DataType, item_type, ConnectivityPtr>& source)
+  {
+    ItemArray<std::remove_const_t<DataType>, item_type, ConnectivityPtr> image;
+
+    image.m_connectivity_ptr = source.m_connectivity_ptr;
+    image.m_arrays_values    = copy(source.m_arrays_values);
+    image.m_size_of_arrays   = source.m_size_of_arrays;
+    return image;
+  }
+
+  PUGS_INLINE
+  bool
+  isBuilt() const noexcept
+  {
+    return m_connectivity_ptr.use_count() != 0;
+  }
+
+  PUGS_INLINE
+  std::shared_ptr<const IConnectivity>
+  connectivity_ptr() const noexcept
+  {
+    if constexpr (std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr>) {
+      return m_connectivity_ptr;
+    } else {
+      return m_connectivity_ptr.lock();
+    }
+  }
+
+  PUGS_INLINE
+  size_t
+  numberOfValues() const noexcept(NO_ASSERT)
+  {
+    Assert(this->isBuilt());
+    return m_arrays_values.size();
+  }
+
+  PUGS_INLINE
+  void
+  fill(const DataType& data) const noexcept
+  {
+    static_assert(not std::is_const_v<DataType>, "Cannot modify ItemArray of const");
+    m_arrays_values.fill(data);
+  }
+
+  // Following Kokkos logic, these classes are view and const view does allow
+  // changes in data
+  PUGS_FORCEINLINE
+  SubArray<DataType>
+  operator[](const ItemId& i) const noexcept(NO_ASSERT)
+  {
+    Assert(this->isBuilt());
+    return SubArray{m_arrays_values, i * m_size_of_arrays, m_size_of_arrays};
+  }
+
+  template <typename IndexType>
+  SubArray<DataType>
+  operator[](const IndexType&) const noexcept(NO_ASSERT)
+  {
+    static_assert(std::is_same_v<IndexType, ItemId>, "ItemArray must be indexed by ItemId");
+  }
+
+  PUGS_INLINE
+  size_t
+  numberOfItems() const noexcept(NO_ASSERT)
+  {
+    Assert(this->isBuilt());
+    return m_connectivity_ptr->template numberOf<item_type>();
+  }
+
+  PUGS_INLINE
+  size_t
+  sizeOfArrays() const
+  {
+    Assert(this->isBuilt());
+    return m_size_of_arrays;
+  }
+
+  template <typename DataType2>
+  PUGS_INLINE ItemArray&
+  operator=(const Array<DataType2>& arrays) noexcept(NO_ASSERT)
+  {
+    // ensures that DataType is the same as source DataType2
+    static_assert(std::is_same_v<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>,
+                  "Cannot assign ItemArray of different type");
+    // ensures that const is not lost through copy
+    static_assert(((std::is_const_v<DataType2> and std::is_const_v<DataType>) or not std::is_const_v<DataType2>),
+                  "Cannot assign ItemArray of const to ItemArray of non-const");
+
+    Assert((arrays.size() == 0) or this->isBuilt(), "Cannot assign array of arrays to a non-built ItemArray\n");
+
+    Assert(m_arrays_values.size() == arrays.size(), "Cannot assign an array of arrays of a different size\n");
+
+    m_arrays_values = arrays;
+
+    return *this;
+  }
+
+  template <typename DataType2, typename ConnectivityPtr2>
+  PUGS_INLINE ItemArray&
+  operator=(const ItemArray<DataType2, item_type, ConnectivityPtr2>& array_per_item) noexcept
+  {
+    // ensures that DataType is the same as source DataType2
+    static_assert(std::is_same_v<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>,
+                  "Cannot assign ItemArray of different type");
+    // ensures that const is not lost through copy
+    static_assert(((std::is_const_v<DataType2> and std::is_const_v<DataType>) or not std::is_const_v<DataType2>),
+                  "Cannot assign ItemArray of const to ItemArray of non-const");
+
+    m_arrays_values  = array_per_item.m_arrays_values;
+    m_size_of_arrays = array_per_item.m_size_of_arrays;
+
+    if constexpr (std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr> and
+                  std::is_same_v<ConnectivityPtr2, ConnectivityWeakPtr>) {
+      m_connectivity_ptr = array_per_item.m_connectivity_ptr.lock();
+    } else {
+      m_connectivity_ptr = array_per_item.m_connectivity_ptr;
+    }
+
+    return *this;
+  }
+
+  template <typename DataType2, typename ConnectivityPtr2>
+  PUGS_INLINE
+  ItemArray(const ItemArray<DataType2, item_type, ConnectivityPtr2>& array_per_item) noexcept
+  {
+    this->operator=(array_per_item);
+  }
+
+  PUGS_INLINE
+  ItemArray() = default;
+
+  PUGS_INLINE
+  ItemArray(const IConnectivity& connectivity, size_t size_of_array) noexcept
+    : m_connectivity_ptr{connectivity.shared_ptr()},
+      m_arrays_values{connectivity.numberOf<item_type>() * size_of_array},
+      m_size_of_arrays{size_of_array}
+  {
+    static_assert(not std::is_const_v<DataType>, "Cannot allocate ItemArray of const data: only view is "
+                                                 "supported");
+    ;
+  }
+
+  PUGS_INLINE
+  ~ItemArray() = default;
+};
+
+template <typename DataType>
+using NodeArray = ItemArray<DataType, ItemType::node>;
+
+template <typename DataType>
+using EdgeArray = ItemArray<DataType, ItemType::edge>;
+
+template <typename DataType>
+using FaceArray = ItemArray<DataType, ItemType::face>;
+
+template <typename DataType>
+using CellArray = ItemArray<DataType, ItemType::cell>;
+
+// Weak versions: should not be used outside of Connectivity
+
+template <typename DataType, ItemType item_type>
+using WeakItemArray = ItemArray<DataType, item_type, std::weak_ptr<const IConnectivity>>;
+
+template <typename DataType>
+using WeakNodeArray = WeakItemArray<DataType, ItemType::node>;
+
+template <typename DataType>
+using WeakEdgeArray = WeakItemArray<DataType, ItemType::edge>;
+
+template <typename DataType>
+using WeakFaceArray = WeakItemArray<DataType, ItemType::face>;
+
+template <typename DataType>
+using WeakCellArray = WeakItemArray<DataType, ItemType::cell>;
+
+#endif   // ITEM_ARRAY_HPP
diff --git a/src/mesh/ItemArrayUtils.hpp b/src/mesh/ItemArrayUtils.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..618dc8c8e061e55f9db3950b7cff71b3b281b111
--- /dev/null
+++ b/src/mesh/ItemArrayUtils.hpp
@@ -0,0 +1,26 @@
+#ifndef ITEM_ARRAY_UTILS_HPP
+#define ITEM_ARRAY_UTILS_HPP
+
+#include <utils/Messenger.hpp>
+
+#include <mesh/Connectivity.hpp>
+#include <mesh/ItemArray.hpp>
+#include <mesh/Synchronizer.hpp>
+#include <mesh/SynchronizerManager.hpp>
+
+#include <iostream>
+
+template <typename DataType, ItemType item_type, typename ConnectivityPtr>
+void
+synchronize(ItemArray<DataType, item_type, ConnectivityPtr>& item_array)
+{
+  static_assert(not std::is_const_v<DataType>, "cannot synchronize ItemArray of const data");
+  if (parallel::size() > 1) {
+    auto& manager                     = SynchronizerManager::instance();
+    const IConnectivity* connectivity = item_array.connectivity_ptr().get();
+    Synchronizer& synchronizer        = manager.getConnectivitySynchronizer(connectivity);
+    synchronizer.synchronize(item_array);
+  }
+}
+
+#endif   // ITEM_ARRAY_UTILS_HPP
diff --git a/src/mesh/ItemValue.hpp b/src/mesh/ItemValue.hpp
index a9049a394d225f68086a8c4038d8159928405612..0f7fe61717fc9d8c571d0107b5ae422cc6b911bb 100644
--- a/src/mesh/ItemValue.hpp
+++ b/src/mesh/ItemValue.hpp
@@ -1,12 +1,11 @@
 #ifndef ITEM_VALUE_HPP
 #define ITEM_VALUE_HPP
 
-#include <utils/Array.hpp>
-#include <utils/PugsAssert.hpp>
-
 #include <mesh/IConnectivity.hpp>
 #include <mesh/ItemId.hpp>
 #include <mesh/ItemType.hpp>
+#include <utils/Array.hpp>
+#include <utils/PugsAssert.hpp>
 
 #include <memory>
 
@@ -37,16 +36,17 @@ class ItemValue
   // Allow const std:weak_ptr version to access our data
   friend ItemValue<std::add_const_t<DataType>, item_type, ConnectivityWeakPtr>;
 
+ public:
   friend PUGS_INLINE ItemValue<std::remove_const_t<DataType>, item_type, ConnectivityPtr>
   copy(const ItemValue<DataType, item_type, ConnectivityPtr>& source)
   {
-    ItemValue<std::remove_const_t<DataType>, item_type, ConnectivityPtr> image(*source.connectivity_ptr());
+    ItemValue<std::remove_const_t<DataType>, item_type, ConnectivityPtr> image;
 
-    image.m_values = copy(source.m_values);
+    image.m_connectivity_ptr = source.m_connectivity_ptr;
+    image.m_values           = copy(source.m_values);
     return image;
   }
 
- public:
   PUGS_INLINE
   bool
   isBuilt() const noexcept
@@ -67,7 +67,7 @@ class ItemValue
 
   PUGS_INLINE
   size_t
-  size() const noexcept(NO_ASSERT)
+  numberOfItems() const noexcept(NO_ASSERT)
   {
     Assert(this->isBuilt());
     return m_values.size();
@@ -84,26 +84,20 @@ class ItemValue
   // Following Kokkos logic, these classes are view and const view does allow
   // changes in data
   PUGS_FORCEINLINE
-  DataType& operator[](const ItemId& i) const noexcept(NO_ASSERT)
+  DataType&
+  operator[](const ItemId& i) const noexcept(NO_ASSERT)
   {
     Assert(this->isBuilt());
     return m_values[i];
   }
 
   template <typename IndexType>
-  DataType& operator[](const IndexType&) const noexcept(NO_ASSERT)
+  DataType&
+  operator[](const IndexType&) const noexcept(NO_ASSERT)
   {
     static_assert(std::is_same_v<IndexType, ItemId>, "ItemValue must be indexed by ItemId");
   }
 
-  PUGS_INLINE
-  size_t
-  numberOfItems() const noexcept(NO_ASSERT)
-  {
-    Assert(this->isBuilt());
-    return m_values.size();
-  }
-
   template <typename DataType2>
   PUGS_INLINE ItemValue&
   operator=(const Array<DataType2>& values) noexcept(NO_ASSERT)
@@ -162,7 +156,6 @@ class ItemValue
   {
     static_assert(not std::is_const_v<DataType>, "Cannot allocate ItemValue of const data: only view is "
                                                  "supported");
-    ;
   }
 
   PUGS_INLINE
diff --git a/src/mesh/ItemValueUtils.hpp b/src/mesh/ItemValueUtils.hpp
index a1039fdbdc8933c27d6ce19ec978894b329520c1..c307ce4d6010e9ce0d40b4afdcb2ab4466e1d009 100644
--- a/src/mesh/ItemValueUtils.hpp
+++ b/src/mesh/ItemValueUtils.hpp
@@ -32,7 +32,7 @@ min(const ItemValue<DataType, item_type, ConnectivityPtr>& item_value)
     operator data_type()
     {
       data_type reduced_value;
-      parallel_reduce(m_item_value.size(), *this, reduced_value);
+      parallel_reduce(m_item_value.numberOfItems(), *this, reduced_value);
       return reduced_value;
     }
 
@@ -50,7 +50,8 @@ min(const ItemValue<DataType, item_type, ConnectivityPtr>& item_value)
     join(volatile data_type& dst, const volatile data_type& src) const
     {
       if (src < dst) {
-        dst = src;
+        // cannot be reached if initial value is the min
+        dst = src;   // LCOV_EXCL_LINE
       }
     }
 
@@ -83,9 +84,11 @@ min(const ItemValue<DataType, item_type, ConnectivityPtr>& item_value)
             return connectivity_3d.isOwned<item_type>();
             break;
           }
+            // LCOV_EXCL_START
           default: {
             throw UnexpectedError("unexpected dimension");
           }
+            // LCOV_EXCL_STOP
           }
         }(*item_value.connectivity_ptr()))
     {
@@ -121,7 +124,7 @@ max(const ItemValue<DataType, item_type, ConnectivityPtr>& item_value)
     operator data_type()
     {
       data_type reduced_value;
-      parallel_reduce(m_item_value.size(), *this, reduced_value);
+      parallel_reduce(m_item_value.numberOfItems(), *this, reduced_value);
       return reduced_value;
     }
 
@@ -139,7 +142,8 @@ max(const ItemValue<DataType, item_type, ConnectivityPtr>& item_value)
     join(volatile data_type& dst, const volatile data_type& src) const
     {
       if (src > dst) {
-        dst = src;
+        // cannot be reached if initial value is the max
+        dst = src;   // LCOV_EXCL_LINE
       }
     }
 
@@ -172,9 +176,11 @@ max(const ItemValue<DataType, item_type, ConnectivityPtr>& item_value)
             return connectivity_3d.isOwned<item_type>();
             break;
           }
+            // LCOV_EXCL_START
           default: {
             throw UnexpectedError("unexpected dimension");
           }
+            // LCOV_EXCL_STOP
           }
         }(*item_value.connectivity_ptr()))
     {
@@ -211,7 +217,7 @@ sum(const ItemValue<DataType, item_type>& item_value)
     operator data_type()
     {
       data_type reduced_value;
-      parallel_reduce(m_item_value.size(), *this, reduced_value);
+      parallel_reduce(m_item_value.numberOfItems(), *this, reduced_value);
       return reduced_value;
     }
 
@@ -264,9 +270,11 @@ sum(const ItemValue<DataType, item_type>& item_value)
             return connectivity_3d.isOwned<item_type>();
             break;
           }
+            // LCOV_EXCL_START
           default: {
             throw UnexpectedError("unexpected dimension");
           }
+            // LCOV_EXCL_STOP
           }
         }(*item_value.connectivity_ptr()))
     {
diff --git a/src/mesh/Mesh.hpp b/src/mesh/Mesh.hpp
index 21c100384ef2072e43af54dda74eda8d6021c2ea..45f4c8d050d7fd7c63e44caf002b6bbcb1c7599a 100644
--- a/src/mesh/Mesh.hpp
+++ b/src/mesh/Mesh.hpp
@@ -49,6 +49,13 @@ class Mesh final : public IMesh
     return m_connectivity->numberOfNodes();
   }
 
+  PUGS_INLINE
+  size_t
+  numberOfEdges() const
+  {
+    return m_connectivity->numberOfEdges();
+  }
+
   PUGS_INLINE
   size_t
   numberOfFaces() const
diff --git a/src/mesh/SubItemArrayPerItem.hpp b/src/mesh/SubItemArrayPerItem.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..431c3f4bee962857ff0b361c56a9ab5e987fde35
--- /dev/null
+++ b/src/mesh/SubItemArrayPerItem.hpp
@@ -0,0 +1,361 @@
+#ifndef SUBITEM_ARRAY_PER_ITEM_HPP
+#define SUBITEM_ARRAY_PER_ITEM_HPP
+
+#include <mesh/ConnectivityMatrix.hpp>
+#include <mesh/IConnectivity.hpp>
+#include <mesh/ItemId.hpp>
+#include <mesh/ItemOfItemType.hpp>
+#include <mesh/ItemType.hpp>
+#include <utils/Array.hpp>
+#include <utils/PugsAssert.hpp>
+#include <utils/SubArray.hpp>
+
+#include <memory>
+
+template <typename DataType, typename ItemOfItem, typename ConnectivityPtr = std::shared_ptr<const IConnectivity>>
+class SubItemArrayPerItem
+{
+ public:
+  static constexpr ItemType item_type{ItemOfItem::item_type};
+  static constexpr ItemType sub_item_type{ItemOfItem::sub_item_type};
+
+  using ItemOfItemType = ItemOfItem;
+  using data_type      = DataType;
+  using ItemId         = ItemIdT<item_type>;
+  using index_type     = ItemId;
+
+ private:
+  using ConnectivitySharedPtr = std::shared_ptr<const IConnectivity>;
+  using ConnectivityWeakPtr   = std::weak_ptr<const IConnectivity>;
+
+  static_assert(std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr> or
+                std::is_same_v<ConnectivityPtr, ConnectivityWeakPtr>);
+
+  ConnectivityPtr m_connectivity_ptr;
+
+  typename ConnectivityMatrix::HostRowType m_host_row_map;
+  Array<DataType> m_arrays_values;
+
+  size_t m_size_of_arrays;
+
+  // Allow const std:shared_ptr version to access our data
+  friend SubItemArrayPerItem<std::add_const_t<DataType>, ItemOfItem, ConnectivitySharedPtr>;
+
+  // Allow const std:weak_ptr version to access our data
+  friend SubItemArrayPerItem<std::add_const_t<DataType>, ItemOfItem, ConnectivityWeakPtr>;
+
+  // Allow const std:shared_ptr version to access our data
+  friend SubItemArrayPerItem<std::remove_const_t<DataType>, ItemOfItem, ConnectivitySharedPtr>;
+
+  // Allow const std:weak_ptr version to access our data
+  friend SubItemArrayPerItem<std::remove_const_t<DataType>, ItemOfItem, ConnectivityWeakPtr>;
+
+ public:
+  using ToShared = SubItemArrayPerItem<DataType, ItemOfItem, ConnectivitySharedPtr>;
+
+  class SubView
+  {
+   public:
+    using data_type = DataType;
+
+   private:
+    PUGS_RESTRICT DataType* const m_sub_arrays;
+    const size_t m_size;
+    const size_t m_size_of_arrays;
+
+   public:
+    template <typename IndexType>
+    PUGS_FORCEINLINE SubArray<DataType>
+    operator[](IndexType i) const noexcept(NO_ASSERT)
+    {
+      static_assert(std::is_integral_v<IndexType>, "SubView is indexed by integral arrays");
+      Assert(static_cast<size_t>(i) < m_size);
+      return SubArray<DataType>(m_sub_arrays, i * m_size_of_arrays, m_size_of_arrays);
+    }
+
+    template <typename IndexType>
+    PUGS_FORCEINLINE SubArray<DataType>
+    operator[](IndexType i) noexcept(NO_ASSERT)
+    {
+      static_assert(std::is_integral_v<IndexType>, "SubView is indexed by integral arrays");
+      Assert(static_cast<size_t>(i) < m_size);
+      return SubArray<DataType>(m_sub_arrays, i * m_size_of_arrays, m_size_of_arrays);
+    }
+
+    PUGS_INLINE
+    size_t
+    size() const noexcept
+    {
+      return m_size;
+    }
+
+    SubView(const SubView&) = delete;
+
+    PUGS_INLINE
+    SubView(SubView&&) noexcept = delete;
+
+    PUGS_INLINE
+    SubView(const Array<DataType>& arrays, size_t begin, size_t end, size_t size_of_arrays) noexcept(NO_ASSERT)
+      : m_sub_arrays{&(arrays[begin * size_of_arrays])}, m_size{end - begin}, m_size_of_arrays{size_of_arrays}
+    {
+      Assert(begin <= end);
+      Assert(end <= arrays.size());
+    }
+  };
+
+  friend PUGS_INLINE SubItemArrayPerItem<std::remove_const_t<DataType>, ItemOfItem, ConnectivityPtr>
+  copy(SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& source)
+  {
+    SubItemArrayPerItem<std::remove_const_t<DataType>, ItemOfItem, ConnectivityPtr> image;
+
+    image.m_connectivity_ptr = source.m_connectivity_ptr;
+    image.m_host_row_map     = source.m_host_row_map;
+    image.m_arrays_values    = copy(source.m_arrays_values);
+    image.m_size_of_arrays   = source.m_size_of_arrays;
+    return image;
+  }
+
+  PUGS_INLINE
+  bool
+  isBuilt() const noexcept
+  {
+    return m_connectivity_ptr.use_count() != 0;
+  }
+
+  PUGS_INLINE
+  std::shared_ptr<const IConnectivity>
+  connectivity_ptr() const noexcept
+  {
+    if constexpr (std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr>) {
+      return m_connectivity_ptr;
+    } else {
+      return m_connectivity_ptr.lock();
+    }
+  }
+
+  template <typename IndexType, typename SubIndexType>
+  PUGS_FORCEINLINE SubArray<DataType>
+  operator()(IndexType item_id, SubIndexType i) const noexcept(NO_ASSERT)
+  {
+    static_assert(std::is_same_v<IndexType, ItemId>, "first index must be of the correct ItemId type");
+    static_assert(std::is_integral_v<SubIndexType>, "second index must be an integral type");
+    Assert(this->isBuilt());
+    Assert(i + m_host_row_map(size_t{item_id}) < m_host_row_map(size_t{item_id} + 1));
+    return SubArray(m_arrays_values, (m_host_row_map(size_t{item_id}) + i) * m_size_of_arrays, m_size_of_arrays);
+  }
+
+  // Following Kokkos logic, these classes are view and const view does allow
+  // changes in data
+  template <typename ArrayIndexType>
+  DataType&
+  operator[](const ArrayIndexType& i) const noexcept(NO_ASSERT)
+  {
+    static_assert(std::is_integral_v<ArrayIndexType>, "index must be an integral type");
+    Assert(this->isBuilt());
+    Assert(static_cast<size_t>(i) < m_arrays_values.size());
+    return m_arrays_values[i];
+  }
+
+  PUGS_INLINE
+  size_t
+  numberOfValues() const noexcept(NO_ASSERT)
+  {
+    Assert(this->isBuilt());
+    return m_arrays_values.size();
+  }
+
+  PUGS_INLINE
+  size_t
+  numberOfItems() const noexcept(NO_ASSERT)
+  {
+    Assert(this->isBuilt());
+    Assert(m_host_row_map.extent(0) > 0);
+    return m_host_row_map.extent(0) - 1;
+  }
+
+  PUGS_INLINE size_t
+  sizeOfArrays() const noexcept(NO_ASSERT)
+  {
+    Assert(this->isBuilt());
+    return m_size_of_arrays;
+  }
+
+  template <typename IndexType>
+  PUGS_INLINE size_t
+  numberOfSubArrays(IndexType item_id) const noexcept(NO_ASSERT)
+  {
+    static_assert(std::is_same_v<IndexType, ItemId>, "index must be an ItemId");
+    Assert(this->isBuilt());
+    Assert(item_id < this->numberOfItems());
+    return m_host_row_map(size_t{item_id} + 1) - m_host_row_map(size_t{item_id});
+  }
+
+  template <typename IndexType>
+  PUGS_INLINE SubView
+  itemArrays(IndexType item_id) noexcept(NO_ASSERT)
+  {
+    static_assert(std::is_same_v<IndexType, ItemId>, "index must be an ItemId");
+    Assert(this->isBuilt());
+    Assert(item_id < this->numberOfItems());
+    const auto& item_begin = m_host_row_map(size_t{item_id});
+    const auto& item_end   = m_host_row_map(size_t{item_id} + 1);
+    return SubView(m_arrays_values, item_begin, item_end, m_size_of_arrays);
+  }
+
+  // Following Kokkos logic, these classes are view and const view does allow
+  // changes in data
+  template <typename IndexType>
+  PUGS_INLINE SubView
+  itemArrays(IndexType item_id) const noexcept(NO_ASSERT)
+  {
+    static_assert(std::is_same_v<IndexType, ItemId>, "index must be an ItemId");
+    Assert(this->isBuilt());
+    Assert(item_id < this->numberOfItems());
+    const auto& item_begin = m_host_row_map(size_t{item_id});
+    const auto& item_end   = m_host_row_map(size_t{item_id} + 1);
+    return SubView(m_arrays_values, item_begin, item_end, m_size_of_arrays);
+  }
+
+  template <typename DataType2, typename ConnectivityPtr2>
+  PUGS_INLINE SubItemArrayPerItem&
+  operator=(const SubItemArrayPerItem<DataType2, ItemOfItem, ConnectivityPtr2>& sub_item_array_per_item) noexcept
+  {
+    // ensures that DataType is the same as source DataType2
+    static_assert(std::is_same_v<std::remove_const_t<DataType>, std::remove_const_t<DataType2>>,
+                  "Cannot assign SubItemArrayPerItem of different type");
+    // ensures that const is not lost through copy
+    static_assert(((std::is_const_v<DataType2> and std::is_const_v<DataType>) or not std::is_const_v<DataType2>),
+                  "Cannot assign SubItemArrayPerItem of const data to "
+                  "SubItemArrayPerItem of non-const data");
+    m_host_row_map   = sub_item_array_per_item.m_host_row_map;
+    m_arrays_values  = sub_item_array_per_item.m_arrays_values;
+    m_size_of_arrays = sub_item_array_per_item.m_size_of_arrays;
+
+    if constexpr (std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr> and
+                  std::is_same_v<ConnectivityPtr2, ConnectivityWeakPtr>) {
+      m_connectivity_ptr = sub_item_array_per_item.m_connectivity_ptr.lock();
+    } else {
+      m_connectivity_ptr = sub_item_array_per_item.m_connectivity_ptr;
+    }
+
+    return *this;
+  }
+
+  template <typename DataType2, typename ConnectivityPtr2>
+  PUGS_INLINE
+  SubItemArrayPerItem(
+    const SubItemArrayPerItem<DataType2, ItemOfItem, ConnectivityPtr2>& sub_item_array_per_item) noexcept
+  {
+    this->operator=(sub_item_array_per_item);
+  }
+
+  SubItemArrayPerItem() = default;
+
+  SubItemArrayPerItem(const IConnectivity& connectivity, size_t size_of_array) noexcept
+    : m_connectivity_ptr{connectivity.shared_ptr()}, m_size_of_arrays(size_of_array)
+  {
+    static_assert(not std::is_const_v<DataType>, "Cannot allocate SubItemArrayPerItem of const data: only "
+                                                 "view is supported");
+
+    ConnectivityMatrix connectivity_matrix = connectivity._getMatrix(item_type, sub_item_type);
+
+    m_host_row_map  = connectivity_matrix.rowsMap();
+    m_arrays_values = Array<std::remove_const_t<DataType>>(connectivity_matrix.numEntries() * m_size_of_arrays);
+  }
+
+  ~SubItemArrayPerItem() = default;
+};
+
+// Item arrays at nodes
+
+template <typename DataType>
+using NodeArrayPerEdge = SubItemArrayPerItem<DataType, NodeOfEdge>;
+
+template <typename DataType>
+using NodeArrayPerFace = SubItemArrayPerItem<DataType, NodeOfFace>;
+
+template <typename DataType>
+using NodeArrayPerCell = SubItemArrayPerItem<DataType, NodeOfCell>;
+
+// Item arrays at edges
+
+template <typename DataType>
+using EdgeArrayPerNode = SubItemArrayPerItem<DataType, EdgeOfNode>;
+
+template <typename DataType>
+using EdgeArrayPerFace = SubItemArrayPerItem<DataType, EdgeOfFace>;
+
+template <typename DataType>
+using EdgeArrayPerCell = SubItemArrayPerItem<DataType, EdgeOfCell>;
+
+// Item arrays at faces
+
+template <typename DataType>
+using FaceArrayPerNode = SubItemArrayPerItem<DataType, FaceOfNode>;
+
+template <typename DataType>
+using FaceArrayPerEdge = SubItemArrayPerItem<DataType, FaceOfEdge>;
+
+template <typename DataType>
+using FaceArrayPerCell = SubItemArrayPerItem<DataType, FaceOfCell>;
+
+// Item arrays at cells
+
+template <typename DataType>
+using CellArrayPerNode = SubItemArrayPerItem<DataType, CellOfNode>;
+
+template <typename DataType>
+using CellArrayPerEdge = SubItemArrayPerItem<DataType, CellOfEdge>;
+
+template <typename DataType>
+using CellArrayPerFace = SubItemArrayPerItem<DataType, CellOfFace>;
+
+// Weak versions: should not be used outside of Connectivity
+// Item arrays at nodes
+
+template <typename DataType, typename ItemOfItem>
+using WeakSubItemArrayPerItem = SubItemArrayPerItem<DataType, ItemOfItem, std::weak_ptr<const IConnectivity>>;
+
+template <typename DataType>
+using WeakNodeArrayPerEdge = WeakSubItemArrayPerItem<DataType, NodeOfEdge>;
+
+template <typename DataType>
+using WeakNodeArrayPerFace = WeakSubItemArrayPerItem<DataType, NodeOfFace>;
+
+template <typename DataType>
+using WeakNodeArrayPerCell = WeakSubItemArrayPerItem<DataType, NodeOfCell>;
+
+// Item arrays at edges
+
+template <typename DataType>
+using WeakEdgeArrayPerNode = WeakSubItemArrayPerItem<DataType, EdgeOfNode>;
+
+template <typename DataType>
+using WeakEdgeArrayPerFace = WeakSubItemArrayPerItem<DataType, EdgeOfFace>;
+
+template <typename DataType>
+using WeakEdgeArrayPerCell = WeakSubItemArrayPerItem<DataType, EdgeOfCell>;
+
+// Item arrays at faces
+
+template <typename DataType>
+using WeakFaceArrayPerNode = WeakSubItemArrayPerItem<DataType, FaceOfNode>;
+
+template <typename DataType>
+using WeakFaceArrayPerEdge = WeakSubItemArrayPerItem<DataType, FaceOfEdge>;
+
+template <typename DataType>
+using WeakFaceArrayPerCell = WeakSubItemArrayPerItem<DataType, FaceOfCell>;
+
+// Item arrays at cells
+
+template <typename DataType>
+using WeakCellArrayPerNode = WeakSubItemArrayPerItem<DataType, CellOfNode>;
+
+template <typename DataType>
+using WeakCellArrayPerEdge = WeakSubItemArrayPerItem<DataType, CellOfEdge>;
+
+template <typename DataType>
+using WeakCellArrayPerFace = WeakSubItemArrayPerItem<DataType, CellOfFace>;
+
+#endif   // SUBITEM_ARRAY_PER_ITEM_HPP
diff --git a/src/mesh/SubItemValuePerItem.hpp b/src/mesh/SubItemValuePerItem.hpp
index 66af694c7255da41ff7570d294dd4777acb0164f..63bff8a35848746535df9d05c7df0a12c899c3a6 100644
--- a/src/mesh/SubItemValuePerItem.hpp
+++ b/src/mesh/SubItemValuePerItem.hpp
@@ -41,6 +41,12 @@ class SubItemValuePerItem
   // Allow const std:weak_ptr version to access our data
   friend SubItemValuePerItem<std::add_const_t<DataType>, ItemOfItem, ConnectivityWeakPtr>;
 
+  // Allow const std:shared_ptr version to access our data
+  friend SubItemValuePerItem<std::remove_const_t<DataType>, ItemOfItem, ConnectivitySharedPtr>;
+
+  // Allow const std:weak_ptr version to access our data
+  friend SubItemValuePerItem<std::remove_const_t<DataType>, ItemOfItem, ConnectivityWeakPtr>;
+
  public:
   using ToShared = SubItemValuePerItem<DataType, ItemOfItem, ConnectivitySharedPtr>;
 
@@ -55,7 +61,8 @@ class SubItemValuePerItem
 
    public:
     template <typename IndexType>
-    PUGS_INLINE const DataType& operator[](IndexType i) const noexcept(NO_ASSERT)
+    PUGS_FORCEINLINE DataType&
+    operator[](IndexType i) const noexcept(NO_ASSERT)
     {
       static_assert(std::is_integral_v<IndexType>, "SubView is indexed by integral values");
       Assert(i < m_size);
@@ -63,7 +70,8 @@ class SubItemValuePerItem
     }
 
     template <typename IndexType>
-    PUGS_FORCEINLINE DataType& operator[](IndexType i) noexcept(NO_ASSERT)
+    PUGS_FORCEINLINE DataType&
+    operator[](IndexType i) noexcept(NO_ASSERT)
     {
       static_assert(std::is_integral_v<IndexType>, "SubView is indexed by integral values");
       Assert(i < m_size);
@@ -77,10 +85,8 @@ class SubItemValuePerItem
       return m_size;
     }
 
-    SubView(const SubView&) = delete;
-
-    PUGS_INLINE
-    SubView(SubView&&) noexcept = default;
+    SubView(const SubView&)     = delete;
+    SubView(SubView&&) noexcept = delete;
 
     PUGS_INLINE
     SubView(const Array<DataType>& values, size_t begin, size_t end) noexcept(NO_ASSERT)
@@ -89,8 +95,21 @@ class SubItemValuePerItem
       Assert(begin <= end);
       Assert(end <= values.size());
     }
+
+    ~SubView() = default;
   };
 
+  friend PUGS_INLINE SubItemValuePerItem<std::remove_const_t<DataType>, ItemOfItem, ConnectivityPtr>
+  copy(SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& source)
+  {
+    SubItemValuePerItem<std::remove_const_t<DataType>, ItemOfItem, ConnectivityPtr> image;
+
+    image.m_connectivity_ptr = source.m_connectivity_ptr;
+    image.m_host_row_map     = source.m_host_row_map;
+    image.m_values           = copy(source.m_values);
+    return image;
+  }
+
   PUGS_INLINE
   bool
   isBuilt() const noexcept
@@ -98,6 +117,17 @@ class SubItemValuePerItem
     return m_connectivity_ptr.use_count() != 0;
   }
 
+  PUGS_INLINE
+  std::shared_ptr<const IConnectivity>
+  connectivity_ptr() const noexcept
+  {
+    if constexpr (std::is_same_v<ConnectivityPtr, ConnectivitySharedPtr>) {
+      return m_connectivity_ptr;
+    } else {
+      return m_connectivity_ptr.lock();
+    }
+  }
+
   template <typename IndexType, typename SubIndexType>
   PUGS_FORCEINLINE DataType&
   operator()(IndexType item_id, SubIndexType i) const noexcept(NO_ASSERT)
@@ -109,32 +139,33 @@ class SubItemValuePerItem
     return m_values[m_host_row_map(size_t{item_id}) + i];
   }
 
-  PUGS_INLINE
-  size_t
-  numberOfValues() const noexcept(NO_ASSERT)
-  {
-    Assert(this->isBuilt());
-    return m_values.size();
-  }
-
   // Following Kokkos logic, these classes are view and const view does allow
   // changes in data
   template <typename ArrayIndexType>
-  DataType& operator[](const ArrayIndexType& i) const noexcept(NO_ASSERT)
+  DataType&
+  operator[](const ArrayIndexType& i) const noexcept(NO_ASSERT)
   {
     static_assert(std::is_integral_v<ArrayIndexType>, "index must be an integral type");
     Assert(this->isBuilt());
-    Assert(i < m_values.size());
+    Assert(static_cast<size_t>(i) < m_values.size());
     return m_values[i];
   }
 
+  PUGS_INLINE
+  size_t
+  numberOfValues() const noexcept(NO_ASSERT)
+  {
+    Assert(this->isBuilt());
+    return m_values.size();
+  }
+
   PUGS_INLINE
   size_t
   numberOfItems() const noexcept(NO_ASSERT)
   {
     Assert(this->isBuilt());
-    Assert(m_host_row_map.extent(0) != 0);
-    return m_host_row_map.extent(0);
+    Assert(m_host_row_map.extent(0) > 0);
+    return m_host_row_map.extent(0) - 1;
   }
 
   template <typename IndexType>
@@ -143,7 +174,7 @@ class SubItemValuePerItem
   {
     static_assert(std::is_same_v<IndexType, ItemId>, "index must be an ItemId");
     Assert(this->isBuilt());
-    Assert(item_id < m_host_row_map.extent(0));
+    Assert(item_id < this->numberOfItems());
     return m_host_row_map(size_t{item_id} + 1) - m_host_row_map(size_t{item_id});
   }
 
@@ -153,7 +184,7 @@ class SubItemValuePerItem
   {
     static_assert(std::is_same_v<IndexType, ItemId>, "index must be an ItemId");
     Assert(this->isBuilt());
-    Assert(item_id < m_host_row_map.extent(0));
+    Assert(item_id < this->numberOfItems());
     const auto& item_begin = m_host_row_map(size_t{item_id});
     const auto& item_end   = m_host_row_map(size_t{item_id} + 1);
     return SubView(m_values, item_begin, item_end);
@@ -167,7 +198,7 @@ class SubItemValuePerItem
   {
     static_assert(std::is_same_v<IndexType, ItemId>, "index must be an ItemId");
     Assert(this->isBuilt());
-    Assert(item_id < m_host_row_map.extent(0));
+    Assert(item_id < this->numberOfItems());
     const auto& item_begin = m_host_row_map(size_t{item_id});
     const auto& item_end   = m_host_row_map(size_t{item_id} + 1);
     return SubView(m_values, item_begin, item_end);
@@ -211,7 +242,6 @@ class SubItemValuePerItem
   {
     static_assert(not std::is_const_v<DataType>, "Cannot allocate SubItemValuePerItem of const data: only "
                                                  "view is supported");
-    ;
 
     ConnectivityMatrix connectivity_matrix = connectivity._getMatrix(item_type, sub_item_type);
 
diff --git a/src/mesh/Synchronizer.hpp b/src/mesh/Synchronizer.hpp
index d23634ac3db35a026b50cb4bd0f8c040c80aa4cd..adf127f238fc201a11ba5b503aed4b926e772a4d 100644
--- a/src/mesh/Synchronizer.hpp
+++ b/src/mesh/Synchronizer.hpp
@@ -2,6 +2,7 @@
 #define SYNCHRONIZER_HPP
 
 #include <mesh/Connectivity.hpp>
+#include <mesh/ItemArray.hpp>
 #include <mesh/ItemValue.hpp>
 #include <utils/Exceptions.hpp>
 #include <utils/Messenger.hpp>
@@ -66,7 +67,7 @@ class Synchronizer
     auto& requested_item_info = this->_getRequestedItemInfo<item_type>();
     requested_item_info       = [&]() {
       std::vector<std::vector<ItemId>> requested_item_vector_info(parallel::size());
-      for (ItemId item_id = 0; item_id < item_owner.size(); ++item_id) {
+      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);
         }
@@ -105,7 +106,7 @@ class Synchronizer
     parallel::exchange(requested_item_number_list_by_proc, provided_item_number_list_by_rank);
 
     std::map<int, ItemId> item_number_to_id_correspondance;
-    for (ItemId item_id = 0; item_id < item_number.size(); ++item_id) {
+    for (ItemId item_id = 0; item_id < item_number.numberOfItems(); ++item_id) {
       item_number_to_id_correspondance[item_number[item_id]] = item_id;
     }
 
@@ -168,6 +169,62 @@ class Synchronizer
     }
   }
 
+  template <typename ConnectivityType, typename DataType, ItemType item_type, typename ConnectivityPtr>
+  PUGS_INLINE void
+  _synchronize(const ConnectivityType& connectivity, ItemArray<DataType, item_type, ConnectivityPtr>& item_array)
+  {
+    static_assert(not std::is_abstract_v<ConnectivityType>, "_synchronize must be called on a concrete connectivity");
+
+    using ItemId = ItemIdT<item_type>;
+
+    const auto& provided_item_info  = this->_getProvidedItemInfo<item_type>();
+    const auto& requested_item_info = this->_getRequestedItemInfo<item_type>();
+
+    Assert(requested_item_info.size() == provided_item_info.size());
+
+    if (provided_item_info.size() == 0) {
+      this->_buildSynchronizeInfo<ConnectivityType, item_type>(connectivity);
+    }
+
+    const size_t size_of_arrays = item_array.sizeOfArrays();
+
+    std::vector<Array<const DataType>> provided_data_list(parallel::size());
+    for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
+      const Array<const ItemId>& provided_item_info_to_rank = provided_item_info[i_rank];
+      Array<DataType> provided_data{provided_item_info_to_rank.size() * size_of_arrays};
+      parallel_for(
+        provided_item_info_to_rank.size(), PUGS_LAMBDA(size_t i) {
+          const size_t j   = i * size_of_arrays;
+          const auto array = item_array[provided_item_info_to_rank[i]];
+          for (size_t k = 0; k < size_of_arrays; ++k) {
+            provided_data[j + k] = array[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 auto& requested_item_info_from_rank = requested_item_info[i_rank];
+      requested_data_list[i_rank] = Array<DataType>{requested_item_info_from_rank.size() * size_of_arrays};
+    }
+
+    parallel::exchange(provided_data_list, requested_data_list);
+
+    for (size_t i_rank = 0; i_rank < parallel::size(); ++i_rank) {
+      const auto& requested_item_info_from_rank = requested_item_info[i_rank];
+      const auto& requested_data                = requested_data_list[i_rank];
+      parallel_for(
+        requested_item_info_from_rank.size(), PUGS_LAMBDA(size_t i) {
+          const size_t j = i * size_of_arrays;
+          auto array     = item_array[requested_item_info_from_rank[i]];
+          for (size_t k = 0; k < size_of_arrays; ++k) {
+            array[k] = requested_data[j + k];
+          }
+        });
+    }
+  }
+
  public:
   template <typename DataType, ItemType item_type, typename ConnectivityPtr>
   PUGS_INLINE void
@@ -189,9 +246,39 @@ class Synchronizer
       this->_synchronize(static_cast<const Connectivity3D&>(connectivity), item_value);
       break;
     }
+      // LCOV_EXCL_START
+    default: {
+      throw UnexpectedError("unexpected dimension");
+    }
+      // LCOV_EXCL_STOP
+    }
+  }
+
+  template <typename DataType, ItemType item_type, typename ConnectivityPtr>
+  PUGS_INLINE void
+  synchronize(ItemArray<DataType, item_type, ConnectivityPtr>& item_value)
+  {
+    Assert(item_value.connectivity_ptr().use_count() > 0, "No connectivity is associated to this ItemValue");
+    const IConnectivity& connectivity = *item_value.connectivity_ptr();
+
+    switch (connectivity.dimension()) {
+    case 1: {
+      this->_synchronize(static_cast<const Connectivity1D&>(connectivity), item_value);
+      break;
+    }
+    case 2: {
+      this->_synchronize(static_cast<const Connectivity2D&>(connectivity), item_value);
+      break;
+    }
+    case 3: {
+      this->_synchronize(static_cast<const Connectivity3D&>(connectivity), item_value);
+      break;
+    }
+      // LCOV_EXCL_START
     default: {
       throw UnexpectedError("unexpected dimension");
     }
+      // LCOV_EXCL_STOP
     }
   }
 
diff --git a/src/output/GnuplotWriter1D.cpp b/src/output/GnuplotWriter1D.cpp
index b25ca82accf3189fbdab58ca4161101647cb463d..79423d5da05b699b995d472313e134a8c9dd4a52 100644
--- a/src/output/GnuplotWriter1D.cpp
+++ b/src/output/GnuplotWriter1D.cpp
@@ -182,7 +182,7 @@ GnuplotWriter1D::_writeItemValues(const std::shared_ptr<const MeshType>& mesh,
         if constexpr (ItemValueT::item_t == item_type) {
           using DataT  = std::decay_t<typename ItemValueT::data_type>;
           size_t index = 0;
-          for (ItemId item_id = 0; item_id < item_value.size(); ++item_id) {
+          for (ItemId item_id = 0; item_id < item_value.numberOfItems(); ++item_id) {
             if (is_owned[item_id]) {
               if constexpr (std::is_arithmetic_v<DataT>) {
                 values[number_of_columns * index + column_number] = item_value[item_id];
diff --git a/src/utils/SubArray.hpp b/src/utils/SubArray.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a5019dec9521039e420aeeef48c0439930a7840c
--- /dev/null
+++ b/src/utils/SubArray.hpp
@@ -0,0 +1,78 @@
+#ifndef SUB_ARRAY_HPP
+#define SUB_ARRAY_HPP
+
+#include <utils/Array.hpp>
+#include <utils/PugsAssert.hpp>
+#include <utils/PugsMacros.hpp>
+#include <utils/PugsUtils.hpp>
+
+#include <algorithm>
+
+template <typename DataType>
+class [[nodiscard]] SubArray
+{
+ public:
+  using data_type  = DataType;
+  using index_type = size_t;
+
+ private:
+  PUGS_RESTRICT DataType* const m_sub_values;
+  const size_t m_size;
+
+  // Allows const version to access our data
+  friend SubArray<std::add_const_t<DataType>>;
+
+ public:
+  PUGS_INLINE size_t size() const noexcept
+  {
+    return m_size;
+  }
+
+  PUGS_INLINE DataType& operator[](index_type i) const noexcept(NO_ASSERT)
+  {
+    Assert(i < m_size);
+    return m_sub_values[i];
+  }
+
+  PUGS_INLINE
+  void fill(const DataType& data) const
+  {
+    static_assert(not std::is_const<DataType>(), "Cannot modify SubArray of const");
+
+    // could consider to use std::fill
+    parallel_for(
+      this->size(), PUGS_LAMBDA(index_type i) { m_sub_values[i] = data; });
+  }
+
+  PUGS_INLINE
+  SubArray& operator=(const SubArray&) = delete;
+
+  PUGS_INLINE
+  SubArray& operator=(SubArray&&) = delete;
+
+  PUGS_INLINE
+  explicit SubArray(const Array<DataType>& array, size_t begin, size_t size)
+    : m_sub_values{&array[0] + begin}, m_size{size}
+  {
+    Assert(begin + size <= array.size(), "SubView is not contained in the source Array");
+  }
+
+  PUGS_INLINE
+  explicit SubArray(DataType* const raw_array, size_t begin, size_t size)
+    : m_sub_values{raw_array + begin}, m_size{size}
+  {}
+
+  PUGS_INLINE
+  SubArray() = delete;
+
+  PUGS_INLINE
+  SubArray(const SubArray&) = delete;
+
+  PUGS_INLINE
+  SubArray(SubArray &&) = delete;
+
+  PUGS_INLINE
+  ~SubArray() = default;
+};
+
+#endif   // SUB_ARRAY_HPP
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index c4413ddc7c6f06665b4efb7b091548fb26140fab..c99154ff0dee78cf7aadf95c70cccfbeb36ffa75 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -85,6 +85,7 @@ add_executable (unit_tests
   test_PugsUtils.cpp
   test_RevisionInfo.cpp
   test_SparseMatrixDescriptor.cpp
+  test_SubArray.cpp
   test_SymbolTable.cpp
   test_Timer.cpp
   test_TinyMatrix.cpp
@@ -99,6 +100,12 @@ add_executable (mpi_unit_tests
   mpi_test_main.cpp
   test_Messenger.cpp
   test_Partitioner.cpp
+  test_ItemArray.cpp
+  test_ItemArrayUtils.cpp
+  test_ItemValue.cpp
+  test_ItemValueUtils.cpp
+  test_SubItemValuePerItem.cpp
+  test_SubItemArrayPerItem.cpp
   )
 
 add_library(test_Pugs_MeshDataBase
diff --git a/tests/test_ItemArray.cpp b/tests/test_ItemArray.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2177d83c2df0dd70674803e7098d0f6b694b9685
--- /dev/null
+++ b/tests/test_ItemArray.cpp
@@ -0,0 +1,256 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/ItemArray.hpp>
+#include <mesh/Mesh.hpp>
+#include <utils/Messenger.hpp>
+
+template class ItemArray<int, ItemType::node>;
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("ItemArray", "[mesh]")
+{
+  SECTION("default constructors")
+  {
+    REQUIRE_NOTHROW(NodeArray<int>{});
+    REQUIRE_NOTHROW(EdgeArray<int>{});
+    REQUIRE_NOTHROW(FaceArray<int>{});
+    REQUIRE_NOTHROW(CellArray<int>{});
+
+    REQUIRE(not NodeArray<int>{}.isBuilt());
+    REQUIRE(not EdgeArray<int>{}.isBuilt());
+    REQUIRE(not FaceArray<int>{}.isBuilt());
+    REQUIRE(not CellArray<int>{}.isBuilt());
+  }
+
+  SECTION("1D")
+  {
+    const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+    const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+
+    REQUIRE_NOTHROW(NodeArray<int>{connectivity, 3});
+    REQUIRE_NOTHROW(EdgeArray<int>{connectivity, 3});
+    REQUIRE_NOTHROW(FaceArray<int>{connectivity, 3});
+    REQUIRE_NOTHROW(CellArray<int>{connectivity, 3});
+
+    REQUIRE(NodeArray<int>{connectivity, 3}.isBuilt());
+    REQUIRE(EdgeArray<int>{connectivity, 3}.isBuilt());
+    REQUIRE(FaceArray<int>{connectivity, 3}.isBuilt());
+    REQUIRE(CellArray<int>{connectivity, 3}.isBuilt());
+
+    NodeArray<int> node_value{connectivity, 3};
+    EdgeArray<int> edge_value{connectivity, 3};
+    FaceArray<int> face_value{connectivity, 3};
+    CellArray<int> cell_value{connectivity, 3};
+
+    REQUIRE(edge_value.numberOfItems() == node_value.numberOfItems());
+    REQUIRE(face_value.numberOfItems() == node_value.numberOfItems());
+    REQUIRE(cell_value.numberOfItems() + 1 == node_value.numberOfItems());
+
+    REQUIRE(node_value.numberOfValues() == 3 * node_value.numberOfItems());
+    REQUIRE(edge_value.numberOfValues() == 3 * edge_value.numberOfItems());
+    REQUIRE(face_value.numberOfValues() == 3 * face_value.numberOfItems());
+    REQUIRE(cell_value.numberOfValues() == 3 * cell_value.numberOfItems());
+
+    REQUIRE(node_value.sizeOfArrays() == 3);
+    REQUIRE(edge_value.sizeOfArrays() == 3);
+    REQUIRE(face_value.sizeOfArrays() == 3);
+    REQUIRE(cell_value.sizeOfArrays() == 3);
+  }
+
+  SECTION("2D")
+  {
+    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+    const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+    REQUIRE_NOTHROW(NodeArray<int>{connectivity, 2});
+    REQUIRE_NOTHROW(EdgeArray<int>{connectivity, 2});
+    REQUIRE_NOTHROW(FaceArray<int>{connectivity, 2});
+    REQUIRE_NOTHROW(CellArray<int>{connectivity, 2});
+
+    REQUIRE(NodeArray<int>{connectivity, 2}.isBuilt());
+    REQUIRE(EdgeArray<int>{connectivity, 2}.isBuilt());
+    REQUIRE(FaceArray<int>{connectivity, 2}.isBuilt());
+    REQUIRE(CellArray<int>{connectivity, 2}.isBuilt());
+
+    NodeArray<int> node_value{connectivity, 2};
+    EdgeArray<int> edge_value{connectivity, 2};
+    FaceArray<int> face_value{connectivity, 2};
+    CellArray<int> cell_value{connectivity, 2};
+
+    REQUIRE(edge_value.numberOfItems() == face_value.numberOfItems());
+
+    REQUIRE(node_value.numberOfValues() == 2 * node_value.numberOfItems());
+    REQUIRE(edge_value.numberOfValues() == 2 * edge_value.numberOfItems());
+    REQUIRE(face_value.numberOfValues() == 2 * face_value.numberOfItems());
+    REQUIRE(cell_value.numberOfValues() == 2 * cell_value.numberOfItems());
+
+    REQUIRE(node_value.sizeOfArrays() == 2);
+    REQUIRE(edge_value.sizeOfArrays() == 2);
+    REQUIRE(face_value.sizeOfArrays() == 2);
+    REQUIRE(cell_value.sizeOfArrays() == 2);
+  }
+
+  SECTION("3D")
+  {
+    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+    REQUIRE_NOTHROW(NodeArray<int>{connectivity, 3});
+    REQUIRE_NOTHROW(EdgeArray<int>{connectivity, 3});
+    REQUIRE_NOTHROW(FaceArray<int>{connectivity, 3});
+    REQUIRE_NOTHROW(CellArray<int>{connectivity, 3});
+
+    REQUIRE(NodeArray<int>{connectivity, 3}.isBuilt());
+    REQUIRE(EdgeArray<int>{connectivity, 3}.isBuilt());
+    REQUIRE(FaceArray<int>{connectivity, 3}.isBuilt());
+    REQUIRE(CellArray<int>{connectivity, 3}.isBuilt());
+
+    NodeArray<int> node_value{connectivity, 3};
+    EdgeArray<int> edge_value{connectivity, 3};
+    FaceArray<int> face_value{connectivity, 3};
+    CellArray<int> cell_value{connectivity, 3};
+
+    REQUIRE(node_value.numberOfValues() == 3 * node_value.numberOfItems());
+    REQUIRE(edge_value.numberOfValues() == 3 * edge_value.numberOfItems());
+    REQUIRE(face_value.numberOfValues() == 3 * face_value.numberOfItems());
+    REQUIRE(cell_value.numberOfValues() == 3 * cell_value.numberOfItems());
+
+    REQUIRE(node_value.sizeOfArrays() == 3);
+    REQUIRE(edge_value.sizeOfArrays() == 3);
+    REQUIRE(face_value.sizeOfArrays() == 3);
+    REQUIRE(cell_value.sizeOfArrays() == 3);
+  }
+
+  SECTION("set values from array")
+  {
+    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+    CellArray<size_t> cell_array{connectivity, 3};
+
+    Array<size_t> array{cell_array.numberOfValues()};
+    for (size_t i = 0; i < array.size(); ++i) {
+      array[i] = i;
+    }
+
+    cell_array = array;
+
+    auto is_same = [](const CellArray<size_t>& cell_array, const Array<size_t>& array) {
+      bool is_same = true;
+      size_t k     = 0;
+      for (CellId cell_id = 0; cell_id < cell_array.numberOfItems(); ++cell_id) {
+        SubArray sub_array = cell_array[cell_id];
+        for (size_t i = 0; i < sub_array.size(); ++i, ++k) {
+          is_same &= (sub_array[i] == array[k]);
+        }
+      }
+      return is_same;
+    };
+
+    REQUIRE(is_same(cell_array, array));
+  }
+
+  SECTION("copy")
+  {
+    auto is_same = [](const auto& cell_array, int value) {
+      bool is_same = true;
+      for (CellId cell_id = 0; cell_id < cell_array.numberOfItems(); ++cell_id) {
+        SubArray sub_array = cell_array[cell_id];
+        for (size_t i = 0; i < sub_array.size(); ++i) {
+          is_same &= (sub_array[i] == value);
+        }
+      }
+      return is_same;
+    };
+
+    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+    CellArray<int> cell_array{connectivity, 4};
+    cell_array.fill(parallel::rank());
+
+    CellArray<const int> cell_array_const_view{cell_array};
+    REQUIRE(cell_array.numberOfValues() == cell_array_const_view.numberOfValues());
+    REQUIRE(is_same(cell_array_const_view, static_cast<std::int64_t>(parallel::rank())));
+
+    CellArray<const int> const_cell_array;
+    const_cell_array = copy(cell_array);
+
+    cell_array.fill(0);
+
+    REQUIRE(is_same(cell_array, 0));
+    REQUIRE(is_same(cell_array_const_view, 0));
+    REQUIRE(is_same(const_cell_array, static_cast<std::int64_t>(parallel::rank())));
+  }
+
+  SECTION("WeakItemArray")
+  {
+    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+    const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+    WeakFaceArray<int> weak_face_array{connectivity, 5};
+
+    weak_face_array.fill(parallel::rank());
+
+    FaceArray<const int> face_array{weak_face_array};
+
+    REQUIRE(face_array.connectivity_ptr() == weak_face_array.connectivity_ptr());
+  }
+
+#ifndef NDEBUG
+  SECTION("error")
+  {
+    SECTION("checking for build ItemArray")
+    {
+      CellArray<int> cell_array;
+      REQUIRE_THROWS_AS(cell_array[CellId{0}], AssertError);
+
+      FaceArray<int> face_array;
+      REQUIRE_THROWS_AS(face_array[FaceId{0}], AssertError);
+
+      EdgeArray<int> edge_array;
+      REQUIRE_THROWS_AS(edge_array[EdgeId{0}], AssertError);
+
+      NodeArray<int> node_array;
+      REQUIRE_THROWS_AS(node_array[NodeId{0}], AssertError);
+    }
+
+    SECTION("checking for bounds violation")
+    {
+      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+      CellArray<int> cell_array{connectivity, 1};
+      CellId invalid_cell_id = connectivity.numberOfCells();
+      REQUIRE_THROWS_AS(cell_array[invalid_cell_id], AssertError);
+
+      FaceArray<int> face_array{connectivity, 2};
+      FaceId invalid_face_id = connectivity.numberOfFaces();
+      REQUIRE_THROWS_AS(face_array[invalid_face_id], AssertError);
+
+      EdgeArray<int> edge_array{connectivity, 1};
+      EdgeId invalid_edge_id = connectivity.numberOfEdges();
+      REQUIRE_THROWS_AS(edge_array[invalid_edge_id], AssertError);
+
+      NodeArray<int> node_array{connectivity, 0};
+      NodeId invalid_node_id = connectivity.numberOfNodes();
+      REQUIRE_THROWS_AS(node_array[invalid_node_id], AssertError);
+    }
+
+    SECTION("set values from invalid array size")
+    {
+      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+      CellArray<size_t> cell_array{connectivity, 2};
+
+      Array<size_t> values{3 + cell_array.numberOfValues()};
+      REQUIRE_THROWS_AS(cell_array = values, AssertError);
+    }
+  }
+#endif   // NDEBUG
+}
diff --git a/tests/test_ItemArrayUtils.cpp b/tests/test_ItemArrayUtils.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dc5611278a166cccdb9225e1eee41de9da6c078a
--- /dev/null
+++ b/tests/test_ItemArrayUtils.cpp
@@ -0,0 +1,770 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.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]")
+{
+  SECTION("Synchronize")
+  {
+    SECTION("1D")
+    {
+      const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+      const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+
+      SECTION("node")
+      {
+        WeakNodeArray<size_t> weak_node_array{connectivity, 4};
+        auto node_number = connectivity.nodeNumber();
+
+        for (NodeId i_node = 0; i_node < mesh_1d.numberOfNodes(); ++i_node) {
+          SubArray array      = weak_node_array[i_node];
+          const size_t number = node_number[i_node];
+          for (size_t i = 0; i < array.size(); ++i) {
+            array[i] = number + (parallel::rank() + 1) * i;
+          }
+        }
+
+        NodeArray<const size_t> node_array{weak_node_array};
+
+        REQUIRE(node_array.connectivity_ptr() == weak_node_array.connectivity_ptr());
+
+        {   // before synchronization
+          auto node_owner    = connectivity.nodeOwner();
+          auto node_is_owned = connectivity.nodeIsOwned();
+
+          bool is_synchronized = (parallel::size() > 1);
+          bool is_valid        = true;
+          for (NodeId i_node = 0; i_node < mesh_1d.numberOfNodes(); ++i_node) {
+            SubArray array      = node_array[i_node];
+            const size_t number = node_number[i_node];
+            const size_t owner  = node_owner[i_node];
+            if (node_is_owned[i_node]) {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_valid &= (array[i] == number + (owner + 1) * i);
+              }
+            } else {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_synchronized &= (array[i] == number + (owner + 1) * i);
+              }
+            }
+          }
+
+          REQUIRE(is_valid);
+          REQUIRE(not is_synchronized);
+        }
+
+        synchronize(weak_node_array);
+
+        {   // after synchronization
+          auto node_owner = connectivity.nodeOwner();
+
+          bool is_synchronized = true;
+          for (NodeId i_node = 0; i_node < mesh_1d.numberOfNodes(); ++i_node) {
+            SubArray array      = node_array[i_node];
+            const size_t number = node_number[i_node];
+            const size_t owner  = node_owner[i_node];
+            for (size_t i = 0; i < array.size(); ++i) {
+              is_synchronized &= (array[i] == number + (owner + 1) * i);
+            }
+          }
+
+          REQUIRE(is_synchronized);
+        }
+      }
+
+      SECTION("edge")
+      {
+        WeakEdgeArray<size_t> weak_edge_array{connectivity, 4};
+        auto edge_number = connectivity.edgeNumber();
+
+        for (EdgeId i_edge = 0; i_edge < mesh_1d.numberOfEdges(); ++i_edge) {
+          SubArray array      = weak_edge_array[i_edge];
+          const size_t number = edge_number[i_edge];
+          for (size_t i = 0; i < array.size(); ++i) {
+            array[i] = number + (parallel::rank() + 1) * i;
+          }
+        }
+
+        EdgeArray<const size_t> edge_array{weak_edge_array};
+
+        REQUIRE(edge_array.connectivity_ptr() == weak_edge_array.connectivity_ptr());
+
+        {   // before synchronization
+          auto edge_owner    = connectivity.edgeOwner();
+          auto edge_is_owned = connectivity.edgeIsOwned();
+
+          bool is_synchronized = (parallel::size() > 1);
+          bool is_valid        = true;
+          for (EdgeId i_edge = 0; i_edge < mesh_1d.numberOfEdges(); ++i_edge) {
+            SubArray array      = edge_array[i_edge];
+            const size_t number = edge_number[i_edge];
+            const size_t owner  = edge_owner[i_edge];
+            if (edge_is_owned[i_edge]) {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_valid &= (array[i] == number + (owner + 1) * i);
+              }
+            } else {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_synchronized &= (array[i] == number + (owner + 1) * i);
+              }
+            }
+          }
+
+          REQUIRE(is_valid);
+          REQUIRE(not is_synchronized);
+        }
+
+        synchronize(weak_edge_array);
+
+        {   // after synchronization
+          auto edge_owner = connectivity.edgeOwner();
+
+          bool is_synchronized = true;
+          for (EdgeId i_edge = 0; i_edge < mesh_1d.numberOfEdges(); ++i_edge) {
+            SubArray array      = edge_array[i_edge];
+            const size_t number = edge_number[i_edge];
+            const size_t owner  = edge_owner[i_edge];
+            for (size_t i = 0; i < array.size(); ++i) {
+              is_synchronized &= (array[i] == number + (owner + 1) * i);
+            }
+          }
+
+          REQUIRE(is_synchronized);
+        }
+      }
+
+      SECTION("face")
+      {
+        WeakFaceArray<size_t> weak_face_array{connectivity, 4};
+        auto face_number = connectivity.faceNumber();
+
+        for (FaceId i_face = 0; i_face < mesh_1d.numberOfFaces(); ++i_face) {
+          SubArray array      = weak_face_array[i_face];
+          const size_t number = face_number[i_face];
+          for (size_t i = 0; i < array.size(); ++i) {
+            array[i] = number + (parallel::rank() + 1) * i;
+          }
+        }
+
+        FaceArray<const size_t> face_array{weak_face_array};
+
+        REQUIRE(face_array.connectivity_ptr() == weak_face_array.connectivity_ptr());
+
+        {   // before synchronization
+          auto face_owner    = connectivity.faceOwner();
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          bool is_synchronized = (parallel::size() > 1);
+          bool is_valid        = true;
+          for (FaceId i_face = 0; i_face < mesh_1d.numberOfFaces(); ++i_face) {
+            SubArray array      = face_array[i_face];
+            const size_t number = face_number[i_face];
+            const size_t owner  = face_owner[i_face];
+            if (face_is_owned[i_face]) {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_valid &= (array[i] == number + (owner + 1) * i);
+              }
+            } else {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_synchronized &= (array[i] == number + (owner + 1) * i);
+              }
+            }
+          }
+
+          REQUIRE(is_valid);
+          REQUIRE(not is_synchronized);
+        }
+
+        synchronize(weak_face_array);
+
+        {   // after synchronization
+          auto face_owner = connectivity.faceOwner();
+
+          bool is_synchronized = true;
+          for (FaceId i_face = 0; i_face < mesh_1d.numberOfFaces(); ++i_face) {
+            SubArray array      = face_array[i_face];
+            const size_t number = face_number[i_face];
+            const size_t owner  = face_owner[i_face];
+            for (size_t i = 0; i < array.size(); ++i) {
+              is_synchronized &= (array[i] == number + (owner + 1) * i);
+            }
+          }
+
+          REQUIRE(is_synchronized);
+        }
+      }
+
+      SECTION("cell")
+      {
+        WeakCellArray<size_t> weak_cell_array{connectivity, 4};
+        auto cell_number = connectivity.cellNumber();
+
+        for (CellId i_cell = 0; i_cell < mesh_1d.numberOfCells(); ++i_cell) {
+          SubArray array      = weak_cell_array[i_cell];
+          const size_t number = cell_number[i_cell];
+          for (size_t i = 0; i < array.size(); ++i) {
+            array[i] = number + (parallel::rank() + 1) * i;
+          }
+        }
+
+        CellArray<const size_t> cell_array{weak_cell_array};
+
+        REQUIRE(cell_array.connectivity_ptr() == weak_cell_array.connectivity_ptr());
+
+        {   // before synchronization
+          auto cell_owner    = connectivity.cellOwner();
+          auto cell_is_owned = connectivity.cellIsOwned();
+
+          bool is_synchronized = (parallel::size() > 1);
+          bool is_valid        = true;
+          for (CellId i_cell = 0; i_cell < mesh_1d.numberOfCells(); ++i_cell) {
+            SubArray array      = cell_array[i_cell];
+            const size_t number = cell_number[i_cell];
+            const size_t owner  = cell_owner[i_cell];
+            if (cell_is_owned[i_cell]) {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_valid &= (array[i] == number + (owner + 1) * i);
+              }
+            } else {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_synchronized &= (array[i] == number + (owner + 1) * i);
+              }
+            }
+          }
+
+          REQUIRE(is_valid);
+          REQUIRE(not is_synchronized);
+        }
+
+        synchronize(weak_cell_array);
+
+        {   // after synchronization
+          auto cell_owner = connectivity.cellOwner();
+
+          bool is_synchronized = true;
+          for (CellId i_cell = 0; i_cell < mesh_1d.numberOfCells(); ++i_cell) {
+            SubArray array      = cell_array[i_cell];
+            const size_t number = cell_number[i_cell];
+            const size_t owner  = cell_owner[i_cell];
+            for (size_t i = 0; i < array.size(); ++i) {
+              is_synchronized &= (array[i] == number + (owner + 1) * i);
+            }
+          }
+
+          REQUIRE(is_synchronized);
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+      const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+      SECTION("node")
+      {
+        WeakNodeArray<size_t> weak_node_array{connectivity, 3};
+        auto node_number = connectivity.nodeNumber();
+
+        for (NodeId i_node = 0; i_node < mesh_2d.numberOfNodes(); ++i_node) {
+          SubArray array      = weak_node_array[i_node];
+          const size_t number = node_number[i_node];
+          for (size_t i = 0; i < array.size(); ++i) {
+            array[i] = number + (parallel::rank() + 1) * i;
+          }
+        }
+
+        NodeArray<const size_t> node_array{weak_node_array};
+
+        REQUIRE(node_array.connectivity_ptr() == weak_node_array.connectivity_ptr());
+
+        {   // before synchronization
+          auto node_owner    = connectivity.nodeOwner();
+          auto node_is_owned = connectivity.nodeIsOwned();
+
+          bool is_synchronized = (parallel::size() > 1);
+          bool is_valid        = true;
+          for (NodeId i_node = 0; i_node < mesh_2d.numberOfNodes(); ++i_node) {
+            SubArray array      = node_array[i_node];
+            const size_t number = node_number[i_node];
+            const size_t owner  = node_owner[i_node];
+            if (node_is_owned[i_node]) {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_valid &= (array[i] == number + (owner + 1) * i);
+              }
+            } else {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_synchronized &= (array[i] == number + (owner + 1) * i);
+              }
+            }
+          }
+
+          REQUIRE(is_valid);
+          REQUIRE(not is_synchronized);
+        }
+
+        synchronize(weak_node_array);
+
+        {   // after synchronization
+          auto node_owner = connectivity.nodeOwner();
+
+          bool is_synchronized = true;
+          for (NodeId i_node = 0; i_node < mesh_2d.numberOfNodes(); ++i_node) {
+            SubArray array      = node_array[i_node];
+            const size_t number = node_number[i_node];
+            const size_t owner  = node_owner[i_node];
+            for (size_t i = 0; i < array.size(); ++i) {
+              is_synchronized &= (array[i] == number + (owner + 1) * i);
+            }
+          }
+
+          REQUIRE(is_synchronized);
+        }
+      }
+
+      SECTION("edge")
+      {
+        WeakEdgeArray<size_t> weak_edge_array{connectivity, 3};
+        auto edge_number = connectivity.edgeNumber();
+
+        for (EdgeId i_edge = 0; i_edge < mesh_2d.numberOfEdges(); ++i_edge) {
+          SubArray array      = weak_edge_array[i_edge];
+          const size_t number = edge_number[i_edge];
+          for (size_t i = 0; i < array.size(); ++i) {
+            array[i] = number + (parallel::rank() + 1) * i;
+          }
+        }
+
+        EdgeArray<const size_t> edge_array{weak_edge_array};
+
+        REQUIRE(edge_array.connectivity_ptr() == weak_edge_array.connectivity_ptr());
+
+        {   // before synchronization
+          auto edge_owner    = connectivity.edgeOwner();
+          auto edge_is_owned = connectivity.edgeIsOwned();
+
+          bool is_synchronized = (parallel::size() > 1);
+          bool is_valid        = true;
+          for (EdgeId i_edge = 0; i_edge < mesh_2d.numberOfEdges(); ++i_edge) {
+            SubArray array      = edge_array[i_edge];
+            const size_t number = edge_number[i_edge];
+            const size_t owner  = edge_owner[i_edge];
+            if (edge_is_owned[i_edge]) {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_valid &= (array[i] == number + (owner + 1) * i);
+              }
+            } else {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_synchronized &= (array[i] == number + (owner + 1) * i);
+              }
+            }
+          }
+
+          REQUIRE(is_valid);
+          REQUIRE(not is_synchronized);
+        }
+
+        synchronize(weak_edge_array);
+
+        {   // after synchronization
+          auto edge_owner = connectivity.edgeOwner();
+
+          bool is_synchronized = true;
+          for (EdgeId i_edge = 0; i_edge < mesh_2d.numberOfEdges(); ++i_edge) {
+            SubArray array      = edge_array[i_edge];
+            const size_t number = edge_number[i_edge];
+            const size_t owner  = edge_owner[i_edge];
+            for (size_t i = 0; i < array.size(); ++i) {
+              is_synchronized &= (array[i] == number + (owner + 1) * i);
+            }
+          }
+
+          REQUIRE(is_synchronized);
+        }
+      }
+
+      SECTION("face")
+      {
+        WeakFaceArray<size_t> weak_face_array{connectivity, 3};
+        auto face_number = connectivity.faceNumber();
+
+        for (FaceId i_face = 0; i_face < mesh_2d.numberOfFaces(); ++i_face) {
+          SubArray array      = weak_face_array[i_face];
+          const size_t number = face_number[i_face];
+          for (size_t i = 0; i < array.size(); ++i) {
+            array[i] = number + (parallel::rank() + 1) * i;
+          }
+        }
+
+        FaceArray<const size_t> face_array{weak_face_array};
+
+        REQUIRE(face_array.connectivity_ptr() == weak_face_array.connectivity_ptr());
+
+        {   // before synchronization
+          auto face_owner    = connectivity.faceOwner();
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          bool is_synchronized = (parallel::size() > 1);
+          bool is_valid        = true;
+          for (FaceId i_face = 0; i_face < mesh_2d.numberOfFaces(); ++i_face) {
+            SubArray array      = face_array[i_face];
+            const size_t number = face_number[i_face];
+            const size_t owner  = face_owner[i_face];
+            if (face_is_owned[i_face]) {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_valid &= (array[i] == number + (owner + 1) * i);
+              }
+            } else {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_synchronized &= (array[i] == number + (owner + 1) * i);
+              }
+            }
+          }
+
+          REQUIRE(is_valid);
+          REQUIRE(not is_synchronized);
+        }
+
+        synchronize(weak_face_array);
+
+        {   // after synchronization
+          auto face_owner = connectivity.faceOwner();
+
+          bool is_synchronized = true;
+          for (FaceId i_face = 0; i_face < mesh_2d.numberOfFaces(); ++i_face) {
+            SubArray array      = face_array[i_face];
+            const size_t number = face_number[i_face];
+            const size_t owner  = face_owner[i_face];
+            for (size_t i = 0; i < array.size(); ++i) {
+              is_synchronized &= (array[i] == number + (owner + 1) * i);
+            }
+          }
+
+          REQUIRE(is_synchronized);
+        }
+      }
+
+      SECTION("cell")
+      {
+        WeakCellArray<size_t> weak_cell_array{connectivity, 3};
+        auto cell_number = connectivity.cellNumber();
+
+        for (CellId i_cell = 0; i_cell < mesh_2d.numberOfCells(); ++i_cell) {
+          SubArray array      = weak_cell_array[i_cell];
+          const size_t number = cell_number[i_cell];
+          for (size_t i = 0; i < array.size(); ++i) {
+            array[i] = number + (parallel::rank() + 1) * i;
+          }
+        }
+
+        CellArray<const size_t> cell_array{weak_cell_array};
+
+        REQUIRE(cell_array.connectivity_ptr() == weak_cell_array.connectivity_ptr());
+
+        {   // before synchronization
+          auto cell_owner    = connectivity.cellOwner();
+          auto cell_is_owned = connectivity.cellIsOwned();
+
+          bool is_synchronized = (parallel::size() > 1);
+          bool is_valid        = true;
+          for (CellId i_cell = 0; i_cell < mesh_2d.numberOfCells(); ++i_cell) {
+            SubArray array      = cell_array[i_cell];
+            const size_t number = cell_number[i_cell];
+            const size_t owner  = cell_owner[i_cell];
+            if (cell_is_owned[i_cell]) {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_valid &= (array[i] == number + (owner + 1) * i);
+              }
+            } else {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_synchronized &= (array[i] == number + (owner + 1) * i);
+              }
+            }
+          }
+
+          REQUIRE(is_valid);
+          REQUIRE(not is_synchronized);
+        }
+
+        synchronize(weak_cell_array);
+
+        {   // after synchronization
+          auto cell_owner = connectivity.cellOwner();
+
+          bool is_synchronized = true;
+          for (CellId i_cell = 0; i_cell < mesh_2d.numberOfCells(); ++i_cell) {
+            SubArray array      = cell_array[i_cell];
+            const size_t number = cell_number[i_cell];
+            const size_t owner  = cell_owner[i_cell];
+            for (size_t i = 0; i < array.size(); ++i) {
+              is_synchronized &= (array[i] == number + (owner + 1) * i);
+            }
+          }
+
+          REQUIRE(is_synchronized);
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+      SECTION("node")
+      {
+        WeakNodeArray<size_t> weak_node_array{connectivity, 4};
+        auto node_number = connectivity.nodeNumber();
+
+        for (NodeId i_node = 0; i_node < mesh_3d.numberOfNodes(); ++i_node) {
+          SubArray array      = weak_node_array[i_node];
+          const size_t number = node_number[i_node];
+          for (size_t i = 0; i < array.size(); ++i) {
+            array[i] = number + (parallel::rank() + 1) * i;
+          }
+        }
+
+        NodeArray<const size_t> node_array{weak_node_array};
+
+        REQUIRE(node_array.connectivity_ptr() == weak_node_array.connectivity_ptr());
+
+        {   // before synchronization
+          auto node_owner    = connectivity.nodeOwner();
+          auto node_is_owned = connectivity.nodeIsOwned();
+
+          bool is_synchronized = (parallel::size() > 1);
+          bool is_valid        = true;
+          for (NodeId i_node = 0; i_node < mesh_3d.numberOfNodes(); ++i_node) {
+            SubArray array      = node_array[i_node];
+            const size_t number = node_number[i_node];
+            const size_t owner  = node_owner[i_node];
+            if (node_is_owned[i_node]) {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_valid &= (array[i] == number + (owner + 1) * i);
+              }
+            } else {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_synchronized &= (array[i] == number + (owner + 1) * i);
+              }
+            }
+          }
+
+          REQUIRE(is_valid);
+          REQUIRE(not is_synchronized);
+        }
+
+        synchronize(weak_node_array);
+
+        {   // after synchronization
+          auto node_owner = connectivity.nodeOwner();
+
+          bool is_synchronized = true;
+          for (NodeId i_node = 0; i_node < mesh_3d.numberOfNodes(); ++i_node) {
+            SubArray array      = node_array[i_node];
+            const size_t number = node_number[i_node];
+            const size_t owner  = node_owner[i_node];
+            for (size_t i = 0; i < array.size(); ++i) {
+              is_synchronized &= (array[i] == number + (owner + 1) * i);
+            }
+          }
+
+          REQUIRE(is_synchronized);
+        }
+      }
+
+      SECTION("edge")
+      {
+        WeakEdgeArray<size_t> weak_edge_array{connectivity, 4};
+        auto edge_number = connectivity.edgeNumber();
+
+        for (EdgeId i_edge = 0; i_edge < mesh_3d.numberOfEdges(); ++i_edge) {
+          SubArray array      = weak_edge_array[i_edge];
+          const size_t number = edge_number[i_edge];
+          for (size_t i = 0; i < array.size(); ++i) {
+            array[i] = number + (parallel::rank() + 1) * i;
+          }
+        }
+
+        EdgeArray<const size_t> edge_array{weak_edge_array};
+
+        REQUIRE(edge_array.connectivity_ptr() == weak_edge_array.connectivity_ptr());
+
+        {   // before synchronization
+          auto edge_owner    = connectivity.edgeOwner();
+          auto edge_is_owned = connectivity.edgeIsOwned();
+
+          bool is_synchronized = (parallel::size() > 1);
+          bool is_valid        = true;
+          for (EdgeId i_edge = 0; i_edge < mesh_3d.numberOfEdges(); ++i_edge) {
+            SubArray array      = edge_array[i_edge];
+            const size_t number = edge_number[i_edge];
+            const size_t owner  = edge_owner[i_edge];
+            if (edge_is_owned[i_edge]) {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_valid &= (array[i] == number + (owner + 1) * i);
+              }
+            } else {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_synchronized &= (array[i] == number + (owner + 1) * i);
+              }
+            }
+          }
+
+          REQUIRE(is_valid);
+          REQUIRE(not is_synchronized);
+        }
+
+        synchronize(weak_edge_array);
+
+        {   // after synchronization
+          auto edge_owner = connectivity.edgeOwner();
+
+          bool is_synchronized = true;
+          for (EdgeId i_edge = 0; i_edge < mesh_3d.numberOfEdges(); ++i_edge) {
+            SubArray array      = edge_array[i_edge];
+            const size_t number = edge_number[i_edge];
+            const size_t owner  = edge_owner[i_edge];
+            for (size_t i = 0; i < array.size(); ++i) {
+              is_synchronized &= (array[i] == number + (owner + 1) * i);
+            }
+          }
+
+          REQUIRE(is_synchronized);
+        }
+      }
+
+      SECTION("face")
+      {
+        WeakFaceArray<size_t> weak_face_array{connectivity, 4};
+        auto face_number = connectivity.faceNumber();
+
+        for (FaceId i_face = 0; i_face < mesh_3d.numberOfFaces(); ++i_face) {
+          SubArray array      = weak_face_array[i_face];
+          const size_t number = face_number[i_face];
+          for (size_t i = 0; i < array.size(); ++i) {
+            array[i] = number + (parallel::rank() + 1) * i;
+          }
+        }
+
+        FaceArray<const size_t> face_array{weak_face_array};
+
+        REQUIRE(face_array.connectivity_ptr() == weak_face_array.connectivity_ptr());
+
+        {   // before synchronization
+          auto face_owner    = connectivity.faceOwner();
+          auto face_is_owned = connectivity.faceIsOwned();
+
+          bool is_synchronized = (parallel::size() > 1);
+          bool is_valid        = true;
+          for (FaceId i_face = 0; i_face < mesh_3d.numberOfFaces(); ++i_face) {
+            SubArray array      = face_array[i_face];
+            const size_t number = face_number[i_face];
+            const size_t owner  = face_owner[i_face];
+            if (face_is_owned[i_face]) {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_valid &= (array[i] == number + (owner + 1) * i);
+              }
+            } else {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_synchronized &= (array[i] == number + (owner + 1) * i);
+              }
+            }
+          }
+
+          REQUIRE(is_valid);
+          REQUIRE(not is_synchronized);
+        }
+
+        synchronize(weak_face_array);
+
+        {   // after synchronization
+          auto face_owner = connectivity.faceOwner();
+
+          bool is_synchronized = true;
+          for (FaceId i_face = 0; i_face < mesh_3d.numberOfFaces(); ++i_face) {
+            SubArray array      = face_array[i_face];
+            const size_t number = face_number[i_face];
+            const size_t owner  = face_owner[i_face];
+            for (size_t i = 0; i < array.size(); ++i) {
+              is_synchronized &= (array[i] == number + (owner + 1) * i);
+            }
+          }
+
+          REQUIRE(is_synchronized);
+        }
+      }
+
+      SECTION("cell")
+      {
+        WeakCellArray<size_t> weak_cell_array{connectivity, 4};
+        auto cell_number = connectivity.cellNumber();
+
+        for (CellId i_cell = 0; i_cell < mesh_3d.numberOfCells(); ++i_cell) {
+          SubArray array      = weak_cell_array[i_cell];
+          const size_t number = cell_number[i_cell];
+          for (size_t i = 0; i < array.size(); ++i) {
+            array[i] = number + (parallel::rank() + 1) * i;
+          }
+        }
+
+        CellArray<const size_t> cell_array{weak_cell_array};
+
+        REQUIRE(cell_array.connectivity_ptr() == weak_cell_array.connectivity_ptr());
+
+        {   // before synchronization
+          auto cell_owner    = connectivity.cellOwner();
+          auto cell_is_owned = connectivity.cellIsOwned();
+
+          bool is_synchronized = (parallel::size() > 1);
+          bool is_valid        = true;
+          for (CellId i_cell = 0; i_cell < mesh_3d.numberOfCells(); ++i_cell) {
+            SubArray array      = cell_array[i_cell];
+            const size_t number = cell_number[i_cell];
+            const size_t owner  = cell_owner[i_cell];
+            if (cell_is_owned[i_cell]) {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_valid &= (array[i] == number + (owner + 1) * i);
+              }
+            } else {
+              for (size_t i = 0; i < array.size(); ++i) {
+                is_synchronized &= (array[i] == number + (owner + 1) * i);
+              }
+            }
+          }
+
+          REQUIRE(is_valid);
+          REQUIRE(not is_synchronized);
+        }
+
+        synchronize(weak_cell_array);
+
+        {   // after synchronization
+          auto cell_owner = connectivity.cellOwner();
+
+          bool is_synchronized = true;
+          for (CellId i_cell = 0; i_cell < mesh_3d.numberOfCells(); ++i_cell) {
+            SubArray array      = cell_array[i_cell];
+            const size_t number = cell_number[i_cell];
+            const size_t owner  = cell_owner[i_cell];
+            for (size_t i = 0; i < array.size(); ++i) {
+              is_synchronized &= (array[i] == number + (owner + 1) * i);
+            }
+          }
+
+          REQUIRE(is_synchronized);
+        }
+      }
+    }
+  }
+}
diff --git a/tests/test_ItemValue.cpp b/tests/test_ItemValue.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..942b4f57bafd0528ed31e82709213207c2dea619
--- /dev/null
+++ b/tests/test_ItemValue.cpp
@@ -0,0 +1,196 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/ItemValue.hpp>
+#include <mesh/Mesh.hpp>
+#include <utils/Messenger.hpp>
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("ItemValue", "[mesh]")
+{
+  SECTION("default constructors")
+  {
+    REQUIRE_NOTHROW(NodeValue<int>{});
+    REQUIRE_NOTHROW(EdgeValue<int>{});
+    REQUIRE_NOTHROW(FaceValue<int>{});
+    REQUIRE_NOTHROW(CellValue<int>{});
+
+    REQUIRE(not NodeValue<int>{}.isBuilt());
+    REQUIRE(not EdgeValue<int>{}.isBuilt());
+    REQUIRE(not FaceValue<int>{}.isBuilt());
+    REQUIRE(not CellValue<int>{}.isBuilt());
+  }
+
+  SECTION("1D")
+  {
+    const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+    const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+
+    REQUIRE_NOTHROW(NodeValue<int>{connectivity});
+    REQUIRE_NOTHROW(EdgeValue<int>{connectivity});
+    REQUIRE_NOTHROW(FaceValue<int>{connectivity});
+    REQUIRE_NOTHROW(CellValue<int>{connectivity});
+
+    REQUIRE(NodeValue<int>{connectivity}.isBuilt());
+    REQUIRE(EdgeValue<int>{connectivity}.isBuilt());
+    REQUIRE(FaceValue<int>{connectivity}.isBuilt());
+    REQUIRE(CellValue<int>{connectivity}.isBuilt());
+
+    NodeValue<int> node_value{connectivity};
+    EdgeValue<int> edge_value{connectivity};
+    FaceValue<int> face_value{connectivity};
+    CellValue<int> cell_value{connectivity};
+
+    REQUIRE(edge_value.numberOfItems() == node_value.numberOfItems());
+    REQUIRE(face_value.numberOfItems() == node_value.numberOfItems());
+    REQUIRE(cell_value.numberOfItems() + 1 == node_value.numberOfItems());
+  }
+
+  SECTION("2D")
+  {
+    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+    const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+    REQUIRE_NOTHROW(NodeValue<int>{connectivity});
+    REQUIRE_NOTHROW(EdgeValue<int>{connectivity});
+    REQUIRE_NOTHROW(FaceValue<int>{connectivity});
+    REQUIRE_NOTHROW(CellValue<int>{connectivity});
+
+    REQUIRE(NodeValue<int>{connectivity}.isBuilt());
+    REQUIRE(EdgeValue<int>{connectivity}.isBuilt());
+    REQUIRE(FaceValue<int>{connectivity}.isBuilt());
+    REQUIRE(CellValue<int>{connectivity}.isBuilt());
+
+    EdgeValue<int> edge_value{connectivity};
+    FaceValue<int> face_value{connectivity};
+
+    REQUIRE(edge_value.numberOfItems() == face_value.numberOfItems());
+  }
+
+  SECTION("3D")
+  {
+    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+    REQUIRE_NOTHROW(NodeValue<int>{connectivity});
+    REQUIRE_NOTHROW(EdgeValue<int>{connectivity});
+    REQUIRE_NOTHROW(FaceValue<int>{connectivity});
+    REQUIRE_NOTHROW(CellValue<int>{connectivity});
+
+    REQUIRE(NodeValue<int>{connectivity}.isBuilt());
+    REQUIRE(EdgeValue<int>{connectivity}.isBuilt());
+    REQUIRE(FaceValue<int>{connectivity}.isBuilt());
+    REQUIRE(CellValue<int>{connectivity}.isBuilt());
+  }
+
+  SECTION("set values from array")
+  {
+    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+    CellValue<size_t> cell_value{connectivity};
+
+    Array<size_t> values{cell_value.numberOfItems()};
+    for (size_t i = 0; i < values.size(); ++i) {
+      values[i] = i;
+    }
+
+    cell_value = values;
+
+    for (CellId i_cell = 0; i_cell < mesh_3d.numberOfCells(); ++i_cell) {
+      REQUIRE(cell_value[i_cell] == i_cell);
+    }
+  }
+
+  SECTION("copy")
+  {
+    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+    CellValue<int> cell_value{connectivity};
+    cell_value.fill(parallel::rank());
+
+    CellValue<const int> const_cell_value;
+    const_cell_value = copy(cell_value);
+
+    cell_value.fill(0);
+
+    for (CellId i_cell = 0; i_cell < mesh_3d.numberOfCells(); ++i_cell) {
+      REQUIRE(cell_value[i_cell] == 0);
+    }
+
+    for (CellId i_cell = 0; i_cell < mesh_3d.numberOfCells(); ++i_cell) {
+      REQUIRE(const_cell_value[i_cell] == static_cast<std::int64_t>(parallel::rank()));
+    }
+  }
+
+  SECTION("WeakItemValue")
+  {
+    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+    const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+    WeakFaceValue<int> weak_face_value{connectivity};
+
+    weak_face_value.fill(parallel::rank());
+
+    FaceValue<const int> face_value{weak_face_value};
+
+    REQUIRE(face_value.connectivity_ptr() == weak_face_value.connectivity_ptr());
+  }
+
+#ifndef NDEBUG
+  SECTION("error")
+  {
+    SECTION("checking for build ItemValue")
+    {
+      CellValue<int> cell_value;
+      REQUIRE_THROWS_AS(cell_value[CellId{0}], AssertError);
+
+      FaceValue<int> face_value;
+      REQUIRE_THROWS_AS(face_value[FaceId{0}], AssertError);
+
+      EdgeValue<int> edge_value;
+      REQUIRE_THROWS_AS(edge_value[EdgeId{0}], AssertError);
+
+      NodeValue<int> node_value;
+      REQUIRE_THROWS_AS(node_value[NodeId{0}], AssertError);
+    }
+
+    SECTION("checking for bounds violation")
+    {
+      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+      CellValue<int> cell_value{connectivity};
+      CellId invalid_cell_id = connectivity.numberOfCells();
+      REQUIRE_THROWS_AS(cell_value[invalid_cell_id], AssertError);
+
+      FaceValue<int> face_value{connectivity};
+      FaceId invalid_face_id = connectivity.numberOfFaces();
+      REQUIRE_THROWS_AS(face_value[invalid_face_id], AssertError);
+
+      EdgeValue<int> edge_value{connectivity};
+      EdgeId invalid_edge_id = connectivity.numberOfEdges();
+      REQUIRE_THROWS_AS(edge_value[invalid_edge_id], AssertError);
+
+      NodeValue<int> node_value{connectivity};
+      NodeId invalid_node_id = connectivity.numberOfNodes();
+      REQUIRE_THROWS_AS(node_value[invalid_node_id], AssertError);
+    }
+
+    SECTION("set values from invalid array size")
+    {
+      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+      CellValue<size_t> cell_value{connectivity};
+
+      Array<size_t> values{3 + cell_value.numberOfItems()};
+      REQUIRE_THROWS_AS(cell_value = values, AssertError);
+    }
+  }
+#endif   // NDEBUG
+}
diff --git a/tests/test_ItemValueUtils.cpp b/tests/test_ItemValueUtils.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f54362188d6e9519d28abb47e69385ba11ccfdb8
--- /dev/null
+++ b/tests/test_ItemValueUtils.cpp
@@ -0,0 +1,241 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.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]")
+{
+  SECTION("Synchronize")
+  {
+    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+    const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+    WeakFaceValue<int> weak_face_value{connectivity};
+
+    weak_face_value.fill(parallel::rank());
+
+    FaceValue<const int> face_value{weak_face_value};
+
+    REQUIRE(face_value.connectivity_ptr() == weak_face_value.connectivity_ptr());
+
+    {   // before synchronization
+      auto face_owner    = connectivity.faceOwner();
+      auto face_is_owned = connectivity.faceIsOwned();
+
+      for (FaceId i_face = 0; i_face < mesh_2d.numberOfFaces(); ++i_face) {
+        if (face_is_owned[i_face]) {
+          REQUIRE(face_owner[i_face] == face_value[i_face]);
+        } else {
+          REQUIRE(face_owner[i_face] != face_value[i_face]);
+        }
+      }
+    }
+
+    synchronize(weak_face_value);
+
+    {   // after synchronization
+      auto face_owner    = connectivity.faceOwner();
+      auto face_is_owned = connectivity.faceIsOwned();
+
+      for (FaceId i_face = 0; i_face < mesh_2d.numberOfFaces(); ++i_face) {
+        REQUIRE(face_owner[i_face] == face_value[i_face]);
+      }
+    }
+  }
+
+  SECTION("min")
+  {
+    SECTION("1D")
+    {
+      const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+      const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+
+      CellValue<int> cell_value{connectivity};
+      cell_value.fill(-1);
+
+      auto cell_is_owned = connectivity.cellIsOwned();
+      parallel_for(
+        mesh_1d.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+          if (cell_is_owned[cell_id]) {
+            cell_value[cell_id] = 10 + parallel::rank();
+          }
+        });
+
+      REQUIRE(min(cell_value) == 10);
+    }
+
+    SECTION("2D")
+    {
+      const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+      const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+      CellValue<int> cell_value{connectivity};
+      cell_value.fill(-1);
+
+      auto cell_is_owned = connectivity.cellIsOwned();
+      parallel_for(
+        mesh_2d.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+          if (cell_is_owned[cell_id]) {
+            cell_value[cell_id] = 10 + parallel::rank();
+          }
+        });
+
+      REQUIRE(min(cell_value) == 10);
+    }
+
+    SECTION("3D")
+    {
+      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+      CellValue<int> cell_value{connectivity};
+      cell_value.fill(-1);
+
+      auto cell_is_owned = connectivity.cellIsOwned();
+      parallel_for(
+        mesh_3d.numberOfCells(), PUGS_LAMBDA(CellId cell_id) {
+          if (cell_is_owned[cell_id]) {
+            cell_value[cell_id] = 10 + parallel::rank();
+          }
+        });
+
+      REQUIRE(min(cell_value) == 10);
+    }
+  }
+
+  SECTION("max")
+  {
+    SECTION("1D")
+    {
+      const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+      const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+
+      CellValue<size_t> cell_value{connectivity};
+      cell_value.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]) {
+            cell_value[cell_id] = parallel::rank() + 1;
+          }
+        });
+
+      REQUIRE(max(cell_value) == parallel::size());
+    }
+
+    SECTION("2D")
+    {
+      const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+      const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+      CellValue<size_t> cell_value{connectivity};
+      cell_value.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]) {
+            cell_value[cell_id] = parallel::rank() + 1;
+          }
+        });
+
+      REQUIRE(max(cell_value) == parallel::size());
+    }
+
+    SECTION("3D")
+    {
+      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+      CellValue<size_t> cell_value{connectivity};
+      cell_value.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]) {
+            cell_value[cell_id] = parallel::rank() + 1;
+          }
+        });
+
+      REQUIRE(max(cell_value) == parallel::size());
+    }
+  }
+
+  SECTION("sum")
+  {
+    SECTION("1D")
+    {
+      const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+      const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+
+      CellValue<size_t> cell_value{connectivity};
+      cell_value.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_value) == 5 * global_number_of_cells);
+    }
+
+    SECTION("2D")
+    {
+      const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+      const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+      FaceValue<size_t> face_value{connectivity};
+      face_value.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_value) == 2 * global_number_of_faces);
+    }
+
+    SECTION("3D")
+    {
+      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+      NodeValue<size_t> node_value{connectivity};
+      node_value.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_value) == 3 * global_number_of_nodes);
+    }
+  }
+}
diff --git a/tests/test_SubArray.cpp b/tests/test_SubArray.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b0fdec7882d7ee3504cb24bdfb76b796649f8202
--- /dev/null
+++ b/tests/test_SubArray.cpp
@@ -0,0 +1,66 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <utils/PugsAssert.hpp>
+#include <utils/SubArray.hpp>
+#include <utils/Types.hpp>
+
+// Instantiate to ensure full coverage is performed
+template class SubArray<int>;
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("SubArray", "[utils]")
+{
+  Array<int> a(10);
+  REQUIRE(a.size() == 10);
+
+  SECTION("shared values")
+  {
+    SubArray sub_a{a, 0, 10};
+    for (size_t i = 0; i < sub_a.size(); ++i) {
+      sub_a[i] = 2 * i;
+    }
+
+    REQUIRE(((a[0] == 0) and (a[1] == 2) and (a[2] == 4) and (a[3] == 6) and (a[4] == 8) and (a[5] == 10) and
+             (a[6] == 12) and (a[7] == 14) and (a[8] == 16) and (a[9] == 18)));
+
+    for (size_t i = 0; i < a.size(); ++i) {
+      a[i] = (i + 1) * (2 * i + 1);
+    }
+
+    REQUIRE(((sub_a[0] == 1) and (sub_a[1] == 6) and (sub_a[2] == 15) and (sub_a[3] == 28) and (sub_a[4] == 45) and
+             (sub_a[5] == 66) and (sub_a[6] == 91) and (sub_a[7] == 120) and (sub_a[8] == 153) and (sub_a[9] == 190)));
+  }
+
+  SECTION("sub array")
+  {
+    a.fill(0);
+    SubArray sub_a{a, 5, 5};
+    for (size_t i = 0; i < sub_a.size(); ++i) {
+      sub_a[i] = i + 1;
+    }
+
+    REQUIRE(((a[0] == 0) and (a[1] == 0) and (a[2] == 0) and (a[3] == 0) and (a[4] == 0) and (a[5] == 1) and
+             (a[6] == 2) and (a[7] == 3) and (a[8] == 4) and (a[9] == 5)));
+
+    for (size_t i = 0; i < a.size(); ++i) {
+      a[i] = (i + 1) * (2 * i + 1);
+    }
+
+    REQUIRE(((sub_a[0] == 66) and (sub_a[1] == 91) and (sub_a[2] == 120) and (sub_a[3] == 153) and (sub_a[4] == 190)));
+  }
+
+#ifndef NDEBUG
+  SECTION("errors")
+  {
+    a.fill(0);
+    SubArray<int> sub_a{a, 5, 5};
+    for (size_t i = 0; i < sub_a.size(); ++i) {
+      sub_a[i] = i + 1;
+    }
+
+    REQUIRE_THROWS_AS(sub_a[5], AssertError);
+  }
+#endif   // NDEBUG
+}
diff --git a/tests/test_SubItemArrayPerItem.cpp b/tests/test_SubItemArrayPerItem.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5438398b0c1d5ba0559214473f41d13dba3cb8b4
--- /dev/null
+++ b/tests/test_SubItemArrayPerItem.cpp
@@ -0,0 +1,869 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/SubItemArrayPerItem.hpp>
+#include <utils/Messenger.hpp>
+
+// Instantiate to ensure full coverage is performed
+template class SubItemArrayPerItem<size_t, NodeOfCell>;
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("SubItemArrayPerItem", "[mesh]")
+{
+  SECTION("default constructors")
+  {
+    REQUIRE_NOTHROW(NodeArrayPerEdge<int>{});
+    REQUIRE_NOTHROW(NodeArrayPerFace<int>{});
+    REQUIRE_NOTHROW(NodeArrayPerCell<int>{});
+
+    REQUIRE_NOTHROW(EdgeArrayPerNode<int>{});
+    REQUIRE_NOTHROW(EdgeArrayPerFace<int>{});
+    REQUIRE_NOTHROW(EdgeArrayPerCell<int>{});
+
+    REQUIRE_NOTHROW(FaceArrayPerNode<int>{});
+    REQUIRE_NOTHROW(FaceArrayPerEdge<int>{});
+    REQUIRE_NOTHROW(FaceArrayPerCell<int>{});
+
+    REQUIRE_NOTHROW(CellArrayPerNode<int>{});
+    REQUIRE_NOTHROW(CellArrayPerEdge<int>{});
+    REQUIRE_NOTHROW(CellArrayPerFace<int>{});
+
+    REQUIRE(not NodeArrayPerEdge<int>{}.isBuilt());
+    REQUIRE(not NodeArrayPerFace<int>{}.isBuilt());
+    REQUIRE(not NodeArrayPerCell<int>{}.isBuilt());
+
+    REQUIRE(not EdgeArrayPerNode<int>{}.isBuilt());
+    REQUIRE(not EdgeArrayPerFace<int>{}.isBuilt());
+    REQUIRE(not EdgeArrayPerCell<int>{}.isBuilt());
+
+    REQUIRE(not FaceArrayPerNode<int>{}.isBuilt());
+    REQUIRE(not FaceArrayPerEdge<int>{}.isBuilt());
+    REQUIRE(not FaceArrayPerCell<int>{}.isBuilt());
+
+    REQUIRE(not CellArrayPerNode<int>{}.isBuilt());
+    REQUIRE(not CellArrayPerEdge<int>{}.isBuilt());
+    REQUIRE(not CellArrayPerFace<int>{}.isBuilt());
+  }
+
+  SECTION("dimensions")
+  {
+    auto number_of_values = [](const auto& sub_item_array_per_item) -> size_t {
+      using SubItemArrayPerItemType = std::decay_t<decltype(sub_item_array_per_item)>;
+      using ItemId                  = typename SubItemArrayPerItemType::ItemId;
+
+      size_t number = 0;
+      for (ItemId item_id = 0; item_id < sub_item_array_per_item.numberOfItems(); ++item_id) {
+        for (size_t i = 0; i < sub_item_array_per_item.numberOfSubArrays(item_id); ++i) {
+          number += sub_item_array_per_item(item_id, i).size();
+        }
+      }
+      return number;
+    };
+
+    SECTION("1D")
+    {
+      const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+      const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+
+      SECTION("per cell")
+      {
+        NodeArrayPerCell<int> node_array_per_cell{connectivity, 3};
+        REQUIRE(node_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(node_array_per_cell.numberOfValues() == number_of_values(node_array_per_cell));
+        REQUIRE(node_array_per_cell.sizeOfArrays() == 3);
+
+        auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+        {
+          bool is_correct = true;
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            is_correct &=
+              (cell_to_node_matrix[cell_id].size() == node_array_per_cell.numberOfSubArrays(cell_id)) and
+              (node_array_per_cell.itemArrays(cell_id).size() == node_array_per_cell.numberOfSubArrays(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        const NodeArrayPerCell<const int> const_node_array_per_cell = node_array_per_cell;
+        {
+          bool is_correct = true;
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            is_correct &=
+              (const_node_array_per_cell.itemArrays(cell_id).size() == node_array_per_cell.numberOfSubArrays(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 4};
+        REQUIRE(edge_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(edge_array_per_cell.numberOfValues() == number_of_values(edge_array_per_cell));
+        REQUIRE(edge_array_per_cell.sizeOfArrays() == 4);
+
+        auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
+        {
+          bool is_correct = true;
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_array_per_cell.numberOfSubArrays(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        FaceArrayPerCell<int> face_array_per_cell{connectivity, 2};
+        REQUIRE(face_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(face_array_per_cell.numberOfValues() == number_of_values(face_array_per_cell));
+        REQUIRE(face_array_per_cell.sizeOfArrays() == 2);
+
+        auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
+        {
+          bool is_correct = true;
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            is_correct &= (cell_to_face_matrix[cell_id].size() == face_array_per_cell.numberOfSubArrays(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per face")
+      {
+        CellArrayPerFace<int> cell_array_per_face{connectivity, 2};
+        REQUIRE(cell_array_per_face.numberOfItems() == connectivity.numberOfFaces());
+        REQUIRE(cell_array_per_face.numberOfValues() == number_of_values(cell_array_per_face));
+        REQUIRE(cell_array_per_face.sizeOfArrays() == 2);
+
+        auto face_to_cell_matrix = connectivity.faceToCellMatrix();
+        {
+          bool is_correct = true;
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            is_correct &= (face_to_cell_matrix[face_id].size() == cell_array_per_face.numberOfSubArrays(face_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per edge")
+      {
+        CellArrayPerEdge<int> cell_array_per_edge{connectivity, 3};
+        REQUIRE(cell_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
+        REQUIRE(cell_array_per_edge.numberOfValues() == number_of_values(cell_array_per_edge));
+        REQUIRE(cell_array_per_edge.sizeOfArrays() == 3);
+
+        auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
+        {
+          bool is_correct = true;
+          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+            is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_array_per_edge.numberOfSubArrays(edge_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per node")
+      {
+        CellArrayPerNode<int> cell_array_per_node{connectivity, 4};
+        REQUIRE(cell_array_per_node.numberOfItems() == connectivity.numberOfNodes());
+        REQUIRE(cell_array_per_node.numberOfValues() == number_of_values(cell_array_per_node));
+        REQUIRE(cell_array_per_node.sizeOfArrays() == 4);
+
+        auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+        {
+          bool is_correct = true;
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            is_correct &= (node_to_cell_matrix[node_id].size() == cell_array_per_node.numberOfSubArrays(node_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+      const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+      SECTION("per cell")
+      {
+        NodeArrayPerCell<int> node_array_per_cell{connectivity, 5};
+        REQUIRE(node_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(node_array_per_cell.numberOfValues() == number_of_values(node_array_per_cell));
+        REQUIRE(node_array_per_cell.sizeOfArrays() == 5);
+
+        auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+        {
+          bool is_correct = true;
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            is_correct &= (cell_to_node_matrix[cell_id].size() == node_array_per_cell.numberOfSubArrays(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 4};
+        REQUIRE(edge_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(edge_array_per_cell.numberOfValues() == number_of_values(edge_array_per_cell));
+        REQUIRE(edge_array_per_cell.sizeOfArrays() == 4);
+
+        auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
+        {
+          bool is_correct = true;
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_array_per_cell.numberOfSubArrays(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        FaceArrayPerCell<int> face_array_per_cell{connectivity, 3};
+        REQUIRE(face_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(face_array_per_cell.numberOfValues() == number_of_values(face_array_per_cell));
+        REQUIRE(face_array_per_cell.sizeOfArrays() == 3);
+
+        auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
+        {
+          bool is_correct = true;
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            is_correct &= (cell_to_face_matrix[cell_id].size() == face_array_per_cell.numberOfSubArrays(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per face")
+      {
+        CellArrayPerFace<int> cell_array_per_face{connectivity, 3};
+        REQUIRE(cell_array_per_face.numberOfItems() == connectivity.numberOfFaces());
+        REQUIRE(cell_array_per_face.numberOfValues() == number_of_values(cell_array_per_face));
+        REQUIRE(cell_array_per_face.sizeOfArrays() == 3);
+
+        auto face_to_cell_matrix = connectivity.faceToCellMatrix();
+        {
+          bool is_correct = true;
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            is_correct &= (face_to_cell_matrix[face_id].size() == cell_array_per_face.numberOfSubArrays(face_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        NodeArrayPerFace<int> node_array_per_face{connectivity, 2};
+        REQUIRE(node_array_per_face.numberOfItems() == connectivity.numberOfFaces());
+        REQUIRE(node_array_per_face.numberOfValues() == number_of_values(node_array_per_face));
+        REQUIRE(node_array_per_face.sizeOfArrays() == 2);
+
+        auto face_to_node_matrix = connectivity.faceToNodeMatrix();
+        {
+          bool is_correct = true;
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            is_correct &= (face_to_node_matrix[face_id].size() == node_array_per_face.numberOfSubArrays(face_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per edge")
+      {
+        CellArrayPerEdge<int> cell_array_per_edge{connectivity, 3};
+        REQUIRE(cell_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
+        REQUIRE(cell_array_per_edge.numberOfValues() == number_of_values(cell_array_per_edge));
+        REQUIRE(cell_array_per_edge.sizeOfArrays() == 3);
+
+        auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
+        {
+          bool is_correct = true;
+          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+            is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_array_per_edge.numberOfSubArrays(edge_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        NodeArrayPerEdge<int> node_array_per_edge{connectivity, 5};
+        REQUIRE(node_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
+        REQUIRE(node_array_per_edge.numberOfValues() == number_of_values(node_array_per_edge));
+        REQUIRE(node_array_per_edge.sizeOfArrays() == 5);
+
+        auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
+        {
+          bool is_correct = true;
+          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+            is_correct &= (edge_to_node_matrix[edge_id].size() == node_array_per_edge.numberOfSubArrays(edge_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per node")
+      {
+        EdgeArrayPerNode<int> edge_array_per_node{connectivity, 4};
+        REQUIRE(edge_array_per_node.numberOfItems() == connectivity.numberOfNodes());
+        REQUIRE(edge_array_per_node.numberOfValues() == number_of_values(edge_array_per_node));
+        REQUIRE(edge_array_per_node.sizeOfArrays() == 4);
+
+        auto node_to_edge_matrix = connectivity.nodeToEdgeMatrix();
+        {
+          bool is_correct = true;
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            is_correct &= (node_to_edge_matrix[node_id].size() == edge_array_per_node.numberOfSubArrays(node_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        FaceArrayPerNode<int> face_array_per_node{connectivity, 3};
+        REQUIRE(face_array_per_node.numberOfItems() == connectivity.numberOfNodes());
+        REQUIRE(face_array_per_node.numberOfValues() == number_of_values(face_array_per_node));
+        REQUIRE(face_array_per_node.sizeOfArrays() == 3);
+
+        auto node_to_face_matrix = connectivity.nodeToFaceMatrix();
+        {
+          bool is_correct = true;
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            is_correct &= (node_to_face_matrix[node_id].size() == face_array_per_node.numberOfSubArrays(node_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        CellArrayPerNode<int> cell_array_per_node{connectivity, 2};
+        REQUIRE(cell_array_per_node.numberOfItems() == connectivity.numberOfNodes());
+        REQUIRE(cell_array_per_node.numberOfValues() == number_of_values(cell_array_per_node));
+        REQUIRE(cell_array_per_node.sizeOfArrays() == 2);
+
+        auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+        {
+          bool is_correct = true;
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            is_correct &= (node_to_cell_matrix[node_id].size() == cell_array_per_node.numberOfSubArrays(node_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+    }
+    SECTION("3D")
+    {
+      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+      SECTION("per cell")
+      {
+        NodeArrayPerCell<int> node_array_per_cell{connectivity, 3};
+        REQUIRE(node_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(node_array_per_cell.numberOfValues() == number_of_values(node_array_per_cell));
+        REQUIRE(node_array_per_cell.sizeOfArrays() == 3);
+
+        auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+        {
+          bool is_correct = true;
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            is_correct &= (cell_to_node_matrix[cell_id].size() == node_array_per_cell.numberOfSubArrays(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 4};
+        REQUIRE(edge_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(edge_array_per_cell.numberOfValues() == number_of_values(edge_array_per_cell));
+        REQUIRE(edge_array_per_cell.sizeOfArrays() == 4);
+
+        auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
+        {
+          bool is_correct = true;
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_array_per_cell.numberOfSubArrays(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        FaceArrayPerCell<int> face_array_per_cell{connectivity, 3};
+        REQUIRE(face_array_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(face_array_per_cell.numberOfValues() == number_of_values(face_array_per_cell));
+        REQUIRE(face_array_per_cell.sizeOfArrays() == 3);
+
+        auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
+        {
+          bool is_correct = true;
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            is_correct &= (cell_to_face_matrix[cell_id].size() == face_array_per_cell.numberOfSubArrays(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per face")
+      {
+        CellArrayPerFace<int> cell_array_per_face{connectivity, 5};
+        REQUIRE(cell_array_per_face.numberOfItems() == connectivity.numberOfFaces());
+        REQUIRE(cell_array_per_face.numberOfValues() == number_of_values(cell_array_per_face));
+        REQUIRE(cell_array_per_face.sizeOfArrays() == 5);
+
+        auto face_to_cell_matrix = connectivity.faceToCellMatrix();
+        {
+          bool is_correct = true;
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            is_correct &= (face_to_cell_matrix[face_id].size() == cell_array_per_face.numberOfSubArrays(face_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        EdgeArrayPerFace<int> edge_array_per_face{connectivity, 3};
+        REQUIRE(edge_array_per_face.numberOfItems() == connectivity.numberOfFaces());
+        REQUIRE(edge_array_per_face.numberOfValues() == number_of_values(edge_array_per_face));
+        REQUIRE(edge_array_per_face.sizeOfArrays() == 3);
+
+        auto face_to_edge_matrix = connectivity.faceToEdgeMatrix();
+        {
+          bool is_correct = true;
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            is_correct &= (face_to_edge_matrix[face_id].size() == edge_array_per_face.numberOfSubArrays(face_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        NodeArrayPerFace<int> node_array_per_face{connectivity, 2};
+        REQUIRE(node_array_per_face.numberOfItems() == connectivity.numberOfFaces());
+        REQUIRE(node_array_per_face.numberOfValues() == number_of_values(node_array_per_face));
+        REQUIRE(node_array_per_face.sizeOfArrays() == 2);
+
+        auto face_to_node_matrix = connectivity.faceToNodeMatrix();
+        {
+          bool is_correct = true;
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            is_correct &= (face_to_node_matrix[face_id].size() == node_array_per_face.numberOfSubArrays(face_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per edge")
+      {
+        CellArrayPerEdge<int> cell_array_per_edge{connectivity, 3};
+        REQUIRE(cell_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
+        REQUIRE(cell_array_per_edge.numberOfValues() == number_of_values(cell_array_per_edge));
+        REQUIRE(cell_array_per_edge.sizeOfArrays() == 3);
+
+        auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
+        {
+          bool is_correct = true;
+          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+            is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_array_per_edge.numberOfSubArrays(edge_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        FaceArrayPerEdge<int> face_array_per_edge{connectivity, 5};
+        REQUIRE(face_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
+        REQUIRE(face_array_per_edge.numberOfValues() == number_of_values(face_array_per_edge));
+        REQUIRE(face_array_per_edge.sizeOfArrays() == 5);
+
+        auto edge_to_face_matrix = connectivity.edgeToFaceMatrix();
+        {
+          bool is_correct = true;
+          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+            is_correct &= (edge_to_face_matrix[edge_id].size() == face_array_per_edge.numberOfSubArrays(edge_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        NodeArrayPerEdge<int> node_array_per_edge{connectivity, 3};
+        REQUIRE(node_array_per_edge.numberOfItems() == connectivity.numberOfEdges());
+        REQUIRE(node_array_per_edge.numberOfValues() == number_of_values(node_array_per_edge));
+        REQUIRE(node_array_per_edge.sizeOfArrays() == 3);
+
+        auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
+        {
+          bool is_correct = true;
+          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+            is_correct &= (edge_to_node_matrix[edge_id].size() == node_array_per_edge.numberOfSubArrays(edge_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per node")
+      {
+        EdgeArrayPerNode<int> edge_array_per_node{connectivity, 3};
+        REQUIRE(edge_array_per_node.numberOfItems() == connectivity.numberOfNodes());
+        REQUIRE(edge_array_per_node.numberOfValues() == number_of_values(edge_array_per_node));
+        REQUIRE(edge_array_per_node.sizeOfArrays() == 3);
+
+        auto node_to_edge_matrix = connectivity.nodeToEdgeMatrix();
+        {
+          bool is_correct = true;
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            is_correct &= (node_to_edge_matrix[node_id].size() == edge_array_per_node.numberOfSubArrays(node_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        FaceArrayPerNode<int> face_array_per_node{connectivity, 4};
+        REQUIRE(face_array_per_node.numberOfItems() == connectivity.numberOfNodes());
+        REQUIRE(face_array_per_node.numberOfValues() == number_of_values(face_array_per_node));
+        REQUIRE(face_array_per_node.sizeOfArrays() == 4);
+
+        auto node_to_face_matrix = connectivity.nodeToFaceMatrix();
+        {
+          bool is_correct = true;
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            is_correct &= (node_to_face_matrix[node_id].size() == face_array_per_node.numberOfSubArrays(node_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        CellArrayPerNode<int> cell_array_per_node{connectivity, 5};
+        REQUIRE(cell_array_per_node.numberOfItems() == connectivity.numberOfNodes());
+        REQUIRE(cell_array_per_node.numberOfValues() == number_of_values(cell_array_per_node));
+        REQUIRE(cell_array_per_node.sizeOfArrays() == 5);
+
+        auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+        {
+          bool is_correct = true;
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            is_correct &= (node_to_cell_matrix[node_id].size() == cell_array_per_node.numberOfSubArrays(node_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+    }
+  }
+
+  SECTION("array view")
+  {
+    SECTION("1D")
+    {
+      const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+      const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+
+      EdgeArrayPerCell<size_t> edge_arrays_per_cell{connectivity, 3};
+      {
+        size_t array = 0;
+        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+          for (size_t i_edge = 0; i_edge < edge_arrays_per_cell.numberOfSubArrays(cell_id); ++i_edge) {
+            for (size_t i = 0; i < edge_arrays_per_cell(cell_id, i_edge).size(); ++i) {
+              edge_arrays_per_cell(cell_id, i_edge)[i] = array++;
+            }
+          }
+        }
+      }
+      {
+        bool is_same = true;
+        for (size_t i = 0; i < edge_arrays_per_cell.numberOfValues(); ++i) {
+          is_same &= (edge_arrays_per_cell[i] == i);
+        }
+        REQUIRE(is_same);
+      }
+
+      for (size_t i = 0; i < edge_arrays_per_cell.numberOfValues(); ++i) {
+        edge_arrays_per_cell[i] = i * i + 1;
+      }
+      {
+        bool is_same = true;
+        size_t i     = 0;
+        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+          for (size_t i_edge = 0; i_edge < edge_arrays_per_cell.numberOfSubArrays(cell_id); ++i_edge) {
+            for (size_t l = 0; l < edge_arrays_per_cell(cell_id, i_edge).size(); ++l, ++i) {
+              is_same &= (edge_arrays_per_cell(cell_id, i_edge)[l] == i * i + 1);
+            }
+          }
+        }
+        REQUIRE(is_same);
+      }
+    }
+
+    SECTION("2D")
+    {
+      const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+      const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+      CellArrayPerFace<size_t> cell_arrays_per_face{connectivity, 3};
+      {
+        size_t array = 0;
+        for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+          for (size_t i_cell = 0; i_cell < cell_arrays_per_face.numberOfSubArrays(face_id); ++i_cell) {
+            for (size_t i = 0; i < cell_arrays_per_face(face_id, i_cell).size(); ++i) {
+              cell_arrays_per_face(face_id, i_cell)[i] = array++;
+            }
+          }
+        }
+      }
+      {
+        bool is_same = true;
+        for (size_t i = 0; i < cell_arrays_per_face.numberOfValues(); ++i) {
+          is_same &= (cell_arrays_per_face[i] == i);
+        }
+        REQUIRE(is_same);
+      }
+      for (size_t i = 0; i < cell_arrays_per_face.numberOfValues(); ++i) {
+        cell_arrays_per_face[i] = 3 * i + 1;
+      }
+      {
+        bool is_same = true;
+        size_t i     = 0;
+        for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+          for (size_t i_cell = 0; i_cell < cell_arrays_per_face.numberOfSubArrays(face_id); ++i_cell) {
+            for (size_t l = 0; l < cell_arrays_per_face(face_id, i_cell).size(); ++l, ++i) {
+              is_same &= (cell_arrays_per_face(face_id, i_cell)[l] == 3 * i + 1);
+            }
+          }
+        }
+        REQUIRE(is_same);
+      }
+    }
+
+    SECTION("3D")
+    {
+      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+      FaceArrayPerNode<size_t> face_arrays_per_node{connectivity, 3};
+      {
+        size_t array = 0;
+        for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+          for (size_t i_face = 0; i_face < face_arrays_per_node.numberOfSubArrays(node_id); ++i_face) {
+            for (size_t i = 0; i < face_arrays_per_node(node_id, i_face).size(); ++i)
+              face_arrays_per_node(node_id, i_face)[i] = array++;
+          }
+        }
+      }
+      {
+        bool is_same = true;
+        for (size_t i = 0; i < face_arrays_per_node.numberOfValues(); ++i) {
+          is_same &= (face_arrays_per_node[i] == i);
+        }
+        REQUIRE(is_same);
+      }
+
+      for (size_t i = 0; i < face_arrays_per_node.numberOfValues(); ++i) {
+        face_arrays_per_node[i] = 3 + i * i;
+      }
+      {
+        bool is_same = true;
+        size_t i     = 0;
+        for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+          for (size_t i_face = 0; i_face < face_arrays_per_node.numberOfSubArrays(node_id); ++i_face) {
+            for (size_t l = 0; l < face_arrays_per_node(node_id, i_face).size(); ++l, ++i) {
+              is_same &= (face_arrays_per_node(node_id, i_face)[l] == 3 + i * i);
+            }
+          }
+        }
+        REQUIRE(is_same);
+      }
+    }
+  }
+
+  SECTION("copy")
+  {
+    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+    SECTION("classic")
+    {
+      NodeArrayPerCell<size_t> node_array_per_cell{connectivity, 3};
+      for (size_t i = 0; i < node_array_per_cell.numberOfValues(); ++i) {
+        node_array_per_cell[i] = i;
+      }
+
+      NodeArrayPerCell<size_t> copy_node_array_per_cell = copy(node_array_per_cell);
+      {
+        bool is_same = true;
+        for (size_t i = 0; i < copy_node_array_per_cell.numberOfValues(); ++i) {
+          is_same &= (copy_node_array_per_cell[i] == node_array_per_cell[i]);
+        }
+
+        REQUIRE(is_same);
+      }
+
+      {
+        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+          auto node_array_list = node_array_per_cell.itemArrays(cell_id);
+          for (size_t i_node = 0; i_node < node_array_per_cell.numberOfSubArrays(cell_id); ++i_node) {
+            auto node_array = node_array_list[i_node];
+            for (size_t i = 0; i < node_array.size(); ++i) {
+              node_array[i] = cell_id + i_node + i;
+            }
+          }
+        }
+      }
+
+      {
+        bool is_same = true;
+        for (size_t i = 0; i < copy_node_array_per_cell.numberOfValues(); ++i) {
+          is_same &= (copy_node_array_per_cell[i] == node_array_per_cell[i]);
+        }
+
+        REQUIRE(not is_same);
+      }
+    }
+
+    SECTION("from weak")
+    {
+      WeakNodeArrayPerCell<size_t> node_array_per_cell{connectivity, 3};
+      for (size_t i = 0; i < node_array_per_cell.numberOfValues(); ++i) {
+        node_array_per_cell[i] = i;
+      }
+
+      NodeArrayPerCell<size_t> copy_node_array_per_cell = copy(node_array_per_cell);
+      {
+        bool is_same = true;
+        for (size_t i = 0; i < copy_node_array_per_cell.numberOfValues(); ++i) {
+          is_same &= (copy_node_array_per_cell[i] == node_array_per_cell[i]);
+        }
+
+        REQUIRE(is_same);
+      }
+
+      {
+        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+          auto node_array_list = node_array_per_cell.itemArrays(cell_id);
+          for (size_t i_node = 0; i_node < node_array_per_cell.numberOfSubArrays(cell_id); ++i_node) {
+            auto node_array = node_array_list[i_node];
+            for (size_t i = 0; i < node_array.size(); ++i) {
+              node_array[i] = cell_id + i_node + i;
+            }
+          }
+        }
+      }
+
+      {
+        bool is_same = true;
+        for (size_t i = 0; i < copy_node_array_per_cell.numberOfValues(); ++i) {
+          is_same &= (copy_node_array_per_cell[i] == node_array_per_cell[i]);
+        }
+
+        REQUIRE(not is_same);
+      }
+    }
+  }
+
+  SECTION("WeakSubItemArrayPerItem")
+  {
+    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+    const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+    WeakFaceArrayPerCell<int> weak_face_array_per_cell{connectivity, 3};
+
+    for (size_t i = 0; i < weak_face_array_per_cell.numberOfValues(); ++i) {
+      weak_face_array_per_cell[i] = i;
+    }
+
+    FaceArrayPerCell<const int> face_array_per_cell{weak_face_array_per_cell};
+
+    REQUIRE(face_array_per_cell.connectivity_ptr() == weak_face_array_per_cell.connectivity_ptr());
+    REQUIRE(face_array_per_cell.sizeOfArrays() == weak_face_array_per_cell.sizeOfArrays());
+
+    bool is_same = true;
+    for (size_t i = 0; i < weak_face_array_per_cell.numberOfValues(); ++i) {
+      is_same &= (face_array_per_cell[i] == weak_face_array_per_cell[i]);
+    }
+    REQUIRE(is_same);
+  }
+
+#ifndef NDEBUG
+  SECTION("error")
+  {
+    SECTION("checking for build SubItemArrayPerItem")
+    {
+      CellArrayPerNode<int> cell_array_per_node;
+      REQUIRE_THROWS_AS(cell_array_per_node[0], AssertError);
+      REQUIRE_THROWS_AS(cell_array_per_node.itemArrays(NodeId{0}), AssertError);
+      REQUIRE_THROWS_AS(cell_array_per_node(NodeId{0}, 0), AssertError);
+      REQUIRE_THROWS_AS(cell_array_per_node.sizeOfArrays(), AssertError);
+      REQUIRE_THROWS_AS(cell_array_per_node.numberOfValues(), AssertError);
+      REQUIRE_THROWS_AS(cell_array_per_node.numberOfItems(), AssertError);
+      REQUIRE_THROWS_AS(cell_array_per_node.numberOfSubArrays(NodeId{0}), AssertError);
+
+      FaceArrayPerCell<int> face_array_per_cell;
+      REQUIRE_THROWS_AS(face_array_per_cell[0], AssertError);
+      REQUIRE_THROWS_AS(face_array_per_cell.itemArrays(CellId{0}), AssertError);
+      REQUIRE_THROWS_AS(face_array_per_cell(CellId{0}, 0), AssertError);
+      REQUIRE_THROWS_AS(face_array_per_cell.sizeOfArrays(), AssertError);
+      REQUIRE_THROWS_AS(face_array_per_cell.numberOfValues(), AssertError);
+      REQUIRE_THROWS_AS(face_array_per_cell.numberOfItems(), AssertError);
+      REQUIRE_THROWS_AS(face_array_per_cell.numberOfSubArrays(CellId{0}), AssertError);
+
+      CellArrayPerEdge<int> cell_array_per_edge;
+      REQUIRE_THROWS_AS(cell_array_per_edge[0], AssertError);
+      REQUIRE_THROWS_AS(cell_array_per_edge.itemArrays(EdgeId{0}), AssertError);
+      REQUIRE_THROWS_AS(cell_array_per_edge(EdgeId{0}, 0), AssertError);
+      REQUIRE_THROWS_AS(cell_array_per_edge.sizeOfArrays(), AssertError);
+      REQUIRE_THROWS_AS(cell_array_per_edge.numberOfValues(), AssertError);
+      REQUIRE_THROWS_AS(cell_array_per_edge.numberOfItems(), AssertError);
+      REQUIRE_THROWS_AS(cell_array_per_edge.numberOfSubArrays(EdgeId{0}), AssertError);
+
+      NodeArrayPerFace<int> node_array_per_face;
+      REQUIRE_THROWS_AS(node_array_per_face[0], AssertError);
+      REQUIRE_THROWS_AS(node_array_per_face.itemArrays(FaceId{0}), AssertError);
+      REQUIRE_THROWS_AS(node_array_per_face(FaceId{0}, 0), AssertError);
+      REQUIRE_THROWS_AS(node_array_per_face.sizeOfArrays(), AssertError);
+      REQUIRE_THROWS_AS(node_array_per_face.numberOfValues(), AssertError);
+      REQUIRE_THROWS_AS(node_array_per_face.numberOfItems(), AssertError);
+      REQUIRE_THROWS_AS(node_array_per_face.numberOfSubArrays(FaceId{0}), AssertError);
+    }
+
+    SECTION("checking for bounds violation")
+    {
+      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+      CellArrayPerFace<int> cell_array_per_face{connectivity, 3};
+      {
+        FaceId invalid_face_id = connectivity.numberOfFaces();
+        REQUIRE_THROWS_AS(cell_array_per_face(invalid_face_id, 0), AssertError);
+      }
+      if (connectivity.numberOfFaces() > 0) {
+        FaceId face_id          = 0;
+        const auto& cell_arrays = cell_array_per_face.itemArrays(face_id);
+        REQUIRE_THROWS_AS(cell_array_per_face(face_id, cell_arrays.size()), AssertError);
+        REQUIRE_THROWS_AS(cell_arrays[cell_arrays.size()], AssertError);
+        REQUIRE_THROWS_AS(cell_array_per_face.itemArrays(face_id)[cell_arrays.size()], AssertError);
+        REQUIRE_THROWS_AS(cell_array_per_face.itemArrays(face_id)[0][cell_array_per_face.sizeOfArrays()], AssertError);
+        REQUIRE_THROWS_AS(cell_array_per_face.itemArrays(face_id)[0][cell_array_per_face.sizeOfArrays()] = 2,
+                          AssertError);
+      }
+
+      FaceArrayPerNode<int> face_array_per_node{connectivity, 5};
+      {
+        NodeId invalid_node_id = connectivity.numberOfNodes();
+        REQUIRE_THROWS_AS(face_array_per_node(invalid_node_id, 0), AssertError);
+      }
+      if (connectivity.numberOfNodes() > 0) {
+        NodeId node_id          = 0;
+        const auto& face_arrays = face_array_per_node.itemArrays(node_id);
+        REQUIRE_THROWS_AS(face_array_per_node(node_id, face_arrays.size()), AssertError);
+        REQUIRE_THROWS_AS(face_arrays[face_arrays.size()], AssertError);
+        REQUIRE_THROWS_AS(face_array_per_node.itemArrays(node_id)[face_arrays.size()], AssertError);
+        REQUIRE_THROWS_AS(face_array_per_node.itemArrays(node_id)[0][face_array_per_node.sizeOfArrays()], AssertError);
+        REQUIRE_THROWS_AS(face_array_per_node.itemArrays(node_id)[0][face_array_per_node.sizeOfArrays()] = 2,
+                          AssertError);
+      }
+
+      EdgeArrayPerCell<int> edge_array_per_cell{connectivity, 3};
+      {
+        CellId invalid_cell_id = connectivity.numberOfCells();
+        REQUIRE_THROWS_AS(edge_array_per_cell(invalid_cell_id, 0), AssertError);
+      }
+      if (connectivity.numberOfCells() > 0) {
+        CellId cell_id          = 0;
+        const auto& edge_arrays = edge_array_per_cell.itemArrays(cell_id);
+        REQUIRE_THROWS_AS(edge_array_per_cell(cell_id, edge_arrays.size()), AssertError);
+        REQUIRE_THROWS_AS(edge_arrays[edge_arrays.size()], AssertError);
+        REQUIRE_THROWS_AS(edge_array_per_cell.itemArrays(cell_id)[edge_arrays.size()], AssertError);
+        REQUIRE_THROWS_AS(edge_array_per_cell.itemArrays(cell_id)[0][edge_array_per_cell.sizeOfArrays()], AssertError);
+        REQUIRE_THROWS_AS(edge_array_per_cell.itemArrays(cell_id)[0][edge_array_per_cell.sizeOfArrays()] == 2,
+                          AssertError);
+      }
+
+      NodeArrayPerEdge<int> node_array_per_edge{connectivity, 3};
+      {
+        EdgeId invalid_edge_id = connectivity.numberOfEdges();
+        REQUIRE_THROWS_AS(node_array_per_edge(invalid_edge_id, 0), AssertError);
+      }
+      if (connectivity.numberOfEdges() > 0) {
+        EdgeId edge_id          = 0;
+        const auto& node_arrays = node_array_per_edge.itemArrays(edge_id);
+        REQUIRE_THROWS_AS(node_array_per_edge(edge_id, node_arrays.size()), AssertError);
+        REQUIRE_THROWS_AS(node_arrays[node_arrays.size()], AssertError);
+        REQUIRE_THROWS_AS(node_array_per_edge.itemArrays(edge_id)[node_arrays.size()], AssertError);
+        REQUIRE_THROWS_AS(node_array_per_edge.itemArrays(edge_id)[0][node_array_per_edge.sizeOfArrays()], AssertError);
+        REQUIRE_THROWS_AS(node_array_per_edge.itemArrays(edge_id)[0][node_array_per_edge.sizeOfArrays()] = 2,
+                          AssertError);
+      }
+    }
+  }
+#endif   // NDEBUG
+}
diff --git a/tests/test_SubItemValuePerItem.cpp b/tests/test_SubItemValuePerItem.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e7c9434aa7362f7cb27a87ee6b144df27f9c2f26
--- /dev/null
+++ b/tests/test_SubItemValuePerItem.cpp
@@ -0,0 +1,818 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+#include <mesh/Connectivity.hpp>
+#include <mesh/Mesh.hpp>
+#include <mesh/SubItemValuePerItem.hpp>
+#include <utils/Messenger.hpp>
+
+// Instantiate to ensure full coverage is performed
+template class SubItemValuePerItem<size_t, NodeOfCell>;
+
+// clazy:excludeall=non-pod-global-static
+
+TEST_CASE("SubItemValuePerItem", "[mesh]")
+{
+  SECTION("default constructors")
+  {
+    REQUIRE_NOTHROW(NodeValuePerEdge<int>{});
+    REQUIRE_NOTHROW(NodeValuePerFace<int>{});
+    REQUIRE_NOTHROW(NodeValuePerCell<int>{});
+
+    REQUIRE_NOTHROW(EdgeValuePerNode<int>{});
+    REQUIRE_NOTHROW(EdgeValuePerFace<int>{});
+    REQUIRE_NOTHROW(EdgeValuePerCell<int>{});
+
+    REQUIRE_NOTHROW(FaceValuePerNode<int>{});
+    REQUIRE_NOTHROW(FaceValuePerEdge<int>{});
+    REQUIRE_NOTHROW(FaceValuePerCell<int>{});
+
+    REQUIRE_NOTHROW(CellValuePerNode<int>{});
+    REQUIRE_NOTHROW(CellValuePerEdge<int>{});
+    REQUIRE_NOTHROW(CellValuePerFace<int>{});
+
+    REQUIRE(not NodeValuePerEdge<int>{}.isBuilt());
+    REQUIRE(not NodeValuePerFace<int>{}.isBuilt());
+    REQUIRE(not NodeValuePerCell<int>{}.isBuilt());
+
+    REQUIRE(not EdgeValuePerNode<int>{}.isBuilt());
+    REQUIRE(not EdgeValuePerFace<int>{}.isBuilt());
+    REQUIRE(not EdgeValuePerCell<int>{}.isBuilt());
+
+    REQUIRE(not FaceValuePerNode<int>{}.isBuilt());
+    REQUIRE(not FaceValuePerEdge<int>{}.isBuilt());
+    REQUIRE(not FaceValuePerCell<int>{}.isBuilt());
+
+    REQUIRE(not CellValuePerNode<int>{}.isBuilt());
+    REQUIRE(not CellValuePerEdge<int>{}.isBuilt());
+    REQUIRE(not CellValuePerFace<int>{}.isBuilt());
+  }
+
+  SECTION("dimensions")
+  {
+    auto number_of_values = [](const auto& sub_item_value_per_item) -> size_t {
+      using SubItemValuePerItemType = std::decay_t<decltype(sub_item_value_per_item)>;
+      using ItemId                  = typename SubItemValuePerItemType::ItemId;
+
+      size_t number = 0;
+      for (ItemId item_id = 0; item_id < sub_item_value_per_item.numberOfItems(); ++item_id) {
+        number += sub_item_value_per_item.numberOfSubValues(item_id);
+      }
+      return number;
+    };
+
+    SECTION("1D")
+    {
+      const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+      const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+
+      SECTION("per cell")
+      {
+        NodeValuePerCell<int> node_value_per_cell{connectivity};
+        REQUIRE(node_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(node_value_per_cell.numberOfValues() == number_of_values(node_value_per_cell));
+
+        auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+        {
+          bool is_correct = true;
+          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));
+          }
+          REQUIRE(is_correct);
+        }
+
+        const NodeValuePerCell<const int> const_node_value_per_cell = node_value_per_cell;
+        {
+          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() == node_value_per_cell.numberOfSubValues(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        EdgeValuePerCell<int> edge_value_per_cell{connectivity};
+        REQUIRE(edge_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(edge_value_per_cell.numberOfValues() == number_of_values(edge_value_per_cell));
+
+        auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
+        {
+          bool is_correct = true;
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_value_per_cell.numberOfSubValues(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        FaceValuePerCell<int> face_value_per_cell{connectivity};
+        REQUIRE(face_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(face_value_per_cell.numberOfValues() == number_of_values(face_value_per_cell));
+
+        auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
+        {
+          bool is_correct = true;
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            is_correct &= (cell_to_face_matrix[cell_id].size() == face_value_per_cell.numberOfSubValues(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per face")
+      {
+        CellValuePerFace<int> cell_value_per_face{connectivity};
+        REQUIRE(cell_value_per_face.numberOfItems() == connectivity.numberOfFaces());
+        REQUIRE(cell_value_per_face.numberOfValues() == number_of_values(cell_value_per_face));
+
+        auto face_to_cell_matrix = connectivity.faceToCellMatrix();
+        {
+          bool is_correct = true;
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            is_correct &= (face_to_cell_matrix[face_id].size() == cell_value_per_face.numberOfSubValues(face_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per edge")
+      {
+        CellValuePerEdge<int> cell_value_per_edge{connectivity};
+        REQUIRE(cell_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
+        REQUIRE(cell_value_per_edge.numberOfValues() == number_of_values(cell_value_per_edge));
+
+        auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
+        {
+          bool is_correct = true;
+          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+            is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_value_per_edge.numberOfSubValues(edge_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per node")
+      {
+        CellValuePerNode<int> cell_value_per_node{connectivity};
+        REQUIRE(cell_value_per_node.numberOfItems() == connectivity.numberOfNodes());
+        REQUIRE(cell_value_per_node.numberOfValues() == number_of_values(cell_value_per_node));
+
+        auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+        {
+          bool is_correct = true;
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            is_correct &= (node_to_cell_matrix[node_id].size() == cell_value_per_node.numberOfSubValues(node_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+    }
+
+    SECTION("2D")
+    {
+      const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+      const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+      SECTION("per cell")
+      {
+        NodeValuePerCell<int> node_value_per_cell{connectivity};
+        REQUIRE(node_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(node_value_per_cell.numberOfValues() == number_of_values(node_value_per_cell));
+
+        auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+        {
+          bool is_correct = true;
+          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));
+          }
+          REQUIRE(is_correct);
+        }
+
+        EdgeValuePerCell<int> edge_value_per_cell{connectivity};
+        REQUIRE(edge_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(edge_value_per_cell.numberOfValues() == number_of_values(edge_value_per_cell));
+
+        auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
+        {
+          bool is_correct = true;
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_value_per_cell.numberOfSubValues(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        FaceValuePerCell<int> face_value_per_cell{connectivity};
+        REQUIRE(face_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(face_value_per_cell.numberOfValues() == number_of_values(face_value_per_cell));
+
+        auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
+        {
+          bool is_correct = true;
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            is_correct &= (cell_to_face_matrix[cell_id].size() == face_value_per_cell.numberOfSubValues(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per face")
+      {
+        CellValuePerFace<int> cell_value_per_face{connectivity};
+        REQUIRE(cell_value_per_face.numberOfItems() == connectivity.numberOfFaces());
+        REQUIRE(cell_value_per_face.numberOfValues() == number_of_values(cell_value_per_face));
+
+        auto face_to_cell_matrix = connectivity.faceToCellMatrix();
+        {
+          bool is_correct = true;
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            is_correct &= (face_to_cell_matrix[face_id].size() == cell_value_per_face.numberOfSubValues(face_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        NodeValuePerFace<int> node_value_per_face{connectivity};
+        REQUIRE(node_value_per_face.numberOfItems() == connectivity.numberOfFaces());
+        REQUIRE(node_value_per_face.numberOfValues() == number_of_values(node_value_per_face));
+
+        auto face_to_node_matrix = connectivity.faceToNodeMatrix();
+        {
+          bool is_correct = true;
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            is_correct &= (face_to_node_matrix[face_id].size() == node_value_per_face.numberOfSubValues(face_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per edge")
+      {
+        CellValuePerEdge<int> cell_value_per_edge{connectivity};
+        REQUIRE(cell_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
+        REQUIRE(cell_value_per_edge.numberOfValues() == number_of_values(cell_value_per_edge));
+
+        auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
+        {
+          bool is_correct = true;
+          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+            is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_value_per_edge.numberOfSubValues(edge_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        NodeValuePerEdge<int> node_value_per_edge{connectivity};
+        REQUIRE(node_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
+        REQUIRE(node_value_per_edge.numberOfValues() == number_of_values(node_value_per_edge));
+
+        auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
+        {
+          bool is_correct = true;
+          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+            is_correct &= (edge_to_node_matrix[edge_id].size() == node_value_per_edge.numberOfSubValues(edge_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per node")
+      {
+        EdgeValuePerNode<int> edge_value_per_node{connectivity};
+        REQUIRE(edge_value_per_node.numberOfItems() == connectivity.numberOfNodes());
+        REQUIRE(edge_value_per_node.numberOfValues() == number_of_values(edge_value_per_node));
+
+        auto node_to_edge_matrix = connectivity.nodeToEdgeMatrix();
+        {
+          bool is_correct = true;
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            is_correct &= (node_to_edge_matrix[node_id].size() == edge_value_per_node.numberOfSubValues(node_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        FaceValuePerNode<int> face_value_per_node{connectivity};
+        REQUIRE(face_value_per_node.numberOfItems() == connectivity.numberOfNodes());
+        REQUIRE(face_value_per_node.numberOfValues() == number_of_values(face_value_per_node));
+
+        auto node_to_face_matrix = connectivity.nodeToFaceMatrix();
+        {
+          bool is_correct = true;
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            is_correct &= (node_to_face_matrix[node_id].size() == face_value_per_node.numberOfSubValues(node_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        CellValuePerNode<int> cell_value_per_node{connectivity};
+        REQUIRE(cell_value_per_node.numberOfItems() == connectivity.numberOfNodes());
+        REQUIRE(cell_value_per_node.numberOfValues() == number_of_values(cell_value_per_node));
+
+        auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+        {
+          bool is_correct = true;
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            is_correct &= (node_to_cell_matrix[node_id].size() == cell_value_per_node.numberOfSubValues(node_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+    }
+
+    SECTION("3D")
+    {
+      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+      SECTION("per cell")
+      {
+        NodeValuePerCell<int> node_value_per_cell{connectivity};
+        REQUIRE(node_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(node_value_per_cell.numberOfValues() == number_of_values(node_value_per_cell));
+
+        auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+        {
+          bool is_correct = true;
+          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));
+          }
+          REQUIRE(is_correct);
+        }
+
+        EdgeValuePerCell<int> edge_value_per_cell{connectivity};
+        REQUIRE(edge_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(edge_value_per_cell.numberOfValues() == number_of_values(edge_value_per_cell));
+
+        auto cell_to_edge_matrix = connectivity.cellToEdgeMatrix();
+        {
+          bool is_correct = true;
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            is_correct &= (cell_to_edge_matrix[cell_id].size() == edge_value_per_cell.numberOfSubValues(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        FaceValuePerCell<int> face_value_per_cell{connectivity};
+        REQUIRE(face_value_per_cell.numberOfItems() == connectivity.numberOfCells());
+        REQUIRE(face_value_per_cell.numberOfValues() == number_of_values(face_value_per_cell));
+
+        auto cell_to_face_matrix = connectivity.cellToFaceMatrix();
+        {
+          bool is_correct = true;
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            is_correct &= (cell_to_face_matrix[cell_id].size() == face_value_per_cell.numberOfSubValues(cell_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per face")
+      {
+        CellValuePerFace<int> cell_value_per_face{connectivity};
+        REQUIRE(cell_value_per_face.numberOfItems() == connectivity.numberOfFaces());
+        REQUIRE(cell_value_per_face.numberOfValues() == number_of_values(cell_value_per_face));
+
+        auto face_to_cell_matrix = connectivity.faceToCellMatrix();
+        {
+          bool is_correct = true;
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            is_correct &= (face_to_cell_matrix[face_id].size() == cell_value_per_face.numberOfSubValues(face_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        EdgeValuePerFace<int> edge_value_per_face{connectivity};
+        REQUIRE(edge_value_per_face.numberOfItems() == connectivity.numberOfFaces());
+        REQUIRE(edge_value_per_face.numberOfValues() == number_of_values(edge_value_per_face));
+
+        auto face_to_edge_matrix = connectivity.faceToEdgeMatrix();
+        {
+          bool is_correct = true;
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            is_correct &= (face_to_edge_matrix[face_id].size() == edge_value_per_face.numberOfSubValues(face_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        NodeValuePerFace<int> node_value_per_face{connectivity};
+        REQUIRE(node_value_per_face.numberOfItems() == connectivity.numberOfFaces());
+        REQUIRE(node_value_per_face.numberOfValues() == number_of_values(node_value_per_face));
+
+        auto face_to_node_matrix = connectivity.faceToNodeMatrix();
+        {
+          bool is_correct = true;
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            is_correct &= (face_to_node_matrix[face_id].size() == node_value_per_face.numberOfSubValues(face_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per edge")
+      {
+        CellValuePerEdge<int> cell_value_per_edge{connectivity};
+        REQUIRE(cell_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
+        REQUIRE(cell_value_per_edge.numberOfValues() == number_of_values(cell_value_per_edge));
+
+        auto edge_to_cell_matrix = connectivity.edgeToCellMatrix();
+        {
+          bool is_correct = true;
+          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+            is_correct &= (edge_to_cell_matrix[edge_id].size() == cell_value_per_edge.numberOfSubValues(edge_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        FaceValuePerEdge<int> face_value_per_edge{connectivity};
+        REQUIRE(face_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
+        REQUIRE(face_value_per_edge.numberOfValues() == number_of_values(face_value_per_edge));
+
+        auto edge_to_face_matrix = connectivity.edgeToFaceMatrix();
+        {
+          bool is_correct = true;
+          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+            is_correct &= (edge_to_face_matrix[edge_id].size() == face_value_per_edge.numberOfSubValues(edge_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        NodeValuePerEdge<int> node_value_per_edge{connectivity};
+        REQUIRE(node_value_per_edge.numberOfItems() == connectivity.numberOfEdges());
+        REQUIRE(node_value_per_edge.numberOfValues() == number_of_values(node_value_per_edge));
+
+        auto edge_to_node_matrix = connectivity.edgeToNodeMatrix();
+        {
+          bool is_correct = true;
+          for (EdgeId edge_id = 0; edge_id < connectivity.numberOfEdges(); ++edge_id) {
+            is_correct &= (edge_to_node_matrix[edge_id].size() == node_value_per_edge.numberOfSubValues(edge_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+
+      SECTION("per node")
+      {
+        EdgeValuePerNode<int> edge_value_per_node{connectivity};
+        REQUIRE(edge_value_per_node.numberOfItems() == connectivity.numberOfNodes());
+        REQUIRE(edge_value_per_node.numberOfValues() == number_of_values(edge_value_per_node));
+
+        auto node_to_edge_matrix = connectivity.nodeToEdgeMatrix();
+        {
+          bool is_correct = true;
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            is_correct &= (node_to_edge_matrix[node_id].size() == edge_value_per_node.numberOfSubValues(node_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        FaceValuePerNode<int> face_value_per_node{connectivity};
+        REQUIRE(face_value_per_node.numberOfItems() == connectivity.numberOfNodes());
+        REQUIRE(face_value_per_node.numberOfValues() == number_of_values(face_value_per_node));
+
+        auto node_to_face_matrix = connectivity.nodeToFaceMatrix();
+        {
+          bool is_correct = true;
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            is_correct &= (node_to_face_matrix[node_id].size() == face_value_per_node.numberOfSubValues(node_id));
+          }
+          REQUIRE(is_correct);
+        }
+
+        CellValuePerNode<int> cell_value_per_node{connectivity};
+        REQUIRE(cell_value_per_node.numberOfItems() == connectivity.numberOfNodes());
+        REQUIRE(cell_value_per_node.numberOfValues() == number_of_values(cell_value_per_node));
+
+        auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+        {
+          bool is_correct = true;
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            is_correct &= (node_to_cell_matrix[node_id].size() == cell_value_per_node.numberOfSubValues(node_id));
+          }
+          REQUIRE(is_correct);
+        }
+      }
+    }
+  }
+
+  SECTION("array view")
+  {
+    SECTION("1D")
+    {
+      const Mesh<Connectivity<1>>& mesh_1d = MeshDataBaseForTests::get().cartesianMesh<1>();
+      const Connectivity<1>& connectivity  = mesh_1d.connectivity();
+
+      EdgeValuePerCell<size_t> edge_values_per_cell{connectivity};
+      {
+        size_t value = 0;
+        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+          for (size_t i_edge = 0; i_edge < edge_values_per_cell.numberOfSubValues(cell_id); ++i_edge) {
+            edge_values_per_cell(cell_id, i_edge) = value++;
+          }
+        }
+      }
+      {
+        bool is_same = true;
+        for (size_t i = 0; i < edge_values_per_cell.numberOfValues(); ++i) {
+          is_same &= (edge_values_per_cell[i] == i);
+        }
+        REQUIRE(is_same);
+      }
+
+      for (size_t i = 0; i < edge_values_per_cell.numberOfValues(); ++i) {
+        edge_values_per_cell[i] = i * i + 1;
+      }
+      {
+        bool is_same = true;
+        size_t i     = 0;
+        for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+          for (size_t i_edge = 0; i_edge < edge_values_per_cell.numberOfSubValues(cell_id); ++i_edge, ++i) {
+            is_same &= (edge_values_per_cell(cell_id, i_edge) == i * i + 1);
+          }
+        }
+        REQUIRE(is_same);
+      }
+    }
+
+    SECTION("2D")
+    {
+      const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+      const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+      CellValuePerFace<size_t> cell_values_per_face{connectivity};
+      {
+        size_t value = 0;
+        for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+          for (size_t i_cell = 0; i_cell < cell_values_per_face.numberOfSubValues(face_id); ++i_cell) {
+            cell_values_per_face(face_id, i_cell) = value++;
+          }
+        }
+      }
+      {
+        bool is_same = true;
+        for (size_t i = 0; i < cell_values_per_face.numberOfValues(); ++i) {
+          is_same &= (cell_values_per_face[i] == i);
+        }
+        REQUIRE(is_same);
+      }
+      for (size_t i = 0; i < cell_values_per_face.numberOfValues(); ++i) {
+        cell_values_per_face[i] = 3 * i + 1;
+      }
+      {
+        bool is_same = true;
+        size_t i     = 0;
+        for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+          for (size_t i_cell = 0; i_cell < cell_values_per_face.numberOfSubValues(face_id); ++i_cell, ++i) {
+            is_same &= (cell_values_per_face(face_id, i_cell) == 3 * i + 1);
+          }
+        }
+        REQUIRE(is_same);
+      }
+    }
+
+    SECTION("3D")
+    {
+      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+      FaceValuePerNode<size_t> face_values_per_node{connectivity};
+      {
+        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++;
+          }
+        }
+      }
+      {
+        bool is_same = true;
+        for (size_t i = 0; i < face_values_per_node.numberOfValues(); ++i) {
+          is_same &= (face_values_per_node[i] == i);
+        }
+        REQUIRE(is_same);
+      }
+
+      for (size_t i = 0; i < face_values_per_node.numberOfValues(); ++i) {
+        face_values_per_node[i] = 3 + i * i;
+      }
+      {
+        bool is_same = true;
+        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);
+          }
+        }
+        REQUIRE(is_same);
+      }
+    }
+  }
+
+  SECTION("copy")
+  {
+    const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+    const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+    SECTION("classic")
+    {
+      NodeValuePerCell<size_t> node_value_per_cell{connectivity};
+
+      {
+        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++;
+          }
+        }
+      }
+
+      NodeValuePerCell<size_t> copy_node_value_per_cell = copy(node_value_per_cell);
+
+      {
+        bool is_same = true;
+        for (size_t i = 0; i < copy_node_value_per_cell.numberOfValues(); ++i) {
+          is_same &= (copy_node_value_per_cell[i] == node_value_per_cell[i]);
+        }
+
+        REQUIRE(is_same);
+      }
+
+      {
+        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;
+          }
+        }
+      }
+
+      {
+        bool is_same = true;
+        for (size_t i = 0; i < copy_node_value_per_cell.numberOfValues(); ++i) {
+          is_same &= (copy_node_value_per_cell[i] == node_value_per_cell[i]);
+        }
+
+        REQUIRE(not is_same);
+      }
+    }
+
+    SECTION("from weak")
+    {
+      WeakNodeValuePerCell<size_t> node_value_per_cell{connectivity};
+
+      {
+        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++;
+          }
+        }
+      }
+
+      NodeValuePerCell<size_t> copy_node_value_per_cell = copy(node_value_per_cell);
+
+      {
+        bool is_same = true;
+        for (size_t i = 0; i < copy_node_value_per_cell.numberOfValues(); ++i) {
+          is_same &= (copy_node_value_per_cell[i] == node_value_per_cell[i]);
+        }
+
+        REQUIRE(is_same);
+      }
+
+      {
+        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;
+          }
+        }
+      }
+
+      {
+        bool is_same = true;
+        for (size_t i = 0; i < copy_node_value_per_cell.numberOfValues(); ++i) {
+          is_same &= (copy_node_value_per_cell[i] == node_value_per_cell[i]);
+        }
+
+        REQUIRE(not is_same);
+      }
+    }
+  }
+
+  SECTION("WeakSubItemValuePerItem")
+  {
+    const Mesh<Connectivity<2>>& mesh_2d = MeshDataBaseForTests::get().cartesianMesh<2>();
+    const Connectivity<2>& connectivity  = mesh_2d.connectivity();
+
+    WeakFaceValuePerCell<int> weak_face_value_per_cell{connectivity};
+
+    for (size_t i = 0; i < weak_face_value_per_cell.numberOfValues(); ++i) {
+      weak_face_value_per_cell[i] = i;
+    }
+
+    FaceValuePerCell<const int> face_value_per_cell{weak_face_value_per_cell};
+
+    REQUIRE(face_value_per_cell.connectivity_ptr() == weak_face_value_per_cell.connectivity_ptr());
+
+    bool is_same = true;
+    for (size_t i = 0; i < weak_face_value_per_cell.numberOfValues(); ++i) {
+      is_same &= (face_value_per_cell[i] == weak_face_value_per_cell[i]);
+    }
+    REQUIRE(is_same);
+  }
+
+#ifndef NDEBUG
+  SECTION("error")
+  {
+    SECTION("checking for build SubItemValuePerItem")
+    {
+      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(NodeId{0}, 0), AssertError);
+      REQUIRE_THROWS_AS(cell_value_per_node.numberOfValues(), AssertError);
+      REQUIRE_THROWS_AS(cell_value_per_node.numberOfItems(), AssertError);
+      REQUIRE_THROWS_AS(cell_value_per_node.numberOfSubValues(NodeId{0}), AssertError);
+
+      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(CellId{0}, 0), AssertError);
+      REQUIRE_THROWS_AS(face_value_per_cell.numberOfValues(), AssertError);
+      REQUIRE_THROWS_AS(face_value_per_cell.numberOfItems(), AssertError);
+      REQUIRE_THROWS_AS(face_value_per_cell.numberOfSubValues(CellId{0}), AssertError);
+
+      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(EdgeId{0}, 0), AssertError);
+      REQUIRE_THROWS_AS(cell_value_per_edge.numberOfValues(), AssertError);
+      REQUIRE_THROWS_AS(cell_value_per_edge.numberOfItems(), AssertError);
+      REQUIRE_THROWS_AS(cell_value_per_edge.numberOfSubValues(EdgeId{0}), AssertError);
+
+      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(FaceId{0}, 0), AssertError);
+      REQUIRE_THROWS_AS(node_value_per_face.numberOfValues(), AssertError);
+      REQUIRE_THROWS_AS(node_value_per_face.numberOfItems(), AssertError);
+      REQUIRE_THROWS_AS(node_value_per_face.numberOfSubValues(FaceId{0}), AssertError);
+    }
+
+    SECTION("checking for bounds violation")
+    {
+      const Mesh<Connectivity<3>>& mesh_3d = MeshDataBaseForTests::get().cartesianMesh<3>();
+      const Connectivity<3>& connectivity  = mesh_3d.connectivity();
+
+      CellValuePerFace<int> cell_value_per_face{connectivity};
+      {
+        FaceId invalid_face_id = connectivity.numberOfFaces();
+        REQUIRE_THROWS_AS(cell_value_per_face(invalid_face_id, 0), AssertError);
+      }
+      if (connectivity.numberOfFaces() > 0) {
+        FaceId face_id          = 0;
+        const auto& cell_values = cell_value_per_face.itemValues(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);
+      }
+
+      FaceValuePerNode<int> face_value_per_node{connectivity};
+      {
+        NodeId invalid_node_id = connectivity.numberOfNodes();
+        REQUIRE_THROWS_AS(face_value_per_node(invalid_node_id, 0), AssertError);
+      }
+      if (connectivity.numberOfNodes() > 0) {
+        NodeId node_id          = 0;
+        const auto& face_values = face_value_per_node.itemValues(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);
+      }
+
+      EdgeValuePerCell<int> edge_value_per_cell{connectivity};
+      {
+        CellId invalid_cell_id = connectivity.numberOfCells();
+        REQUIRE_THROWS_AS(edge_value_per_cell(invalid_cell_id, 0), AssertError);
+      }
+      if (connectivity.numberOfCells() > 0) {
+        CellId cell_id          = 0;
+        const auto& edge_values = edge_value_per_cell.itemValues(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);
+      }
+
+      NodeValuePerEdge<int> node_value_per_edge{connectivity};
+      {
+        EdgeId invalid_edge_id = connectivity.numberOfEdges();
+        REQUIRE_THROWS_AS(node_value_per_edge(invalid_edge_id, 0), AssertError);
+      }
+      if (connectivity.numberOfEdges() > 0) {
+        EdgeId edge_id          = 0;
+        const auto& node_values = node_value_per_edge.itemValues(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);
+      }
+    }
+  }
+#endif   // NDEBUG
+}