Skip to content
Snippets Groups Projects
Select Git revision
  • 55011bb929e438654a1672dcd015d4e47a1a34d8
  • develop default protected
  • feature/advection
  • feature/composite-scheme-other-fluxes
  • origin/stage/bouguettaia
  • save_clemence
  • feature/local-dt-fsi
  • feature/variational-hydro
  • feature/gmsh-reader
  • feature/reconstruction
  • feature/kinetic-schemes
  • feature/composite-scheme-sources
  • feature/serraille
  • feature/composite-scheme
  • hyperplastic
  • feature/polynomials
  • feature/gks
  • feature/implicit-solver-o2
  • feature/coupling_module
  • feature/implicit-solver
  • feature/merge-local-dt-fsi
  • v0.5.0 protected
  • v0.4.1 protected
  • v0.4.0 protected
  • v0.3.0 protected
  • v0.2.0 protected
  • v0.1.0 protected
  • Kidder
  • v0.0.4 protected
  • v0.0.3 protected
  • v0.0.2 protected
  • v0 protected
  • v0.0.1 protected
33 results

MeshFlatNodeBoundary.cpp

Blame
  • ParallelChecker.hpp 60.47 KiB
    #ifndef PARALLEL_CHECKER_HPP
    #define PARALLEL_CHECKER_HPP
    
    #include <utils/pugs_config.hpp>
    #ifdef PUGS_HAS_HDF5
    #include <highfive/highfive.hpp>
    #endif   // PUGS_HAS_HDF5
    
    #include <mesh/Connectivity.hpp>
    #include <mesh/ItemArrayVariant.hpp>
    #include <mesh/ItemValueVariant.hpp>
    #include <mesh/SubItemArrayPerItemVariant.hpp>
    #include <mesh/SubItemValuePerItemVariant.hpp>
    #include <scheme/DiscreteFunctionVariant.hpp>
    #include <utils/Demangle.hpp>
    #include <utils/Filesystem.hpp>
    #include <utils/Messenger.hpp>
    #include <utils/SourceLocation.hpp>
    
    #include <fstream>
    
    template <typename DataType, ItemType item_type, typename ConnectivityPtr>
    void parallel_check(const ItemValue<DataType, item_type, ConnectivityPtr>& item_value,
                        const std::string& name,
                        const SourceLocation& source_location = SourceLocation{});
    
    template <typename DataType, ItemType item_type, typename ConnectivityPtr>
    void parallel_check(const ItemArray<DataType, item_type, ConnectivityPtr>& item_array,
                        const std::string& name,
                        const SourceLocation& source_location = SourceLocation{});
    
    template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
    void parallel_check(const SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& subitem_value_per_item,
                        const std::string& name,
                        const SourceLocation& source_location = SourceLocation{});
    
    template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
    void parallel_check(const SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& subitem_array_per_item,
                        const std::string& name,
                        const SourceLocation& source_location = SourceLocation{});
    
    class ParallelChecker
    {
     public:
      enum class Mode
      {
        automatic,   // write in sequential, read in parallel
        read,
        write
      };
    
      // to allow special manipulations in tests
      friend class ParallelCheckerTester;
    
     private:
      static ParallelChecker* m_instance;
    
      Mode m_mode  = Mode::automatic;
      size_t m_tag = 0;
    
      std::string m_filename = "parallel_checker.h5";
      std::string m_path     = "";
    
      ParallelChecker() = default;
    
     public:
      template <typename DataType, ItemType item_type, typename ConnectivityPtr>
      friend void parallel_check(const ItemValue<DataType, item_type, ConnectivityPtr>& item_value,
                                 const std::string& name,
                                 const SourceLocation& source_location);
    
      template <typename DataType, ItemType item_type, typename ConnectivityPtr>
      friend void parallel_check(const ItemArray<DataType, item_type, ConnectivityPtr>& item_array,
                                 const std::string& name,
                                 const SourceLocation& source_location);
    
      template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
      friend void parallel_check(const SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& subitem_value_per_item,
                                 const std::string& name,
                                 const SourceLocation& source_location);
    
      template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
      friend void parallel_check(const SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& subitem_array_per_item,
                                 const std::string& name,
                                 const SourceLocation& source_location);
    
    #ifdef PUGS_HAS_HDF5
     private:
      template <typename T>
      struct TinyVectorDataType;
    
      template <size_t Dimension, typename DataT>
      struct TinyVectorDataType<TinyVector<Dimension, DataT>> : public HighFive::DataType
      {
        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 TinyMatrixDataType;
    
      template <size_t M, size_t N, typename DataT>
      struct TinyMatrixDataType<TinyMatrix<M, N, DataT>> : public HighFive::DataType
      {
        TinyMatrixDataType()
        {
          hsize_t dim[]     = {M, N};
          auto h5_data_type = HighFive::create_datatype<DataT>();
          _hid              = H5Tarray_create(h5_data_type.getId(), 2, dim);
        }
      };
    
      HighFive::File
      _createOrOpenFileRW() const
      {
        if (m_tag == 0) {
          createDirectoryIfNeeded(m_filename);
          return HighFive::File{m_filename, HighFive::File::Truncate};
        } else {
          return HighFive::File{m_filename, HighFive::File::ReadWrite};
        }
      }
    
      void
      _printHeader(const std::string& name, const SourceLocation& source_location) const
      {
        std::cout << rang::fg::cyan << " - " << rang::fgB::cyan << "parallel checker" << rang::fg::cyan << " for \""
                  << rang::fgB::magenta << name << rang::fg::cyan << "\" tag " << rang::fgB::blue << m_tag
                  << rang::fg::reset << '\n';
        std::cout << rang::fg::cyan << " | from " << rang::fgB::blue << source_location.filename() << rang::fg::reset << ':'
                  << rang::style::bold << source_location.line() << rang::style::reset << '\n';
      }
    
      template <typename DataType>
      void
      _writeArray(HighFive::Group& group, const std::string& name, const Array<DataType>& array) const
      {
        using data_type = std::remove_const_t<DataType>;
        if constexpr (is_tiny_vector_v<data_type>) {
          auto dataset = group.createDataSet(name, HighFive::DataSpace{std::vector<size_t>{array.size()}},
                                             TinyVectorDataType<data_type>{});
    
          dataset.template write_raw<typename data_type::data_type>(&(array[0][0]), TinyVectorDataType<data_type>{});
        } else if constexpr (is_tiny_matrix_v<data_type>) {
          auto dataset = group.createDataSet(name, HighFive::DataSpace{std::vector<size_t>{array.size()}},
                                             TinyMatrixDataType<data_type>{});
    
          dataset.template write_raw<typename data_type::data_type>(&(array[0](0, 0)), TinyMatrixDataType<data_type>{});
        } else {
          auto dataset = group.createDataSet<data_type>(name, HighFive::DataSpace{std::vector<size_t>{array.size()}});
          dataset.template write_raw<data_type>(&(array[0]));
        }
      }
    
      template <typename DataType>
      void
      _writeTable(HighFive::Group& group, const std::string& name, const Table<DataType>& table) const
      {
        using data_type = std::remove_const_t<DataType>;
        if constexpr (is_tiny_vector_v<data_type>) {
          auto dataset =
            group.createDataSet(name,
                                HighFive::DataSpace{std::vector<size_t>{table.numberOfRows(), table.numberOfColumns()}},
                                TinyVectorDataType<data_type>{});
    
          dataset.template write_raw<typename data_type::data_type>(&(table(0, 0)[0]), TinyVectorDataType<data_type>{});
        } else if constexpr (is_tiny_matrix_v<data_type>) {
          auto dataset =
            group.createDataSet(name,
                                HighFive::DataSpace{std::vector<size_t>{table.numberOfRows(), table.numberOfColumns()}},
                                TinyMatrixDataType<data_type>{});
    
          dataset.template write_raw<typename data_type::data_type>(&(table(0, 0)(0, 0)), TinyMatrixDataType<data_type>{});
        } else {
          auto dataset =
            group.createDataSet<data_type>(name, HighFive::DataSpace{
                                                   std::vector<size_t>{table.numberOfRows(), table.numberOfColumns()}});
          dataset.template write_raw<data_type>(&(table(0, 0)));
        }
      }
    
      template <typename DataType>
      Array<std::remove_const_t<DataType>>
      _readArray(HighFive::Group& group, const std::string& name) const
      {
        using data_type = std::remove_const_t<DataType>;
    
        auto dataset = group.getDataSet(name);
        Array<data_type> array(dataset.getElementCount());
    
        if constexpr (is_tiny_vector_v<data_type>) {
          dataset.read<data_type>(&(array[0]), TinyVectorDataType<data_type>{});
        } else if constexpr (is_tiny_matrix_v<data_type>) {
          dataset.read<data_type>(&(array[0]), TinyMatrixDataType<data_type>{});
        } else {
          dataset.read<data_type>(&(array[0]));
        }
        return array;
      }
    
      template <typename DataType>
      Table<std::remove_const_t<DataType>>
      _readTable(HighFive::Group& group, const std::string& name) const
      {
        using data_type = std::remove_const_t<DataType>;
    
        auto dataset = group.getDataSet(name);
        Table<data_type> table(dataset.getDimensions()[0], dataset.getDimensions()[1]);
    
        if constexpr (is_tiny_vector_v<data_type>) {
          dataset.read<data_type>(&(table(0, 0)), TinyVectorDataType<data_type>{});
        } else if constexpr (is_tiny_matrix_v<data_type>) {
          dataset.read<data_type>(&(table(0, 0)), TinyMatrixDataType<data_type>{});
        } else {
          dataset.read<data_type>(&(table(0, 0)));
        }
        return table;
      }
    
      size_t
      _getConnectivityId(const std::shared_ptr<const IConnectivity>& i_connectivity) const
      {
        switch (i_connectivity->dimension()) {
        case 1: {
          return dynamic_cast<const Connectivity<1>&>(*i_connectivity).id();
        }
        case 2: {
          return dynamic_cast<const Connectivity<2>&>(*i_connectivity).id();
        }
        case 3: {
          return dynamic_cast<const Connectivity<3>&>(*i_connectivity).id();
        }
          // LCOV_EXCL_START
        default: {
          throw UnexpectedError("unexpected connectivity dimension");
        }
          // LCOV_EXCL_STOP
        }
      }
    
      template <ItemType item_type>
      Array<const int>
      _getItemNumber(const std::shared_ptr<const IConnectivity>& i_connectivity) const
      {
        switch (i_connectivity->dimension()) {
        case 1: {
          const Connectivity<1>& connectivity = dynamic_cast<const Connectivity<1>&>(*i_connectivity);
          return connectivity.number<item_type>().arrayView();
        }
        case 2: {
          const Connectivity<2>& connectivity = dynamic_cast<const Connectivity<2>&>(*i_connectivity);
          return connectivity.number<item_type>().arrayView();
        }
        case 3: {
          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
        }
      }
    
      template <ItemType item_type, ItemType sub_item_type>
      Array<const typename ConnectivityMatrix::IndexType>
      _getSubItemRowsMap(const std::shared_ptr<const IConnectivity>& i_connectivity)
      {
        switch (i_connectivity->dimension()) {
        case 1: {
          const Connectivity<1>& connectivity = dynamic_cast<const Connectivity<1>&>(*i_connectivity);
          return connectivity.getMatrix(item_type, sub_item_type).rowsMap();
        }
        case 2: {
          const Connectivity<2>& connectivity = dynamic_cast<const Connectivity<2>&>(*i_connectivity);
          return connectivity.getMatrix(item_type, sub_item_type).rowsMap();
        }
        case 3: {
          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
        }
      }
    
      template <ItemType item_type>
      Array<const int>
      _getItemOwner(const std::shared_ptr<const IConnectivity>& i_connectivity) const
      {
        switch (i_connectivity->dimension()) {
        case 1: {
          const Connectivity<1>& connectivity = dynamic_cast<const Connectivity<1>&>(*i_connectivity);
          return connectivity.owner<item_type>().arrayView();
        }
        case 2: {
          const Connectivity<2>& connectivity = dynamic_cast<const Connectivity<2>&>(*i_connectivity);
          return connectivity.owner<item_type>().arrayView();
        }
        case 3: {
          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");
        }
      }
    
      template <ItemType item_type>
      void
      _writeItemNumbers(const std::shared_ptr<const IConnectivity> i_connectivity,
                        HighFive::File file,
                        HighFive::Group group) const
      {
        std::string item_number_path = "/connectivities/" + std::to_string(this->_getConnectivityId(i_connectivity)) + '/' +
                                       std::string{itemName(item_type)};
    
        if (not file.exist(item_number_path)) {
          HighFive::Group item_number_group = file.createGroup(item_number_path);
          this->_writeArray(item_number_group, "numbers", this->_getItemNumber<item_type>(i_connectivity));
        }
    
        HighFive::DataSet item_numbers = file.getDataSet(item_number_path + "/numbers");
        group.createHardLink("numbers", item_numbers);
      }
    
      template <typename ItemOfItem>
      void
      _writeSubItemRowsMap(const std::shared_ptr<const IConnectivity> i_connectivity,
                           HighFive::File file,
                           HighFive::Group group)
      {
        constexpr ItemType item_type     = ItemOfItem::item_type;
        constexpr ItemType sub_item_type = ItemOfItem::sub_item_type;
    
        std::string subitem_of_item_row_map_path =
          "/connectivities/" + std::to_string(this->_getConnectivityId(i_connectivity)) + '/' +
          std::string{itemName(sub_item_type)} + "of" + std::string{itemName(item_type)};
    
        if (not file.exist(subitem_of_item_row_map_path)) {
          HighFive::Group subitem_of_item_row_map_group = file.createGroup(subitem_of_item_row_map_path);
          this->_writeArray(subitem_of_item_row_map_group, "rows_map",
                            this->_getSubItemRowsMap<item_type, sub_item_type>(i_connectivity));
        }
    
        HighFive::DataSet subitem_of_item_row_map = file.getDataSet(subitem_of_item_row_map_path + "/rows_map");
        group.createHardLink("rows_map", subitem_of_item_row_map);
      }
    
      template <typename DataType, ItemType item_type>
      bool
      _checkIsComparable(const std::string& name,
                         const SourceLocation& source_location,
                         const std::vector<size_t> data_shape,
                         const std::shared_ptr<const IConnectivity>& i_connectivity,
                         HighFive::Group group) const
      {
        const std::string reference_name          = group.getAttribute("name").read<std::string>();
        const std::string reference_file_name     = group.getAttribute("filename").read<std::string>();
        const std::string reference_function_name = group.getAttribute("function").read<std::string>();
        const size_t reference_line_number        = group.getAttribute("line").read<size_t>();
        const size_t reference_dimension          = group.getAttribute("dimension").read<size_t>();
        const std::string reference_item_type     = group.getAttribute("item_type").read<std::string>();
        const std::string reference_data_type     = group.getAttribute("data_type").read<std::string>();
    
        bool is_comparable = true;
        if (i_connectivity->dimension() != reference_dimension) {
          std::cout << rang::fg::cyan << " | " << rang::fgB::red << "different support dimensions: reference ("
                    << rang::fgB::yellow << reference_dimension << rang::fgB::red << ") / target (" << rang::fgB::yellow
                    << i_connectivity->dimension() << rang::fg::reset << ")\n";
          is_comparable = false;
        }
        if (itemName(item_type) != reference_item_type) {
          std::cout << rang::fg::cyan << " | " << rang::fgB::red << "different item types: reference (" << rang::fgB::yellow
                    << reference_item_type << rang::fgB::red << ") / target (" << rang::fgB::yellow << itemName(item_type)
                    << rang::fg::reset << ")\n";
          is_comparable = false;
        }
        if (demangle<DataType>() != reference_data_type) {
          std::cout << rang::fg::cyan << " | " << rang::fgB::red << "different data types: reference (" << rang::fgB::yellow
                    << reference_data_type << rang::fgB::red << ") / target (" << rang::fgB::yellow << demangle<DataType>()
                    << rang::fg::reset << ")\n";
          is_comparable = false;
        }
        std::vector reference_data_shape = group.getDataSet(reference_name).getSpace().getDimensions();
        if (reference_data_shape.size() != data_shape.size()) {
          std::cout << rang::fg::cyan << " | " << rang::fgB::red << "different data shape kind: reference ("
                    << rang::fgB::yellow << reference_data_shape.size() << "d array" << rang::fgB::red << ") / target ("
                    << rang::fgB::yellow << data_shape.size() << "d array" << rang::fg::reset << ")\n";
          is_comparable = false;
        }
        {
          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 ("
                      << rang::fgB::yellow << "*";
            for (size_t i = 1; i < reference_data_shape.size(); ++i) {
              std::cout << ":" << reference_data_shape[i];
            }
            std::cout << rang::fgB::red << ") / target (" << rang::fgB::yellow << "*";
            for (size_t i = 1; i < data_shape.size(); ++i) {
              std::cout << ":" << data_shape[i];
            }
            std::cout << rang::fg::reset << ")\n";
            is_comparable = false;
          }
        }
        if (name != reference_name) {
          // Just warn for different labels (maybe useful for some kind of
          // debugging...)
          std::cout << rang::fg::cyan << " | " << rang::fgB::magenta << "different names: reference (" << rang::fgB::yellow
                    << rang::style::reversed << reference_name << rang::style::reset << rang::fgB::magenta << ") / target ("
                    << rang::fgB::yellow << rang::style::reversed << name << rang::style::reset << rang::fg::reset << ")\n";
          std::cout << rang::fg::cyan << " | " << rang::fgB::magenta << "reference from " << rang::fgB::blue
                    << reference_file_name << rang::fg::reset << ':' << rang::style::bold << reference_line_number
                    << rang::style::reset << '\n';
          if ((reference_function_name.size() > 0) or (source_location.function().size() > 0)) {
            std::cout << rang::fg::cyan << " | " << rang::fgB::magenta << "reference function " << rang::fgB::blue
                      << reference_function_name << rang::fg::reset << '\n';
            std::cout << rang::fg::cyan << " | " << rang::fgB::magenta << "target function " << rang::fgB::blue
                      << source_location.function() << rang::fg::reset << '\n';
          }
        }
    
        return is_comparable;
      }
    
      template <ItemType sub_item_type>
      bool
      _checkIsComparable(HighFive::Group group) const
      {
        const std::string reference_sub_item_type = group.getAttribute("subitem_type").read<std::string>();
    
        bool is_comparable = true;
        if (itemName(sub_item_type) != reference_sub_item_type) {
          std::cout << rang::fg::cyan << " | " << rang::fgB::red << "different sub_item types: reference ("
                    << rang::fgB::yellow << reference_sub_item_type << rang::fgB::red << ") / target (" << rang::fgB::yellow
                    << itemName(sub_item_type) << rang::fg::reset << ")\n";
          is_comparable = false;
        }
    
        return is_comparable;
      }
    
      template <typename DataType, ItemType item_type>
      void
      _throwIfNotComparable(const std::string& name,
                            const SourceLocation& source_location,
                            const std::vector<size_t> data_shape,
                            const std::shared_ptr<const IConnectivity>& i_connectivity,
                            HighFive::Group group) const
      {
        bool is_comparable =
          this->_checkIsComparable<DataType, item_type>(name, source_location, data_shape, i_connectivity, group);
        if (not parallel::allReduceAnd(is_comparable)) {
          throw NormalError("cannot compare data");
        }
      }
    
      template <typename DataType, typename ItemOfItem>
      void
      _throwIfNotComparable(const std::string& name,
                            const SourceLocation& source_location,
                            const std::vector<size_t> data_shape,
                            const std::shared_ptr<const IConnectivity>& i_connectivity,
                            HighFive::Group group) const
      {
        bool is_comparable = this->_checkIsComparable<DataType, ItemOfItem::item_type>(name, source_location, data_shape,
                                                                                       i_connectivity, group)   //
                             and this->_checkIsComparable<ItemOfItem::sub_item_type>(group);
    
        if (not parallel::allReduceAnd(is_comparable)) {
          throw NormalError("cannot compare data");
        }
      }
    
     private:
      template <typename DataType, ItemType item_type, typename ConnectivityPtr>
      void
      write(const ItemValue<DataType, item_type, ConnectivityPtr>& item_value,
            const std::string& name,
            const SourceLocation& source_location)
      {
        HighFive::SilenceHDF5 m_silence_hdf5{true};
        this->_printHeader(name, source_location);
    
        try {
          HighFive::File file = this->_createOrOpenFileRW();
    
          auto group = file.createGroup("values/" + std::to_string(m_tag));
    
          group.createAttribute("filename", std::string{source_location.filename()});
          group.createAttribute("function", std::string{source_location.function()});
          group.createAttribute("line", static_cast<size_t>(source_location.line()));
          group.createAttribute("name", name);
    
          std::shared_ptr<const IConnectivity> i_connectivity = item_value.connectivity_ptr();
          group.createAttribute("dimension", static_cast<size_t>(i_connectivity->dimension()));
          group.createAttribute("item_type", std::string{itemName(item_type)});
          group.createAttribute("data_type", demangle<DataType>());
    
          this->_writeArray(group, name, item_value.arrayView());
    
          this->_writeItemNumbers<item_type>(i_connectivity, file, group);
    
          ++m_tag;
    
          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>
      void
      write(const ItemArray<DataType, item_type, ConnectivityPtr>& item_array,
            const std::string& name,
            const SourceLocation& source_location)
      {
        HighFive::SilenceHDF5 m_silence_hdf5{true};
        this->_printHeader(name, source_location);
    
        try {
          HighFive::File file = this->_createOrOpenFileRW();
    
          auto group = file.createGroup("values/" + std::to_string(m_tag));
    
          group.createAttribute("filename", std::string{source_location.filename()});
          group.createAttribute("function", std::string{source_location.function()});
          group.createAttribute("line", static_cast<size_t>(source_location.line()));
          group.createAttribute("name", name);
    
          std::shared_ptr<const IConnectivity> i_connectivity = item_array.connectivity_ptr();
          group.createAttribute("dimension", static_cast<size_t>(i_connectivity->dimension()));
          group.createAttribute("item_type", std::string{itemName(item_type)});
          group.createAttribute("data_type", demangle<DataType>());
    
          this->_writeTable(group, name, item_array.tableView());
    
          this->_writeItemNumbers<item_type>(i_connectivity, file, group);
    
          ++m_tag;
    
          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>
      void
      write(const SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& subitem_value_per_item,
            const std::string& name,
            const SourceLocation& source_location)
      {
        constexpr ItemType item_type     = ItemOfItem::item_type;
        constexpr ItemType sub_item_type = ItemOfItem::sub_item_type;
    
        HighFive::SilenceHDF5 m_silence_hdf5{true};
        this->_printHeader(name, source_location);
    
        try {
          HighFive::File file = this->_createOrOpenFileRW();
    
          auto group = file.createGroup("values/" + std::to_string(m_tag));
    
          group.createAttribute("filename", std::string{source_location.filename()});
          group.createAttribute("function", std::string{source_location.function()});
          group.createAttribute("line", static_cast<size_t>(source_location.line()));
          group.createAttribute("name", name);
    
          std::shared_ptr<const IConnectivity> i_connectivity = subitem_value_per_item.connectivity_ptr();
          group.createAttribute("dimension", static_cast<size_t>(i_connectivity->dimension()));
          group.createAttribute("item_type", std::string{itemName(item_type)});
          group.createAttribute("subitem_type", std::string{itemName(sub_item_type)});
    
          group.createAttribute("data_type", demangle<DataType>());
    
          this->_writeArray(group, name, subitem_value_per_item.arrayView());
    
          this->_writeItemNumbers<item_type>(i_connectivity, file, group);
          this->_writeSubItemRowsMap<ItemOfItem>(i_connectivity, file, group);
    
          ++m_tag;
    
          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>
      void
      write(const SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& subitem_value_per_item,
            const std::string& name,
            const SourceLocation& source_location)
      {
        constexpr ItemType item_type     = ItemOfItem::item_type;
        constexpr ItemType sub_item_type = ItemOfItem::sub_item_type;
    
        HighFive::SilenceHDF5 m_silence_hdf5{true};
        this->_printHeader(name, source_location);
    
        try {
          HighFive::File file = this->_createOrOpenFileRW();
    
          auto group = file.createGroup("values/" + std::to_string(m_tag));
    
          group.createAttribute("filename", std::string{source_location.filename()});
          group.createAttribute("function", std::string{source_location.function()});
          group.createAttribute("line", static_cast<size_t>(source_location.line()));
          group.createAttribute("name", name);
    
          std::shared_ptr<const IConnectivity> i_connectivity = subitem_value_per_item.connectivity_ptr();
          group.createAttribute("dimension", static_cast<size_t>(i_connectivity->dimension()));
          group.createAttribute("item_type", std::string{itemName(item_type)});
          group.createAttribute("subitem_type", std::string{itemName(sub_item_type)});
    
          group.createAttribute("data_type", demangle<DataType>());
    
          this->_writeTable(group, name, subitem_value_per_item.tableView());
    
          this->_writeItemNumbers<item_type>(i_connectivity, file, group);
          this->_writeSubItemRowsMap<ItemOfItem>(i_connectivity, file, group);
    
          ++m_tag;
    
          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>
      void
      compare(const ItemValue<DataType, item_type, ConnectivityPtr>& item_value,
              const std::string& name,
              const SourceLocation& source_location)
      {
        HighFive::SilenceHDF5 m_silence_hdf5{true};
        this->_printHeader(name, source_location);
    
        try {
          HighFive::File file{m_filename, HighFive::File::ReadOnly};
    
          auto group = file.getGroup("values/" + std::to_string(m_tag));
    
          const std::string reference_name = group.getAttribute("name").read<std::string>();
    
          std::shared_ptr<const IConnectivity> i_connectivity = item_value.connectivity_ptr();
    
          this->_throwIfNotComparable<DataType, item_type>(name, source_location,
                                                           std::vector<size_t>{item_value.numberOfItems()}, i_connectivity,
                                                           group);
    
          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);
    
          using ItemId = ItemIdT<item_type>;
    
          std::unordered_map<int, ItemId> item_number_to_item_id_map;
    
          for (ItemId item_id = 0; item_id < item_numbers.size(); ++item_id) {
            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());
    
          Array<int> index_in_reference(item_numbers.size());
          index_in_reference.fill(-1);
          for (size_t i = 0; i < reference_item_numbers.size(); ++i) {
            const auto& i_number_to_item_id = item_number_to_item_id_map.find(reference_item_numbers[i]);
            if (i_number_to_item_id != item_number_to_item_id_map.end()) {
              index_in_reference[i_number_to_item_id->second] = i;
            }
          }
    
          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);
    
          bool has_own_differences = false;
          bool is_same             = true;
    
          for (ItemId item_id = 0; item_id < item_value.numberOfItems(); ++item_id) {
            if (reference_item_value[index_in_reference[item_id]] != item_value[item_id]) {
              is_same = false;
              if (static_cast<size_t>(owner[item_id]) == parallel::rank()) {
                has_own_differences = true;
              }
            }
          }
    
          is_same             = parallel::allReduceAnd(is_same);
          has_own_differences = parallel::allReduceOr(has_own_differences);
    
          if (is_same) {
            std::cout << rang::fg::cyan << " | compare: " << rang::fgB::green << "success" << rang::fg::reset << '\n';
          } else {
            if (has_own_differences) {
              std::cout << rang::fg::cyan << " | compare: " << rang::fgB::red << "failed!" << rang::fg::reset;
            } else {
              std::cout << rang::fg::cyan << " | compare: " << rang::fgB::yellow << "not synchronized" << rang::fg::reset;
            }
            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{m_path + "parallel_differences_"} + stringify(m_tag) + std::string{"_"} +
                                 stringify(parallel::rank()));
    
              fout.precision(15);
              for (ItemId item_id = 0; item_id < item_value.numberOfItems(); ++item_id) {
                if (reference_item_value[index_in_reference[item_id]] != item_value[item_id]) {
                  const bool is_own_difference = (parallel::rank() == static_cast<size_t>(owner[item_id]));
                  if (is_own_difference) {
                    fout << rang::fgB::red << "[ own ]" << rang::fg::reset;
                  } else {
                    fout << rang::fgB::yellow << "[ghost]" << rang::fg::reset;
                  }
                  fout << " rank=" << parallel::rank() << " owner=" << owner[item_id] << " item_id=" << item_id
                       << " number=" << item_numbers[item_id]
                       << " reference=" << reference_item_value[index_in_reference[item_id]]
                       << " target=" << item_value[item_id]
                       << " difference=" << reference_item_value[index_in_reference[item_id]] - item_value[item_id] << '\n';
                  if (static_cast<size_t>(owner[item_id]) == parallel::rank()) {
                    has_own_differences = true;
                  }
                }
              }
            }
    
            if (parallel::allReduceAnd(has_own_differences)) {
              throw NormalError("calculations differ!");
            }
          }
    
          ++m_tag;
        }
        // LCOV_EXCL_START
        catch (HighFive::Exception& e) {
          throw NormalError(e.what());
        }
        // LCOV_EXCL_STOP
      }
    
      template <typename DataType, ItemType item_type, typename ConnectivityPtr>
      void
      compare(const ItemArray<DataType, item_type, ConnectivityPtr>& item_array,
              const std::string& name,
              const SourceLocation& source_location)
      {
        HighFive::SilenceHDF5 m_silence_hdf5{true};
        this->_printHeader(name, source_location);
    
        try {
          HighFive::File file{m_filename, HighFive::File::ReadOnly};
    
          auto group = file.getGroup("values/" + std::to_string(m_tag));
    
          const std::string reference_name = group.getAttribute("name").read<std::string>();
    
          std::shared_ptr<const IConnectivity> i_connectivity = item_array.connectivity_ptr();
    
          this->_throwIfNotComparable<DataType, item_type>(name, source_location,
                                                           std::vector<size_t>{item_array.numberOfItems(),
                                                                               item_array.sizeOfArrays()},
                                                           i_connectivity, group);
    
          Array<const int> reference_item_numbers    = this->_readArray<int>(group, "numbers");
          Table<const DataType> reference_item_array = this->_readTable<DataType>(group, reference_name);
    
          Array<const int> item_numbers = this->_getItemNumber<item_type>(i_connectivity);
    
          using ItemId = ItemIdT<item_type>;
    
          std::unordered_map<int, ItemId> item_number_to_item_id_map;
    
          for (ItemId item_id = 0; item_id < item_numbers.size(); ++item_id) {
            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());
    
          Array<int> index_in_reference(item_numbers.size());
          index_in_reference.fill(-1);
          for (size_t i = 0; i < reference_item_numbers.size(); ++i) {
            const auto& i_number_to_item_id = item_number_to_item_id_map.find(reference_item_numbers[i]);
            if (i_number_to_item_id != item_number_to_item_id_map.end()) {
              index_in_reference[i_number_to_item_id->second] = i;
            }
          }
    
          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);
    
          bool has_own_differences = false;
          bool is_same             = true;
    
          for (ItemId item_id = 0; item_id < item_array.numberOfItems(); ++item_id) {
            for (size_t i = 0; i < reference_item_array.numberOfColumns(); ++i) {
              if (reference_item_array[index_in_reference[item_id]][i] != item_array[item_id][i]) {
                is_same = false;
                if (static_cast<size_t>(owner[item_id]) == parallel::rank()) {
                  has_own_differences = true;
                }
              }
            }
          }
    
          is_same             = parallel::allReduceAnd(is_same);
          has_own_differences = parallel::allReduceOr(has_own_differences);
    
          if (is_same) {
            std::cout << rang::fg::cyan << " | compare: " << rang::fgB::green << "success" << rang::fg::reset << '\n';
          } else {
            if (has_own_differences) {
              std::cout << rang::fg::cyan << " | compare: " << rang::fgB::red << "failed!" << rang::fg::reset;
            } 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::ofstream fout(std::string{m_path + "parallel_differences_"} + stringify(m_tag) + std::string{"_"} +
                                 stringify(parallel::rank()));
    
              fout.precision(15);
              for (ItemId item_id = 0; item_id < item_array.numberOfItems(); ++item_id) {
                for (size_t i = 0; i < item_array.sizeOfArrays(); ++i) {
                  if (reference_item_array[index_in_reference[item_id]][i] != item_array[item_id][i]) {
                    const bool is_own_difference = (parallel::rank() == static_cast<size_t>(owner[item_id]));
                    if (is_own_difference) {
                      fout << rang::fgB::red << "[ own ]" << rang::fg::reset;
                    } else {
                      fout << rang::fgB::yellow << "[ghost]" << rang::fg::reset;
                    }
                    fout << " rank=" << parallel::rank() << " owner=" << owner[item_id] << " item_id=" << item_id
                         << " column=" << i << " number=" << item_numbers[item_id]
                         << " reference=" << reference_item_array[index_in_reference[item_id]][i]
                         << " target=" << item_array[item_id][i]
                         << " difference=" << reference_item_array[index_in_reference[item_id]][i] - item_array[item_id][i]
                         << '\n';
                    if (static_cast<size_t>(owner[item_id]) == parallel::rank()) {
                      has_own_differences = true;
                    }
                  }
                }
              }
            }
    
            if (parallel::allReduceAnd(has_own_differences)) {
              throw NormalError("calculations differ!");
            }
          }
    
          ++m_tag;
        }
        // LCOV_EXCL_START
        catch (HighFive::Exception& e) {
          throw NormalError(e.what());
        }
        // LCOV_EXCL_STOP
      }
    
      template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
      void
      compare(const SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& subitem_value_per_item,
              const std::string& name,
              const SourceLocation& source_location)
      {
        HighFive::SilenceHDF5 m_silence_hdf5{true};
        this->_printHeader(name, source_location);
    
        try {
          HighFive::File file{m_filename, HighFive::File::ReadOnly};
    
          auto group = file.getGroup("values/" + std::to_string(m_tag));
    
          const std::string reference_name = group.getAttribute("name").read<std::string>();
    
          std::shared_ptr<const IConnectivity> i_connectivity = subitem_value_per_item.connectivity_ptr();
    
          this->_throwIfNotComparable<DataType, ItemOfItem>(name, source_location,
                                                            std::vector<size_t>{subitem_value_per_item.numberOfItems()},
                                                            i_connectivity, group);
    
          constexpr ItemType item_type     = ItemOfItem::item_type;
          constexpr ItemType sub_item_type = ItemOfItem::sub_item_type;
          using IndexType                  = typename ConnectivityMatrix::IndexType;
    
          Array<const int> reference_item_numbers           = this->_readArray<int>(group, "numbers");
          Array<const IndexType> reference_subitem_rows_map = this->_readArray<IndexType>(group, "rows_map");
    
          Array<const DataType> reference_subitem_value_per_item = this->_readArray<DataType>(group, reference_name);
    
          Array<const int> item_numbers           = this->_getItemNumber<item_type>(i_connectivity);
          Array<const IndexType> sub_item_row_map = this->_getSubItemRowsMap<item_type, sub_item_type>(i_connectivity);
    
          using ItemId = ItemIdT<item_type>;
    
          std::unordered_map<int, ItemId> item_number_to_item_id_map;
    
          for (ItemId item_id = 0; item_id < item_numbers.size(); ++item_id) {
            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());
    
          Array<int> item_index_in_reference(item_numbers.size());
          item_index_in_reference.fill(-1);
          for (size_t i = 0; i < reference_item_numbers.size(); ++i) {
            const auto& i_number_to_item_id = item_number_to_item_id_map.find(reference_item_numbers[i]);
            if (i_number_to_item_id != item_number_to_item_id_map.end()) {
              item_index_in_reference[i_number_to_item_id->second] = i;
            }
          }
    
          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);
    
          bool has_own_differences = false;
          bool is_same             = true;
    
          for (ItemId item_id = 0; item_id < subitem_value_per_item.numberOfItems(); ++item_id) {
            const size_t reference_item_index     = item_index_in_reference[item_id];
            const size_t index_begin_in_reference = reference_subitem_rows_map[reference_item_index];
            const size_t index_end_in_reference   = reference_subitem_rows_map[reference_item_index + 1];
    
            bool item_is_same = true;
            if ((index_end_in_reference - index_begin_in_reference) != subitem_value_per_item[item_id].size()) {
              item_is_same = false;
            } else {
              for (size_t i_sub_item = 0; i_sub_item < subitem_value_per_item[item_id].size(); ++i_sub_item) {
                if (reference_subitem_value_per_item[index_begin_in_reference + i_sub_item] !=
                    subitem_value_per_item[item_id][i_sub_item]) {
                  item_is_same = false;
                }
              }
            }
            if (not item_is_same) {
              is_same = false;
              if (static_cast<size_t>(owner[item_id]) == parallel::rank()) {
                has_own_differences = true;
              }
            }
          }
    
          is_same             = parallel::allReduceAnd(is_same);
          has_own_differences = parallel::allReduceOr(has_own_differences);
    
          if (is_same) {
            std::cout << rang::fg::cyan << " | compare: " << rang::fgB::green << "success" << rang::fg::reset << '\n';
          } else {
            if (has_own_differences) {
              std::cout << rang::fg::cyan << " | compare: " << rang::fgB::red << "failed!" << rang::fg::reset;
            } else {
              std::cout << rang::fg::cyan << " | compare: " << rang::fgB::yellow << "not synchronized" << rang::fg::reset;
            }
            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{m_path + "parallel_differences_"} + stringify(m_tag) + std::string{"_"} +
                                 stringify(parallel::rank()));
    
              fout.precision(15);
              for (ItemId item_id = 0; item_id < subitem_value_per_item.numberOfItems(); ++item_id) {
                const size_t reference_item_index     = item_index_in_reference[item_id];
                const size_t index_begin_in_reference = reference_subitem_rows_map[reference_item_index];
                const size_t index_end_in_reference   = reference_subitem_rows_map[reference_item_index + 1];
                if ((index_end_in_reference - index_begin_in_reference) != subitem_value_per_item[item_id].size()) {
                  const bool is_own_difference = (parallel::rank() == static_cast<size_t>(owner[item_id]));
                  if (is_own_difference) {
                    fout << rang::fgB::red << "[ own ]" << rang::fg::reset;
                  } else {
                    fout << rang::fgB::yellow << "[ghost]" << rang::fg::reset;
                  }
                  fout << " rank=" << parallel::rank() << " owner=" << owner[item_id] << " item_id=" << item_id
                       << " number=" << item_numbers[item_id]
                       << " reference[subitems number]=" << index_end_in_reference - index_begin_in_reference
                       << " target[subitems number]=" << subitem_value_per_item[item_id].size() << '\n';
    
                } else {
                  for (size_t i_sub_item = 0; i_sub_item < subitem_value_per_item[item_id].size(); ++i_sub_item) {
                    if (reference_subitem_value_per_item[index_begin_in_reference + i_sub_item] !=
                        subitem_value_per_item[item_id][i_sub_item]) {
                      const bool is_own_difference = (parallel::rank() == static_cast<size_t>(owner[item_id]));
                      if (is_own_difference) {
                        fout << rang::fgB::red << "[ own ]" << rang::fg::reset;
                      } else {
                        fout << rang::fgB::yellow << "[ghost]" << rang::fg::reset;
                      }
                      fout << " rank=" << parallel::rank() << " owner=" << owner[item_id] << " item_id=" << item_id
                           << " number=" << item_numbers[item_id] << " i_subitem=" << i_sub_item
                           << " reference=" << reference_subitem_value_per_item[index_begin_in_reference + i_sub_item]
                           << " target=" << subitem_value_per_item[item_id][i_sub_item] << " difference="
                           << reference_subitem_value_per_item[index_begin_in_reference + i_sub_item] -
                                subitem_value_per_item[item_id][i_sub_item]
                           << '\n';
                    }
                  }
                }
              }
            }
    
            if (has_own_differences) {
              throw NormalError("calculations differ!");
            }
          }
    
          ++m_tag;
        }
        // LCOV_EXCL_START
        catch (HighFive::Exception& e) {
          throw NormalError(e.what());
        }
        // LCOV_EXCL_STOP
      }
    
      template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
      void
      compare(const SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& subitem_array_per_item,
              const std::string& name,
              const SourceLocation& source_location)
      {
        HighFive::SilenceHDF5 m_silence_hdf5{true};
        this->_printHeader(name, source_location);
    
        try {
          HighFive::File file{m_filename, HighFive::File::ReadOnly};
    
          auto group = file.getGroup("values/" + std::to_string(m_tag));
    
          const std::string reference_name = group.getAttribute("name").read<std::string>();
    
          std::shared_ptr<const IConnectivity> i_connectivity = subitem_array_per_item.connectivity_ptr();
    
          this->_throwIfNotComparable<DataType, ItemOfItem>(name, source_location,
                                                            std::vector<size_t>{subitem_array_per_item.numberOfItems(),
                                                                                subitem_array_per_item.sizeOfArrays()},
                                                            i_connectivity, group);
    
          constexpr ItemType item_type     = ItemOfItem::item_type;
          constexpr ItemType sub_item_type = ItemOfItem::sub_item_type;
          using IndexType                  = typename ConnectivityMatrix::IndexType;
    
          Array<const int> reference_item_numbers           = this->_readArray<int>(group, "numbers");
          Array<const IndexType> reference_subitem_rows_map = this->_readArray<IndexType>(group, "rows_map");
    
          Table<const DataType> reference_subitem_array_per_item = this->_readTable<DataType>(group, reference_name);
    
          Array<const int> item_numbers           = this->_getItemNumber<item_type>(i_connectivity);
          Array<const IndexType> sub_item_row_map = this->_getSubItemRowsMap<item_type, sub_item_type>(i_connectivity);
    
          using ItemId = ItemIdT<item_type>;
    
          std::unordered_map<int, ItemId> item_number_to_item_id_map;
    
          for (ItemId item_id = 0; item_id < item_numbers.size(); ++item_id) {
            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());
    
          Array<int> item_index_in_reference(item_numbers.size());
          item_index_in_reference.fill(-1);
          for (size_t i = 0; i < reference_item_numbers.size(); ++i) {
            const auto& i_number_to_item_id = item_number_to_item_id_map.find(reference_item_numbers[i]);
            if (i_number_to_item_id != item_number_to_item_id_map.end()) {
              item_index_in_reference[i_number_to_item_id->second] = i;
            }
          }
    
          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);
    
          bool has_own_differences = false;
          bool is_same             = true;
    
          for (ItemId item_id = 0; item_id < subitem_array_per_item.numberOfItems(); ++item_id) {
            const size_t reference_item_index     = item_index_in_reference[item_id];
            const size_t index_begin_in_reference = reference_subitem_rows_map[reference_item_index];
            const size_t index_end_in_reference   = reference_subitem_rows_map[reference_item_index + 1];
    
            bool item_is_same = true;
            if ((index_end_in_reference - index_begin_in_reference) != subitem_array_per_item[item_id].numberOfRows()) {
              item_is_same = false;
            } else {
              for (size_t i_sub_item = 0; i_sub_item < subitem_array_per_item[item_id].numberOfRows(); ++i_sub_item) {
                for (size_t i = 0; i < subitem_array_per_item.sizeOfArrays(); ++i) {
                  if (reference_subitem_array_per_item[index_begin_in_reference + i_sub_item][i] !=
                      subitem_array_per_item[item_id][i_sub_item][i]) {
                    item_is_same = false;
                  }
                }
              }
            }
            if (not item_is_same) {
              is_same = false;
              if (static_cast<size_t>(owner[item_id]) == parallel::rank()) {
                has_own_differences = true;
              }
            }
          }
    
          is_same             = parallel::allReduceAnd(is_same);
          has_own_differences = parallel::allReduceOr(has_own_differences);
    
          if (is_same) {
            std::cout << rang::fg::cyan << " | compare: " << rang::fgB::green << "success" << rang::fg::reset << '\n';
          } else {
            if (has_own_differences) {
              std::cout << rang::fg::cyan << " | compare: " << rang::fgB::red << "failed!" << rang::fg::reset;
            } else {
              std::cout << rang::fg::cyan << " | compare: " << rang::fgB::yellow << "not synchronized" << rang::fg::reset;
            }
            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{m_path + "parallel_differences_"} + stringify(m_tag) + std::string{"_"} +
                                 stringify(parallel::rank()));
    
              fout.precision(15);
              for (ItemId item_id = 0; item_id < subitem_array_per_item.numberOfItems(); ++item_id) {
                const size_t reference_item_index     = item_index_in_reference[item_id];
                const size_t index_begin_in_reference = reference_subitem_rows_map[reference_item_index];
                const size_t index_end_in_reference   = reference_subitem_rows_map[reference_item_index + 1];
                if ((index_end_in_reference - index_begin_in_reference) != subitem_array_per_item[item_id].numberOfRows()) {
                  const bool is_own_difference = (parallel::rank() == static_cast<size_t>(owner[item_id]));
                  if (is_own_difference) {
                    fout << rang::fgB::red << "[ own ]" << rang::fg::reset;
                  } else {
                    fout << rang::fgB::yellow << "[ghost]" << rang::fg::reset;
                  }
                  fout << " rank=" << parallel::rank() << " owner=" << owner[item_id] << " item_id=" << item_id
                       << " number=" << item_numbers[item_id]
                       << " reference[subitems number]=" << index_end_in_reference - index_begin_in_reference
                       << " target[subitems number]=" << subitem_array_per_item[item_id].numberOfRows() << '\n';
    
                } else {
                  for (size_t i_sub_item = 0; i_sub_item < subitem_array_per_item[item_id].numberOfRows(); ++i_sub_item) {
                    for (size_t i = 0; i < subitem_array_per_item.sizeOfArrays(); ++i) {
                      if (reference_subitem_array_per_item[index_begin_in_reference + i_sub_item][i] !=
                          subitem_array_per_item[item_id][i_sub_item][i]) {
                        const bool is_own_difference = (parallel::rank() == static_cast<size_t>(owner[item_id]));
                        if (is_own_difference) {
                          fout << rang::fgB::red << "[ own ]" << rang::fg::reset;
                        } else {
                          fout << rang::fgB::yellow << "[ghost]" << rang::fg::reset;
                        }
                        fout << " rank=" << parallel::rank() << " owner=" << owner[item_id] << " item_id=" << item_id
                             << " number=" << item_numbers[item_id] << " i_subitem=" << i_sub_item << " i_value=" << i
                             << " reference=" << reference_subitem_array_per_item[index_begin_in_reference + i_sub_item]
                             << " target=" << subitem_array_per_item[item_id][i_sub_item] << " difference="
                             << reference_subitem_array_per_item[index_begin_in_reference + i_sub_item][i] -
                                  subitem_array_per_item[item_id][i_sub_item][i]
                             << '\n';
                      }
                    }
                  }
                }
              }
            }
    
            if (has_own_differences) {
              throw NormalError("calculations differ!");
            }
          }
    
          ++m_tag;
        }
        // LCOV_EXCL_START
        catch (HighFive::Exception& e) {
          throw NormalError(e.what());
        }
        // LCOV_EXCL_STOP
      }
    
    #else    // PUGS_HAS_HDF5
    
      template <typename T>
      void
      write(const T&, const std::string&, const SourceLocation&)
      {
        throw NormalError("parallel checker cannot be used without HDF5 support");
      }
    
      template <typename T>
      void
      compare(const T&, const std::string&, const SourceLocation&)
      {
        throw NormalError("parallel checker cannot be used without HDF5 support");
      }
    #endif   // PUGS_HAS_HDF5
    
     public:
      static void create();
      static void destroy();
    
      static ParallelChecker&
      instance()
      {
        Assert(m_instance != nullptr, "ParallelChecker was not created");
        return *m_instance;
      }
    
      Mode
      mode() const
      {
        return m_mode;
      }
    
      void
      setMode(const Mode& mode)
      {
        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;
      }
    
      const std::string&
      filename() const
      {
        return m_filename;
      }
    
      void
      setFilename(const std::string& filename)
      {
        if (m_tag != 0) {
          throw UnexpectedError("Cannot modify parallel checker file if it was already used");
        }
        m_filename = filename;
        m_path     = std::filesystem::path(filename).remove_filename();
      }
    
      bool
      isWriting() const
      {
        bool is_writting = false;
        switch (m_mode) {
        case Mode::automatic: {
          is_writting = (parallel::size() == 1);
          break;
        }
        case Mode::write: {
          is_writting = true;
          break;
        }
        case Mode::read: {
          is_writting = false;
          break;
        }
        }
    
        return is_writting;
      }
    };
    
    template <typename DataType, ItemType item_type, typename ConnectivityPtr>
    void
    parallel_check(const ItemValue<DataType, item_type, ConnectivityPtr>& item_value,
                   const std::string& name,
                   const SourceLocation& source_location)
    {
      if (ParallelChecker::instance().isWriting()) {
        ParallelChecker::instance().write(item_value, name, source_location);
      } else {
        ParallelChecker::instance().compare(item_value, name, source_location);
      }
    }
    
    template <typename DataType, ItemType item_type, typename ConnectivityPtr>
    void
    parallel_check(const ItemArray<DataType, item_type, ConnectivityPtr>& item_array,
                   const std::string& name,
                   const SourceLocation& source_location)
    {
      if (ParallelChecker::instance().isWriting()) {
        ParallelChecker::instance().write(item_array, name, source_location);
      } else {
        ParallelChecker::instance().compare(item_array, name, source_location);
      }
    }
    
    template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
    void
    parallel_check(const SubItemValuePerItem<DataType, ItemOfItem, ConnectivityPtr>& subitem_value_per_item,
                   const std::string& name,
                   const SourceLocation& source_location)
    {
      if (ParallelChecker::instance().isWriting()) {
        ParallelChecker::instance().write(subitem_value_per_item, name, source_location);
      } else {
        ParallelChecker::instance().compare(subitem_value_per_item, name, source_location);
      }
    }
    
    template <typename DataType, typename ItemOfItem, typename ConnectivityPtr>
    void
    parallel_check(const SubItemArrayPerItem<DataType, ItemOfItem, ConnectivityPtr>& subitem_array_per_item,
                   const std::string& name,
                   const SourceLocation& source_location)
    {
      if (ParallelChecker::instance().isWriting()) {
        ParallelChecker::instance().write(subitem_array_per_item, name, source_location);
      } else {
        ParallelChecker::instance().compare(subitem_array_per_item, name, source_location);
      }
    }
    
    template <size_t Dimension, typename DataType>
    void PUGS_INLINE
    parallel_check(const DiscreteFunctionP0<Dimension, DataType>& discrete_function,
                   const std::string& name,
                   const SourceLocation& source_location = SourceLocation{})
    {
      parallel_check(discrete_function.cellValues(), name, source_location);
    }
    
    template <size_t Dimension, typename DataType>
    void PUGS_INLINE
    parallel_check(const DiscreteFunctionP0Vector<Dimension, DataType>& discrete_function,
                   const std::string& name,
                   const SourceLocation& source_location = SourceLocation{})
    {
      parallel_check(discrete_function.cellArrays(), name, source_location);
    }
    
    void parallel_check(const ItemValueVariant& item_value_variant,
                        const std::string& name,
                        const SourceLocation& source_location = SourceLocation{});
    
    void parallel_check(const ItemArrayVariant& item_array_variant,
                        const std::string& name,
                        const SourceLocation& source_location = SourceLocation{});
    
    void parallel_check(const SubItemValuePerItemVariant& subitem_value_per_item_variant,
                        const std::string& name,
                        const SourceLocation& source_location = SourceLocation{});
    
    void parallel_check(const SubItemArrayPerItemVariant& subitem_array_per_item_variant,
                        const std::string& name,
                        const SourceLocation& source_location = SourceLocation{});
    
    void parallel_check(const DiscreteFunctionVariant& discrete_function_variant,
                        const std::string& name,
                        const SourceLocation& source_location = SourceLocation{});
    
    #endif   // PARALLEL_CHECKER_HPP