diff --git a/mesh_handle/competence_pack.hxx b/mesh_handle/competence_pack.hxx index e3963566b7..b3f148ffbb 100644 --- a/mesh_handle/competence_pack.hxx +++ b/mesh_handle/competence_pack.hxx @@ -3,7 +3,7 @@ t8code is a C library to manage a collection (a forest) of multiple connected adaptive space-trees of general element classes in parallel. - Copyright (C) 2025 the developers + Copyright (C) 2026 the developers t8code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -28,6 +28,7 @@ #pragma once #include "competences.hxx" +#include "data_handler.hxx" namespace t8_mesh_handle { // --- Element competence pack. --- @@ -52,10 +53,15 @@ using all_cache_element_competences = element_competence_pack; -/** Predefined competence pack combining all competences related to faces. */ +/** Predefined element competence pack combining all competences related to faces. */ using cache_face_element_competences = element_competence_pack; +/** Predefined element competence pack combining all competences related to data. + * Please note that you must combine this with \ref t8_mesh_handle::data_mesh_competences. */ +using data_element_competences + = element_competence_pack; + // --- Mesh competence pack. --- /** Class to pack different mesh competences into one template parameter for the \ref mesh class. * \tparam TMeshCompetence The mesh competences to be packed. @@ -75,4 +81,11 @@ struct mesh_competence_pack using is_mesh_competence_pack = void; /**< Tag to identify this class. */ }; +/** Predefined mesh competence pack combining all competences related to data. + * If you want to access the data also via the elements, combine this with \ref t8_mesh_handle::data_element_competences. + */ +template +using data_mesh_competences = mesh_competence_pack::template type, + new_element_data_mesh_competence::template type>; + } // namespace t8_mesh_handle diff --git a/mesh_handle/data_handler.hxx b/mesh_handle/data_handler.hxx index 7c98c19164..cb6ef83912 100644 --- a/mesh_handle/data_handler.hxx +++ b/mesh_handle/data_handler.hxx @@ -22,14 +22,18 @@ /** \file data_handler.hxx * Handler for the element data of a \ref t8_mesh_handle::mesh. - * The file defines a mesh and an element competence for element data handling. - * Use both competences together if you want to manage element data for the elements of the mesh and access it directly for each element. + * The file defines mesh and element competences for element data handling. + * The mesh competences make it possible to manage element data and exchange it for ghost elements between processes. + * The element competences makes it possible to access these element data directly for each element of the mesh. + * The competences with new element data additionally provide the possibility to set new element data that will + * be used to update the element data on commit (or on the related function call). */ #pragma once #include #include #include +#include #include #include @@ -43,19 +47,20 @@ concept T8MPISafeType /** Handler for the element data of a \ref mesh. * Use this competence if you want to manage element data for the elements of the mesh. - * Use the helper \ref element_data_competence to get this competence with the correct template parameters form for the mesh. + * Use the helper \ref element_data_mesh_competence to get this competence with the correct template parameters form. * If you want to access the data not only in vector form but also directly for each element, - * you can combine this competence with the \ref access_element_data competence. + * you can combine this competence with \ref element_data_element_competence. * In summary you can use the competences like this: - * mesh, - * mesh_competence_pack::template type>>; + * mesh, + * mesh_competence_pack::template type>>; + * Some predefined competences are also defined in \ref competence_pack.hxx. * * \tparam TUnderlying Use the \ref mesh class here. * \tparam TElementDataType The element data type you want to use for each element of the mesh. * The data type has to be MPI safe as the data for ghost elements will be exchanged via MPI. */ template -class handle_element_data: public t8_crtp_basic { +class element_data_mesh_competence_impl: public t8_crtp_basic { public: using ElementDataType = TElementDataType; /**< Make Type of the element data publicly accessible. */ @@ -69,9 +74,8 @@ class handle_element_data: public t8_crtp_basic { const auto num_local_elements = this->underlying ().get_num_local_elements (); const auto num_ghosts = this->underlying ().get_num_ghosts (); T8_ASSERT (element_data.size () == static_cast (num_local_elements)); - m_element_data = std::move (element_data); m_element_data.reserve (num_local_elements + num_ghosts); - m_element_data.resize (num_local_elements); + m_element_data = std::move (element_data); } /** Get the element data vector. @@ -109,35 +113,52 @@ class handle_element_data: public t8_crtp_basic { std::vector m_element_data; /**< Vector storing the (local) element data. */ }; -/** Wrapper for \ref handle_element_data to hide TUnderlying and provide the form needed to pass it as a mesh competence. - * Use mesh_competence_pack::template type> +/** Wrapper for \ref element_data_mesh_competence_impl to hide TUnderlying and provide the form needed to pass it + * as a mesh competence. + * Use mesh_competence_pack::template type> * to get this competence with the correct template parameter form for the mesh. * \tparam TElementDataType The element data type you want to use for each element of the mesh. * The data type has to be MPI safe as the data for ghost elements will be exchanged via MPI. */ template -struct element_data_competence +struct element_data_mesh_competence { /** Type to provide the form needed for the mesh competence pack. * \tparam TUnderlying Use the \ref mesh class here. */ template - using type = handle_element_data; + using type = element_data_mesh_competence_impl; }; // --- Element competence for element data management. --- /** Element competence to enable that element data can be accessed directly for each element of the mesh. - * \note This competence requires that the mesh has the \ref handle_element_data competence to manage the - * element data vector and exchange ghost data. + * \note This competence requires that the mesh has \ref element_data_mesh_competence_impl + * (or \ref element_data_mesh_competence) competence that defines the element data vector and the element data type. * \tparam TUnderlying Use the \ref element with specified competences as template parameter. */ template -struct access_element_data: public t8_crtp_basic +struct element_data_element_competence: public t8_crtp_operator { public: - // --- Getter and setter for element data. --- + /** Set the element data for the element. + * \note You can only set element data for non-ghost elements. + * \param [in] element_data The element data to be set of type TMeshClass::ElementDataType. + */ + void + set_element_data (auto element_data) + { + T8_ASSERT (this->underlying ().m_mesh->has_element_data_handler_competence ()); + SC_CHECK_ABORT (!this->underlying ().is_ghost_element (), "Element data cannot be set for ghost elements.\n"); + // Resize for the case that no data vector has been set previously. + this->underlying ().m_mesh->m_element_data.reserve (this->underlying ().m_mesh->get_num_local_elements () + + this->underlying ().m_mesh->get_num_ghosts ()); + this->underlying ().m_mesh->m_element_data.resize (this->underlying ().m_mesh->get_num_local_elements ()); + this->underlying ().m_mesh->m_element_data[this->underlying ().get_element_handle_id ()] = std::move (element_data); + } + /** Getter for the element data. - * For ghost elements ensure that \ref handle_element_data::exchange_ghost_data is called on each process first. + * For ghost elements ensure that \ref element_data_mesh_competence_impl::exchange_ghost_data + * is called on each process first. * Element data for non-ghost elements can be accessed (if set) directly. * \return Element data with data of Type TMeshClass::ElementDataType. */ @@ -150,21 +171,140 @@ struct access_element_data: public t8_crtp_basic "Element data not set.\n"); return this->underlying ().m_mesh->m_element_data[handle_id]; } +}; - /** Set the element data for the element. +// --- Competences for new element data. --- +/* Using setter of \ref element_data_mesh_competence and \ref element_data_element_competence, the element data + * are updated in place such that we cannot access the old data afterwards. With the following competences, + * an additional data vector is defined for the mesh where new element data can be stored that will replace the element + * data vector on commit. + */ + +/** Detail namespace should be uninteresting for users. */ +namespace detail +{ +/** Dummy for the inheritance of \ref new_element_data_mesh_competence_impl. + * The dummy class is used in the inheritance pattern to avoid diamond shaped inheritance + * if the competence is used together with \ref element_data_mesh_competence_impl. + * \tparam TUnderlying Use the \ref mesh class here. + */ +template +struct new_element_data_helper +{ +}; +} // namespace detail + +/** Define new element data vector for the mesh. + * Using setter of \ref element_data_mesh_competence and \ref element_data_element_competence, the element data + * are updated in place such that we cannot access the old data afterwards. + * Use this competence if you want to manage new element data separately that will be used to update the element data + * on commit (or if \ref write_new_to_element_data is called). + * \note This competence only makes sense if the mesh also has \ref element_data_mesh_competence. + * You can use the predefined competence pack \ref data_mesh_competences to get both competences together. + * Use the helper \ref new_element_data_mesh_competence to get this competence with the correct template parameters form. + * If you want to access the data not only in vector form but also directly for each element, + * you can combine this competence with \ref new_element_data_element_competence. + * + * \tparam TUnderlying Use the \ref mesh class here. + * \tparam TElementDataType The element data type you want to use for each element of the mesh. + * The data type has to be MPI safe as the data for ghost elements will be exchanged via MPI. + * \note TElementDataType must be the same as the datatype in \ref element_data_mesh_competence_impl. + */ +template +class new_element_data_mesh_competence_impl: public t8_crtp_operator { + public: + /** Set the new element data vector. The vector should have the length of num_local_elements. + * \param [in] new_element_data The element data vector to set with one entry of class TElementDataType + * for each local mesh element (excluding ghosts). + */ + void + set_new_element_data (std::vector new_element_data) + { + const auto num_local_elements = this->underlying ().get_num_local_elements (); + T8_ASSERT (new_element_data.size () == static_cast (num_local_elements)); + m_new_element_data = std::move (new_element_data); + m_new_element_data.resize (num_local_elements); + } + + /** Get the new element data vector. + * The new element data of the local mesh elements can be set using \ref set_new_element_data. + * \return New element data vector with data of Type TElementDataType. + */ + const auto& + get_new_element_data () const + { + return m_new_element_data; + } + + /** Overwrite the element data vector of the mesh with the new element data vector. + */ + void + write_new_to_element_data () + { + T8_ASSERT (this->underlying ().has_element_data_handler_competence ()); + this->underlying ().set_element_data (m_new_element_data); + m_new_element_data.clear (); + } + + protected: + std::vector m_new_element_data; /**< Vector storing the (local) new element data. */ +}; + +/** Wrapper for \ref new_element_data_mesh_competence_impl to hide TUnderlying and provide the form needed to pass + * it as a mesh competence. + * Use mesh_competence_pack::template type> + * to get this competence with the correct template parameter form for the mesh. + * \tparam TElementDataType The element data type you want to use for each element of the mesh. + * The data type has to be MPI safe as the data for ghost elements will be exchanged via MPI. + * \note TElementDataType must be the same as the datatype in \ref element_data_mesh_competence. + */ +template +struct new_element_data_mesh_competence +{ + /** Type to provide the form needed for the mesh competence pack. + * \tparam TUnderlying Use the \ref mesh class here. + */ + template + using type = new_element_data_mesh_competence_impl; +}; + +// --- Element competence for new element data. --- +/** Element competence to enable that element data can be accessed directly for each element of the mesh. + * \note This competence requires that the mesh has \ref new_element_data_mesh_competence_impl such that + * the new data vector is available and the element data type is defined. + * \tparam TUnderlying Use the \ref element with specified competences as template parameter. + */ +template +struct new_element_data_element_competence: public t8_crtp_operator +{ + public: + /** Set the new element data for the element. * \note You can only set element data for non-ghost elements. - * \param [in] element_data The element data to be set of Type TMeshClass::ElementDataType. + * \param [in] new_element_data New element data to be set of Type TMeshClass::ElementDataType. */ void - set_element_data (auto element_data) + set_new_element_data (auto new_element_data) { - T8_ASSERT (this->underlying ().m_mesh->has_element_data_handler_competence ()); - SC_CHECK_ABORT (!this->underlying ().is_ghost_element (), "Element data cannot be set for ghost elements.\n"); + T8_ASSERT (this->underlying ().m_mesh->has_new_element_data_handler_competence ()); + SC_CHECK_ABORT (!this->underlying ().is_ghost_element (), "New element data cannot be set for ghost elements.\n"); // Resize for the case that no data vector has been set previously. - this->underlying ().m_mesh->m_element_data.reserve (this->underlying ().m_mesh->get_num_local_elements () - + this->underlying ().m_mesh->get_num_ghosts ()); - this->underlying ().m_mesh->m_element_data.resize (this->underlying ().m_mesh->get_num_local_elements ()); - this->underlying ().m_mesh->m_element_data[this->underlying ().get_element_handle_id ()] = std::move (element_data); + this->underlying ().m_mesh->m_new_element_data.resize (this->underlying ().m_mesh->get_num_local_elements ()); + this->underlying ().m_mesh->m_new_element_data[this->underlying ().get_element_handle_id ()] + = std::move (new_element_data); + } + + /** Getter for new element data. + * \return New element data with data of Type TMeshClass::ElementDataType. + */ + const auto& + get_new_element_data () const + { + T8_ASSERT (this->underlying ().m_mesh->has_new_element_data_handler_competence ()); + + const t8_locidx_t handle_id = this->underlying ().get_element_handle_id (); + T8_ASSERTF (static_cast (handle_id) < this->underlying ().m_mesh->m_new_element_data.size (), + "Element data not set.\n"); + return this->underlying ().m_mesh->m_new_element_data[handle_id]; } }; diff --git a/mesh_handle/element.hxx b/mesh_handle/element.hxx index 49123e3c4f..1774080849 100644 --- a/mesh_handle/element.hxx +++ b/mesh_handle/element.hxx @@ -65,8 +65,10 @@ class element: public TCompetences>... { using SelfType = element; /**< Type of the current class with all template parameters specified. */ friend TMeshClass; /**< Define TMeshClass as friend to be able to access e.g. the constructor. */ - friend struct access_element_data< - SelfType>; /**< Define the competence to access element data as friend to be able to access e.g. the mesh. */ + friend struct element_data_element_competence< + SelfType>; /**< Define the competence as friend to be able to access e.g. the mesh from competence. */ + friend struct new_element_data_element_competence< + SelfType>; /**< Define the competence as friend to be able to access e.g. the mesh from competence. */ /** Private constructor for an element of a mesh. This could be a simple mesh element or a ghost element. * This constructor should only be called by the TMeshClass (and invisible for the user). diff --git a/mesh_handle/mesh.hxx b/mesh_handle/mesh.hxx index 463a0c9e24..68daf4140c 100644 --- a/mesh_handle/mesh.hxx +++ b/mesh_handle/mesh.hxx @@ -58,7 +58,7 @@ concept MeshCompetencePack = requires { typename TType::is_mesh_competence_pack; * \note Please pack your competences using the \ref element_competence_pack class. * \tparam TMeshCompetences The competences you want to add to the default functionality of the mesh. * \note Please pack your competences using the \ref t8_mesh_handle::mesh_competence_pack class. - * One of the most important competences to add is \ref handle_element_data. + * One of the most important competences to add is \ref element_data_mesh_competence. */ template , MeshCompetencePack TMeshCompetencePack = mesh_competence_pack<>> @@ -71,8 +71,10 @@ class mesh: public TMeshCompetencePack::template apply::const_iterator; /**< Constant iterator type for the mesh elements. */ using mesh_iterator = - typename std::vector::iterator; /**< Non-const iterator type for the mesh elements. */ - friend struct access_element_data; /**< Friend struct to access its element data vector. */ + typename std::vector::iterator; /**< Non-const iterator type for the mesh elements. */ + friend struct element_data_element_competence; /**< Friend struct to access its element data vector. */ + friend struct new_element_data_element_competence< + element_class>; /**< Friend struct to access its element data vector. */ /** Callback function prototype to decide for refining and coarsening of a family of elements * or one element in a mesh handle. @@ -249,6 +251,25 @@ class mesh: public TMeshCompetencePack::template apply (static_cast (this)->operator[] (local_index)); } + // --- Methods to check for mesh competences. --- + /** Function that checks if a competence for element data handling is given. + * \return true if mesh has a data handler, false otherwise. + */ + static constexpr bool + has_element_data_handler_competence () + { + return requires (SelfType& mesh) { mesh.get_element_data (); }; + } + + /** Function that checks if a competence for element data handling is given. + * \return true if mesh has a data handler, false otherwise. + */ + static constexpr bool + has_new_element_data_handler_competence () + { + return requires (SelfType& mesh) { mesh.get_new_element_data (); }; + } + // --- Methods to change the mesh, e.g. adapt, partition, balance, ... --- /** Wrapper to convert an adapt callback with user data of type \ref adapt_callback_type_with_userdata * into a callback without user data of type \ref adapt_callback_type using the defined user data \a user_data. @@ -357,6 +378,7 @@ class mesh: public TMeshCompetencePack::template applywrite_new_to_element_data (); + } } private: diff --git a/test/mesh_handle/t8_gtest_adapt_partition_balance.cxx b/test/mesh_handle/t8_gtest_adapt_partition_balance.cxx index df460cd05a..9d33607db3 100644 --- a/test/mesh_handle/t8_gtest_adapt_partition_balance.cxx +++ b/test/mesh_handle/t8_gtest_adapt_partition_balance.cxx @@ -112,9 +112,7 @@ TEST (t8_gtest_handle_adapt, compare_adapt_with_forest) t8_cmesh_t cmesh = t8_cmesh_new_hypercube_hybrid (sc_MPI_COMM_WORLD, 0, 0); const t8_scheme *init_scheme = t8_scheme_new_default (); t8_forest_t forest = t8_forest_new_uniform (cmesh, init_scheme, level, 0, sc_MPI_COMM_WORLD); - using mesh_class = t8_mesh_handle::mesh< - t8_mesh_handle::element_competence_pack, - t8_mesh_handle::mesh_competence_pack::template type>>; + using mesh_class = t8_mesh_handle::mesh<>; mesh_class mesh_handle = mesh_class (forest); struct dummy_user_data user_data = { t8_3D_vec ({ 0.5, 0.5, 1 }), /**< Midpoints of the sphere. */ diff --git a/test/mesh_handle/t8_gtest_handle_data.cxx b/test/mesh_handle/t8_gtest_handle_data.cxx index ba6c7abe9b..4038cdbfea 100644 --- a/test/mesh_handle/t8_gtest_handle_data.cxx +++ b/test/mesh_handle/t8_gtest_handle_data.cxx @@ -3,7 +3,7 @@ This file is part of t8code. t8code is a C library to manage a collection (a forest) of multiple connected adaptive space-trees of general element classes in parallel. -Copyright (C) 2025 the developers +Copyright (C) 2026 the developers t8code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -41,15 +41,20 @@ struct data_per_element { int level; double volume; + + bool + operator== (const data_per_element &) const + = default; }; /** Check that element data can be set for the handle and that exchanging data for the ghosts works. */ TEST (t8_gtest_handle_data, set_and_get_element_data) { const int level = 2; - using mesh_class = t8_mesh_handle::mesh< - t8_mesh_handle::element_competence_pack, - t8_mesh_handle::mesh_competence_pack::template type>>; + using mesh_class + = t8_mesh_handle::mesh, + t8_mesh_handle::mesh_competence_pack< + t8_mesh_handle::element_data_mesh_competence::template type>>; auto mesh = t8_mesh_handle::handle_hypercube_hybrid_uniform_default (level, sc_MPI_COMM_WORLD, true, true, false); @@ -57,14 +62,16 @@ TEST (t8_gtest_handle_data, set_and_get_element_data) // Ensure that we actually test with ghost elements. EXPECT_GT (mesh->get_num_ghosts (), 0); } + EXPECT_TRUE (mesh->has_element_data_handler_competence ()); + EXPECT_FALSE (mesh->has_new_element_data_handler_competence ()); - // Create element data for all local mesh elements. + // Create element data for all local mesh elements and set via mesh competence. std::vector element_data; for (const auto &elem : *mesh) { element_data.push_back ({ elem.get_level (), elem.get_volume () }); } mesh->set_element_data (std::move (element_data)); - // Get element data and check that the data for all elements (including ghosts) is correct. + // Exchange element data for ghosts and check that the data for all elements (including ghosts) is correct. mesh->exchange_ghost_data (); auto mesh_element_data = mesh->get_element_data (); for (t8_locidx_t ielem = 0; ielem < mesh->get_num_local_elements () + mesh->get_num_ghosts (); ielem++) { @@ -72,8 +79,7 @@ TEST (t8_gtest_handle_data, set_and_get_element_data) EXPECT_EQ (mesh_element_data[ielem].volume, (*mesh)[ielem].get_volume ()) << "ielem = " << ielem; } - // Modify element data for elements that are in the first half of the global trees. - EXPECT_TRUE (mesh->has_element_data_handler_competence ()); + // Modify element data via the element competence for elements that are in the first half of the global trees. auto forest = mesh->get_forest (); t8_gloidx_t barrier = t8_forest_get_num_global_trees (forest) / 2.0; const int newlevel = 42; @@ -84,6 +90,7 @@ TEST (t8_gtest_handle_data, set_and_get_element_data) elem.set_element_data (elem_data); } } + // Exchange data for ghosts and check that the data for all elements (including ghosts) is correct. mesh->exchange_ghost_data (); for (auto &elem : *mesh) { if (t8_forest_global_tree_id (forest, elem.get_local_tree_id ()) < barrier) { @@ -110,3 +117,41 @@ TEST (t8_gtest_handle_data, set_and_get_element_data) } } } + +/** Check that new element data works and element data is updated on commit. */ +TEST (t8_gtest_handle_data, set_and_get_new_element_data) +{ + const int level = 2; + using mesh_class = t8_mesh_handle::mesh>; + auto mesh + = t8_mesh_handle::handle_hypercube_hybrid_uniform_default (level, sc_MPI_COMM_WORLD, true, true, false); + + EXPECT_TRUE (mesh->has_new_element_data_handler_competence ()); + // Create element data for all local mesh elements and set via mesh competence. + std::vector element_data; + for (const auto &elem : *mesh) { + element_data.push_back ({ elem.get_level (), elem.get_volume () }); + } + mesh->set_element_data (element_data); + + // Set new element data and check that data is correctly stored in the mesh. + const int newlevel = 42; + const double newvolume = 42.42; + std::vector new_element_data (mesh->get_num_local_elements (), { newlevel, newvolume }); + mesh->set_new_element_data (new_element_data); + EXPECT_EQ (mesh->get_element_data (), element_data); + EXPECT_EQ (mesh->get_new_element_data (), new_element_data); + // Also check that we can access the new element data via the elements. + for (auto &elem : *mesh) { + EXPECT_EQ (elem.get_new_element_data ().level, newlevel); + EXPECT_EQ (elem.get_new_element_data ().volume, newvolume); + } + // Commit mesh and check if element data is updated and new element data vector is cleared. + mesh->commit (); + for (auto &elem : *mesh) { + EXPECT_EQ (elem.get_element_data ().level, newlevel); + EXPECT_EQ (elem.get_element_data ().volume, newvolume); + } + EXPECT_TRUE (mesh->get_new_element_data ().empty ()); +}