diff --git a/mesh_handle/competence_pack.hxx b/mesh_handle/competence_pack.hxx index 34f030d8b3..ecc844420d 100644 --- a/mesh_handle/competence_pack.hxx +++ b/mesh_handle/competence_pack.hxx @@ -64,9 +64,14 @@ using all_cache_element_competences 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; +/** Predefined element data competence pack. + * Please note that you must combine this with \ref t8_mesh_handle::data_mesh_competences_basic. */ +using data_element_competences_basic = element_competence_pack; + +/** Predefined element competence pack combining the element data competence and competence to set new element data. + * Please note that you must combine this with \ref t8_mesh_handle::new_data_mesh_competences. */ +using new_data_element_competences + = element_competence_pack; // --- Mesh competence pack. --- /** Class to pack different mesh competences into one template parameter for the \ref mesh class. @@ -90,11 +95,19 @@ struct mesh_competence_pack /** Empty competence pack. */ using empty_mesh_competences = mesh_competence_pack<>; -/** 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. +/** Predefined mesh competence pack to handle element data. + * If you want to access the data also via the elements, combine this with \ref t8_mesh_handle::data_element_competences_basic. + */ +template +using data_mesh_competences_basic = mesh_competence_pack::template type>; + +/** Predefined mesh competence pack combining all competences related to data and competence to set new element data. + * If you want to access the data also via the elements, combine this with \ref t8_mesh_handle::new_data_element_competences. */ template -using data_mesh_competences = mesh_competence_pack::template type>; +using new_data_mesh_competences + = mesh_competence_pack::template type, + new_element_data_mesh_competence::template type>; // --- Compute union of competence packs. --- /** Compute the unique union of the competences of several competence_pack. This could be diff --git a/mesh_handle/data_handler.hxx b/mesh_handle/data_handler.hxx index 90942d1bd1..c284caae96 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,9 +47,9 @@ 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_mesh_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 element_data_element_competence 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>>; @@ -133,13 +137,12 @@ struct element_data_mesh_competence * \tparam TUnderlying Use the \ref element with specified competences as template parameter. */ template -struct element_data_element_competence: 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. + * \param [in] element_data The element data to be set of type TMeshClass::ElementDataType. */ void set_element_data (auto element_data) @@ -170,4 +173,139 @@ struct element_data_element_competence: public t8_crtp_basic } }; +// --- 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 new_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] new_element_data New element data to be set of Type TMeshClass::ElementDataType. + */ + void + set_new_element_data (auto new_element_data) + { + 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_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]; + } +}; + } // namespace t8_mesh_handle diff --git a/mesh_handle/element.hxx b/mesh_handle/element.hxx index 8611a59ae8..bafc9c72a0 100644 --- a/mesh_handle/element.hxx +++ b/mesh_handle/element.hxx @@ -55,6 +55,7 @@ namespace t8_mesh_handle * 2.) for the cached options to keep the number of member variables of the default element to a minimum to save memory. * The choice between calculate and cache is a tradeoff between runtime and memory usage. * + * \tparam TMeshClass The class of the mesh the element belongs to. * \tparam TCompetences The competences you want to add to the default functionality of the element. */ @@ -65,8 +66,9 @@ class element: public TCompetences>... { parameters specified. */ friend TMeshClass; /**< Define TMeshClass as friend to be able to access e.g. the constructor. */ friend struct element_data_element_competence< - SelfType>; /**< Define the competence to access element data as friend to - be able to access e.g. the mesh. */ + 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 2afed7ebab..c12dd28047 100644 --- a/mesh_handle/mesh.hxx +++ b/mesh_handle/mesh.hxx @@ -74,6 +74,8 @@ class mesh: public TMeshCompetencePack::template apply::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. @@ -250,6 +252,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. @@ -358,6 +379,7 @@ class mesh: public TMeshCompetencePack::template applywrite_new_to_element_data (); + } } private: diff --git a/test/mesh_handle/t8_gtest_handle_data.cxx b/test/mesh_handle/t8_gtest_handle_data.cxx index ff370d612c..76938612b4 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,14 +41,18 @@ 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>; + 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); @@ -56,14 +60,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++) { @@ -71,8 +77,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; @@ -83,6 +88,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,6 +116,45 @@ 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_element_data_handler_competence ()); + 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 ()); +} + /** Check that the unique union of multiple mesh competence packs works as intended. * This is done in this file because there is only one mesh competence at the moment, where we need a data class. * We use the data class defined here. @@ -118,9 +163,9 @@ TEST (t8_gtest_handle_data, test_union_mesh_competence_pack) { using namespace t8_mesh_handle; using mesh_class = mesh< - union_competence_packs_type, - union_competence_packs_type, data_mesh_competences, - empty_mesh_competences>>; + union_competence_packs_type, + union_competence_packs_type, + data_mesh_competences_basic, empty_mesh_competences>>; EXPECT_TRUE (mesh_class::has_element_data_handler_competence ()); using element_class = typename mesh_class::element_class;