From 437955468c428b3c2a892ee192e35957cb117845 Mon Sep 17 00:00:00 2001
From: Stephane Del Pino <stephane.delpino44@gmail.com>
Date: Thu, 30 Nov 2023 08:16:47 +0100
Subject: [PATCH] Add unit tests for ParallelChecker

---
 src/dev/ParallelChecker.hpp          |  127 +-
 src/mesh/ItemOfItemType.hpp          |    2 +-
 src/utils/BuildInfo.cpp              |    4 +-
 tests/CMakeLists.txt                 |   10 +
 tests/ParallelCheckerTester.cpp      |   43 +
 tests/ParallelCheckerTester.hpp      |   23 +
 tests/test_ParallelChecker_read.cpp  | 2078 ++++++++++++++++++++++++++
 tests/test_ParallelChecker_write.cpp |  654 ++++++++
 8 files changed, 2917 insertions(+), 24 deletions(-)
 create mode 100644 tests/ParallelCheckerTester.cpp
 create mode 100644 tests/ParallelCheckerTester.hpp
 create mode 100644 tests/test_ParallelChecker_read.cpp
 create mode 100644 tests/test_ParallelChecker_write.cpp

diff --git a/src/dev/ParallelChecker.hpp b/src/dev/ParallelChecker.hpp
index e195a4280..97d82ee4b 100644
--- a/src/dev/ParallelChecker.hpp
+++ b/src/dev/ParallelChecker.hpp
@@ -3,7 +3,7 @@
 
 #include <utils/pugs_config.hpp>
 #ifdef PUGS_HAS_HDF5
-#include <highfive/H5File.hpp>
+#include <highfive/highfive.hpp>
 #endif   // PUGS_HAS_HDF5
 
 #include <mesh/Connectivity.hpp>
@@ -49,6 +49,9 @@ class ParallelChecker
     write
   };
 
+  // to allow special manipulations in tests
+  friend class ParallelCheckerTester;
+
  private:
   static ParallelChecker* m_instance;
 
@@ -56,6 +59,7 @@ class ParallelChecker
   size_t m_tag = 0;
 
   std::string m_filename = "parallel_checker.h5";
+  std::string m_path     = "";
 
   ParallelChecker() = default;
 
@@ -230,9 +234,11 @@ class ParallelChecker
     case 3: {
       return dynamic_cast<const Connectivity<3>&>(*i_connectivity).id();
     }
+      // LCOV_EXCL_START
     default: {
       throw UnexpectedError("unexpected connectivity dimension");
     }
+      // LCOV_EXCL_STOP
     }
   }
 
@@ -253,9 +259,11 @@ class ParallelChecker
       const Connectivity<3>& connectivity = dynamic_cast<const Connectivity<3>&>(*i_connectivity);
       return connectivity.number<item_type>().arrayView();
     }
+      // LCOV_EXCL_START
     default: {
       throw UnexpectedError("unexpected connectivity dimension");
     }
+      // LCOV_EXCL_STOP
     }
   }
 
@@ -276,9 +284,11 @@ class ParallelChecker
       const Connectivity<3>& connectivity = dynamic_cast<const Connectivity<3>&>(*i_connectivity);
       return connectivity.getMatrix(item_type, sub_item_type).rowsMap();
     }
+      // LCOV_EXCL_START
     default: {
       throw UnexpectedError("unexpected connectivity dimension");
     }
+      // LCOV_EXCL_STOP
     }
   }
 
@@ -299,9 +309,50 @@ class ParallelChecker
       const Connectivity<3>& connectivity = dynamic_cast<const Connectivity<3>&>(*i_connectivity);
       return connectivity.owner<item_type>().arrayView();
     }
+      // LCOV_EXCL_START
     default: {
       throw UnexpectedError("unexpected connectivity dimension");
     }
+      // LCOV_EXCL_STOP
+    }
+  }
+
+  template <ItemType item_type>
+  void
+  _checkGlobalNumberOfItems(const std::shared_ptr<const IConnectivity>& i_connectivity,
+                            size_t reference_number_of_items)
+  {
+    Array<const bool> is_owned;
+    switch (i_connectivity->dimension()) {
+    case 1: {
+      const Connectivity<1>& connectivity = dynamic_cast<const Connectivity<1>&>(*i_connectivity);
+      is_owned                            = connectivity.isOwned<item_type>().arrayView();
+      break;
+    }
+    case 2: {
+      const Connectivity<2>& connectivity = dynamic_cast<const Connectivity<2>&>(*i_connectivity);
+      is_owned                            = connectivity.isOwned<item_type>().arrayView();
+      break;
+    }
+    case 3: {
+      const Connectivity<3>& connectivity = dynamic_cast<const Connectivity<3>&>(*i_connectivity);
+      is_owned                            = connectivity.isOwned<item_type>().arrayView();
+      break;
+    }
+      // LCOV_EXCL_START
+    default: {
+      throw UnexpectedError("unexpected connectivity dimension");
+    }
+      // LCOV_EXCL_STOP
+    }
+
+    size_t number_of_items = 0;
+    for (size_t i = 0; i < is_owned.size(); ++i) {
+      number_of_items += is_owned[i];
+    }
+
+    if (parallel::allReduceSum(number_of_items) != reference_number_of_items) {
+      throw NormalError("number of items differs from reference");
     }
   }
 
@@ -389,9 +440,11 @@ class ParallelChecker
       is_comparable = false;
     }
     {
-      bool same_shapes = true;
-      for (size_t i = 1; i < reference_data_shape.size(); ++i) {
-        same_shapes &= (reference_data_shape[i] == data_shape[i]);
+      bool same_shapes = (reference_data_shape.size() == data_shape.size());
+      if (same_shapes) {
+        for (size_t i = 1; i < reference_data_shape.size(); ++i) {
+          same_shapes &= (reference_data_shape[i] == data_shape[i]);
+        }
       }
       if (not same_shapes) {
         std::cout << rang::fg::cyan << " | " << rang::fgB::red << "different data shape: reference ("
@@ -400,7 +453,7 @@ class ParallelChecker
           std::cout << ":" << reference_data_shape[i];
         }
         std::cout << rang::fgB::red << ") / target (" << rang::fgB::yellow << "*";
-        for (size_t i = 1; i < reference_data_shape.size(); ++i) {
+        for (size_t i = 1; i < data_shape.size(); ++i) {
           std::cout << ":" << data_shape[i];
         }
         std::cout << rang::fg::reset << ")\n";
@@ -509,9 +562,11 @@ class ParallelChecker
 
       std::cout << rang::fg::cyan << " | writing " << rang::fgB::green << "success" << rang::fg::reset << '\n';
     }
+    // LCOV_EXCL_START
     catch (HighFive::Exception& e) {
       throw NormalError(e.what());
     }
+    // LCOV_EXCL_STOP
   }
 
   template <typename DataType, ItemType item_type, typename ConnectivityPtr>
@@ -546,9 +601,11 @@ class ParallelChecker
 
       std::cout << rang::fg::cyan << " | writing " << rang::fgB::green << "success" << rang::fg::reset << '\n';
     }
+    // LCOV_EXCL_START
     catch (HighFive::Exception& e) {
       throw NormalError(e.what());
     }
+    // LCOV_EXCL_STOP
   }
 
   template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
@@ -589,9 +646,11 @@ class ParallelChecker
 
       std::cout << rang::fg::cyan << " | writing " << rang::fgB::green << "success" << rang::fg::reset << '\n';
     }
+    // LCOV_EXCL_START
     catch (HighFive::Exception& e) {
       throw NormalError(e.what());
     }
+    // LCOV_EXCL_STOP
   }
 
   template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
@@ -632,9 +691,11 @@ class ParallelChecker
 
       std::cout << rang::fg::cyan << " | writing " << rang::fgB::green << "success" << rang::fg::reset << '\n';
     }
+    // LCOV_EXCL_START
     catch (HighFive::Exception& e) {
       throw NormalError(e.what());
     }
+    // LCOV_EXCL_STOP
   }
 
   template <typename DataType, ItemType item_type, typename ConnectivityPtr>
@@ -659,8 +720,7 @@ class ParallelChecker
                                                        std::vector<size_t>{item_value.numberOfItems()}, i_connectivity,
                                                        group);
 
-      Array<const int> reference_item_numbers = this->_readArray<int>(group, "numbers");
-
+      Array<const int> reference_item_numbers    = this->_readArray<int>(group, "numbers");
       Array<const DataType> reference_item_value = this->_readArray<DataType>(group, reference_name);
 
       Array<const int> item_numbers = this->_getItemNumber<item_type>(i_connectivity);
@@ -673,9 +733,11 @@ class ParallelChecker
         const auto& [iterator, success] =
           item_number_to_item_id_map.insert(std::make_pair(item_numbers[item_id], item_id));
 
+        // LCOV_EXCL_START
         if (not success) {
           throw UnexpectedError("item numbers have duplicate values");
         }
+        // LCOV_EXCL_STOP
       }
 
       Assert(item_number_to_item_id_map.size() == item_numbers.size());
@@ -692,6 +754,7 @@ class ParallelChecker
       if (parallel::allReduceMin(min(index_in_reference)) < 0) {
         throw NormalError("some item numbers are not defined in reference");
       }
+      this->_checkGlobalNumberOfItems<item_type>(i_connectivity, reference_item_numbers.size());
 
       Array<const int> owner = this->_getItemOwner<item_type>(i_connectivity);
 
@@ -718,11 +781,11 @@ class ParallelChecker
         } else {
           std::cout << rang::fg::cyan << " | compare: " << rang::fgB::yellow << "not synchronized" << rang::fg::reset;
         }
-        std::cout << rang::fg::cyan << " [see \"" << rang::fgB::blue << "parallel_differences_" << m_tag << "_*"
-                  << rang::fg::cyan << "\" files for details]" << rang::fg::reset << '\n';
+        std::cout << rang::fg::cyan << " [see \"" << rang::fgB::blue << m_path + "parallel_differences_" << m_tag
+                  << "_*" << rang::fg::cyan << "\" files for details]" << rang::fg::reset << '\n';
 
         {
-          std::ofstream fout(std::string{"parallel_differences_"} + stringify(m_tag) + std::string{"_"} +
+          std::ofstream fout(std::string{m_path + "parallel_differences_"} + stringify(m_tag) + std::string{"_"} +
                              stringify(parallel::rank()));
 
           fout.precision(15);
@@ -753,9 +816,11 @@ class ParallelChecker
 
       ++m_tag;
     }
+    // LCOV_EXCL_START
     catch (HighFive::Exception& e) {
       throw NormalError(e.what());
     }
+    // LCOV_EXCL_STOP
   }
 
   template <typename DataType, ItemType item_type, typename ConnectivityPtr>
@@ -794,9 +859,11 @@ class ParallelChecker
         const auto& [iterator, success] =
           item_number_to_item_id_map.insert(std::make_pair(item_numbers[item_id], item_id));
 
+        // LCOV_EXCL_START
         if (not success) {
           throw UnexpectedError("item numbers have duplicate values");
         }
+        // LCOV_EXCL_STOP
       }
 
       Assert(item_number_to_item_id_map.size() == item_numbers.size());
@@ -813,6 +880,7 @@ class ParallelChecker
       if (parallel::allReduceMin(min(index_in_reference)) < 0) {
         throw NormalError("some item numbers are not defined in reference");
       }
+      this->_checkGlobalNumberOfItems<item_type>(i_connectivity, reference_item_numbers.size());
 
       Array<const int> owner = this->_getItemOwner<item_type>(i_connectivity);
 
@@ -845,7 +913,7 @@ class ParallelChecker
                   << rang::fg::cyan << "\" files for details]" << rang::fg::reset << '\n';
 
         {
-          std::ofstream fout(std::string{"parallel_differences_"} + stringify(m_tag) + std::string{"_"} +
+          std::ofstream fout(std::string{m_path + "parallel_differences_"} + stringify(m_tag) + std::string{"_"} +
                              stringify(parallel::rank()));
 
           fout.precision(15);
@@ -879,9 +947,11 @@ class ParallelChecker
 
       ++m_tag;
     }
+    // LCOV_EXCL_START
     catch (HighFive::Exception& e) {
       throw NormalError(e.what());
     }
+    // LCOV_EXCL_STOP
   }
 
   template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
@@ -926,9 +996,11 @@ class ParallelChecker
         const auto& [iterator, success] =
           item_number_to_item_id_map.insert(std::make_pair(item_numbers[item_id], item_id));
 
+        // LCOV_EXCL_START
         if (not success) {
           throw UnexpectedError("item numbers have duplicate values");
         }
+        // LCOV_EXCL_STOP
       }
 
       Assert(item_number_to_item_id_map.size() == item_numbers.size());
@@ -945,6 +1017,7 @@ class ParallelChecker
       if (parallel::allReduceMin(min(item_index_in_reference)) < 0) {
         throw NormalError("some item numbers are not defined in reference");
       }
+      this->_checkGlobalNumberOfItems<item_type>(i_connectivity, reference_item_numbers.size());
 
       Array<const int> owner = this->_getItemOwner<item_type>(i_connectivity);
 
@@ -986,11 +1059,11 @@ class ParallelChecker
         } else {
           std::cout << rang::fg::cyan << " | compare: " << rang::fgB::yellow << "not synchronized" << rang::fg::reset;
         }
-        std::cout << rang::fg::cyan << " [see \"" << rang::fgB::blue << "parallel_differences_" << m_tag << "_*"
-                  << rang::fg::cyan << "\" files for details]" << rang::fg::reset << '\n';
+        std::cout << rang::fg::cyan << " [see \"" << rang::fgB::blue << m_path + "parallel_differences_" << m_tag
+                  << "_*" << rang::fg::cyan << "\" files for details]" << rang::fg::reset << '\n';
 
         {
-          std::ofstream fout(std::string{"parallel_differences_"} + stringify(m_tag) + std::string{"_"} +
+          std::ofstream fout(std::string{m_path + "parallel_differences_"} + stringify(m_tag) + std::string{"_"} +
                              stringify(parallel::rank()));
 
           fout.precision(15);
@@ -1040,9 +1113,11 @@ class ParallelChecker
 
       ++m_tag;
     }
+    // LCOV_EXCL_START
     catch (HighFive::Exception& e) {
       throw NormalError(e.what());
     }
+    // LCOV_EXCL_STOP
   }
 
   template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
@@ -1088,9 +1163,11 @@ class ParallelChecker
         const auto& [iterator, success] =
           item_number_to_item_id_map.insert(std::make_pair(item_numbers[item_id], item_id));
 
+        // LCOV_EXCL_START
         if (not success) {
           throw UnexpectedError("item numbers have duplicate values");
         }
+        // LCOV_EXCL_STOP
       }
 
       Assert(item_number_to_item_id_map.size() == item_numbers.size());
@@ -1107,6 +1184,7 @@ class ParallelChecker
       if (parallel::allReduceMin(min(item_index_in_reference)) < 0) {
         throw NormalError("some item numbers are not defined in reference");
       }
+      this->_checkGlobalNumberOfItems<item_type>(i_connectivity, reference_item_numbers.size());
 
       Array<const int> owner = this->_getItemOwner<item_type>(i_connectivity);
 
@@ -1150,11 +1228,11 @@ class ParallelChecker
         } else {
           std::cout << rang::fg::cyan << " | compare: " << rang::fgB::yellow << "not synchronized" << rang::fg::reset;
         }
-        std::cout << rang::fg::cyan << " [see \"" << rang::fgB::blue << "parallel_differences_" << m_tag << "_*"
-                  << rang::fg::cyan << "\" files for details]" << rang::fg::reset << '\n';
+        std::cout << rang::fg::cyan << " [see \"" << rang::fgB::blue << m_path + "parallel_differences_" << m_tag
+                  << "_*" << rang::fg::cyan << "\" files for details]" << rang::fg::reset << '\n';
 
         {
-          std::ofstream fout(std::string{"parallel_differences_"} + stringify(m_tag) + std::string{"_"} +
+          std::ofstream fout(std::string{m_path + "parallel_differences_"} + stringify(m_tag) + std::string{"_"} +
                              stringify(parallel::rank()));
 
           fout.precision(15);
@@ -1206,9 +1284,11 @@ class ParallelChecker
 
       ++m_tag;
     }
+    // LCOV_EXCL_START
     catch (HighFive::Exception& e) {
       throw NormalError(e.what());
     }
+    // LCOV_EXCL_STOP
   }
 
 #else    // PUGS_HAS_HDF5
@@ -1235,6 +1315,7 @@ class ParallelChecker
   static ParallelChecker&
   instance()
   {
+    Assert(m_instance != nullptr, "ParallelChecker was not created");
     return *m_instance;
   }
 
@@ -1250,6 +1331,13 @@ class ParallelChecker
     if (m_tag != 0) {
       throw UnexpectedError("Cannot modify parallel checker mode if it was already used");
     }
+
+    // LCOV_EXCL_START
+    if ((mode == Mode::write) and (parallel::size() > 1)) {
+      throw NotImplementedError("parallel check write in parallel");
+    }
+    // LCOV_EXCL_STOP
+
     m_mode = mode;
   }
 
@@ -1266,6 +1354,7 @@ class ParallelChecker
       throw UnexpectedError("Cannot modify parallel checker file if it was already used");
     }
     m_filename = filename;
+    m_path     = std::filesystem::path(filename).remove_filename();
   }
 
   bool
@@ -1287,10 +1376,6 @@ class ParallelChecker
     }
     }
 
-    if ((is_writting) and (parallel::size() > 1)) {
-      throw NotImplementedError("parallel check write in parallel");
-    }
-
     return is_writting;
   }
 };
diff --git a/src/mesh/ItemOfItemType.hpp b/src/mesh/ItemOfItemType.hpp
index c6ec3cf16..970736ada 100644
--- a/src/mesh/ItemOfItemType.hpp
+++ b/src/mesh/ItemOfItemType.hpp
@@ -31,7 +31,7 @@ using FaceOfNode = ItemOfItemType<ItemType::face, ItemType::node>;
 using EdgeOfNode = ItemOfItemType<ItemType::edge, ItemType::node>;
 
 template <ItemType sub_item_type, ItemType item_type>
-constexpr inline int item_of_item_type_index;
+constexpr inline int item_of_item_type_index = -1;
 
 template <>
 constexpr inline int item_of_item_type_index<ItemType::face, ItemType::cell> = 0;
diff --git a/src/utils/BuildInfo.cpp b/src/utils/BuildInfo.cpp
index 8a02d93a3..c6ac6cde6 100644
--- a/src/utils/BuildInfo.cpp
+++ b/src/utils/BuildInfo.cpp
@@ -18,7 +18,7 @@
 #endif   // PUGS_HAS_PETSC
 
 #ifdef PUGS_HAS_HDF5
-#include <highfive/H5File.hpp>
+#include <highfive/highfive.hpp>
 #endif   // PUGS_HAS_HDF5
 
 std::string
@@ -85,7 +85,7 @@ BuildInfo::hdf5Library()
 
 #ifdef H5_HAVE_PARALLEL
   return stringify(H5_VERSION) + " [parallel]";
-#else
+#else    // H5_HAVE_PARALLEL
   return stringify(H5_VERSION) + " [sequential]";
 #endif   // H5_HAVE_PARALLEL
 
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index fa8fabd7f..aaace485d 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -114,6 +114,7 @@ add_executable (unit_tests
   test_NameProcessor.cpp
   test_NaNHelper.cpp
   test_OStream.cpp
+  test_ParallelChecker_write.cpp
   test_ParseError.cpp
   test_PETScUtils.cpp
   test_PrimalToDiamondDualConnectivityDataMapper.cpp
@@ -198,6 +199,7 @@ add_executable (mpi_unit_tests
   test_MeshNodeInterface.cpp
   test_Messenger.cpp
   test_OFStream.cpp
+  test_ParallelChecker_read.cpp
   test_Partitioner.cpp
   test_RandomEngine.cpp
   test_SubItemArrayPerItemVariant.cpp
@@ -212,8 +214,15 @@ add_executable (mpi_unit_tests
 add_library(test_Pugs_MeshDataBase
   MeshDataBaseForTests.cpp)
 
+add_library(test_Pugs_ParallelCheckerTester
+  ParallelCheckerTester.cpp)
+
+target_link_libraries (test_Pugs_ParallelCheckerTester
+  ${HIGHFIVE_TARGET})
+
 target_link_libraries (unit_tests
   test_Pugs_MeshDataBase
+  test_Pugs_ParallelCheckerTester
   PugsLanguageAST
   PugsLanguageModules
   PugsLanguageAlgorithms
@@ -238,6 +247,7 @@ target_link_libraries (unit_tests
 
 target_link_libraries (mpi_unit_tests
   test_Pugs_MeshDataBase
+  test_Pugs_ParallelCheckerTester
   PugsAlgebra
   PugsAnalysis
   PugsUtils
diff --git a/tests/ParallelCheckerTester.cpp b/tests/ParallelCheckerTester.cpp
new file mode 100644
index 000000000..80e92e449
--- /dev/null
+++ b/tests/ParallelCheckerTester.cpp
@@ -0,0 +1,43 @@
+#include <ParallelCheckerTester.hpp>
+
+bool
+ParallelCheckerTester::isCreated() const
+{
+  return ParallelChecker::m_instance != nullptr;
+}
+
+std::string
+ParallelCheckerTester::getFilename() const
+{
+  return ParallelChecker::instance().m_filename;
+}
+
+ParallelChecker::Mode
+ParallelCheckerTester::getMode() const
+{
+  return ParallelChecker::instance().m_mode;
+}
+
+size_t
+ParallelCheckerTester::getTag() const
+{
+  return ParallelChecker::instance().m_tag;
+}
+
+void
+ParallelCheckerTester::setFilename(const std::string& filename) const
+{
+  ParallelChecker::instance().m_filename = filename;
+}
+
+void
+ParallelCheckerTester::setMode(ParallelChecker::Mode mode) const
+{
+  ParallelChecker::instance().m_mode = mode;
+}
+
+void
+ParallelCheckerTester::setTag(size_t tag) const
+{
+  ParallelChecker::instance().m_tag = tag;
+}
diff --git a/tests/ParallelCheckerTester.hpp b/tests/ParallelCheckerTester.hpp
new file mode 100644
index 000000000..9aefed506
--- /dev/null
+++ b/tests/ParallelCheckerTester.hpp
@@ -0,0 +1,23 @@
+#ifndef PARALLEL_CHECKER_TESTER_HPP
+#define PARALLEL_CHECKER_TESTER_HPP
+
+#include <dev/ParallelChecker.hpp>
+
+class ParallelCheckerTester
+{
+ public:
+  bool isCreated() const;
+
+  std::string getFilename() const;
+  ParallelChecker::Mode getMode() const;
+  size_t getTag() const;
+
+  void setFilename(const std::string& filename) const;
+  void setMode(ParallelChecker::Mode mode) const;
+  void setTag(size_t tag) const;
+
+  ParallelCheckerTester()  = default;
+  ~ParallelCheckerTester() = default;
+};
+
+#endif   // PARALLEL_CHECKER_TESTER_HPP
diff --git a/tests/test_ParallelChecker_read.cpp b/tests/test_ParallelChecker_read.cpp
new file mode 100644
index 000000000..497e1729d
--- /dev/null
+++ b/tests/test_ParallelChecker_read.cpp
@@ -0,0 +1,2078 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <dev/ParallelChecker.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+
+#include <mesh/ItemArrayUtils.hpp>
+#include <mesh/SubItemArrayPerItemUtils.hpp>
+#include <mesh/SubItemValuePerItemUtils.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+#ifdef PUGS_HAS_HDF5
+
+#include <ParallelCheckerTester.hpp>
+
+template <typename T>
+struct test_TinyVectorDataType;
+
+template <size_t Dimension, typename DataT>
+struct test_TinyVectorDataType<TinyVector<Dimension, DataT>> : public HighFive::DataType
+{
+  test_TinyVectorDataType()
+  {
+    hsize_t dim[]     = {Dimension};
+    auto h5_data_type = HighFive::create_datatype<DataT>();
+    _hid              = H5Tarray_create(h5_data_type.getId(), 1, dim);
+  }
+};
+
+template <typename T>
+struct test_TinyMatrixDataType;
+
+template <size_t M, size_t N, typename DataT>
+struct test_TinyMatrixDataType<TinyMatrix<M, N, DataT>> : public HighFive::DataType
+{
+  test_TinyMatrixDataType()
+  {
+    hsize_t dim[]     = {M, N};
+    auto h5_data_type = HighFive::create_datatype<DataT>();
+    _hid              = H5Tarray_create(h5_data_type.getId(), 2, dim);
+  }
+};
+
+TEST_CASE("ParallelChecker_read", "[dev]")
+{
+  {
+    ParallelCheckerTester pc_tester;
+    if (pc_tester.isCreated()) {
+      REQUIRE_NOTHROW(ParallelChecker::destroy());
+    }
+  }
+  REQUIRE_NOTHROW(ParallelChecker::create());
+  REQUIRE_NOTHROW(ParallelChecker::instance().setMode(ParallelChecker::Mode::read));
+
+  std::string tmp_dirname;
+
+  {
+    if (parallel::rank() == 0) {
+      tmp_dirname = [&]() -> std::string {
+        std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_test_read_h5_XXXXXX";
+        return std::string{mkdtemp(&temp_filename[0])};
+      }();
+    }
+    parallel::broadcast(tmp_dirname, 0);
+
+    std::filesystem::path path = tmp_dirname;
+    REQUIRE_NOTHROW(ParallelChecker::instance().setFilename(path / "parallel_check.h5"));
+  }
+
+  auto get_item_numbers = []<typename ConnectivityT>(const ConnectivityT& connectivity, ItemType item_type) {
+    Array<const int> number;
+    Array<const bool> is_owned;
+
+    switch (item_type) {
+    case ItemType::cell: {
+      number   = connectivity.cellNumber().arrayView();
+      is_owned = connectivity.cellIsOwned().arrayView();
+      break;
+    }
+    case ItemType::face: {
+      number   = connectivity.faceNumber().arrayView();
+      is_owned = connectivity.faceIsOwned().arrayView();
+      break;
+    }
+    case ItemType::edge: {
+      number   = connectivity.edgeNumber().arrayView();
+      is_owned = connectivity.edgeIsOwned().arrayView();
+      break;
+    }
+    case ItemType::node: {
+      number   = connectivity.nodeNumber().arrayView();
+      is_owned = connectivity.nodeIsOwned().arrayView();
+      break;
+    }
+    }
+
+    if (parallel::size() > 1) {
+      const size_t nb_local_item = [is_owned]() {
+        size_t count = 0;
+        for (size_t i = 0; i < is_owned.size(); ++i) {
+          count += is_owned[i];
+        }
+        return count;
+      }();
+
+      Array<int> owned_number{nb_local_item};
+      for (size_t i = 0, l = 0; i < is_owned.size(); ++i) {
+        if (is_owned[i]) {
+          owned_number[l++] = number[i];
+        }
+      }
+
+      number = parallel::allGatherVariable(owned_number);
+    }
+
+    return number;
+  };
+
+  auto get_subitem_rows_map =
+    []<typename ConnectivityT>(const ConnectivityT& connectivity, ItemType item_type,
+                               ItemType subitem_type) -> Array<const typename ConnectivityMatrix::IndexType> {
+    Array rows_map = connectivity.getMatrix(item_type, subitem_type).rowsMap();
+
+    if (parallel::size() == 1) {
+      return rows_map;
+    } else {
+      Array<const bool> is_owned;
+      switch (item_type) {
+      case ItemType::cell: {
+        is_owned = connectivity.cellIsOwned().arrayView();
+        break;
+      }
+      case ItemType::face: {
+        is_owned = connectivity.faceIsOwned().arrayView();
+        break;
+      }
+      case ItemType::edge: {
+        is_owned = connectivity.edgeIsOwned().arrayView();
+        break;
+      }
+      case ItemType::node: {
+        is_owned = connectivity.nodeIsOwned().arrayView();
+        break;
+      }
+      }
+
+      Array<size_t> nb_subitem_per_item(rows_map.size() - 1);
+
+      for (size_t i = 0; i < nb_subitem_per_item.size(); ++i) {
+        nb_subitem_per_item[i] = rows_map[i + 1] - rows_map[i];
+      }
+
+      const size_t nb_local_item = [is_owned]() {
+        size_t count = 0;
+        for (size_t i = 0; i < is_owned.size(); ++i) {
+          count += is_owned[i];
+        }
+        return count;
+      }();
+
+      {
+        Array<size_t> owned_nb_subitem_per_item{nb_local_item};
+        for (size_t i = 0, l = 0; i < is_owned.size(); ++i) {
+          if (is_owned[i]) {
+            owned_nb_subitem_per_item[l++] = nb_subitem_per_item[i];
+          }
+        }
+        nb_subitem_per_item = parallel::allGatherVariable(owned_nb_subitem_per_item);
+      }
+
+      Array<typename ConnectivityMatrix::IndexType> global_rows_map{nb_subitem_per_item.size() + 1};
+      global_rows_map[0] = 0;
+      for (size_t i = 0; i < nb_subitem_per_item.size(); ++i) {
+        global_rows_map[i + 1] = global_rows_map[i] + nb_subitem_per_item[i];
+      }
+      return global_rows_map;
+    }
+  };
+
+  SECTION("check parallel write implementation")
+  {
+    if (parallel::size() == 1) {
+      REQUIRE_NOTHROW(ParallelChecker::instance().setMode(ParallelChecker::Mode::write));
+    } else {
+      REQUIRE_THROWS_WITH(ParallelChecker::instance().setMode(ParallelChecker::Mode::write),
+                          "not implemented yet: parallel check write in parallel");
+    }
+
+    ParallelCheckerTester pc_tester;
+    pc_tester.setMode(ParallelChecker::Mode::automatic);
+    REQUIRE(ParallelChecker::instance().isWriting() == (parallel::size() == 1));
+  }
+
+  SECTION("check ItemValue/ItemArray")
+  {
+    // ItemValues
+    {   // 1d
+      auto mesh                           = MeshDataBaseForTests::get().unordered1DMesh();
+      std::string filename                = ParallelChecker::instance().filename();
+      const Connectivity<1>& connectivity = mesh->connectivity();
+
+      const std::string name = "sin";
+
+      SourceLocation source_location;
+
+      Array numbers = get_item_numbers(connectivity, ItemType::cell);
+
+      int tag = 12;
+      if (parallel::rank() == 0) {
+        HighFive::File file(filename, HighFive::File::Overwrite);
+        HighFive::Group group = file.createGroup("/values/" + std::to_string(tag));
+
+        group.createDataSet<int>("numbers", HighFive::DataSpace{std::vector<size_t>{numbers.size()}})
+          .write_raw<int>(&(numbers[0]));
+
+        Array<double> values{numbers.size()};
+        for (size_t i = 0; i < numbers.size(); ++i) {
+          values[i] = std::sin(numbers[i]);
+        }
+        group.createDataSet<double>(name, HighFive::DataSpace{std::vector<size_t>{numbers.size()}})
+          .write_raw<double>(&(values[0]));
+
+        group.createAttribute("filename", source_location.filename());
+        group.createAttribute("line", source_location.line());
+        group.createAttribute("function", source_location.function());
+        group.createAttribute("name", name);
+        group.createAttribute("dimension", connectivity.dimension());
+        group.createAttribute("item_type", std::string{itemName(ItemType::cell)});
+        group.createAttribute("data_type", demangle<double>());
+      }
+      parallel::barrier();
+      ParallelCheckerTester pc_tester;
+      pc_tester.setTag(tag);
+
+      CellValue<double> values{connectivity};
+      CellValue<const int> cell_number = connectivity.cellNumber();
+      for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+        values[cell_id] = std::sin(cell_number[cell_id]);
+      }
+
+      REQUIRE_NOTHROW(parallel_check(values, "sin", source_location));
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different name in ref");
+      REQUIRE_NOTHROW(parallel_check(values, "not_sin", source_location));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source file in ref");
+      REQUIRE_NOTHROW(parallel_check(values, "sin",
+                                     SourceLocation{"other-source-file", source_location.line(),
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source line in ref");
+      REQUIRE_NOTHROW(parallel_check(values, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line() + 100,
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source function in ref");
+      REQUIRE_NOTHROW(parallel_check(values, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line(),
+                                                    source_location.column(), "foo"}));
+
+      if (parallel::size() > 1) {
+        CellValue<double> not_sync     = copy(values);
+        CellValue<const bool> is_owned = connectivity.cellIsOwned();
+        if (parallel::rank() == 0) {
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            if (not is_owned[cell_id]) {
+              not_sync[cell_id] += 3.2;
+              break;
+            }
+          }
+        }
+        REQUIRE(not isSynchronized(not_sync));
+        pc_tester.setTag(tag);
+        UNSCOPED_INFO("can have different ghost values in ref (no exception)");
+        REQUIRE_NOTHROW(parallel_check(not_sync, "sin", source_location));
+      }
+
+      {
+        CellValue<double> different = copy(values);
+        bool has_difference         = false;
+        if (parallel::rank() == 0) {
+          CellValue<const bool> is_owned = connectivity.cellIsOwned();
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            if (is_owned[cell_id]) {
+              different[cell_id] += 3.2;
+              has_difference = true;
+              break;
+            }
+          }
+        }
+        has_difference = parallel::allReduceOr(has_difference);
+
+        REQUIRE(has_difference);
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(different, "sin", source_location), "error: calculations differ!");
+      }
+
+      {
+        CellValue<int> other_data_type{connectivity};
+        other_data_type.fill(0);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_data_type, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        CellArray<double> arrays{connectivity, 1};
+        arrays.fill(0);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(arrays, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        CellArray<double> arrays{connectivity, 2};
+        arrays.fill(0);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(arrays, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        if (parallel::rank() == 0) {
+          HighFive::File file(filename, HighFive::File::ReadWrite);
+          HighFive::Group group = file.getGroup("/values/" + std::to_string(tag));
+          group.getAttribute("dimension").write(size_t{2});
+        }
+        parallel::barrier();
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(values, "sin", source_location), "error: cannot compare data");
+
+        if (parallel::rank() == 0) {
+          HighFive::File file(filename, HighFive::File::ReadWrite);
+          HighFive::Group group = file.getGroup("/values/" + std::to_string(tag));
+          group.getAttribute("dimension").write(connectivity.dimension());
+        }
+        parallel::barrier();
+      }
+
+      {
+        if (parallel::rank() == 0) {
+          HighFive::File file(filename, HighFive::File::ReadWrite);
+          HighFive::Group group = file.getGroup("/values/" + std::to_string(tag));
+          group.getAttribute("item_type").write(std::string{itemName(ItemType::node)});
+        }
+        parallel::barrier();
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(values, "sin", source_location), "error: cannot compare data");
+
+        if (parallel::rank() == 0) {
+          HighFive::File file(filename, HighFive::File::ReadWrite);
+          HighFive::Group group = file.getGroup("/values/" + std::to_string(tag));
+          group.getAttribute("item_type").write(std::string{itemName(ItemType::cell)});
+        }
+        parallel::barrier();
+      }
+
+      {
+        auto other_mesh                           = MeshDataBaseForTests::get().cartesian1DMesh();
+        const Connectivity<1>& other_connectivity = other_mesh->connectivity();
+
+        CellValue<double> other_shape{other_connectivity};
+        other_shape.fill(1);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_shape, "sin", source_location),
+                            "error: some item numbers are not defined in reference");
+      }
+    }
+
+    // ItemArray
+    {   // 1d
+      auto mesh                           = MeshDataBaseForTests::get().unordered1DMesh();
+      std::string filename                = ParallelChecker::instance().filename();
+      const Connectivity<1>& connectivity = mesh->connectivity();
+
+      const std::string name = "sin";
+
+      SourceLocation source_location;
+
+      Array numbers = get_item_numbers(connectivity, ItemType::cell);
+
+      int tag = 12;
+      if (parallel::rank() == 0) {
+        HighFive::File file(filename, HighFive::File::Overwrite);
+        HighFive::Group group = file.createGroup("/values/" + std::to_string(tag));
+
+        group.createDataSet<int>("numbers", HighFive::DataSpace{std::vector<size_t>{numbers.size()}})
+          .write_raw<int>(&(numbers[0]));
+
+        Table<double> arrays{numbers.size(), 2};
+        for (size_t i = 0; i < numbers.size(); ++i) {
+          for (size_t j = 0; j < 2; ++j) {
+            arrays[i][j] = std::sin(2 * numbers[i] + j);
+          }
+        }
+        group
+          .createDataSet<double>(name, HighFive::DataSpace{std::vector<size_t>{arrays.numberOfRows(),
+                                                                               arrays.numberOfColumns()}})
+          .write_raw<double>(&(arrays(0, 0)));
+
+        group.createAttribute("filename", source_location.filename());
+        group.createAttribute("line", source_location.line());
+        group.createAttribute("function", source_location.function());
+        group.createAttribute("name", name);
+        group.createAttribute("dimension", connectivity.dimension());
+        group.createAttribute("item_type", std::string{itemName(ItemType::cell)});
+        group.createAttribute("data_type", demangle<double>());
+      }
+      parallel::barrier();
+      ParallelCheckerTester pc_tester;
+      pc_tester.setTag(tag);
+
+      CellArray<double> arrays{connectivity, 2};
+      CellValue<const int> cell_number = connectivity.cellNumber();
+      for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+        for (size_t j = 0; j < 2; ++j) {
+          arrays[cell_id][j] = std::sin(2 * cell_number[cell_id] + j);
+        }
+      }
+
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin", source_location));
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different name in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "not_sin", source_location));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source file in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{"other-source-file", source_location.line(),
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source line in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line() + 100,
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source function in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line(),
+                                                    source_location.column(), "foo"}));
+
+      if (parallel::size() > 1) {
+        CellArray<double> not_sync     = copy(arrays);
+        CellValue<const bool> is_owned = connectivity.cellIsOwned();
+        if (parallel::rank() == 0) {
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            if (not is_owned[cell_id]) {
+              not_sync[cell_id][0] += 3.2;
+              break;
+            }
+          }
+        }
+        REQUIRE(not isSynchronized(not_sync));
+        pc_tester.setTag(tag);
+        UNSCOPED_INFO("can have different ghost values in ref (no exception)");
+        REQUIRE_NOTHROW(parallel_check(not_sync, "sin", source_location));
+      }
+
+      {
+        CellArray<double> different = copy(arrays);
+        bool has_difference         = false;
+        if (parallel::rank() == 0) {
+          CellValue<const bool> is_owned = connectivity.cellIsOwned();
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            if (is_owned[cell_id]) {
+              different[cell_id][0] += 3.2;
+              has_difference = true;
+              break;
+            }
+          }
+        }
+        has_difference = parallel::allReduceOr(has_difference);
+
+        REQUIRE(has_difference);
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(different, "sin", source_location), "error: calculations differ!");
+      }
+
+      {
+        CellValue<int> other_data_type{connectivity};
+        other_data_type.fill(0);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_data_type, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        CellArray<double> arrays{connectivity, 1};
+        arrays.fill(0);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(arrays, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        auto other_mesh                           = MeshDataBaseForTests::get().cartesian1DMesh();
+        const Connectivity<1>& other_connectivity = other_mesh->connectivity();
+
+        CellArray<double> other_shape{other_connectivity, 2};
+        other_shape.fill(1);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_shape, "sin", source_location),
+                            "error: some item numbers are not defined in reference");
+      }
+    }
+
+    // ItemValues
+    {   // 2d
+      auto mesh                           = MeshDataBaseForTests::get().hybrid2DMesh();
+      std::string filename                = ParallelChecker::instance().filename();
+      const Connectivity<2>& connectivity = mesh->connectivity();
+
+      const std::string name = "sin";
+
+      SourceLocation source_location;
+
+      Array numbers = get_item_numbers(connectivity, ItemType::node);
+
+      using DataType = TinyVector<3>;
+
+      int tag = 9;
+      if (parallel::rank() == 0) {
+        HighFive::File file(filename, HighFive::File::Overwrite);
+        HighFive::Group group = file.createGroup("/values/" + std::to_string(tag));
+
+        group.createDataSet<int>("numbers", HighFive::DataSpace{std::vector<size_t>{numbers.size()}})
+          .write_raw<int>(&(numbers[0]));
+
+        Array<DataType> values{numbers.size()};
+        for (size_t i = 0; i < numbers.size(); ++i) {
+          for (size_t j = 0; j < DataType::Dimension; ++j) {
+            values[i][j] = std::sin(numbers[i] + j);
+          }
+        }
+        group
+          .createDataSet(name, HighFive::DataSpace{std::vector<size_t>{numbers.size()}},
+                         test_TinyVectorDataType<DataType>{})
+          .template write_raw<double>(&(values[0][0]), test_TinyVectorDataType<DataType>{});
+
+        group.createAttribute("filename", source_location.filename());
+        group.createAttribute("line", source_location.line());
+        group.createAttribute("function", source_location.function());
+        group.createAttribute("name", name);
+        group.createAttribute("dimension", connectivity.dimension());
+        group.createAttribute("item_type", std::string{itemName(ItemType::node)});
+        group.createAttribute("data_type", demangle<DataType>());
+      }
+      parallel::barrier();
+      ParallelCheckerTester pc_tester;
+      pc_tester.setTag(tag);
+
+      NodeValue<DataType> values{connectivity};
+      NodeValue<const int> node_number = connectivity.nodeNumber();
+      for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+        for (size_t j = 0; j < DataType::Dimension; ++j) {
+          values[node_id][j] = std::sin(node_number[node_id] + j);
+        }
+      }
+
+      REQUIRE_NOTHROW(parallel_check(values, "sin", source_location));
+
+      if (parallel::size() > 1) {
+        NodeValue<DataType> not_sync   = copy(values);
+        NodeValue<const bool> is_owned = connectivity.nodeIsOwned();
+        if (parallel::rank() == 0) {
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            if (not is_owned[node_id]) {
+              not_sync[node_id][0] += 3.2;
+              break;
+            }
+          }
+        }
+        REQUIRE(not isSynchronized(not_sync));
+        pc_tester.setTag(tag);
+        UNSCOPED_INFO("can have different ghost values in ref (no exception)");
+        REQUIRE_NOTHROW(parallel_check(not_sync, "sin", source_location));
+      }
+
+      {
+        NodeValue<DataType> different = copy(values);
+        bool has_difference           = false;
+        if (parallel::rank() == 0) {
+          NodeValue<const bool> is_owned = connectivity.nodeIsOwned();
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            if (is_owned[node_id]) {
+              different[node_id][0] += 3.2;
+              has_difference = true;
+              break;
+            }
+          }
+        }
+        has_difference = parallel::allReduceOr(has_difference);
+
+        REQUIRE(has_difference);
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(different, "sin", source_location), "error: calculations differ!");
+      }
+    }
+
+    // ItemArray
+    {   // 2d
+      auto mesh                           = MeshDataBaseForTests::get().hybrid2DMesh();
+      std::string filename                = ParallelChecker::instance().filename();
+      const Connectivity<2>& connectivity = mesh->connectivity();
+
+      const std::string name = "sin";
+
+      SourceLocation source_location;
+
+      using DataType = TinyMatrix<3, 2>;
+
+      Array numbers = get_item_numbers(connectivity, ItemType::face);
+
+      int tag = 12;
+      if (parallel::rank() == 0) {
+        HighFive::File file(filename, HighFive::File::Overwrite);
+        HighFive::Group group = file.createGroup("/values/" + std::to_string(tag));
+
+        group.createDataSet<int>("numbers", HighFive::DataSpace{std::vector<size_t>{numbers.size()}})
+          .write_raw<int>(&(numbers[0]));
+
+        Table<DataType> arrays{numbers.size(), 2};
+        for (size_t i = 0; i < arrays.numberOfRows(); ++i) {
+          for (size_t j = 0; j < arrays.numberOfColumns(); ++j) {
+            for (size_t k = 0; k < DataType::NumberOfRows; ++k) {
+              for (size_t l = 0; l < DataType::NumberOfColumns; ++l) {
+                arrays[i][j](k, l) = std::sin(2 * numbers[i] + j + 3 * k + 2 * l);
+              }
+            }
+          }
+        }
+        group
+          .createDataSet(name,
+                         HighFive::DataSpace{std::vector<size_t>{arrays.numberOfRows(), arrays.numberOfColumns()}},
+                         test_TinyMatrixDataType<DataType>{})
+          .template write_raw<double>(&(arrays[0][0](0, 0)), test_TinyMatrixDataType<DataType>{});
+
+        group.createAttribute("filename", source_location.filename());
+        group.createAttribute("line", source_location.line());
+        group.createAttribute("function", source_location.function());
+        group.createAttribute("name", name);
+        group.createAttribute("dimension", connectivity.dimension());
+        group.createAttribute("item_type", std::string{itemName(ItemType::face)});
+        group.createAttribute("data_type", demangle<DataType>());
+      }
+      parallel::barrier();
+      ParallelCheckerTester pc_tester;
+      pc_tester.setTag(tag);
+
+      FaceArray<DataType> arrays{connectivity, 2};
+      FaceValue<const int> face_number = connectivity.faceNumber();
+      for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+        for (size_t j = 0; j < arrays.sizeOfArrays(); ++j) {
+          for (size_t k = 0; k < DataType::NumberOfRows; ++k) {
+            for (size_t l = 0; l < DataType::NumberOfColumns; ++l) {
+              arrays[face_id][j](k, l) = std::sin(2 * face_number[face_id] + j + 3 * k + 2 * l);
+            }
+          }
+        }
+      }
+
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin", source_location));
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different name in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "not_sin", source_location));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source file in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{"other-source-file", source_location.line(),
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source line in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line() + 100,
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source function in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line(),
+                                                    source_location.column(), "foo"}));
+
+      if (parallel::size() > 1) {
+        FaceArray<DataType> not_sync   = copy(arrays);
+        FaceValue<const bool> is_owned = connectivity.faceIsOwned();
+        if (parallel::rank() == 0) {
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            if (not is_owned[face_id]) {
+              not_sync[face_id][0](0, 0) += 3.2;
+              break;
+            }
+          }
+        }
+        REQUIRE(not isSynchronized(not_sync));
+        pc_tester.setTag(tag);
+        UNSCOPED_INFO("can have different ghost values in ref (no exception)");
+        REQUIRE_NOTHROW(parallel_check(not_sync, "sin", source_location));
+      }
+
+      {
+        FaceArray<DataType> different = copy(arrays);
+        bool has_difference           = false;
+        if (parallel::rank() == 0) {
+          FaceValue<const bool> is_owned = connectivity.faceIsOwned();
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            if (is_owned[face_id]) {
+              different[face_id][0](0, 0) += 3.2;
+              has_difference = true;
+              break;
+            }
+          }
+        }
+        has_difference = parallel::allReduceOr(has_difference);
+
+        REQUIRE(has_difference);
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(different, "sin", source_location), "error: calculations differ!");
+      }
+
+      {
+        FaceValue<TinyVector<6>> other_data_type{connectivity};
+        other_data_type.fill(zero);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_data_type, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        FaceArray<DataType> arrays{connectivity, 1};
+        arrays.fill(zero);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(arrays, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        auto other_mesh                           = MeshDataBaseForTests::get().cartesian2DMesh();
+        const Connectivity<2>& other_connectivity = other_mesh->connectivity();
+
+        FaceArray<DataType> other_shape{other_connectivity, 2};
+        other_shape.fill(zero);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_shape, "sin", source_location),
+                            "error: number of items differs from reference");
+      }
+    }
+
+    // ItemValues
+    {   // 3d
+      auto mesh                           = MeshDataBaseForTests::get().hybrid3DMesh();
+      std::string filename                = ParallelChecker::instance().filename();
+      const Connectivity<3>& connectivity = mesh->connectivity();
+
+      const std::string name = "sin";
+
+      SourceLocation source_location;
+
+      Array numbers = get_item_numbers(connectivity, ItemType::node);
+
+      using DataType = TinyMatrix<2, 3>;
+
+      int tag = 9;
+      if (parallel::rank() == 0) {
+        HighFive::File file(filename, HighFive::File::Overwrite);
+        HighFive::Group group = file.createGroup("/values/" + std::to_string(tag));
+
+        group.createDataSet<int>("numbers", HighFive::DataSpace{std::vector<size_t>{numbers.size()}})
+          .write_raw<int>(&(numbers[0]));
+
+        Array<DataType> values{numbers.size()};
+        for (size_t i = 0; i < numbers.size(); ++i) {
+          for (size_t j = 0; j < DataType::NumberOfRows; ++j) {
+            for (size_t k = 0; k < DataType::NumberOfColumns; ++k) {
+              values[i](j, k) = std::sin(numbers[i] + j + 2 * k);
+            }
+          }
+        }
+        group
+          .createDataSet(name, HighFive::DataSpace{std::vector<size_t>{numbers.size()}},
+                         test_TinyMatrixDataType<DataType>{})
+          .template write_raw<double>(&(values[0](0, 0)), test_TinyMatrixDataType<DataType>{});
+
+        group.createAttribute("filename", source_location.filename());
+        group.createAttribute("line", source_location.line());
+        group.createAttribute("function", source_location.function());
+        group.createAttribute("name", name);
+        group.createAttribute("dimension", connectivity.dimension());
+        group.createAttribute("item_type", std::string{itemName(ItemType::node)});
+        group.createAttribute("data_type", demangle<DataType>());
+      }
+      parallel::barrier();
+      ParallelCheckerTester pc_tester;
+      pc_tester.setTag(tag);
+
+      NodeValue<DataType> values{connectivity};
+      NodeValue<const int> node_number = connectivity.nodeNumber();
+      for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+        for (size_t j = 0; j < DataType::NumberOfRows; ++j) {
+          for (size_t k = 0; k < DataType::NumberOfColumns; ++k) {
+            values[node_id](j, k) = std::sin(node_number[node_id] + j + 2 * k);
+          }
+        }
+      }
+
+      REQUIRE_NOTHROW(parallel_check(values, "sin", source_location));
+
+      if (parallel::size() > 1) {
+        NodeValue<DataType> not_sync   = copy(values);
+        NodeValue<const bool> is_owned = connectivity.nodeIsOwned();
+        if (parallel::rank() == 0) {
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            if (not is_owned[node_id]) {
+              not_sync[node_id](0, 0) += 3.2;
+              break;
+            }
+          }
+        }
+        REQUIRE(not isSynchronized(not_sync));
+        pc_tester.setTag(tag);
+        UNSCOPED_INFO("can have different ghost values in ref (no exception)");
+        REQUIRE_NOTHROW(parallel_check(not_sync, "sin", source_location));
+      }
+
+      {
+        NodeValue<DataType> different = copy(values);
+        bool has_difference           = false;
+        if (parallel::rank() == 0) {
+          NodeValue<const bool> is_owned = connectivity.nodeIsOwned();
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            if (is_owned[node_id]) {
+              different[node_id](0, 0) += 3.2;
+              has_difference = true;
+              break;
+            }
+          }
+        }
+        has_difference = parallel::allReduceOr(has_difference);
+
+        REQUIRE(has_difference);
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(different, "sin", source_location), "error: calculations differ!");
+      }
+    }
+
+    // ItemArray
+    {   // 3d
+      auto mesh                           = MeshDataBaseForTests::get().hybrid3DMesh();
+      std::string filename                = ParallelChecker::instance().filename();
+      const Connectivity<3>& connectivity = mesh->connectivity();
+
+      const std::string name = "sin";
+
+      SourceLocation source_location;
+
+      using DataType = TinyVector<2>;
+
+      Array numbers = get_item_numbers(connectivity, ItemType::face);
+
+      int tag = 7;
+      if (parallel::rank() == 0) {
+        HighFive::File file(filename, HighFive::File::Overwrite);
+        HighFive::Group group = file.createGroup("/values/" + std::to_string(tag));
+
+        group.createDataSet<int>("numbers", HighFive::DataSpace{std::vector<size_t>{numbers.size()}})
+          .write_raw<int>(&(numbers[0]));
+
+        Table<DataType> arrays{numbers.size(), 2};
+        for (size_t i = 0; i < arrays.numberOfRows(); ++i) {
+          for (size_t j = 0; j < arrays.numberOfColumns(); ++j) {
+            for (size_t k = 0; k < DataType::Dimension; ++k) {
+              arrays[i][j][k] = std::sin(2 * numbers[i] + j + 3 * k);
+            }
+          }
+        }
+        group
+          .createDataSet(name,
+                         HighFive::DataSpace{std::vector<size_t>{arrays.numberOfRows(), arrays.numberOfColumns()}},
+                         test_TinyVectorDataType<DataType>{})
+          .template write_raw<double>(&(arrays[0][0][0]), test_TinyVectorDataType<DataType>{});
+
+        group.createAttribute("filename", source_location.filename());
+        group.createAttribute("line", source_location.line());
+        group.createAttribute("function", source_location.function());
+        group.createAttribute("name", name);
+        group.createAttribute("dimension", connectivity.dimension());
+        group.createAttribute("item_type", std::string{itemName(ItemType::face)});
+        group.createAttribute("data_type", demangle<DataType>());
+      }
+      parallel::barrier();
+      ParallelCheckerTester pc_tester;
+      pc_tester.setTag(tag);
+
+      FaceArray<DataType> arrays{connectivity, 2};
+      FaceValue<const int> face_number = connectivity.faceNumber();
+      for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+        for (size_t j = 0; j < arrays.sizeOfArrays(); ++j) {
+          for (size_t k = 0; k < DataType::Dimension; ++k) {
+            arrays[face_id][j][k] = std::sin(2 * face_number[face_id] + j + 3 * k);
+          }
+        }
+      }
+
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin", source_location));
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different name in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "not_sin", source_location));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source file in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{"other-source-file", source_location.line(),
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source line in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line() + 100,
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source function in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line(),
+                                                    source_location.column(), "foo"}));
+
+      if (parallel::size() > 1) {
+        FaceArray<DataType> not_sync   = copy(arrays);
+        FaceValue<const bool> is_owned = connectivity.faceIsOwned();
+        if (parallel::rank() == 0) {
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            if (not is_owned[face_id]) {
+              not_sync[face_id][0][0] += 3.2;
+              break;
+            }
+          }
+        }
+        REQUIRE(not isSynchronized(not_sync));
+        pc_tester.setTag(tag);
+        UNSCOPED_INFO("can have different ghost values in ref (no exception)");
+        REQUIRE_NOTHROW(parallel_check(not_sync, "sin", source_location));
+      }
+
+      {
+        FaceArray<DataType> different = copy(arrays);
+        bool has_difference           = false;
+        if (parallel::rank() == 0) {
+          FaceValue<const bool> is_owned = connectivity.faceIsOwned();
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            if (is_owned[face_id]) {
+              different[face_id][0][0] += 3.2;
+              has_difference = true;
+              break;
+            }
+          }
+        }
+        has_difference = parallel::allReduceOr(has_difference);
+
+        REQUIRE(has_difference);
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(different, "sin", source_location), "error: calculations differ!");
+      }
+
+      {
+        FaceValue<TinyVector<6>> other_data_type{connectivity};
+        other_data_type.fill(zero);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_data_type, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        FaceArray<DataType> arrays{connectivity, 1};
+        arrays.fill(zero);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(arrays, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        auto other_mesh                           = MeshDataBaseForTests::get().cartesian2DMesh();
+        const Connectivity<2>& other_connectivity = other_mesh->connectivity();
+
+        FaceArray<DataType> other_shape{other_connectivity, 2};
+        other_shape.fill(zero);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_shape, "sin", source_location), "error: cannot compare data");
+      }
+    }
+  }
+
+  SECTION("check SubItemValuePerItem/SubItemArrayPerItem")
+  {
+    // SubItemValuePerItem
+    {   // 1d
+      auto mesh                           = MeshDataBaseForTests::get().unordered1DMesh();
+      std::string filename                = ParallelChecker::instance().filename();
+      const Connectivity<1>& connectivity = mesh->connectivity();
+
+      const std::string name = "sin";
+
+      SourceLocation source_location;
+      Array numbers = get_item_numbers(connectivity, ItemType::node);
+      Array<const typename ConnectivityMatrix::IndexType> rows_map =
+        get_subitem_rows_map(connectivity, ItemType::node, ItemType::cell);
+
+      int tag = 6;
+      if (parallel::rank() == 0) {
+        HighFive::File file(filename, HighFive::File::Overwrite);
+        HighFive::Group group = file.createGroup("/values/" + std::to_string(tag));
+
+        group.createDataSet<int>("numbers", HighFive::DataSpace{std::vector<size_t>{numbers.size()}})
+          .write_raw<int>(&(numbers[0]));
+        group
+          .createDataSet<typename ConnectivityMatrix::IndexType>("rows_map", HighFive::DataSpace{std::vector<size_t>{
+                                                                               rows_map.size()}})
+          .write_raw<typename ConnectivityMatrix::IndexType>(&(rows_map[0]));
+
+        Array<double> values{rows_map[rows_map.size() - 1]};
+        for (size_t i = 0; i < numbers.size(); ++i) {
+          for (size_t i_row = rows_map[i]; i_row < rows_map[i + 1]; ++i_row) {
+            const size_t j = i_row - rows_map[i];
+            values[i_row]  = std::sin(numbers[i] + 2 * j);
+          }
+        }
+
+        group.createDataSet<double>(name, HighFive::DataSpace{std::vector<size_t>{values.size()}})
+          .write_raw<double>(&(values[0]));
+
+        group.createAttribute("filename", source_location.filename());
+        group.createAttribute("line", source_location.line());
+        group.createAttribute("function", source_location.function());
+        group.createAttribute("name", name);
+        group.createAttribute("dimension", connectivity.dimension());
+        group.createAttribute("item_type", std::string{itemName(ItemType::node)});
+        group.createAttribute("subitem_type", std::string{itemName(ItemType::cell)});
+        group.createAttribute("data_type", demangle<double>());
+      }
+
+      parallel::barrier();
+      ParallelCheckerTester pc_tester;
+      pc_tester.setTag(tag);
+
+      auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+
+      CellValuePerNode<double> values{connectivity};
+      NodeValue<const int> node_number = connectivity.nodeNumber();
+      for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+        auto cell_list = node_to_cell_matrix[node_id];
+        for (size_t i_cell = 0; i_cell < cell_list.size(); ++i_cell) {
+          values[node_id][i_cell] = std::sin(node_number[node_id] + 2 * i_cell);
+        }
+      }
+
+      REQUIRE_NOTHROW(parallel_check(values, "sin", source_location));
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different name in ref");
+      REQUIRE_NOTHROW(parallel_check(values, "not_sin", source_location));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source file in ref");
+      REQUIRE_NOTHROW(parallel_check(values, "sin",
+                                     SourceLocation{"other-source-file", source_location.line(),
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source line in ref");
+      REQUIRE_NOTHROW(parallel_check(values, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line() + 100,
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source function in ref");
+      REQUIRE_NOTHROW(parallel_check(values, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line(),
+                                                    source_location.column(), "foo"}));
+
+      if (parallel::size() > 1) {
+        CellValuePerNode<double> not_sync = copy(values);
+        NodeValue<const bool> is_owned    = connectivity.nodeIsOwned();
+        if (parallel::rank() == 0) {
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            if (not is_owned[node_id]) {
+              not_sync[node_id][0] += 3.2;
+              break;
+            }
+          }
+        }
+        REQUIRE(not isSynchronized(not_sync));
+        pc_tester.setTag(tag);
+        UNSCOPED_INFO("can have different ghost values in ref (no exception)");
+        REQUIRE_NOTHROW(parallel_check(not_sync, "sin", source_location));
+      }
+
+      {
+        CellValuePerNode<double> different = copy(values);
+        bool has_difference                = false;
+        if (parallel::rank() == 0) {
+          NodeValue<const bool> is_owned = connectivity.nodeIsOwned();
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            if (is_owned[node_id]) {
+              different[node_id][0] += 3.2;
+              has_difference = true;
+              break;
+            }
+          }
+        }
+        has_difference = parallel::allReduceOr(has_difference);
+
+        REQUIRE(has_difference);
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(different, "sin", source_location), "error: calculations differ!");
+      }
+
+      {
+        CellValuePerNode<int> other_data_type{connectivity};
+        other_data_type.fill(0);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_data_type, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        CellArrayPerNode<double> arrays{connectivity, 1};
+        arrays.fill(0);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(arrays, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        if (parallel::rank() == 0) {
+          HighFive::File file(filename, HighFive::File::ReadWrite);
+          HighFive::Group group = file.getGroup("/values/" + std::to_string(tag));
+          group.getAttribute("dimension").write(size_t{2});
+        }
+        parallel::barrier();
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(values, "sin", source_location), "error: cannot compare data");
+
+        if (parallel::rank() == 0) {
+          HighFive::File file(filename, HighFive::File::ReadWrite);
+          HighFive::Group group = file.getGroup("/values/" + std::to_string(tag));
+          group.getAttribute("dimension").write(connectivity.dimension());
+        }
+        parallel::barrier();
+      }
+
+      {
+        if (parallel::rank() == 0) {
+          HighFive::File file(filename, HighFive::File::ReadWrite);
+          HighFive::Group group = file.getGroup("/values/" + std::to_string(tag));
+          group.getAttribute("item_type").write(std::string{itemName(ItemType::face)});
+        }
+        parallel::barrier();
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(values, "sin", source_location), "error: cannot compare data");
+
+        if (parallel::rank() == 0) {
+          HighFive::File file(filename, HighFive::File::ReadWrite);
+          HighFive::Group group = file.getGroup("/values/" + std::to_string(tag));
+          group.getAttribute("item_type").write(std::string{itemName(ItemType::node)});
+        }
+        parallel::barrier();
+      }
+
+      {
+        if (parallel::rank() == 0) {
+          HighFive::File file(filename, HighFive::File::ReadWrite);
+          HighFive::Group group = file.getGroup("/values/" + std::to_string(tag));
+          group.getAttribute("subitem_type").write(std::string{itemName(ItemType::face)});
+        }
+        parallel::barrier();
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(values, "sin", source_location), "error: cannot compare data");
+
+        if (parallel::rank() == 0) {
+          HighFive::File file(filename, HighFive::File::ReadWrite);
+          HighFive::Group group = file.getGroup("/values/" + std::to_string(tag));
+          group.getAttribute("subitem_type").write(std::string{itemName(ItemType::cell)});
+        }
+        parallel::barrier();
+      }
+
+      {
+        auto other_mesh                           = MeshDataBaseForTests::get().cartesian1DMesh();
+        const Connectivity<1>& other_connectivity = other_mesh->connectivity();
+
+        CellValuePerNode<double> other_shape{other_connectivity};
+        other_shape.fill(1);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_shape, "sin", source_location),
+                            "error: some item numbers are not defined in reference");
+      }
+    }
+
+    // SubItemArrayPerItem
+    {   // 1d
+      auto mesh                           = MeshDataBaseForTests::get().unordered1DMesh();
+      std::string filename                = ParallelChecker::instance().filename();
+      const Connectivity<1>& connectivity = mesh->connectivity();
+
+      const std::string name = "sin";
+
+      SourceLocation source_location;
+
+      Array numbers = get_item_numbers(connectivity, ItemType::cell);
+      Array<const typename ConnectivityMatrix::IndexType> rows_map =
+        get_subitem_rows_map(connectivity, ItemType::cell, ItemType::node);
+
+      int tag = 12;
+      if (parallel::rank() == 0) {
+        HighFive::File file(filename, HighFive::File::Overwrite);
+        HighFive::Group group = file.createGroup("/values/" + std::to_string(tag));
+
+        group.createDataSet<int>("numbers", HighFive::DataSpace{std::vector<size_t>{numbers.size()}})
+          .write_raw<int>(&(numbers[0]));
+        group
+          .createDataSet<typename ConnectivityMatrix::IndexType>("rows_map", HighFive::DataSpace{std::vector<size_t>{
+                                                                               rows_map.size()}})
+          .write_raw<typename ConnectivityMatrix::IndexType>(&(rows_map[0]));
+
+        Table<double> arrays{rows_map[rows_map.size() - 1], 2};
+        for (size_t i = 0; i < numbers.size(); ++i) {
+          for (size_t i_row = rows_map[i]; i_row < rows_map[i + 1]; ++i_row) {
+            const size_t j = i_row - rows_map[i];
+            for (size_t k = 0; k < 2; ++k) {
+              arrays[i_row][k] = std::sin(2 * numbers[i] + (1 + k) * j);
+            }
+          }
+        }
+        group
+          .createDataSet<double>(name, HighFive::DataSpace{std::vector<size_t>{arrays.numberOfRows(),
+                                                                               arrays.numberOfColumns()}})
+          .write_raw<double>(&(arrays(0, 0)));
+
+        group.createAttribute("filename", source_location.filename());
+        group.createAttribute("line", source_location.line());
+        group.createAttribute("function", source_location.function());
+        group.createAttribute("name", name);
+        group.createAttribute("dimension", connectivity.dimension());
+        group.createAttribute("item_type", std::string{itemName(ItemType::cell)});
+        group.createAttribute("subitem_type", std::string{itemName(ItemType::node)});
+        group.createAttribute("data_type", demangle<double>());
+      }
+      parallel::barrier();
+      ParallelCheckerTester pc_tester;
+      pc_tester.setTag(tag);
+
+      auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+
+      NodeArrayPerCell<double> arrays{connectivity, 2};
+      CellValue<const int> cell_number = connectivity.cellNumber();
+      for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+        for (size_t j = 0; j < cell_to_node_matrix[cell_id].size(); ++j) {
+          for (size_t k = 0; k < 2; ++k) {
+            arrays[cell_id][j][k] = std::sin(2 * cell_number[cell_id] + (1 + k) * j);
+          }
+        }
+      }
+
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin", source_location));
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different name in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "not_sin", source_location));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source file in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{"other-source-file", source_location.line(),
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source line in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line() + 100,
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source function in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line(),
+                                                    source_location.column(), "foo"}));
+
+      if (parallel::size() > 1) {
+        NodeArrayPerCell<double> not_sync = copy(arrays);
+        CellValue<const bool> is_owned    = connectivity.cellIsOwned();
+        if (parallel::rank() == 0) {
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            if (not is_owned[cell_id]) {
+              not_sync[cell_id][0][1] += 3.2;
+              break;
+            }
+          }
+        }
+        REQUIRE(not isSynchronized(not_sync));
+        pc_tester.setTag(tag);
+        UNSCOPED_INFO("can have different ghost values in ref (no exception)");
+        REQUIRE_NOTHROW(parallel_check(not_sync, "sin", source_location));
+      }
+
+      {
+        NodeArrayPerCell<double> different = copy(arrays);
+        bool has_difference                = false;
+        if (parallel::rank() == 0) {
+          CellValue<const bool> is_owned = connectivity.cellIsOwned();
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            if (is_owned[cell_id]) {
+              different[cell_id][0][1] += 3.2;
+              has_difference = true;
+              break;
+            }
+          }
+        }
+        has_difference = parallel::allReduceOr(has_difference);
+
+        REQUIRE(has_difference);
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(different, "sin", source_location), "error: calculations differ!");
+      }
+
+      {
+        CellValue<int> other_data_type{connectivity};
+        other_data_type.fill(0);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_data_type, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        CellArray<double> arrays{connectivity, 1};
+        arrays.fill(0);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(arrays, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        auto other_mesh                           = MeshDataBaseForTests::get().cartesian1DMesh();
+        const Connectivity<1>& other_connectivity = other_mesh->connectivity();
+
+        CellArray<double> other_shape{other_connectivity, 2};
+        other_shape.fill(1);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_shape, "sin", source_location),
+                            "error: some item numbers are not defined in reference");
+      }
+    }
+
+    // SubItemValuePerItem
+    {   // 2d
+      auto mesh                           = MeshDataBaseForTests::get().hybrid2DMesh();
+      std::string filename                = ParallelChecker::instance().filename();
+      const Connectivity<2>& connectivity = mesh->connectivity();
+
+      const std::string name = "sin";
+
+      SourceLocation source_location;
+
+      using DataType = TinyMatrix<3, 2>;
+
+      Array numbers = get_item_numbers(connectivity, ItemType::face);
+
+      int tag = 12;
+      if (parallel::rank() == 0) {
+        HighFive::File file(filename, HighFive::File::Overwrite);
+        HighFive::Group group = file.createGroup("/values/" + std::to_string(tag));
+
+        group.createDataSet<int>("numbers", HighFive::DataSpace{std::vector<size_t>{numbers.size()}})
+          .write_raw<int>(&(numbers[0]));
+
+        Table<DataType> arrays{numbers.size(), 2};
+        for (size_t i = 0; i < arrays.numberOfRows(); ++i) {
+          for (size_t j = 0; j < arrays.numberOfColumns(); ++j) {
+            for (size_t k = 0; k < DataType::NumberOfRows; ++k) {
+              for (size_t l = 0; l < DataType::NumberOfColumns; ++l) {
+                arrays[i][j](k, l) = std::sin(2 * numbers[i] + j + 3 * k + 2 * l);
+              }
+            }
+          }
+        }
+        group
+          .createDataSet(name,
+                         HighFive::DataSpace{std::vector<size_t>{arrays.numberOfRows(), arrays.numberOfColumns()}},
+                         test_TinyMatrixDataType<DataType>{})
+          .template write_raw<double>(&(arrays[0][0](0, 0)), test_TinyMatrixDataType<DataType>{});
+
+        group.createAttribute("filename", source_location.filename());
+        group.createAttribute("line", source_location.line());
+        group.createAttribute("function", source_location.function());
+        group.createAttribute("name", name);
+        group.createAttribute("dimension", connectivity.dimension());
+        group.createAttribute("item_type", std::string{itemName(ItemType::face)});
+        group.createAttribute("data_type", demangle<DataType>());
+      }
+      parallel::barrier();
+      ParallelCheckerTester pc_tester;
+      pc_tester.setTag(tag);
+
+      FaceArray<DataType> arrays{connectivity, 2};
+      FaceValue<const int> face_number = connectivity.faceNumber();
+      for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+        for (size_t j = 0; j < arrays.sizeOfArrays(); ++j) {
+          for (size_t k = 0; k < DataType::NumberOfRows; ++k) {
+            for (size_t l = 0; l < DataType::NumberOfColumns; ++l) {
+              arrays[face_id][j](k, l) = std::sin(2 * face_number[face_id] + j + 3 * k + 2 * l);
+            }
+          }
+        }
+      }
+
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin", source_location));
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different name in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "not_sin", source_location));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source file in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{"other-source-file", source_location.line(),
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source line in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line() + 100,
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source function in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line(),
+                                                    source_location.column(), "foo"}));
+
+      if (parallel::size() > 1) {
+        FaceArray<DataType> not_sync   = copy(arrays);
+        FaceValue<const bool> is_owned = connectivity.faceIsOwned();
+        if (parallel::rank() == 0) {
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            if (not is_owned[face_id]) {
+              not_sync[face_id][0](0, 0) += 3.2;
+              break;
+            }
+          }
+        }
+        REQUIRE(not isSynchronized(not_sync));
+        pc_tester.setTag(tag);
+        UNSCOPED_INFO("can have different ghost values in ref (no exception)");
+        REQUIRE_NOTHROW(parallel_check(not_sync, "sin", source_location));
+      }
+
+      {
+        FaceArray<DataType> different = copy(arrays);
+        bool has_difference           = false;
+        if (parallel::rank() == 0) {
+          FaceValue<const bool> is_owned = connectivity.faceIsOwned();
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            if (is_owned[face_id]) {
+              different[face_id][0](0, 0) += 3.2;
+              has_difference = true;
+              break;
+            }
+          }
+        }
+        has_difference = parallel::allReduceOr(has_difference);
+
+        REQUIRE(has_difference);
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(different, "sin", source_location), "error: calculations differ!");
+      }
+
+      {
+        FaceValue<TinyVector<6>> other_data_type{connectivity};
+        other_data_type.fill(zero);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_data_type, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        FaceArray<DataType> arrays{connectivity, 1};
+        arrays.fill(zero);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(arrays, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        auto other_mesh                           = MeshDataBaseForTests::get().cartesian2DMesh();
+        const Connectivity<2>& other_connectivity = other_mesh->connectivity();
+
+        FaceArray<DataType> other_shape{other_connectivity, 2};
+        other_shape.fill(zero);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_shape, "sin", source_location),
+                            "error: number of items differs from reference");
+      }
+    }
+
+    // SubItemArrayPerItem
+    {   // 2d
+      auto mesh                           = MeshDataBaseForTests::get().hybrid2DMesh();
+      std::string filename                = ParallelChecker::instance().filename();
+      const Connectivity<2>& connectivity = mesh->connectivity();
+
+      const std::string name = "sin";
+
+      SourceLocation source_location;
+      Array numbers = get_item_numbers(connectivity, ItemType::node);
+      Array<const typename ConnectivityMatrix::IndexType> rows_map =
+        get_subitem_rows_map(connectivity, ItemType::node, ItemType::cell);
+
+      int tag = 6;
+      if (parallel::rank() == 0) {
+        HighFive::File file(filename, HighFive::File::Overwrite);
+        HighFive::Group group = file.createGroup("/values/" + std::to_string(tag));
+
+        group.createDataSet<int>("numbers", HighFive::DataSpace{std::vector<size_t>{numbers.size()}})
+          .write_raw<int>(&(numbers[0]));
+        group
+          .createDataSet<typename ConnectivityMatrix::IndexType>("rows_map", HighFive::DataSpace{std::vector<size_t>{
+                                                                               rows_map.size()}})
+          .write_raw<typename ConnectivityMatrix::IndexType>(&(rows_map[0]));
+
+        Array<double> values{rows_map[rows_map.size() - 1]};
+        for (size_t i = 0; i < numbers.size(); ++i) {
+          for (size_t i_row = rows_map[i]; i_row < rows_map[i + 1]; ++i_row) {
+            const size_t j = i_row - rows_map[i];
+            values[i_row]  = std::sin(numbers[i] + 2 * j);
+          }
+        }
+
+        group.createDataSet<double>(name, HighFive::DataSpace{std::vector<size_t>{values.size()}})
+          .write_raw<double>(&(values[0]));
+
+        group.createAttribute("filename", source_location.filename());
+        group.createAttribute("line", source_location.line());
+        group.createAttribute("function", source_location.function());
+        group.createAttribute("name", name);
+        group.createAttribute("dimension", connectivity.dimension());
+        group.createAttribute("item_type", std::string{itemName(ItemType::node)});
+        group.createAttribute("subitem_type", std::string{itemName(ItemType::cell)});
+        group.createAttribute("data_type", demangle<double>());
+      }
+
+      parallel::barrier();
+      ParallelCheckerTester pc_tester;
+      pc_tester.setTag(tag);
+
+      auto node_to_cell_matrix = connectivity.nodeToCellMatrix();
+
+      CellValuePerNode<double> values{connectivity};
+      NodeValue<const int> node_number = connectivity.nodeNumber();
+      for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+        auto cell_list = node_to_cell_matrix[node_id];
+        for (size_t i_cell = 0; i_cell < cell_list.size(); ++i_cell) {
+          values[node_id][i_cell] = std::sin(node_number[node_id] + 2 * i_cell);
+        }
+      }
+
+      REQUIRE_NOTHROW(parallel_check(values, "sin", source_location));
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different name in ref");
+      REQUIRE_NOTHROW(parallel_check(values, "not_sin", source_location));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source file in ref");
+      REQUIRE_NOTHROW(parallel_check(values, "sin",
+                                     SourceLocation{"other-source-file", source_location.line(),
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source line in ref");
+      REQUIRE_NOTHROW(parallel_check(values, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line() + 100,
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source function in ref");
+      REQUIRE_NOTHROW(parallel_check(values, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line(),
+                                                    source_location.column(), "foo"}));
+
+      if (parallel::size() > 1) {
+        CellValuePerNode<double> not_sync = copy(values);
+        NodeValue<const bool> is_owned    = connectivity.nodeIsOwned();
+        if (parallel::rank() == 0) {
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            if (not is_owned[node_id]) {
+              not_sync[node_id][0] += 3.2;
+              break;
+            }
+          }
+        }
+        REQUIRE(not isSynchronized(not_sync));
+        pc_tester.setTag(tag);
+        UNSCOPED_INFO("can have different ghost values in ref (no exception)");
+        REQUIRE_NOTHROW(parallel_check(not_sync, "sin", source_location));
+      }
+
+      {
+        CellValuePerNode<double> different = copy(values);
+        bool has_difference                = false;
+        if (parallel::rank() == 0) {
+          NodeValue<const bool> is_owned = connectivity.nodeIsOwned();
+          for (NodeId node_id = 0; node_id < connectivity.numberOfNodes(); ++node_id) {
+            if (is_owned[node_id]) {
+              different[node_id][0] += 3.2;
+              has_difference = true;
+              break;
+            }
+          }
+        }
+        has_difference = parallel::allReduceOr(has_difference);
+
+        REQUIRE(has_difference);
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(different, "sin", source_location), "error: calculations differ!");
+      }
+
+      {
+        CellValuePerNode<int> other_data_type{connectivity};
+        other_data_type.fill(0);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_data_type, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        CellArrayPerNode<double> arrays{connectivity, 1};
+        arrays.fill(0);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(arrays, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        if (parallel::rank() == 0) {
+          HighFive::File file(filename, HighFive::File::ReadWrite);
+          HighFive::Group group = file.getGroup("/values/" + std::to_string(tag));
+          group.getAttribute("dimension").write(size_t{1});
+        }
+        parallel::barrier();
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(values, "sin", source_location), "error: cannot compare data");
+
+        if (parallel::rank() == 0) {
+          HighFive::File file(filename, HighFive::File::ReadWrite);
+          HighFive::Group group = file.getGroup("/values/" + std::to_string(tag));
+          group.getAttribute("dimension").write(connectivity.dimension());
+        }
+        parallel::barrier();
+      }
+
+      {
+        if (parallel::rank() == 0) {
+          HighFive::File file(filename, HighFive::File::ReadWrite);
+          HighFive::Group group = file.getGroup("/values/" + std::to_string(tag));
+          group.getAttribute("item_type").write(std::string{itemName(ItemType::face)});
+        }
+        parallel::barrier();
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(values, "sin", source_location), "error: cannot compare data");
+
+        if (parallel::rank() == 0) {
+          HighFive::File file(filename, HighFive::File::ReadWrite);
+          HighFive::Group group = file.getGroup("/values/" + std::to_string(tag));
+          group.getAttribute("item_type").write(std::string{itemName(ItemType::node)});
+        }
+        parallel::barrier();
+      }
+
+      {
+        if (parallel::rank() == 0) {
+          HighFive::File file(filename, HighFive::File::ReadWrite);
+          HighFive::Group group = file.getGroup("/values/" + std::to_string(tag));
+          group.getAttribute("subitem_type").write(std::string{itemName(ItemType::face)});
+        }
+        parallel::barrier();
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(values, "sin", source_location), "error: cannot compare data");
+
+        if (parallel::rank() == 0) {
+          HighFive::File file(filename, HighFive::File::ReadWrite);
+          HighFive::Group group = file.getGroup("/values/" + std::to_string(tag));
+          group.getAttribute("subitem_type").write(std::string{itemName(ItemType::cell)});
+        }
+        parallel::barrier();
+      }
+
+      {
+        auto other_mesh                           = MeshDataBaseForTests::get().cartesian2DMesh();
+        const Connectivity<2>& other_connectivity = other_mesh->connectivity();
+
+        CellValuePerNode<double> other_shape{other_connectivity};
+        other_shape.fill(1);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_shape, "sin", source_location),
+                            "error: some item numbers are not defined in reference");
+      }
+    }
+
+    // SubItemValuePerItem
+    {   // 3d
+      auto mesh                           = MeshDataBaseForTests::get().hybrid3DMesh();
+      std::string filename                = ParallelChecker::instance().filename();
+      const Connectivity<3>& connectivity = mesh->connectivity();
+
+      const std::string name = "sin";
+
+      SourceLocation source_location;
+
+      using DataType = TinyVector<2>;
+
+      Array numbers = get_item_numbers(connectivity, ItemType::face);
+
+      int tag = 7;
+      if (parallel::rank() == 0) {
+        HighFive::File file(filename, HighFive::File::Overwrite);
+        HighFive::Group group = file.createGroup("/values/" + std::to_string(tag));
+
+        group.createDataSet<int>("numbers", HighFive::DataSpace{std::vector<size_t>{numbers.size()}})
+          .write_raw<int>(&(numbers[0]));
+
+        Table<DataType> arrays{numbers.size(), 2};
+        for (size_t i = 0; i < arrays.numberOfRows(); ++i) {
+          for (size_t j = 0; j < arrays.numberOfColumns(); ++j) {
+            for (size_t k = 0; k < DataType::Dimension; ++k) {
+              arrays[i][j][k] = std::sin(2 * numbers[i] + j + 3 * k);
+            }
+          }
+        }
+        group
+          .createDataSet(name,
+                         HighFive::DataSpace{std::vector<size_t>{arrays.numberOfRows(), arrays.numberOfColumns()}},
+                         test_TinyVectorDataType<DataType>{})
+          .template write_raw<double>(&(arrays[0][0][0]), test_TinyVectorDataType<DataType>{});
+
+        group.createAttribute("filename", source_location.filename());
+        group.createAttribute("line", source_location.line());
+        group.createAttribute("function", source_location.function());
+        group.createAttribute("name", name);
+        group.createAttribute("dimension", connectivity.dimension());
+        group.createAttribute("item_type", std::string{itemName(ItemType::face)});
+        group.createAttribute("data_type", demangle<DataType>());
+      }
+      parallel::barrier();
+      ParallelCheckerTester pc_tester;
+      pc_tester.setTag(tag);
+
+      FaceArray<DataType> arrays{connectivity, 2};
+      FaceValue<const int> face_number = connectivity.faceNumber();
+      for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+        for (size_t j = 0; j < arrays.sizeOfArrays(); ++j) {
+          for (size_t k = 0; k < DataType::Dimension; ++k) {
+            arrays[face_id][j][k] = std::sin(2 * face_number[face_id] + j + 3 * k);
+          }
+        }
+      }
+
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin", source_location));
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different name in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "not_sin", source_location));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source file in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{"other-source-file", source_location.line(),
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source line in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line() + 100,
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source function in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line(),
+                                                    source_location.column(), "foo"}));
+
+      if (parallel::size() > 1) {
+        FaceArray<DataType> not_sync   = copy(arrays);
+        FaceValue<const bool> is_owned = connectivity.faceIsOwned();
+        if (parallel::rank() == 0) {
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            if (not is_owned[face_id]) {
+              not_sync[face_id][0][0] += 3.2;
+              break;
+            }
+          }
+        }
+        REQUIRE(not isSynchronized(not_sync));
+        pc_tester.setTag(tag);
+        UNSCOPED_INFO("can have different ghost values in ref (no exception)");
+        REQUIRE_NOTHROW(parallel_check(not_sync, "sin", source_location));
+      }
+
+      {
+        FaceArray<DataType> different = copy(arrays);
+        bool has_difference           = false;
+        if (parallel::rank() == 0) {
+          FaceValue<const bool> is_owned = connectivity.faceIsOwned();
+          for (FaceId face_id = 0; face_id < connectivity.numberOfFaces(); ++face_id) {
+            if (is_owned[face_id]) {
+              different[face_id][0][0] += 3.2;
+              has_difference = true;
+              break;
+            }
+          }
+        }
+        has_difference = parallel::allReduceOr(has_difference);
+
+        REQUIRE(has_difference);
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(different, "sin", source_location), "error: calculations differ!");
+      }
+
+      {
+        FaceValue<TinyVector<6>> other_data_type{connectivity};
+        other_data_type.fill(zero);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_data_type, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        FaceArray<DataType> arrays{connectivity, 1};
+        arrays.fill(zero);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(arrays, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        auto other_mesh                           = MeshDataBaseForTests::get().cartesian2DMesh();
+        const Connectivity<2>& other_connectivity = other_mesh->connectivity();
+
+        FaceArray<DataType> other_shape{other_connectivity, 2};
+        other_shape.fill(zero);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_shape, "sin", source_location), "error: cannot compare data");
+      }
+    }
+
+    // SubItemArrayPerItem
+    {   // 3d
+      auto mesh                           = MeshDataBaseForTests::get().hybrid3DMesh();
+      std::string filename                = ParallelChecker::instance().filename();
+      const Connectivity<3>& connectivity = mesh->connectivity();
+
+      const std::string name = "sin";
+
+      SourceLocation source_location;
+
+      Array numbers = get_item_numbers(connectivity, ItemType::cell);
+      Array<const typename ConnectivityMatrix::IndexType> rows_map =
+        get_subitem_rows_map(connectivity, ItemType::cell, ItemType::node);
+
+      int tag = 12;
+      if (parallel::rank() == 0) {
+        HighFive::File file(filename, HighFive::File::Overwrite);
+        HighFive::Group group = file.createGroup("/values/" + std::to_string(tag));
+
+        group.createDataSet<int>("numbers", HighFive::DataSpace{std::vector<size_t>{numbers.size()}})
+          .write_raw<int>(&(numbers[0]));
+        group
+          .createDataSet<typename ConnectivityMatrix::IndexType>("rows_map", HighFive::DataSpace{std::vector<size_t>{
+                                                                               rows_map.size()}})
+          .write_raw<typename ConnectivityMatrix::IndexType>(&(rows_map[0]));
+
+        Table<double> arrays{rows_map[rows_map.size() - 1], 2};
+        for (size_t i = 0; i < numbers.size(); ++i) {
+          for (size_t i_row = rows_map[i]; i_row < rows_map[i + 1]; ++i_row) {
+            const size_t j = i_row - rows_map[i];
+            for (size_t k = 0; k < 2; ++k) {
+              arrays[i_row][k] = std::sin(2 * numbers[i] + (1 + k) * j);
+            }
+          }
+        }
+        group
+          .createDataSet<double>(name, HighFive::DataSpace{std::vector<size_t>{arrays.numberOfRows(),
+                                                                               arrays.numberOfColumns()}})
+          .write_raw<double>(&(arrays(0, 0)));
+
+        group.createAttribute("filename", source_location.filename());
+        group.createAttribute("line", source_location.line());
+        group.createAttribute("function", source_location.function());
+        group.createAttribute("name", name);
+        group.createAttribute("dimension", connectivity.dimension());
+        group.createAttribute("item_type", std::string{itemName(ItemType::cell)});
+        group.createAttribute("subitem_type", std::string{itemName(ItemType::node)});
+        group.createAttribute("data_type", demangle<double>());
+      }
+      parallel::barrier();
+      ParallelCheckerTester pc_tester;
+      pc_tester.setTag(tag);
+
+      auto cell_to_node_matrix = connectivity.cellToNodeMatrix();
+
+      NodeArrayPerCell<double> arrays{connectivity, 2};
+      CellValue<const int> cell_number = connectivity.cellNumber();
+      for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+        for (size_t j = 0; j < cell_to_node_matrix[cell_id].size(); ++j) {
+          for (size_t k = 0; k < 2; ++k) {
+            arrays[cell_id][j][k] = std::sin(2 * cell_number[cell_id] + (1 + k) * j);
+          }
+        }
+      }
+
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin", source_location));
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different name in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "not_sin", source_location));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source file in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{"other-source-file", source_location.line(),
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source line in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line() + 100,
+                                                    source_location.column(), source_location.function()}));
+
+      pc_tester.setTag(tag);
+      UNSCOPED_INFO("can have different source function in ref");
+      REQUIRE_NOTHROW(parallel_check(arrays, "sin",
+                                     SourceLocation{source_location.filename(), source_location.line(),
+                                                    source_location.column(), "foo"}));
+
+      if (parallel::size() > 1) {
+        NodeArrayPerCell<double> not_sync = copy(arrays);
+        CellValue<const bool> is_owned    = connectivity.cellIsOwned();
+        if (parallel::rank() == 0) {
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            if (not is_owned[cell_id]) {
+              not_sync[cell_id][0][1] += 3.2;
+              break;
+            }
+          }
+        }
+        REQUIRE(not isSynchronized(not_sync));
+        pc_tester.setTag(tag);
+        UNSCOPED_INFO("can have different ghost values in ref (no exception)");
+        REQUIRE_NOTHROW(parallel_check(not_sync, "sin", source_location));
+      }
+
+      {
+        NodeArrayPerCell<double> different = copy(arrays);
+        bool has_difference                = false;
+        if (parallel::rank() == 0) {
+          CellValue<const bool> is_owned = connectivity.cellIsOwned();
+          for (CellId cell_id = 0; cell_id < connectivity.numberOfCells(); ++cell_id) {
+            if (is_owned[cell_id]) {
+              different[cell_id][0][1] += 3.2;
+              has_difference = true;
+              break;
+            }
+          }
+        }
+        has_difference = parallel::allReduceOr(has_difference);
+
+        REQUIRE(has_difference);
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(different, "sin", source_location), "error: calculations differ!");
+      }
+
+      {
+        CellValue<int> other_data_type{connectivity};
+        other_data_type.fill(0);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_data_type, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        CellArray<double> arrays{connectivity, 1};
+        arrays.fill(0);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(arrays, "sin", source_location), "error: cannot compare data");
+      }
+
+      {
+        auto other_mesh                           = MeshDataBaseForTests::get().cartesian3DMesh();
+        const Connectivity<3>& other_connectivity = other_mesh->connectivity();
+
+        CellArray<double> other_shape{other_connectivity, 2};
+        other_shape.fill(1);
+
+        pc_tester.setTag(tag);
+        REQUIRE_THROWS_WITH(parallel_check(other_shape, "sin", source_location),
+                            "error: some item numbers are not defined in reference");
+      }
+    }
+  }
+
+  std::error_code err_code;
+  std::filesystem::remove_all(tmp_dirname, err_code);
+  // error is not handled to avoid exception throws if the directory
+  // has been removed by another processor
+
+  REQUIRE_NOTHROW(ParallelChecker::destroy());
+}
+
+#else   // PUGS_HAS_HDF5
+
+TEST_CASE("ParallelChecker_read", "[dev]")
+{
+  REQUIRE_NOTHROW(ParallelChecker::create());
+  if (parallel::size() > 1) {
+    REQUIRE_THROWS_WITH(ParallelChecker::instance().setMode(ParallelChecker::Mode::write),
+                        "not implemented yet: parallel check write in parallel");
+  } else {
+    REQUIRE_NOTHROW(ParallelChecker::instance().setMode(ParallelChecker::Mode::write));
+    REQUIRE_NOTHROW(ParallelChecker::instance().isWriting());
+  }
+  REQUIRE_NOTHROW(ParallelChecker::instance().setMode(ParallelChecker::Mode::automatic));
+  REQUIRE_NOTHROW(ParallelChecker::instance().isWriting() == (parallel::size() > 1));
+  REQUIRE_NOTHROW(ParallelChecker::instance().setMode(ParallelChecker::Mode::read));
+  REQUIRE_NOTHROW(not ParallelChecker::instance().isWriting());
+
+  auto mesh = MeshDataBaseForTests::get().unordered1DMesh();
+
+  const Connectivity<1>& connectivity = mesh->connectivity();
+
+  NodeValue<double> nv{connectivity};
+  REQUIRE_THROWS_WITH(parallel_check(nv, "test"), "error: parallel checker cannot be used without HDF5 support");
+
+  REQUIRE_THROWS_WITH(parallel_check(ItemValueVariant{nv}, "test"),
+                      "error: parallel checker cannot be used without HDF5 support");
+
+  NodeArray<double> na{connectivity, 2};
+  REQUIRE_THROWS_WITH(parallel_check(na, "test"), "error: parallel checker cannot be used without HDF5 support");
+
+  REQUIRE_THROWS_WITH(parallel_check(ItemArrayVariant{na}, "test"),
+                      "error: parallel checker cannot be used without HDF5 support");
+
+  NodeValuePerCell<double> nvpc{connectivity};
+  REQUIRE_THROWS_WITH(parallel_check(nvpc, "test"), "error: parallel checker cannot be used without HDF5 support");
+
+  REQUIRE_THROWS_WITH(parallel_check(SubItemValuePerItemVariant{nvpc}, "test"),
+                      "error: parallel checker cannot be used without HDF5 support");
+
+  NodeArrayPerCell<double> napc{connectivity, 2};
+  REQUIRE_THROWS_WITH(parallel_check(napc, "test"), "error: parallel checker cannot be used without HDF5 support");
+
+  REQUIRE_THROWS_WITH(parallel_check(SubItemArrayPerItemVariant{napc}, "test"),
+                      "error: parallel checker cannot be used without HDF5 support");
+
+  REQUIRE_NOTHROW(ParallelChecker::destroy());
+}
+
+#endif   // PUGS_HAS_HDF5
diff --git a/tests/test_ParallelChecker_write.cpp b/tests/test_ParallelChecker_write.cpp
new file mode 100644
index 000000000..285485237
--- /dev/null
+++ b/tests/test_ParallelChecker_write.cpp
@@ -0,0 +1,654 @@
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_all.hpp>
+
+#include <dev/ParallelChecker.hpp>
+
+#include <MeshDataBaseForTests.hpp>
+
+#include <filesystem>
+
+// clazy:excludeall=non-pod-global-static
+
+#ifdef PUGS_HAS_HDF5
+
+#include <ParallelCheckerTester.hpp>
+
+TEST_CASE("ParallelChecker_write", "[dev]")
+{
+  {
+    ParallelCheckerTester pc_tester;
+    if (pc_tester.isCreated()) {
+      REQUIRE_NOTHROW(ParallelChecker::destroy());
+    }
+  }
+
+  REQUIRE_NOTHROW(ParallelChecker::create());
+  REQUIRE_NOTHROW(ParallelChecker::instance().setMode(ParallelChecker::Mode::write));
+
+  auto get_pc_options = []() -> std::tuple<std::string, ParallelChecker::Mode, size_t> {
+    ParallelCheckerTester pc_tester;
+    return std::make_tuple(pc_tester.getFilename(), pc_tester.getMode(), pc_tester.getTag());
+  };
+
+  auto set_pc_options = [](const std::tuple<std::string, ParallelChecker::Mode, size_t>& options) {
+    auto [filename, mode, tag] = options;
+    ParallelCheckerTester pc_tester;
+    pc_tester.setFilename(filename);
+    pc_tester.setMode(mode);
+    pc_tester.setTag(tag);
+  };
+
+  SECTION("set config at init")
+  {
+    auto [filename, mode, tag] = get_pc_options();
+
+    REQUIRE(ParallelChecker::instance().filename() == "parallel_checker.h5");
+    REQUIRE(ParallelChecker::instance().mode() == mode);
+    REQUIRE(tag == 0);
+
+    ParallelChecker::instance().setFilename("foo.h5");
+    ParallelChecker::instance().setMode(ParallelChecker::Mode::automatic);
+
+    REQUIRE(ParallelChecker::instance().filename() == "foo.h5");
+    REQUIRE(ParallelChecker::instance().mode() == ParallelChecker::Mode::automatic);
+  }
+
+  std::string tmp_dirname;
+
+  {
+    if (parallel::rank() == 0) {
+      tmp_dirname = [&]() -> std::string {
+        std::string temp_filename = std::filesystem::temp_directory_path() / "pugs_test_write_h5_XXXXXX";
+        return std::string{mkdtemp(&temp_filename[0])};
+      }();
+    }
+    parallel::broadcast(tmp_dirname, 0);
+
+    std::filesystem::path path = tmp_dirname;
+    REQUIRE_NOTHROW(ParallelChecker::instance().setFilename(path / "parallel_check.h5"));
+  }
+
+  SECTION("set values")
+  {
+    set_pc_options(std::make_tuple(std::string{"foo.h5"}, ParallelChecker::Mode::write, 37));
+
+    auto pc_options = get_pc_options();
+
+    auto [filename, mode, tag] = pc_options;
+
+    REQUIRE(ParallelChecker::instance().filename() == filename);
+    REQUIRE(ParallelChecker::instance().filename() == "foo.h5");
+    REQUIRE(ParallelChecker::instance().mode() == ParallelChecker::Mode::write);
+    REQUIRE(ParallelChecker::instance().mode() == mode);
+    REQUIRE(tag == 37);
+  }
+
+  SECTION("is writing")
+  {
+    ParallelCheckerTester pc_tester;
+
+    pc_tester.setMode(ParallelChecker::Mode::write);
+    REQUIRE(ParallelChecker::instance().isWriting());
+
+    pc_tester.setMode(ParallelChecker::Mode::read);
+    REQUIRE(not ParallelChecker::instance().isWriting());
+
+    pc_tester.setMode(ParallelChecker::Mode::automatic);
+    REQUIRE(ParallelChecker::instance().isWriting() == (parallel::size() == 1));
+  }
+
+  SECTION("check ItemValue/ItemArray attributes")
+  {
+    auto check = []<typename ItemValueT>(const ItemValueT& item_value, const std::string& var_name,
+                                         const SourceLocation& source_location, const size_t tag) {
+      ItemType item_type = ItemValueT::item_t;
+      using DataType     = typename ItemValueT::data_type;
+
+      HighFive::File file(ParallelChecker::instance().filename(), HighFive::File::ReadOnly);
+      HighFive::Group group_var0 = file.getGroup("values/" + std::to_string(tag));
+      REQUIRE(group_var0.getNumberObjects() == 2);
+
+      REQUIRE(group_var0.exist("numbers"));
+      REQUIRE(group_var0.exist(var_name));
+
+      REQUIRE(group_var0.getNumberAttributes() == 7);
+      REQUIRE(group_var0.hasAttribute("filename"));
+      REQUIRE(group_var0.hasAttribute("function"));
+      REQUIRE(group_var0.hasAttribute("line"));
+      REQUIRE(group_var0.hasAttribute("dimension"));
+      REQUIRE(group_var0.hasAttribute("data_type"));
+      REQUIRE(group_var0.hasAttribute("item_type"));
+      REQUIRE(group_var0.hasAttribute("name"));
+
+      REQUIRE(group_var0.getAttribute("filename").read<std::string>() == source_location.filename());
+      REQUIRE(group_var0.getAttribute("function").read<std::string>() == source_location.function());
+      REQUIRE(group_var0.getAttribute("line").read<size_t>() == source_location.line());
+      REQUIRE(group_var0.getAttribute("dimension").read<size_t>() == item_value.connectivity_ptr()->dimension());
+      REQUIRE(group_var0.getAttribute("data_type").read<std::string>() == demangle<DataType>());
+      REQUIRE(group_var0.getAttribute("item_type").read<std::string>() == itemName(item_type));
+      REQUIRE(group_var0.getAttribute("name").read<std::string>() == var_name);
+    };
+
+    // ItemValues
+    {   // 1d
+      auto mesh = MeshDataBaseForTests::get().unordered1DMesh();
+
+      const Connectivity<1>& connectivity = mesh->connectivity();
+
+      ParallelCheckerTester pc_tester;
+      {
+        CellValue<double> var{connectivity};
+        var.fill(1);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(var, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+      {
+        DiscreteFunctionP0<1, double> var{mesh};
+        var.fill(1);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(var, var_name, source_location);
+        check(var.cellValues(), var_name, source_location, tag);
+      }
+      {
+        NodeValue<TinyVector<2>> var{connectivity};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(ItemValueVariant{var}, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+    }
+
+    {   // 2d
+      auto mesh = MeshDataBaseForTests::get().hybrid2DMesh();
+
+      const Connectivity<2>& connectivity = mesh->connectivity();
+
+      ParallelCheckerTester pc_tester;
+      {
+        FaceValue<TinyMatrix<3, 2>> var{connectivity};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(var, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+      {
+        FaceValue<TinyVector<1>> var{connectivity};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(ItemValueVariant{var}, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+    }
+
+    {   // 3d
+      auto mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+
+      const Connectivity<3>& connectivity = mesh->connectivity();
+
+      ParallelCheckerTester pc_tester;
+      {
+        EdgeValue<TinyMatrix<2, 2>> var{connectivity};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(var, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+      {
+        NodeValue<TinyVector<3>> var{connectivity};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(ItemValueVariant{var}, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+      {
+        DiscreteFunctionP0<3, TinyVector<3>> var{mesh};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(DiscreteFunctionVariant{var}, var_name, source_location);
+        check(var.cellValues(), var_name, source_location, tag);
+      }
+    }
+
+    // ItemArrays
+    {   // 1d
+      auto mesh = MeshDataBaseForTests::get().unordered1DMesh();
+
+      const Connectivity<1>& connectivity = mesh->connectivity();
+
+      ParallelCheckerTester pc_tester;
+      {
+        CellArray<double> var{connectivity, 2};
+        var.fill(1);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(var, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+      {
+        DiscreteFunctionP0Vector<1, double> var{mesh, 2};
+        var.fill(1);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(var, var_name, source_location);
+        check(var.cellArrays(), var_name, source_location, tag);
+      }
+      {
+        NodeArray<TinyVector<2>> var{connectivity, 1};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(ItemArrayVariant{var}, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+    }
+
+    {   // 2d
+      auto mesh = MeshDataBaseForTests::get().hybrid2DMesh();
+
+      const Connectivity<2>& connectivity = mesh->connectivity();
+
+      ParallelCheckerTester pc_tester;
+      {
+        FaceArray<TinyMatrix<3, 2>> var{connectivity, 2};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(var, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+      {
+        FaceArray<TinyVector<1>> var{connectivity, 3};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(ItemArrayVariant{var}, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+    }
+
+    {   // 3d
+      auto mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+
+      const Connectivity<3>& connectivity = mesh->connectivity();
+
+      ParallelCheckerTester pc_tester;
+      {
+        EdgeArray<TinyMatrix<2, 2>> var{connectivity, 2};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(var, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+      {
+        NodeArray<TinyVector<3>> var{connectivity, 3};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(ItemArrayVariant{var}, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+      {
+        DiscreteFunctionP0Vector<3, double> var{mesh, 3};
+        var.fill(0);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(DiscreteFunctionVariant{var}, var_name, source_location);
+        check(var.cellArrays(), var_name, source_location, tag);
+      }
+    }
+  }
+
+  SECTION("check SubItemValuePerItem/SubItemArrayPerItem attributes")
+  {
+    auto check = []<typename SubItemValuePerItemT>(const SubItemValuePerItemT& item_value, const std::string& var_name,
+                                                   const SourceLocation& source_location, const size_t tag) {
+      ItemType item_type     = SubItemValuePerItemT::item_type;
+      ItemType sub_item_type = SubItemValuePerItemT::sub_item_type;
+      using DataType         = typename SubItemValuePerItemT::data_type;
+
+      HighFive::File file(ParallelChecker::instance().filename(), HighFive::File::ReadOnly);
+      HighFive::Group group_var0 = file.getGroup("values/" + std::to_string(tag));
+      REQUIRE(group_var0.getNumberObjects() == 3);
+
+      REQUIRE(group_var0.exist("numbers"));
+      REQUIRE(group_var0.exist("rows_map"));
+      REQUIRE(group_var0.exist(var_name));
+
+      REQUIRE(group_var0.getNumberAttributes() == 8);
+      REQUIRE(group_var0.hasAttribute("filename"));
+      REQUIRE(group_var0.hasAttribute("function"));
+      REQUIRE(group_var0.hasAttribute("line"));
+      REQUIRE(group_var0.hasAttribute("dimension"));
+      REQUIRE(group_var0.hasAttribute("data_type"));
+      REQUIRE(group_var0.hasAttribute("item_type"));
+      REQUIRE(group_var0.hasAttribute("subitem_type"));
+      REQUIRE(group_var0.hasAttribute("name"));
+
+      REQUIRE(group_var0.getAttribute("filename").read<std::string>() == source_location.filename());
+      REQUIRE(group_var0.getAttribute("function").read<std::string>() == source_location.function());
+      REQUIRE(group_var0.getAttribute("line").read<size_t>() == source_location.line());
+      REQUIRE(group_var0.getAttribute("dimension").read<size_t>() == item_value.connectivity_ptr()->dimension());
+      REQUIRE(group_var0.getAttribute("data_type").read<std::string>() == demangle<DataType>());
+      REQUIRE(group_var0.getAttribute("item_type").read<std::string>() == itemName(item_type));
+      REQUIRE(group_var0.getAttribute("subitem_type").read<std::string>() == itemName(sub_item_type));
+      REQUIRE(group_var0.getAttribute("name").read<std::string>() == var_name);
+    };
+
+    // ItemValues
+    {   // 1d
+      auto mesh = MeshDataBaseForTests::get().unordered1DMesh();
+
+      const Connectivity<1>& connectivity = mesh->connectivity();
+
+      ParallelCheckerTester pc_tester;
+      {
+        CellValuePerNode<double> var{connectivity};
+        var.fill(1);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(var, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+      {
+        NodeValuePerCell<TinyVector<2>> var{connectivity};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(SubItemValuePerItemVariant{var}, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+    }
+
+    {   // 2d
+      auto mesh = MeshDataBaseForTests::get().hybrid2DMesh();
+
+      const Connectivity<2>& connectivity = mesh->connectivity();
+
+      ParallelCheckerTester pc_tester;
+      {
+        FaceValuePerCell<TinyMatrix<3, 2>> var{connectivity};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(var, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+      {
+        FaceValuePerNode<TinyVector<1>> var{connectivity};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(SubItemValuePerItemVariant{var}, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+    }
+
+    {   // 3d
+      auto mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+
+      const Connectivity<3>& connectivity = mesh->connectivity();
+
+      ParallelCheckerTester pc_tester;
+      {
+        EdgeValuePerFace<TinyMatrix<2, 2>> var{connectivity};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(var, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+      {
+        NodeValuePerCell<TinyVector<3>> var{connectivity};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(SubItemValuePerItemVariant{var}, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+    }
+
+    // ItemArrays
+    {   // 1d
+      auto mesh = MeshDataBaseForTests::get().unordered1DMesh();
+
+      const Connectivity<1>& connectivity = mesh->connectivity();
+
+      ParallelCheckerTester pc_tester;
+      {
+        CellArrayPerNode<double> var{connectivity, 2};
+        var.fill(1);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(var, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+      {
+        NodeArrayPerCell<TinyVector<2>> var{connectivity, 1};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(SubItemArrayPerItemVariant{var}, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+    }
+
+    {   // 2d
+      auto mesh = MeshDataBaseForTests::get().hybrid2DMesh();
+
+      const Connectivity<2>& connectivity = mesh->connectivity();
+
+      ParallelCheckerTester pc_tester;
+      {
+        FaceArrayPerNode<TinyMatrix<3, 2>> var{connectivity, 2};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(var, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+      {
+        FaceArrayPerCell<TinyVector<1>> var{connectivity, 3};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(SubItemArrayPerItemVariant{var}, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+    }
+
+    {   // 3d
+      auto mesh = MeshDataBaseForTests::get().hybrid3DMesh();
+
+      const Connectivity<3>& connectivity = mesh->connectivity();
+
+      ParallelCheckerTester pc_tester;
+      {
+        EdgeArrayPerFace<TinyMatrix<2, 2>> var{connectivity, 2};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(var, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+      {
+        NodeArrayPerEdge<TinyVector<3>> var{connectivity, 3};
+        var.fill(zero);
+
+        const SourceLocation source_location;
+        const size_t tag           = pc_tester.getTag();
+        const std::string var_name = "var_" + std::to_string(tag);
+
+        parallel_check(SubItemArrayPerItemVariant{var}, var_name, source_location);
+        check(var, var_name, source_location, tag);
+      }
+    }
+  }
+
+  SECTION("invalid set config at after first write")
+  {
+    auto [filename, mode, tag] = get_pc_options();
+
+    REQUIRE(ParallelChecker::instance().filename() == filename);
+    REQUIRE(ParallelChecker::instance().mode() == mode);
+    REQUIRE(tag == 0);
+
+    set_pc_options(std::make_tuple(filename, mode, 2ul));
+
+    REQUIRE_THROWS_WITH(ParallelChecker::instance().setFilename("foo.h5"),
+                        "unexpected error: Cannot modify parallel checker file if it was already used");
+    REQUIRE_THROWS_WITH(ParallelChecker::instance().setMode(ParallelChecker::Mode::automatic),
+                        "unexpected error: Cannot modify parallel checker mode if it was already used");
+  }
+
+#ifndef NDEBUG
+  SECTION("bad creation/destruction/access")
+  {
+    REQUIRE_THROWS_WITH(ParallelChecker::create(), "ParallelChecker has already been created");
+    REQUIRE_NOTHROW(ParallelChecker::destroy());
+
+    REQUIRE_THROWS_WITH(ParallelChecker::destroy(), "ParallelChecker has already been destroyed");
+    REQUIRE_THROWS_WITH(ParallelChecker::instance(), "ParallelChecker was not created");
+
+    REQUIRE_NOTHROW(ParallelChecker::create());
+  }
+#endif
+
+  std::filesystem::remove_all(std::filesystem::path{tmp_dirname});
+  REQUIRE_NOTHROW(ParallelChecker::destroy());
+}
+
+#else   // PUGS_HAS_HDF5
+
+TEST_CASE("ParallelChecker_write", "[dev]")
+{
+  REQUIRE_NOTHROW(ParallelChecker::create());
+  REQUIRE_NOTHROW(ParallelChecker::instance().setMode(ParallelChecker::Mode::read));
+  REQUIRE_NOTHROW(not ParallelChecker::instance().isWriting());
+  REQUIRE_NOTHROW(ParallelChecker::instance().setMode(ParallelChecker::Mode::write));
+  REQUIRE_NOTHROW(ParallelChecker::instance().isWriting());
+  REQUIRE_NOTHROW(ParallelChecker::instance().setMode(ParallelChecker::Mode::automatic));
+  REQUIRE_NOTHROW(ParallelChecker::instance().isWriting() == (parallel::size() == 1));
+
+  auto mesh = MeshDataBaseForTests::get().unordered1DMesh();
+
+  const Connectivity<1>& connectivity = mesh->connectivity();
+
+  NodeValue<double> nv{connectivity};
+  REQUIRE_THROWS_WITH(parallel_check(nv, "test"), "error: parallel checker cannot be used without HDF5 support");
+
+  REQUIRE_THROWS_WITH(parallel_check(ItemValueVariant{nv}, "test"),
+                      "error: parallel checker cannot be used without HDF5 support");
+
+  NodeArray<double> na{connectivity, 2};
+  REQUIRE_THROWS_WITH(parallel_check(na, "test"), "error: parallel checker cannot be used without HDF5 support");
+
+  REQUIRE_THROWS_WITH(parallel_check(ItemArrayVariant{na}, "test"),
+                      "error: parallel checker cannot be used without HDF5 support");
+
+  NodeValuePerCell<double> nvpc{connectivity};
+  REQUIRE_THROWS_WITH(parallel_check(nvpc, "test"), "error: parallel checker cannot be used without HDF5 support");
+
+  REQUIRE_THROWS_WITH(parallel_check(SubItemValuePerItemVariant{nvpc}, "test"),
+                      "error: parallel checker cannot be used without HDF5 support");
+
+  NodeArrayPerCell<double> napc{connectivity, 2};
+  REQUIRE_THROWS_WITH(parallel_check(napc, "test"), "error: parallel checker cannot be used without HDF5 support");
+
+  REQUIRE_THROWS_WITH(parallel_check(SubItemArrayPerItemVariant{napc}, "test"),
+                      "error: parallel checker cannot be used without HDF5 support");
+
+  REQUIRE_NOTHROW(ParallelChecker::destroy());
+}
+
+#endif   // PUGS_HAS_HDF5
-- 
GitLab