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