From b83ef5a37877a08480bae321d3818670c3c52a2e Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Fri, 27 Mar 2026 15:30:33 +0100 Subject: [PATCH 01/20] functionality for new element data --- mesh_handle/competence_pack.hxx | 25 +++- mesh_handle/data_handler.hxx | 152 +++++++++++++++++++++- mesh_handle/element.hxx | 6 +- mesh_handle/mesh.hxx | 37 ++++-- test/mesh_handle/t8_gtest_handle_data.cxx | 65 +++++++-- 5 files changed, 249 insertions(+), 36 deletions(-) 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..1fb4c2e1b6 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 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; From 734affab150d6e78a0e5f4dc2ad99a9b25fc335e Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Fri, 27 Mar 2026 15:47:15 +0100 Subject: [PATCH 02/20] [run ci] doxygen --- mesh_handle/data_handler.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mesh_handle/data_handler.hxx b/mesh_handle/data_handler.hxx index 1fb4c2e1b6..c284caae96 100644 --- a/mesh_handle/data_handler.hxx +++ b/mesh_handle/data_handler.hxx @@ -200,7 +200,7 @@ struct new_element_data_helper * 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. + * 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. From 08434d3b10c64c65496e296be36fef4a141751d4 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Mon, 30 Mar 2026 10:51:50 +0200 Subject: [PATCH 03/20] add file --- .gitignore | 7 +++++++ mesh_handle/internal/interpolate.hxx | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 mesh_handle/internal/interpolate.hxx diff --git a/.gitignore b/.gitignore index ec93b8c0f4..f768eefa47 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,10 @@ Testing/ *~ *.brep *.msh + +# Custom DO NOT MERGE +.cache/ +.vscode/ +general_own/ +compile_commands.json +scripts/configure_gitignore.sh diff --git a/mesh_handle/internal/interpolate.hxx b/mesh_handle/internal/interpolate.hxx new file mode 100644 index 0000000000..31812c449d --- /dev/null +++ b/mesh_handle/internal/interpolate.hxx @@ -0,0 +1,26 @@ +/* + 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) 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 + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + t8code is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with t8code; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +/** \file interpolate.hxx + * This file provides helper functionality to interpolate element data for a \ref t8_mesh_handle::mesh + * according to a user defined callback. + */ From a50f0a467fe1071898bf59ed07dab807cb0baf5c Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Mon, 30 Mar 2026 16:32:27 +0200 Subject: [PATCH 04/20] first ideas --- mesh_handle/data_handler.hxx | 100 +++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/mesh_handle/data_handler.hxx b/mesh_handle/data_handler.hxx index c284caae96..494ec6d140 100644 --- a/mesh_handle/data_handler.hxx +++ b/mesh_handle/data_handler.hxx @@ -308,4 +308,104 @@ struct new_element_data_element_competence: public t8_crtp_operator +struct interpolate_element_data_mesh_competence_helper +{ +}; +} // namespace detail + +/** TODO + */ +template +class interpolate_element_data_mesh_competence_impl: + public t8_crtp_operator> { + public: + /** TODO + */ + void + set_interpolate_data (interpolate_callback_type adapt_callback) + { + if (!m_uncommitted_forest.has_value ()) { + t8_forest_t new_forest; + t8_forest_init (&new_forest); + m_uncommitted_forest = new_forest; + } + // Create and register adaptation context holding the mesh handle and the user defined callback. + detail::adapt_registry::register_context ( + m_forest, std::make_unique> (*this, std::move (adapt_callback))); + + // Set up the forest for adaptation using the wrapper callback. + t8_forest_set_adapt (m_uncommitted_forest.value (), m_forest, detail::mesh_adapt_callback_wrapper, recursive); + } +}; + +/** 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; +}; + } // namespace t8_mesh_handle From a0de5f812dea0fb5c2d966def33e088576961109 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Thu, 2 Apr 2026 11:00:37 +0200 Subject: [PATCH 05/20] gitignore remove --- .gitignore | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index f768eefa47..0f2dc271a3 100644 --- a/.gitignore +++ b/.gitignore @@ -27,11 +27,4 @@ Testing/ *.pvtu *~ *.brep -*.msh - -# Custom DO NOT MERGE -.cache/ -.vscode/ -general_own/ -compile_commands.json -scripts/configure_gitignore.sh +*.msh \ No newline at end of file From 2c3585f671246a3d190e4253009df2c2cdd0f7e9 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Tue, 7 Apr 2026 10:21:24 +0200 Subject: [PATCH 06/20] ideas for interpolate --- .gitignore | 2 +- mesh_handle/competence_pack.hxx | 9 ++ mesh_handle/data_handler.hxx | 120 ++++++---------------- mesh_handle/mesh.hxx | 21 +++- test/mesh_handle/t8_gtest_common.hxx | 102 ++++++++++++++++++ test/mesh_handle/t8_gtest_interpolate.cxx | 90 ++++++++++++++++ 6 files changed, 252 insertions(+), 92 deletions(-) create mode 100644 test/mesh_handle/t8_gtest_common.hxx create mode 100644 test/mesh_handle/t8_gtest_interpolate.cxx diff --git a/.gitignore b/.gitignore index 0f2dc271a3..ec93b8c0f4 100644 --- a/.gitignore +++ b/.gitignore @@ -27,4 +27,4 @@ Testing/ *.pvtu *~ *.brep -*.msh \ No newline at end of file +*.msh diff --git a/mesh_handle/competence_pack.hxx b/mesh_handle/competence_pack.hxx index ecc844420d..10de347a3d 100644 --- a/mesh_handle/competence_pack.hxx +++ b/mesh_handle/competence_pack.hxx @@ -109,6 +109,15 @@ using new_data_mesh_competences = mesh_competence_pack::template type, new_element_data_mesh_competence::template type>; +/** Predefined mesh competence pack with the functionality to work with element data and to interpolate element data after adaptation. + * If you want to access the data also via the elements, combine this with \ref t8_mesh_handle::new_data_element_competences. + */ +template +using interpolate_data_mesh_competence + = mesh_competence_pack::template type, + new_element_data_mesh_competence::template type, + interpolate_element_data_mesh_competence>; + // --- Compute union of competence packs. --- /** Compute the unique union of the competences of several competence_pack. This could be * \ref t8_mesh_handle::element_competence_pack or \ref t8_mesh_handle::mesh_competence_pack. diff --git a/mesh_handle/data_handler.hxx b/mesh_handle/data_handler.hxx index 494ec6d140..40431e0826 100644 --- a/mesh_handle/data_handler.hxx +++ b/mesh_handle/data_handler.hxx @@ -36,6 +36,7 @@ #include #include #include +#include namespace t8_mesh_handle { @@ -309,103 +310,46 @@ struct new_element_data_element_competence: public t8_crtp_operator +class interpolate_element_data_mesh_competence: + public t8_crtp_operator { + public: + // TODO: has_interpolate function und den callback type + + /** Callback function prototype to replace the element data of one set of elements with another. + * This is used to interpolate element data after adaptation. The callback allows the user to make changes to the + * elements that are either refined, coarsened or the same. + * \param [in] forest_old The forest that is adapted + * \param [in, out] forest_new The forest that is newly constructed from \a forest_old + * \param [in] refine -1 if family in \a forest_old got coarsened, 0 if element + * has not been touched, 1 if element got refined. See return of adapt_callback_type. + * \param [in] num_outgoing The number of outgoing elements. + * \param [in] first_outgoing The local handle index of the first outgoing element in the old mesh. + * \param [in] num_incoming The number of incoming elements. + * \param [in] first_incoming The tree local index of the first incoming element in the new mesh. + * + * If an element is being refined, \a refine and \a num_outgoing will be 1 and + * \a num_incoming will be the number of children. + * If a family is being coarsened, \a refine will be -1, \a num_outgoing will be + * the number of family members and \a num_incoming will be 1. + * Else \a refine will be 0 and \a num_outgoing and \a num_incoming will both be 1. */ -/* TODO: Do we really need the forest argument? Since the forest is not committed yet it - * seems dangerous to expose to the user. */ -typedef int (*t8_forest_adapt_t) (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t which_tree, - const t8_eclass_t tree_class, t8_locidx_t lelement_id, const t8_scheme_c* scheme, - const int is_family, const int num_elements, t8_element_t* elements[]); + using interpolate_callback_type + = std::function; -/** Detail namespace should be uninteresting for users. */ -namespace detail -{ -/** Dummy for the inheritance of \ref interpolate_element_data_mesh_competence_impl. - * The dummy class is used in the inheritance pattern to avoid diamond shaped inheritance. - * \tparam TUnderlying Use the \ref mesh class here. - */ -template -struct interpolate_element_data_mesh_competence_helper -{ -}; -} // namespace detail + // TODO: with userdata -/** TODO - */ -template -class interpolate_element_data_mesh_competence_impl: - public t8_crtp_operator> { - public: /** TODO */ void - set_interpolate_data (interpolate_callback_type adapt_callback) + set_interpolate_data (interpolate_callback_type interpolate_callback) { - if (!m_uncommitted_forest.has_value ()) { - t8_forest_t new_forest; - t8_forest_init (&new_forest); - m_uncommitted_forest = new_forest; - } - // Create and register adaptation context holding the mesh handle and the user defined callback. - detail::adapt_registry::register_context ( - m_forest, std::make_unique> (*this, std::move (adapt_callback))); - - // Set up the forest for adaptation using the wrapper callback. - t8_forest_set_adapt (m_uncommitted_forest.value (), m_forest, detail::mesh_adapt_callback_wrapper, recursive); + //TODO } }; -/** 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; -}; - } // namespace t8_mesh_handle diff --git a/mesh_handle/mesh.hxx b/mesh_handle/mesh.hxx index c12dd28047..80265dcdc6 100644 --- a/mesh_handle/mesh.hxx +++ b/mesh_handle/mesh.hxx @@ -271,6 +271,15 @@ class mesh: public TMeshCompetencePack::template apply + +#include +#include +#include +#include +#include +#include +#include +#include + +/** Dummy user data taken from tutorial for test purposes. */ +struct dummy_user_data +{ + t8_3D_vec midpoint; /**< The midpoint of our sphere. */ + double refine_if_inside_radius; /**< If an element's center is smaller than this value, we refine the element. */ + double coarsen_if_outside_radius; /**< If an element's center is larger this value, we coarsen its family. */ +}; + +/** Dummy element data taken from a tutorial for test purposes. */ +struct data_per_element +{ + int level; + double volume; + + bool + operator== (const data_per_element &) const + = default; +}; + +/** Callback function for the mesh handle to decide for refining or coarsening of (a family of) elements. + * The function header fits the definition of \ref TMesh::adapt_callback_type_with_userdata. + * \tparam TMeshClass The mesh handle class. + * \param [in] mesh The mesh that should be adapted. + * \param [in] elements One element or a family of elements to consider for adaptation. + * \param [in] user_data The user data to be used during the adaptation process. + * \return 1 if the first entry in \a elements should be refined, + * -1 if the family \a elements shall be coarsened, + * 0 else. + */ +template +int +adapt_callback_test ([[maybe_unused]] const TMeshClass &mesh, + std::span elements, const dummy_user_data &user_data) +{ + auto element_centroid = elements[0].get_centroid (); + double dist = t8_dist (element_centroid, user_data.midpoint); + if (dist < user_data.refine_if_inside_radius) { + return 1; + } + // Check if we got a family and if yes, if we should coarsen. + if ((elements.size () > 1) && (dist > user_data.coarsen_if_outside_radius)) { + return -1; + } + return 0; +} + +/** Adapt callback implementation for a forest. + * This callback defines the same adaptation rules as \ref adapt_callback_test, + * but it is used for the forest instead of the mesh handle. + */ +int +forest_adapt_callback_example (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t which_tree, + [[maybe_unused]] t8_eclass_t tree_class, [[maybe_unused]] t8_locidx_t lelement_id, + [[maybe_unused]] const t8_scheme *scheme, const int is_family, + [[maybe_unused]] const int num_elements, t8_element_t *elements[]) +{ + const struct dummy_user_data *adapt_data = (const struct dummy_user_data *) t8_forest_get_user_data (forest); + t8_3D_vec centroid; + t8_forest_element_centroid (forest_from, which_tree, elements[0], centroid.data ()); + double dist = t8_dist (centroid, adapt_data->midpoint); + if (dist < adapt_data->refine_if_inside_radius) { + return 1; + } + else if (is_family && dist > adapt_data->coarsen_if_outside_radius) { + return -1; + } + return 0; +} diff --git a/test/mesh_handle/t8_gtest_interpolate.cxx b/test/mesh_handle/t8_gtest_interpolate.cxx new file mode 100644 index 0000000000..033653781d --- /dev/null +++ b/test/mesh_handle/t8_gtest_interpolate.cxx @@ -0,0 +1,90 @@ +/* +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) 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 +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +t8code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with t8code; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +/** + * \file t8_gtest_handle_data.cxx + * Tests to check that the element data functionality of the \ref t8_mesh_handle::mesh works as intended. + */ + +#include +#include +#include "t8_gtest_common.hxx" + +#include +#include +#include +#include +#include +#include +#include + +template +void +interpolate_callback (const TMeshClass& mesh_old, const TMeshClass& mesh_new, const int refine, const int num_old, + const t8_locidx_t first_old, const int num_new, const t8_locidx_t first_new) +{ + /* Do not adapt or coarsen */ + if (refine == 0) { + (*mesh_new)[first_new].set_element_data ((*mesh_old)[first_old].get_element_data ()); + } + /* The old element is refined, we copy the element values. */ + else if (refine == 1) { + for (t8_locidx_t i = 0; i < num_new; i++) { + (*mesh_new)[first_new + i].set_element_data ((*mesh_old)[first_old].get_element_data ()); + } + } + /* Old element is coarsened */ + else if (refine == -1) { + double tmp_value = 0; + for (t8_locidx_t i = 0; i < num_old; i++) { + tmp_value += (*mesh_old)[first_old].get_element_data (); + } + (*mesh_new)[first_new + i].set_element_data (tmp_value / num_old); + } +} + +TEST (t8_gtest_handle_data, test_interpolate_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, false, + false, false); + + struct dummy_user_data user_data = { + t8_3D_vec ({ 0.5, 0.5, 1 }), /**< Midpoints of the sphere. */ + 0.2, /**< Refine if inside this radius. */ + 0.4 /**< Coarsen if outside this radius. */ + }; + + // 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)); + + mesh_handle.set_adapt ( + mesh_class::mesh_adapt_callback_wrapper (adapt_callback_test, user_data), false); + mesh_handle.set_interpolate_data (interpolate_callback); + mesh_handle.commit (); +} From 0377490d390101425f77d7dfd4ea7eb897a2b015 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Tue, 7 Apr 2026 12:17:02 +0200 Subject: [PATCH 07/20] use gtest common --- .../t8_gtest_adapt_partition_balance.cxx | 62 +------------------ test/mesh_handle/t8_gtest_handle_data.cxx | 31 +--------- test/mesh_handle/t8_gtest_mesh_handle.cxx | 17 +++++ 3 files changed, 19 insertions(+), 91 deletions(-) diff --git a/test/mesh_handle/t8_gtest_adapt_partition_balance.cxx b/test/mesh_handle/t8_gtest_adapt_partition_balance.cxx index bdb773ba08..e0a6773850 100644 --- a/test/mesh_handle/t8_gtest_adapt_partition_balance.cxx +++ b/test/mesh_handle/t8_gtest_adapt_partition_balance.cxx @@ -31,73 +31,13 @@ along with t8code; if not, write to the Free Software Foundation, Inc., */ #include #include +#include "t8_gtest_common.hxx" #include -#include -#include #include #include #include #include -#include - -/** Dummy user data taken from tutorial for test purposes. */ -struct dummy_user_data -{ - t8_3D_vec midpoint; /**< The midpoint of our sphere. */ - double refine_if_inside_radius; /**< If an element's center is smaller than this value, we refine the element. */ - double coarsen_if_outside_radius; /**< If an element's center is larger this value, we coarsen its family. */ -}; - -/** Callback function for the mesh handle to decide for refining or coarsening of (a family of) elements. - * The function header fits the definition of \ref TMesh::adapt_callback_type_with_userdata. - * \tparam TMeshClass The mesh handle class. - * \param [in] mesh The mesh that should be adapted. - * \param [in] elements One element or a family of elements to consider for adaptation. - * \param [in] user_data The user data to be used during the adaptation process. - * \return 1 if the first entry in \a elements should be refined, - * -1 if the family \a elements shall be coarsened, - * 0 else. - */ -template -int -adapt_callback_test ([[maybe_unused]] const TMeshClass &mesh, - std::span elements, const dummy_user_data &user_data) -{ - auto element_centroid = elements[0].get_centroid (); - double dist = t8_dist (element_centroid, user_data.midpoint); - if (dist < user_data.refine_if_inside_radius) { - return 1; - } - // Check if we got a family and if yes, if we should coarsen. - if ((elements.size () > 1) && (dist > user_data.coarsen_if_outside_radius)) { - return -1; - } - return 0; -} - -/** Adapt callback implementation for a forest. - * This callback defines the same adaptation rules as \ref adapt_callback_test, - * but it is used for the forest instead of the mesh handle. - */ -int -forest_adapt_callback_example (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t which_tree, - [[maybe_unused]] t8_eclass_t tree_class, [[maybe_unused]] t8_locidx_t lelement_id, - [[maybe_unused]] const t8_scheme *scheme, const int is_family, - [[maybe_unused]] const int num_elements, t8_element_t *elements[]) -{ - const struct dummy_user_data *adapt_data = (const struct dummy_user_data *) t8_forest_get_user_data (forest); - t8_3D_vec centroid; - t8_forest_element_centroid (forest_from, which_tree, elements[0], centroid.data ()); - double dist = t8_dist (centroid, adapt_data->midpoint); - if (dist < adapt_data->refine_if_inside_radius) { - return 1; - } - else if (is_family && dist > adapt_data->coarsen_if_outside_radius) { - return -1; - } - return 0; -} /** Test the adapt partition and balance routine of a mesh handle. * The test compares the results of the mesh handle to a forest adapted with the same criterion and balanced and partitioned similarly. diff --git a/test/mesh_handle/t8_gtest_handle_data.cxx b/test/mesh_handle/t8_gtest_handle_data.cxx index 76938612b4..d9788aa174 100644 --- a/test/mesh_handle/t8_gtest_handle_data.cxx +++ b/test/mesh_handle/t8_gtest_handle_data.cxx @@ -27,6 +27,7 @@ along with t8code; if not, write to the Free Software Foundation, Inc., #include #include +#include "t8_gtest_common.hxx" #include #include @@ -36,17 +37,6 @@ along with t8code; if not, write to the Free Software Foundation, Inc., #include #include -/** Dummy element data taken from a tutorial for test purposes. */ -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) { @@ -154,22 +144,3 @@ TEST (t8_gtest_handle_data, set_and_get_new_element_data) } 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. - */ -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_basic, empty_mesh_competences>>; - EXPECT_TRUE (mesh_class::has_element_data_handler_competence ()); - using element_class = typename mesh_class::element_class; - - EXPECT_TRUE (element_class::has_element_data_handler_competence ()); - EXPECT_TRUE (element_class::has_volume_cache ()); - EXPECT_TRUE (element_class::has_diameter_cache ()); -} diff --git a/test/mesh_handle/t8_gtest_mesh_handle.cxx b/test/mesh_handle/t8_gtest_mesh_handle.cxx index 16a25abc3e..67f0a2e1c1 100644 --- a/test/mesh_handle/t8_gtest_mesh_handle.cxx +++ b/test/mesh_handle/t8_gtest_mesh_handle.cxx @@ -29,6 +29,7 @@ along with t8code; if not, write to the Free Software Foundation, Inc., #include #include #include +#include "t8_gtest_common.hxx" #include #include @@ -196,4 +197,20 @@ TEST (t8_mesh_handle_test, test_union_element_competence_pack) EXPECT_FALSE (element_class::has_face_neighbor_cache ()); } +/** Check that the unique union of multiple mesh competence packs works as intended. */ +TEST (t8_mesh_handle_test, 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_basic, empty_mesh_competences>>; + EXPECT_TRUE (mesh_class::has_element_data_handler_competence ()); + using element_class = typename mesh_class::element_class; + + EXPECT_TRUE (element_class::has_element_data_handler_competence ()); + EXPECT_TRUE (element_class::has_volume_cache ()); + EXPECT_TRUE (element_class::has_diameter_cache ()); +} + INSTANTIATE_TEST_SUITE_P (t8_gtest_mesh, t8_mesh_handle_test, testing::Combine (AllEclasses, testing::Range (2, 3))); From 6211e6bd624bc56e738ffe9aab890504d4fccf52 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Tue, 7 Apr 2026 15:38:40 +0200 Subject: [PATCH 08/20] no errors, very first version of test --- mesh_handle/competence_pack.hxx | 1 - mesh_handle/data_handler.hxx | 31 +++++++++++++---------- mesh_handle/mesh.hxx | 2 +- test/CMakeLists.txt | 1 + test/mesh_handle/t8_gtest_interpolate.cxx | 29 +++++++++++---------- 5 files changed, 36 insertions(+), 28 deletions(-) diff --git a/mesh_handle/competence_pack.hxx b/mesh_handle/competence_pack.hxx index 10de347a3d..13650ed9c1 100644 --- a/mesh_handle/competence_pack.hxx +++ b/mesh_handle/competence_pack.hxx @@ -115,7 +115,6 @@ using new_data_mesh_competences template using interpolate_data_mesh_competence = mesh_competence_pack::template type, - new_element_data_mesh_competence::template type, interpolate_element_data_mesh_competence>; // --- Compute union of competence packs. --- diff --git a/mesh_handle/data_handler.hxx b/mesh_handle/data_handler.hxx index 40431e0826..3a6c459ff3 100644 --- a/mesh_handle/data_handler.hxx +++ b/mesh_handle/data_handler.hxx @@ -325,31 +325,36 @@ class interpolate_element_data_mesh_competence: * \param [in, out] forest_new The forest that is newly constructed from \a forest_old * \param [in] refine -1 if family in \a forest_old got coarsened, 0 if element * has not been touched, 1 if element got refined. See return of adapt_callback_type. - * \param [in] num_outgoing The number of outgoing elements. - * \param [in] first_outgoing The local handle index of the first outgoing element in the old mesh. - * \param [in] num_incoming The number of incoming elements. - * \param [in] first_incoming The tree local index of the first incoming element in the new mesh. + * \param [in] num_old The number of outgoing elements. + * \param [in] first_old The local handle index of the first outgoing element in the old mesh. + * \param [in] num_new The number of incoming elements. + * \param [in] first_new The tree local index of the first incoming element in the new mesh. * - * If an element is being refined, \a refine and \a num_outgoing will be 1 and - * \a num_incoming will be the number of children. - * If a family is being coarsened, \a refine will be -1, \a num_outgoing will be - * the number of family members and \a num_incoming will be 1. - * Else \a refine will be 0 and \a num_outgoing and \a num_incoming will both be 1. + * If an element is being refined, \a refine and \a num_old will be 1 and + * \a num_new will be the number of children. + * If a family is being coarsened, \a refine will be -1, \a num_old will be + * the number of family members and \a num_new will be 1. + * Else \a refine will be 0 and \a num_old and \a num_new will both be 1. */ using interpolate_callback_type - = std::function; + = std::function; // TODO: with userdata /** TODO */ void - set_interpolate_data (interpolate_callback_type interpolate_callback) + set_interpolate_data ([[maybe_unused]] interpolate_callback_type interpolate_callback) { //TODO } + + int + get_tag () + { + return 0; + } }; } // namespace t8_mesh_handle diff --git a/mesh_handle/mesh.hxx b/mesh_handle/mesh.hxx index 80265dcdc6..cf35a5cc71 100644 --- a/mesh_handle/mesh.hxx +++ b/mesh_handle/mesh.hxx @@ -277,7 +277,7 @@ class mesh: public TMeshCompetencePack::template apply void -interpolate_callback (const TMeshClass& mesh_old, const TMeshClass& mesh_new, const int refine, const int num_old, +interpolate_callback (const TMeshClass& mesh_old, TMeshClass& mesh_new, const int refine, const int num_old, const t8_locidx_t first_old, const int num_new, const t8_locidx_t first_new) { - /* Do not adapt or coarsen */ + /* Do not adapt or coarsen. */ if (refine == 0) { - (*mesh_new)[first_new].set_element_data ((*mesh_old)[first_old].get_element_data ()); + mesh_new[first_new].set_element_data (mesh_old[first_old].get_element_data ()); } - /* The old element is refined, we copy the element values. */ + /* The old element is refined. Volume data is averaged for the children. */ else if (refine == 1) { for (t8_locidx_t i = 0; i < num_new; i++) { - (*mesh_new)[first_new + i].set_element_data ((*mesh_old)[first_old].get_element_data ()); + mesh_new[first_new + i].set_element_data (data_per_element { + mesh_old[first_old].get_element_data ().level + 1, mesh_old[first_old].get_element_data ().volume / num_new }); } } - /* Old element is coarsened */ + /* Old element is coarsened. */ else if (refine == -1) { - double tmp_value = 0; + double tmp_volume = 0; for (t8_locidx_t i = 0; i < num_old; i++) { - tmp_value += (*mesh_old)[first_old].get_element_data (); + tmp_volume += mesh_old[first_old + i].get_element_data ().volume; } - (*mesh_new)[first_new + i].set_element_data (tmp_value / num_old); + mesh_new[first_new].set_element_data ( + data_per_element { mesh_old[first_old].get_element_data ().level - 1, tmp_volume }); } } TEST (t8_gtest_handle_data, test_interpolate_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, false, false, false); @@ -83,8 +85,9 @@ TEST (t8_gtest_handle_data, test_interpolate_data) } mesh->set_element_data (std::move (element_data)); - mesh_handle.set_adapt ( + mesh->set_adapt ( mesh_class::mesh_adapt_callback_wrapper (adapt_callback_test, user_data), false); - mesh_handle.set_interpolate_data (interpolate_callback); - mesh_handle.commit (); + mesh->set_interpolate_data (interpolate_callback); + mesh->commit (); + EXPECT_EQ (1, 1); } From 9668d8d794ea5fe960817aa68c9c245771a60615 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Tue, 7 Apr 2026 17:01:02 +0200 Subject: [PATCH 09/20] interpolate --- mesh_handle/data_handler.hxx | 4 +- mesh_handle/internal/adapt.hxx | 4 +- mesh_handle/internal/interpolate.hxx | 121 +++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 3 deletions(-) diff --git a/mesh_handle/data_handler.hxx b/mesh_handle/data_handler.hxx index 3a6c459ff3..3e028c719b 100644 --- a/mesh_handle/data_handler.hxx +++ b/mesh_handle/data_handler.hxx @@ -347,7 +347,9 @@ class interpolate_element_data_mesh_competence: void set_interpolate_data ([[maybe_unused]] interpolate_callback_type interpolate_callback) { - //TODO + // HIER WEITER, problem new mesh noch nicht bekannt, context umschreiben. + detail::interpolate_registry::register_context ( + m_forest, std::make_unique> (*this, *this, std::move (cb))); } int diff --git a/mesh_handle/internal/adapt.hxx b/mesh_handle/internal/adapt.hxx index 61b390e41f..0145c0833b 100644 --- a/mesh_handle/internal/adapt.hxx +++ b/mesh_handle/internal/adapt.hxx @@ -101,8 +101,8 @@ struct mesh_adapt_context final: mesh_adapt_context_base } private: - TMesh& m_mesh_handle; /**< The mesh handle to adapt. */ - typename TMesh::adapt_callback_type m_adapt_callback; /**< The adapt callback. */ + TMesh& m_mesh_handle; /**< The mesh handle to adapt. */ + const typename TMesh::adapt_callback_type m_adapt_callback; /**< The adapt callback. */ }; /** Registry pattern is used to register contexts, which provides access to the adapt callback and the mesh handle. diff --git a/mesh_handle/internal/interpolate.hxx b/mesh_handle/internal/interpolate.hxx index 31812c449d..11372f1183 100644 --- a/mesh_handle/internal/interpolate.hxx +++ b/mesh_handle/internal/interpolate.hxx @@ -24,3 +24,124 @@ * This file provides helper functionality to interpolate element data for a \ref t8_mesh_handle::mesh * according to a user defined callback. */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace t8_mesh_handle +{ +namespace detail +{ + +/** + * Base class for interpolation contexts (type-erased). + */ +struct mesh_interpolate_context_base +{ + virtual ~mesh_interpolate_context_base () = default; + + /** + * Virtual interpolation callback. + */ + virtual void + interpolate (const int refine, const int num_old, const t8_locidx_t first_old, const int num_new, + const t8_locidx_t first_new) + = 0; +}; + +/** + * Concrete interpolation context storing mesh handles + user callback. + */ +template +struct mesh_interpolate_context final: mesh_interpolate_context_base +{ + using callback_type = typename TMesh::interpolate_callback_type; + + mesh_interpolate_context (const TMesh& mesh_old, TMesh& mesh_new, callback_type interpolate_callback) + : m_mesh_old (mesh_old), m_mesh_new (mesh_new), m_callback (std::move (interpolate_callback)) + { + } + + void + interpolate (const int refine, const int num_old, const t8_locidx_t first_old, const int num_new, + const t8_locidx_t first_new) override + { + T8_ASSERTF (m_callback, "No interpolate callback set."); + m_callback (m_mesh_old, m_mesh_new, refine, num_old, first_old, num_new, first_new); + } + + private: + const TMesh& m_mesh_old; + TMesh& m_mesh_new; + const callback_type m_callback; +}; + +/** + * Registry for interpolate contexts (same idea as adapt_registry). + */ +class interpolate_registry { + public: + /** Need forest and not mesh for key because this cannot have a template parameter. */ + static void + register_context (t8_forest_t forest, std::unique_ptr context) + { + auto& map = get_map (); + auto [it, inserted] = map.emplace (forest, std::move (context)); + if (!inserted) { + t8_global_productionf ("Interpolate context already registered!"); + } + } + + static void + unregister_context (t8_forest_t forest) + { + auto& map = get_map (); + [[maybe_unused]] const auto erased = map.erase (forest); + T8_ASSERT (erased == 1); + } + + static mesh_interpolate_context_base* + get (t8_forest_t forest) + { + auto& map = get_map (); + auto it = map.find (forest); + return it != map.end () ? it->second.get () : nullptr; + } + + private: + static std::unordered_map>& + get_map () + { + static std::unordered_map> map; + return map; + } +}; + +/** + * Wrapper matching t8_forest_replace_t + */ +inline void +mesh_replace_callback_wrapper (t8_forest_t forest_old, [[maybe_unused]] t8_forest_t forest_new, t8_locidx_t which_tree, + const t8_eclass_t tree_class, const t8_scheme_c* scheme, const int refine, + const int num_outgoing, const t8_locidx_t first_outgoing, const int num_incoming, + const t8_locidx_t first_incoming) +{ + auto* context = interpolate_registry::get (forest_old); + if (!context) { + t8_global_productionf ("Interpolate context not found. Did you forget to register it?"); + return; + } + + // Convert tree-local indices to mesh-global indices + const t8_locidx_t first_old_global = t8_forest_get_tree_element_offset (forest_old, which_tree) + first_outgoing; + const t8_locidx_t first_new_global = t8_forest_get_tree_element_offset (forest_new, which_tree) + first_incoming; + context->interpolate (refine, num_outgoing, first_old_global, num_incoming, first_new_global); +} + +} // namespace detail +} // namespace t8_mesh_handle From 236175d23ff0dd64922b4dabf508dcea005d0abf Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Wed, 8 Apr 2026 11:16:03 +0200 Subject: [PATCH 10/20] working version --- mesh_handle/data_handler.hxx | 15 +++++++++++---- mesh_handle/internal/interpolate.hxx | 9 +++++---- mesh_handle/mesh.hxx | 22 +++++++++++++++++----- test/mesh_handle/t8_gtest_interpolate.cxx | 15 +++++++++++++-- 4 files changed, 46 insertions(+), 15 deletions(-) diff --git a/mesh_handle/data_handler.hxx b/mesh_handle/data_handler.hxx index 3e028c719b..e80d306b6c 100644 --- a/mesh_handle/data_handler.hxx +++ b/mesh_handle/data_handler.hxx @@ -90,6 +90,12 @@ class element_data_mesh_competence_impl: public t8_crtp_basic { return m_element_data; } + std::vector + take_element_data () + { + return std::move (m_element_data); + } + /** Exchange the element data for ghost elements between processes. * This routine has to be called on each process after setting the element data for all local elements. */ @@ -345,11 +351,9 @@ class interpolate_element_data_mesh_competence: /** TODO */ void - set_interpolate_data ([[maybe_unused]] interpolate_callback_type interpolate_callback) + set_interpolate_callback (interpolate_callback_type&& interpolate_callback) { - // HIER WEITER, problem new mesh noch nicht bekannt, context umschreiben. - detail::interpolate_registry::register_context ( - m_forest, std::make_unique> (*this, *this, std::move (cb))); + m_interpolate_callback = std::forward (interpolate_callback); } int @@ -357,6 +361,9 @@ class interpolate_element_data_mesh_competence: { return 0; } + + protected: + interpolate_callback_type m_interpolate_callback; }; } // namespace t8_mesh_handle diff --git a/mesh_handle/internal/interpolate.hxx b/mesh_handle/internal/interpolate.hxx index 11372f1183..3f81b5ab4b 100644 --- a/mesh_handle/internal/interpolate.hxx +++ b/mesh_handle/internal/interpolate.hxx @@ -62,8 +62,8 @@ struct mesh_interpolate_context final: mesh_interpolate_context_base { using callback_type = typename TMesh::interpolate_callback_type; - mesh_interpolate_context (const TMesh& mesh_old, TMesh& mesh_new, callback_type interpolate_callback) - : m_mesh_old (mesh_old), m_mesh_new (mesh_new), m_callback (std::move (interpolate_callback)) + mesh_interpolate_context (const TMesh& mesh_old, TMesh& mesh_new, callback_type&& interpolate_callback) + : m_mesh_old (mesh_old), m_mesh_new (mesh_new), m_callback (std::forward (interpolate_callback)) { } @@ -127,8 +127,9 @@ class interpolate_registry { */ inline void mesh_replace_callback_wrapper (t8_forest_t forest_old, [[maybe_unused]] t8_forest_t forest_new, t8_locidx_t which_tree, - const t8_eclass_t tree_class, const t8_scheme_c* scheme, const int refine, - const int num_outgoing, const t8_locidx_t first_outgoing, const int num_incoming, + [[maybe_unused]] const t8_eclass_t tree_class, + [[maybe_unused]] const t8_scheme_c* scheme, const int refine, const int num_outgoing, + const t8_locidx_t first_outgoing, const int num_incoming, const t8_locidx_t first_incoming) { auto* context = interpolate_registry::get (forest_old); diff --git a/mesh_handle/mesh.hxx b/mesh_handle/mesh.hxx index cf35a5cc71..650624f8be 100644 --- a/mesh_handle/mesh.hxx +++ b/mesh_handle/mesh.hxx @@ -29,12 +29,14 @@ #include #include "element.hxx" #include "competence_pack.hxx" -#include "internal/adapt.hxx" #include "data_handler.hxx" +#include "internal/adapt.hxx" +#include "internal/interpolate.hxx" #include #include #include #include +#include #include #include #include @@ -406,13 +408,23 @@ class mesh: public TMeshCompetencePack::template applym_interpolate_callback) { + SelfType new_mesh (m_uncommitted_forest.value ()); + t8_forest_ref (m_uncommitted_forest.value ()); + detail::interpolate_registry::register_context ( + m_forest, std::make_unique> ( + *this, new_mesh, std::move (this->m_interpolate_callback))); + t8_forest_iterate_replace (m_uncommitted_forest.value (), m_forest, detail::mesh_replace_callback_wrapper); + detail::interpolate_registry::unregister_context (m_forest); + this->m_element_data = new_mesh.take_element_data (); + } + else { + t8_global_infof ("No interpoaltion context set.\n"); + } } else { t8_global_infof ("The element data was not interpolated during adaptation. Use set_element_data() to provide " diff --git a/test/mesh_handle/t8_gtest_interpolate.cxx b/test/mesh_handle/t8_gtest_interpolate.cxx index a1e34a2bdc..bbd0766876 100644 --- a/test/mesh_handle/t8_gtest_interpolate.cxx +++ b/test/mesh_handle/t8_gtest_interpolate.cxx @@ -87,7 +87,18 @@ TEST (t8_gtest_handle_data, test_interpolate_data) mesh->set_adapt ( mesh_class::mesh_adapt_callback_wrapper (adapt_callback_test, user_data), false); - mesh->set_interpolate_data (interpolate_callback); + mesh->set_interpolate_callback (interpolate_callback); mesh->commit (); - EXPECT_EQ (1, 1); + bool tested_something = false; + for (auto& elem : *mesh) { + if (!tested_something && (elem.get_level () != level)) { + tested_something = true; + } + EXPECT_EQ (elem.get_level (), elem.get_element_data ().level); + if (!(elem.get_element_data ().level > level)) { + // For refined elements, the volume is averaged and thus not exact. + EXPECT_EQ (elem.get_volume (), elem.get_element_data ().volume); + } + } + EXPECT_TRUE (tested_something); } From 4533f7d686ed36fabc93764b33342a0df3ee1599 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Wed, 8 Apr 2026 13:01:05 +0200 Subject: [PATCH 11/20] remove new element data --- mesh_handle/competence_pack.hxx | 13 -- mesh_handle/data_handler.hxx | 137 ---------------------- mesh_handle/element.hxx | 2 - mesh_handle/mesh.hxx | 15 --- test/mesh_handle/t8_gtest_handle_data.cxx | 40 ------- test/mesh_handle/t8_gtest_mesh_handle.cxx | 9 +- 6 files changed, 5 insertions(+), 211 deletions(-) diff --git a/mesh_handle/competence_pack.hxx b/mesh_handle/competence_pack.hxx index 13650ed9c1..07f81675f8 100644 --- a/mesh_handle/competence_pack.hxx +++ b/mesh_handle/competence_pack.hxx @@ -68,11 +68,6 @@ using cache_face_element_competences * 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. * \tparam TMeshCompetence The mesh competences to be packed. @@ -101,14 +96,6 @@ using empty_mesh_competences = mesh_competence_pack<>; 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 new_data_mesh_competences - = mesh_competence_pack::template type, - new_element_data_mesh_competence::template type>; - /** Predefined mesh competence pack with the functionality to work with element data and to interpolate element data after adaptation. * If you want to access the data also via the elements, combine this with \ref t8_mesh_handle::new_data_element_competences. */ diff --git a/mesh_handle/data_handler.hxx b/mesh_handle/data_handler.hxx index e80d306b6c..14778933d9 100644 --- a/mesh_handle/data_handler.hxx +++ b/mesh_handle/data_handler.hxx @@ -25,8 +25,6 @@ * 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 @@ -180,141 +178,6 @@ struct element_data_element_competence: public t8_crtp_operator -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]; - } -}; - // --- Mesh competence to interpolate data. --- /** TODO */ diff --git a/mesh_handle/element.hxx b/mesh_handle/element.hxx index bafc9c72a0..1afea60aba 100644 --- a/mesh_handle/element.hxx +++ b/mesh_handle/element.hxx @@ -67,8 +67,6 @@ class element: public TCompetences>... { 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 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 650624f8be..09ba7188e4 100644 --- a/mesh_handle/mesh.hxx +++ b/mesh_handle/mesh.hxx @@ -76,8 +76,6 @@ 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. @@ -264,15 +262,6 @@ 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 d9788aa174..80dbcb9529 100644 --- a/test/mesh_handle/t8_gtest_handle_data.cxx +++ b/test/mesh_handle/t8_gtest_handle_data.cxx @@ -51,7 +51,6 @@ TEST (t8_gtest_handle_data, set_and_get_element_data) 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 and set via mesh competence. std::vector element_data; @@ -105,42 +104,3 @@ 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 ()); -} diff --git a/test/mesh_handle/t8_gtest_mesh_handle.cxx b/test/mesh_handle/t8_gtest_mesh_handle.cxx index 67f0a2e1c1..3c4b61150e 100644 --- a/test/mesh_handle/t8_gtest_mesh_handle.cxx +++ b/test/mesh_handle/t8_gtest_mesh_handle.cxx @@ -201,10 +201,11 @@ TEST (t8_mesh_handle_test, test_union_element_competence_pack) TEST (t8_mesh_handle_test, 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_basic, empty_mesh_competences>>; + using mesh_class + = mesh, + 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; From 4c0b66d61f57eb81af7291fefc3fee514b1ce928 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Thu, 9 Apr 2026 14:13:55 +0200 Subject: [PATCH 12/20] status --- mesh_handle/data_handler.hxx | 10 ++++---- mesh_handle/mesh.hxx | 28 +++++++++++++++++++---- src/t8_vtk/t8_vtk.h | 2 +- test/mesh_handle/t8_gtest_interpolate.cxx | 1 + 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/mesh_handle/data_handler.hxx b/mesh_handle/data_handler.hxx index 7a2edbf5c0..2bba66291c 100644 --- a/mesh_handle/data_handler.hxx +++ b/mesh_handle/data_handler.hxx @@ -35,6 +35,7 @@ #include #include #include +#include namespace t8_mesh_handle { @@ -217,14 +218,15 @@ class interpolate_element_data_mesh_competence: m_interpolate_callback = std::forward (interpolate_callback); } - int - get_tag () + protected: + bool + set_partition_called () { - return 0; + return m_set_for_coarsening.has_value (); } - protected: interpolate_callback_type m_interpolate_callback; + std::optional m_set_for_coarsening; }; } // namespace t8_mesh_handle diff --git a/mesh_handle/mesh.hxx b/mesh_handle/mesh.hxx index 7495b4919c..a9b172c90c 100644 --- a/mesh_handle/mesh.hxx +++ b/mesh_handle/mesh.hxx @@ -265,12 +265,11 @@ class mesh: public TMeshCompetencePack::template applym_set_for_coarsening = set_for_coarsening; + return; + } if (!m_uncommitted_forest.has_value ()) { t8_forest_t new_forest; t8_forest_init (&new_forest); @@ -336,7 +339,7 @@ class mesh: public TMeshCompetencePack::template applym_element_data = new_mesh.take_element_data (); + t8_forest_unref (&m_forest); + if (this->set_partition_called ()) { + t8_forest_init (&m_forest); + t8_forest_set_partition (m_forest, m_uncommitted_forest.value (), this->m_set_for_coarsening); + if (t8_forest_get_num_ghosts (m_uncommitted_forest.value ()) > 0) { + t8_forest_set_ghost (m_forest, true, T8_GHOST_FACES); + } + t8_forest_commit (m_forest); + // TODO: repartition data with t8_forest_partition_data. + } + else { + // Update underlying forest of the mesh. + m_forest = m_uncommitted_forest.value (); + } + m_uncommitted_forest.reset (); + update_elements (); + return; } else { t8_global_infof ("No interpoaltion context set.\n"); diff --git a/src/t8_vtk/t8_vtk.h b/src/t8_vtk/t8_vtk.h index e3ed2dd595..9b9cb88725 100644 --- a/src/t8_vtk/t8_vtk.h +++ b/src/t8_vtk/t8_vtk.h @@ -23,7 +23,7 @@ /** \file t8_vtk.h * This header file collects macros that are needed for * the forest and cmesh vtk routines. - * \see t8_forest_vtk.h \see t8_cmesh_vtk_writer.h \see t8_cmesh_vtk_reader.hxx + * \see t8_forest_vtk.h \see t8_cmesh_vtk_writer.h \see t8_cmesh_vtk_reader.hxx TODO */ #ifndef T8_VTK_H diff --git a/test/mesh_handle/t8_gtest_interpolate.cxx b/test/mesh_handle/t8_gtest_interpolate.cxx index bbd0766876..381c84a1f6 100644 --- a/test/mesh_handle/t8_gtest_interpolate.cxx +++ b/test/mesh_handle/t8_gtest_interpolate.cxx @@ -87,6 +87,7 @@ TEST (t8_gtest_handle_data, test_interpolate_data) mesh->set_adapt ( mesh_class::mesh_adapt_callback_wrapper (adapt_callback_test, user_data), false); + mesh->set_balance (); mesh->set_interpolate_callback (interpolate_callback); mesh->commit (); bool tested_something = false; From 56de23b3aa9e77fe30475fabb7c57eba4b123d29 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Tue, 28 Jul 2026 11:27:12 +0200 Subject: [PATCH 13/20] fix things from merge --- mesh_handle/competence_pack.hxx | 2 +- mesh_handle/internal/adapt.hxx | 7 +++-- mesh_handle/mesh.hxx | 30 +++++++------------ .../competences/t8_gtest_dg_competences.cxx | 10 +++---- .../competences/t8_gtest_handle_data.cxx | 11 +++---- .../t8_gtest_adapt_partition_balance.cxx | 8 ++--- test/mesh_handle/t8_gtest_common.hxx | 2 +- test/mesh_handle/t8_gtest_interpolate.cxx | 15 +++++----- .../mesh_handle/t8_mesh_element_data.cxx | 4 +-- 9 files changed, 40 insertions(+), 49 deletions(-) diff --git a/mesh_handle/competence_pack.hxx b/mesh_handle/competence_pack.hxx index 1798633603..4bc37220b1 100644 --- a/mesh_handle/competence_pack.hxx +++ b/mesh_handle/competence_pack.hxx @@ -98,7 +98,7 @@ template using data_mesh_competences_basic = mesh_competence_pack::template type>; /** Predefined mesh competence pack with the functionality to work with element data and to interpolate element data after adaptation. - * If you want to access the data also via the elements, combine this with \ref t8_mesh_handle::new_data_element_competences. + * If you want to access the data also via the elements, combine this with \ref t8_mesh_handle::data_element_competences_basic. */ template using interpolate_data_mesh_competence diff --git a/mesh_handle/internal/adapt.hxx b/mesh_handle/internal/adapt.hxx index 24d303be64..4cb863ccaa 100644 --- a/mesh_handle/internal/adapt.hxx +++ b/mesh_handle/internal/adapt.hxx @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -68,7 +69,7 @@ struct mesh_adapt_context_base * Struct inherits from \ref mesh_adapt_context_base and implements the virtual adapt callback using the mesh and the callback. * \tparam TMeshClass The mesh handle class. */ -template +template struct mesh_adapt_context final: mesh_adapt_context_base { /** Constructor of the context with the mesh handle and the user defined callback. @@ -98,8 +99,8 @@ struct mesh_adapt_context final: mesh_adapt_context_base } private: - TMesh& m_mesh_handle; /**< The mesh handle to adapt. */ - const typename TMesh::adapt_callback_type m_adapt_callback; /**< The adapt callback. */ + TMeshClass& m_mesh_handle; /**< The mesh handle to adapt. */ + const typename TMeshClass::adapt_callback_type m_adapt_callback; /**< The adapt callback. */ }; /** Registry pattern is used to register contexts, which provides access to the adapt callback and the mesh handle. diff --git a/mesh_handle/mesh.hxx b/mesh_handle/mesh.hxx index c8cb64e8b6..891e8bc989 100644 --- a/mesh_handle/mesh.hxx +++ b/mesh_handle/mesh.hxx @@ -276,24 +276,6 @@ 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 (); }; - } - - /** TODO - */ - static constexpr bool - has_interpolate_data_competence () - { - return requires (SelfType& mesh) { mesh.set_partition_called (); }; - } - // --- 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. @@ -436,7 +418,7 @@ class mesh: public TMeshCompetencePack::template applyset_partition_called ()) { t8_forest_init (&m_forest); - t8_forest_set_partition (m_forest, m_uncommitted_forest.value (), this->m_set_for_coarsening); + t8_forest_set_partition (m_forest, m_uncommitted_forest.value (), this->m_set_for_coarsening.value ()); if (t8_forest_get_num_ghosts (m_uncommitted_forest.value ()) > 0) { t8_forest_set_ghost (m_forest, true, T8_GHOST_FACES); } @@ -452,7 +434,7 @@ class mesh: public TMeshCompetencePack::template apply /** Store the rank on each element. */ -struct data_per_element +struct rank_data_per_element { int rank; ///< Rank of the element. }; @@ -66,9 +66,9 @@ TEST (t8_gtest_dg_competences, remote_ranks) { const int level = 2; using namespace t8_mesh_handle; - using mesh_class - = mesh, - data_mesh_competences>>; + using mesh_class = mesh, + data_mesh_competences_basic>>; auto mesh = handle_hypercube_hybrid_uniform_default (level, sc_MPI_COMM_WORLD, true, false); mesh->set_adapt (mesh_adapt_callback_test_refine_second); mesh->set_partition (); @@ -87,7 +87,7 @@ TEST (t8_gtest_dg_competences, remote_ranks) SC_CHECK_MPI (mpiret); // Set local rank for all local mesh elements. - std::vector element_data (num_local, { mpirank }); + std::vector element_data (num_local, { mpirank }); mesh->set_element_data (std::move (element_data)); // Get element data and check that the remote ranks competence works as expected. mesh->exchange_ghost_data (); diff --git a/test/mesh_handle/competences/t8_gtest_handle_data.cxx b/test/mesh_handle/competences/t8_gtest_handle_data.cxx index fd8bc802be..c9f66e2ea0 100644 --- a/test/mesh_handle/competences/t8_gtest_handle_data.cxx +++ b/test/mesh_handle/competences/t8_gtest_handle_data.cxx @@ -27,7 +27,7 @@ along with t8code; if not, write to the Free Software Foundation, Inc., #include #include -#include "t8_gtest_common.hxx" +#include #include #include @@ -112,10 +112,11 @@ TEST (t8_gtest_handle_data, set_and_get_element_data) 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>>; + using mesh_class + = mesh, + 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; diff --git a/test/mesh_handle/t8_gtest_adapt_partition_balance.cxx b/test/mesh_handle/t8_gtest_adapt_partition_balance.cxx index 532e3e6d67..765a965345 100644 --- a/test/mesh_handle/t8_gtest_adapt_partition_balance.cxx +++ b/test/mesh_handle/t8_gtest_adapt_partition_balance.cxx @@ -89,10 +89,10 @@ TEST (t8_gtest_handle_adapt, compare_with_forest) t8_forest_t forest = t8_forest_new_uniform (cmesh, init_scheme, level, 0, sc_MPI_COMM_WORLD); 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. */ - 0.2, /**< Refine if inside this radius. */ - 0.4 /**< Coarsen if outside this radius. */ + dummy_user_data user_data { + t8_3D_vec { 0.5, 0.5, 1 }, /**< Midpoints of the sphere. */ + 0.2, /**< Refine if inside this radius. */ + 0.4 /**< Coarsen if outside this radius. */ }; // Ref the forest as we want to keep using it after the adapt call to compare results. diff --git a/test/mesh_handle/t8_gtest_common.hxx b/test/mesh_handle/t8_gtest_common.hxx index 9eec0b2d08..6af725fcce 100644 --- a/test/mesh_handle/t8_gtest_common.hxx +++ b/test/mesh_handle/t8_gtest_common.hxx @@ -24,7 +24,7 @@ along with t8code; if not, write to the Free Software Foundation, Inc., #include #include -#include +#include #include #include #include diff --git a/test/mesh_handle/t8_gtest_interpolate.cxx b/test/mesh_handle/t8_gtest_interpolate.cxx index 381c84a1f6..cce8e436f5 100644 --- a/test/mesh_handle/t8_gtest_interpolate.cxx +++ b/test/mesh_handle/t8_gtest_interpolate.cxx @@ -32,7 +32,7 @@ along with t8code; if not, write to the Free Software Foundation, Inc., #include #include #include -#include +#include #include #include #include @@ -69,13 +69,12 @@ TEST (t8_gtest_handle_data, test_interpolate_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, false, - false, false); + auto mesh = t8_mesh_handle::handle_hypercube_hybrid_uniform_default (level, sc_MPI_COMM_WORLD); - struct dummy_user_data user_data = { - t8_3D_vec ({ 0.5, 0.5, 1 }), /**< Midpoints of the sphere. */ - 0.2, /**< Refine if inside this radius. */ - 0.4 /**< Coarsen if outside this radius. */ + dummy_user_data user_data { + t8_3D_vec { 0.5, 0.5, 1 }, /**< Midpoints of the sphere. */ + 0.2, /**< Refine if inside this radius. */ + 0.4 /**< Coarsen if outside this radius. */ }; // Create element data for all local mesh elements and set via mesh competence. @@ -86,7 +85,7 @@ TEST (t8_gtest_handle_data, test_interpolate_data) mesh->set_element_data (std::move (element_data)); mesh->set_adapt ( - mesh_class::mesh_adapt_callback_wrapper (adapt_callback_test, user_data), false); + mesh_class::mesh_adapt_callback_wrapper (adapt_callback_test, user_data)); mesh->set_balance (); mesh->set_interpolate_callback (interpolate_callback); mesh->commit (); diff --git a/tutorials/mesh_handle/t8_mesh_element_data.cxx b/tutorials/mesh_handle/t8_mesh_element_data.cxx index 03249f0002..7422d160e0 100644 --- a/tutorials/mesh_handle/t8_mesh_element_data.cxx +++ b/tutorials/mesh_handle/t8_mesh_element_data.cxx @@ -204,8 +204,8 @@ main (int argc, char **argv) { /* We put the mesh in its own scope so that it is automatically destroyed at the end of the scope. * This is only necessary because sc_finalize checks if there are leftover references. * This unique pointer would have been destroyed automatically at the end of the programme. */ - using mesh_class = t8_mesh_handle::mesh>; + using mesh_class = t8_mesh_handle::mesh>; auto mesh = build_mesh (comm, level); t8_mesh_handle::write_mesh_to_vtk (*mesh, prefix_mesh); From ca55272e5ab1b29a0035af8f5483c1b88e0b71bc Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Tue, 28 Jul 2026 14:00:14 +0200 Subject: [PATCH 14/20] renaming --- mesh_handle/competences/element_data_competences.hxx | 8 ++++---- mesh_handle/mesh.hxx | 5 +++-- test/mesh_handle/t8_gtest_interpolate.cxx | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/mesh_handle/competences/element_data_competences.hxx b/mesh_handle/competences/element_data_competences.hxx index 1e0a55e0b8..b8118db012 100644 --- a/mesh_handle/competences/element_data_competences.hxx +++ b/mesh_handle/competences/element_data_competences.hxx @@ -183,9 +183,9 @@ template class interpolate_element_data_mesh_competence: public t8_crtp_operator { public: - // TODO: has_interpolate function und den callback type + // TODO: has_interpolate function - /** Callback function prototype to replace the element data of one set of elements with another. + /** TODO Callback function prototype to replace the element data of one set of elements with another. * This is used to interpolate element data after adaptation. The callback allows the user to make changes to the * elements that are either refined, coarsened or the same. * \param [in] forest_old The forest that is adapted @@ -221,11 +221,11 @@ class interpolate_element_data_mesh_competence: bool set_partition_called () { - return m_set_for_coarsening.has_value (); + return m_partition_set_for_coarsening.has_value (); } interpolate_callback_type m_interpolate_callback; - std::optional m_set_for_coarsening; + std::optional m_partition_set_for_coarsening; }; } // namespace t8_mesh_handle diff --git a/mesh_handle/mesh.hxx b/mesh_handle/mesh.hxx index 891e8bc989..52eb013383 100644 --- a/mesh_handle/mesh.hxx +++ b/mesh_handle/mesh.hxx @@ -332,7 +332,7 @@ class mesh: public TMeshCompetencePack::template applym_set_for_coarsening = set_for_coarsening; + this->m_partition_set_for_coarsening = set_for_coarsening; return; } if (!m_uncommitted_forest.has_value ()) { @@ -418,7 +418,8 @@ class mesh: public TMeshCompetencePack::template applyset_partition_called ()) { t8_forest_init (&m_forest); - t8_forest_set_partition (m_forest, m_uncommitted_forest.value (), this->m_set_for_coarsening.value ()); + t8_forest_set_partition (m_forest, m_uncommitted_forest.value (), + this->m_partition_set_for_coarsening.value ()); if (t8_forest_get_num_ghosts (m_uncommitted_forest.value ()) > 0) { t8_forest_set_ghost (m_forest, true, T8_GHOST_FACES); } diff --git a/test/mesh_handle/t8_gtest_interpolate.cxx b/test/mesh_handle/t8_gtest_interpolate.cxx index cce8e436f5..0a94cd0608 100644 --- a/test/mesh_handle/t8_gtest_interpolate.cxx +++ b/test/mesh_handle/t8_gtest_interpolate.cxx @@ -37,6 +37,7 @@ along with t8code; if not, write to the Free Software Foundation, Inc., #include #include +// TODO: Vector of elements instead of numbers? template void interpolate_callback (const TMeshClass& mesh_old, TMeshClass& mesh_new, const int refine, const int num_old, From 058f919a9b68bd201efaddd70a1c816c446c06ae Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Wed, 29 Jul 2026 09:32:08 +0200 Subject: [PATCH 15/20] documentation --- .../competences/element_data_competences.hxx | 6 + mesh_handle/internal/interpolate.hxx | 113 ++++++++++++++---- mesh_handle/mesh.hxx | 21 +++- 3 files changed, 116 insertions(+), 24 deletions(-) diff --git a/mesh_handle/competences/element_data_competences.hxx b/mesh_handle/competences/element_data_competences.hxx index b8118db012..c272f02591 100644 --- a/mesh_handle/competences/element_data_competences.hxx +++ b/mesh_handle/competences/element_data_competences.hxx @@ -83,6 +83,12 @@ class element_data_mesh_competence_impl: public t8_crtp_basic { return m_element_data; } + /** Get the element data vector by moving it out of the competence. + * In contrast to \ref get_element_data, this transfers ownership of the data instead of + * returning a reference. After this call the internal element data vector is left empty (moved-from), + * so \ref set_element_data should be used before accessing the data again. + * \return Element data vector with data of Type TElementDataType, moved out of the competence. + */ std::vector take_element_data () { diff --git a/mesh_handle/internal/interpolate.hxx b/mesh_handle/internal/interpolate.hxx index 3f81b5ab4b..4f51996d0a 100644 --- a/mesh_handle/internal/interpolate.hxx +++ b/mesh_handle/internal/interpolate.hxx @@ -21,8 +21,13 @@ */ /** \file interpolate.hxx - * This file provides helper functionality to interpolate element data for a \ref t8_mesh_handle::mesh - * according to a user defined callback. + * This file provides helper functionality to interpolate the element data of a \ref t8_mesh_handle::mesh + * onto a newly adapted mesh according to a user defined callback. + * During adaptation the element data attached to the old elements has to be transferred to the new (refined, + * coarsened or unchanged) elements. t8code reports this old-to-new correspondence through + * \ref t8_forest_iterate_replace, which expects a plain C callback of type \ref t8_forest_replace_t. + * The helpers in this file bridge the gap between the mesh handle patterns and these functions, + * following the same registry pattern as \ref adapt.hxx. */ #pragma once @@ -35,18 +40,32 @@ namespace t8_mesh_handle { + +/** Namespace detail to hide implementation details from the user. */ namespace detail { -/** - * Base class for interpolation contexts (type-erased). +/** Virtual base class for mesh interpolation contexts. + * We need this base class and not only \ref mesh_interpolate_context for the \ref interpolate_registry. + * interpolate_registry should not be templated because we need to access registered contexts in + * \ref mesh_replace_callback_wrapper, where we do not know the type of the mesh. Therefore, we work with a map of + * forests to instances of this (type erased) base class to remain template free. */ struct mesh_interpolate_context_base { + /** Virtual destructor for safe polymorphic deletion. + */ virtual ~mesh_interpolate_context_base () = default; - /** - * Virtual interpolation callback. + /** Pure virtual callback to interpolate the element data of one set of old elements onto a set of new elements. + * The indices refer to the flat, process local element numbering of the mesh handle (across all local trees), + * not to the tree local numbering used by the forest. The conversion is done in \ref mesh_replace_callback_wrapper. + * \param [in] refine -1 if a family in the old mesh got coarsened, 0 if the element was not touched, + * 1 if the element got refined. + * \param [in] num_old The number of outgoing (old) elements. + * \param [in] first_old The local flat element ID of the first outgoing element in the old mesh. + * \param [in] num_new The number of incoming (new) elements. + * \param [in] first_new The local flat element ID of the first incoming element in the new mesh. */ virtual void interpolate (const int refine, const int num_old, const t8_locidx_t first_old, const int num_new, @@ -54,49 +73,78 @@ struct mesh_interpolate_context_base = 0; }; -/** - * Concrete interpolation context storing mesh handles + user callback. +/** Templated mesh interpolation context holding the old and new mesh handle and the user defined callback. + * Struct inherits from \ref mesh_interpolate_context_base and implements the virtual interpolate callback using the + * two mesh handles and the callback. + * Type erasure via the base class lets \ref interpolate_registry store this context without being templated on the + * mesh type. + * \tparam TMesh The mesh handle class. */ template struct mesh_interpolate_context final: mesh_interpolate_context_base { - using callback_type = typename TMesh::interpolate_callback_type; + using callback_type = typename TMesh::interpolate_callback_type; /**< The user defined interpolate callback type. */ + /** Constructor of the context with the old and new mesh handle and the user defined callback. + * \param [in] mesh_old The old mesh that is being adapted. Only read from during interpolation. + * \param [in, out] mesh_new The new mesh constructed from \a mesh_old. Written to during interpolation. + * \param [in] interpolate_callback The interpolate callback. Moved into the context. + */ mesh_interpolate_context (const TMesh& mesh_old, TMesh& mesh_new, callback_type&& interpolate_callback) : m_mesh_old (mesh_old), m_mesh_new (mesh_new), m_callback (std::forward (interpolate_callback)) { } + /** Interpolation of one group of elements using the old and the new mesh and the user defined callback. + * This function is called by \ref mesh_replace_callback_wrapper for each group. + * \param [in] refine -1 if a family got coarsened, 0 if the element was not touched, 1 if it got refined. + * \param [in] num_old The number of outgoing (old) elements. + * \param [in] first_old The local flat element ID of the first outgoing element in the old mesh. + * \param [in] num_new The number of incoming (new) elements. + * \param [in] first_new The local flat element ID of the first incoming element in the new mesh. + */ void interpolate (const int refine, const int num_old, const t8_locidx_t first_old, const int num_new, const t8_locidx_t first_new) override { + // Check if the interpolate callback is set and call it using the old and new mesh handle. T8_ASSERTF (m_callback, "No interpolate callback set."); m_callback (m_mesh_old, m_mesh_new, refine, num_old, first_old, num_new, first_new); } private: - const TMesh& m_mesh_old; - TMesh& m_mesh_new; - const callback_type m_callback; + const TMesh& m_mesh_old; /**< The old mesh to read the element data from. */ + TMesh& m_mesh_new; /**< The new mesh to write the interpolated element data to. */ + const callback_type m_callback; /**< The user defined interpolate callback. */ }; -/** - * Registry for interpolate contexts (same idea as adapt_registry). +/** Registry pattern is used to register contexts, which provide access to the interpolate callback and the mesh + * handles. This globally accessible static class is required to get the meshes and the callback in the forest + * replace callback \ref mesh_replace_callback_wrapper, as the predefined \ref t8_forest_replace_t header does not + * permit to pass these as function arguments. It uses the same idea as \ref adapt_registry. */ class interpolate_registry { public: - /** Need forest and not mesh for key because this cannot have a template parameter. */ + /** Static function to register \a context using \a forest as identifier. + * This makes the context publicly available through the registry. + * \param [in] forest The forest identifier. In our case, this is the old forest we interpolate data from. + * ( Consistent to the adapt_registry.) + * \param [in] context The context to register. Use unique pointer to ensure proper memory management and ownership. + * \note We need the forest and not the mesh as key because this class must not be templated on the mesh type. + */ static void register_context (t8_forest_t forest, std::unique_ptr context) { auto& map = get_map (); auto [it, inserted] = map.emplace (forest, std::move (context)); if (!inserted) { - t8_global_productionf ("Interpolate context already registered!"); + t8_global_errorf ("ERROR: Context already registered!"); } } + /** Static function to unregister a context using \a forest as identifier. + * \param [in] forest The forest identifier. In our case, this is the old forest we interpolate from. + */ static void unregister_context (t8_forest_t forest) { @@ -105,6 +153,10 @@ class interpolate_registry { T8_ASSERT (erased == 1); } + /** Getter for a context using \a forest as identifier. + * \param [in] forest The forest identifier. In our case, this is the old forest we interpolate from. + * \return Pointer to the context registered with the id \a forest if found, nullptr otherwise. + */ static mesh_interpolate_context_base* get (t8_forest_t forest) { @@ -114,6 +166,10 @@ class interpolate_registry { } private: + /** Get the static map associating t8_forest_t with mesh_interpolate_context_base references. + * We use a getter instead of private member variable to ensure single initialization. + * \return Reference to the static unordered map of t8_forest_t to mesh_interpolate_context_base references. + */ static std::unordered_map>& get_map () { @@ -122,25 +178,42 @@ class interpolate_registry { } }; -/** - * Wrapper matching t8_forest_replace_t +/** Wrapper around the mesh handle interpolation functionality to be able to pass the callback to the classic replace + * routine \ref t8_forest_iterate_replace of a forest. The function header fits the definition of + * \ref t8_forest_replace_t. + * \param [in] forest_old The forest that is adapted fitting the old element data. + * \param [in, out] forest_new The forest that is newly constructed from \a forest_old. + * Data will be interpolated to match this forest. + * \param [in] which_tree The local tree containing \a first_outgoing and \a first_incoming. + * \param [in] tree_class Unused; The eclass of the local tree containing \a first_outgoing and \a first_incoming. + * \param [in] scheme Unused; The scheme of the forest. + * \param [in] refine -1 if a family got coarsened, 0 if the element was not touched, 1 if it got refined. + * \param [in] num_outgoing The number of outgoing elements. + * \param [in] first_outgoing The tree local index of the first outgoing element. + * 0 <= first_outgoing < which_tree->num_elements + * \param [in] num_incoming The number of incoming elements. + * \param [in] first_incoming The tree local index of the first incoming element. + * 0 <= first_incom < new_which_tree->num_elements */ inline void -mesh_replace_callback_wrapper (t8_forest_t forest_old, [[maybe_unused]] t8_forest_t forest_new, t8_locidx_t which_tree, +mesh_replace_callback_wrapper (t8_forest_t forest_old, t8_forest_t forest_new, t8_locidx_t which_tree, [[maybe_unused]] const t8_eclass_t tree_class, [[maybe_unused]] const t8_scheme_c* scheme, const int refine, const int num_outgoing, const t8_locidx_t first_outgoing, const int num_incoming, const t8_locidx_t first_incoming) { + // Get the static interpolate context from the registry. + // Via this, we can access the old and new mesh handle and the user defined interpolate callback. auto* context = interpolate_registry::get (forest_old); if (!context) { t8_global_productionf ("Interpolate context not found. Did you forget to register it?"); return; } - // Convert tree-local indices to mesh-global indices + // Convert the tree local indices reported by the forest to the flat, process local indices used in the mesh handle. const t8_locidx_t first_old_global = t8_forest_get_tree_element_offset (forest_old, which_tree) + first_outgoing; const t8_locidx_t first_new_global = t8_forest_get_tree_element_offset (forest_new, which_tree) + first_incoming; + // Call the actual interpolate callback stored in the context. context->interpolate (refine, num_outgoing, first_old_global, num_incoming, first_new_global); } diff --git a/mesh_handle/mesh.hxx b/mesh_handle/mesh.hxx index 52eb013383..f1668ac2a9 100644 --- a/mesh_handle/mesh.hxx +++ b/mesh_handle/mesh.hxx @@ -300,7 +300,7 @@ class mesh: public TMeshCompetencePack::template applym_partition_set_for_coarsening = set_for_coarsening; return; @@ -352,7 +354,7 @@ class mesh: public TMeshCompetencePack::template applym_interpolate_callback) { + // Create new intermediate mesh to interpolate the data from the current mesh to the new mesh. SelfType new_mesh (m_uncommitted_forest.value ()); t8_forest_ref (m_uncommitted_forest.value ()); + // Register the interpolate context with the callback for the new mesh. With this, the standard + // iterate replace can be called. detail::interpolate_registry::register_context ( m_forest, std::make_unique> ( *this, new_mesh, std::move (this->m_interpolate_callback))); t8_forest_iterate_replace (m_uncommitted_forest.value (), m_forest, detail::mesh_replace_callback_wrapper); detail::interpolate_registry::unregister_context (m_forest); + // Override the element data of the current mesh with the interpolated data from the "new mesh". this->m_element_data = new_mesh.take_element_data (); + // Now we update the forest of the current mesh with the new forest and partition it if required. t8_forest_unref (&m_forest); if (this->set_partition_called ()) { t8_forest_init (&m_forest); @@ -427,9 +439,10 @@ class mesh: public TMeshCompetencePack::template apply Date: Wed, 29 Jul 2026 12:41:04 +0200 Subject: [PATCH 16/20] Add user data option for interpolate callback --- mesh_handle/competence_pack.hxx | 2 +- .../competences/element_data_competences.hxx | 126 +++++++++++++----- mesh_handle/internal/interpolate.hxx | 16 ++- mesh_handle/mesh.hxx | 73 +++++++++- test/mesh_handle/t8_gtest_interpolate.cxx | 69 +++++++--- test/mesh_handle/t8_gtest_mesh_handle.cxx | 2 +- 6 files changed, 224 insertions(+), 64 deletions(-) diff --git a/mesh_handle/competence_pack.hxx b/mesh_handle/competence_pack.hxx index 4bc37220b1..75950daec4 100644 --- a/mesh_handle/competence_pack.hxx +++ b/mesh_handle/competence_pack.hxx @@ -101,7 +101,7 @@ using data_mesh_competences_basic = mesh_competence_pack -using interpolate_data_mesh_competence +using interpolate_data_mesh_competence_pack = mesh_competence_pack::template type, interpolate_element_data_mesh_competence>; diff --git a/mesh_handle/competences/element_data_competences.hxx b/mesh_handle/competences/element_data_competences.hxx index c272f02591..10676a5e53 100644 --- a/mesh_handle/competences/element_data_competences.hxx +++ b/mesh_handle/competences/element_data_competences.hxx @@ -25,6 +25,7 @@ * 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. + * A competence to interpolate data after adaptation using a user defined callback is provided. */ #pragma once @@ -37,9 +38,39 @@ #include #include #include +#include namespace t8_mesh_handle { + +/** Namespace detail to hide implementation details from the user. */ +namespace detail +{ +/** Helper function to wrap a span based interpolation callback (see \ref mesh::interpolate_callback_type) into + * the element-index based \ref interpolate_element_data_mesh_competence::internal_interpolate_callback_type. + * The returned wrapper receives the index/count pairs, builds the element spans, and forwards them to \a callback. + * The spans are built here and not in \ref interpolate_mesh_competence because \a TMesh is complete, + * so element_class is nameable — which it is not inside the competence (see note on + * \ref interpolate_element_data_mesh_competence::internal_interpolate_callback_type). + * This is used in \ref interpolate_element_data_mesh_competence::set_interpolate_callback + * \tparam TMesh The (complete) mesh handle type. + * \param [in] callback The span based user callback of type \ref mesh::interpolate_callback_type. Taken by value and + * moved into the returned wrapper, which owns it. + * \return Callback of type \ref interpolate_element_data_mesh_competence::internal_interpolate_callback_type. + */ +template +auto +to_replace_callback (typename TMesh::interpolate_callback_type callback) +{ + return + [callback = std::move (callback)] (const TMesh& mesh_old, TMesh& mesh_new, const int refine, const int num_old, + const t8_locidx_t first_old, const int num_new, const t8_locidx_t first_new) { + callback (mesh_old, mesh_new, refine, std::span (&mesh_old[first_old], num_old), + std::span (&mesh_new[first_new], num_new)); + }; +} +} // namespace detail + // --- Mesh competence for element data management. --- /** 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. @@ -183,55 +214,90 @@ struct element_data_element_competence: public t8_crtp_operator class interpolate_element_data_mesh_competence: public t8_crtp_operator { public: - // TODO: has_interpolate function - - /** TODO Callback function prototype to replace the element data of one set of elements with another. - * This is used to interpolate element data after adaptation. The callback allows the user to make changes to the - * elements that are either refined, coarsened or the same. - * \param [in] forest_old The forest that is adapted - * \param [in, out] forest_new The forest that is newly constructed from \a forest_old - * \param [in] refine -1 if family in \a forest_old got coarsened, 0 if element - * has not been touched, 1 if element got refined. See return of adapt_callback_type. - * \param [in] num_old The number of outgoing elements. - * \param [in] first_old The local handle index of the first outgoing element in the old mesh. - * \param [in] num_new The number of incoming elements. - * \param [in] first_new The tree local index of the first incoming element in the new mesh. - * - * If an element is being refined, \a refine and \a num_old will be 1 and - * \a num_new will be the number of children. - * If a family is being coarsened, \a refine will be -1, \a num_old will be - * the number of family members and \a num_new will be 1. - * Else \a refine will be 0 and \a num_old and \a num_new will both be 1. - */ - using interpolate_callback_type + /** Mesh internal, element-index based storage for the interpolation callback. + * Users should use the easier span based callback type \ref mesh::interpolate_callback_type. + * \see set_interpolate_callback for registering a callback. + * The span based callback is automatically wrapped in \ref set_interpolate_callback to match this type and stored as + * \ref m_interpolate_callback to be used in the next \ref mesh::commit. + * \note We can not store or the span based \ref mesh::interpolate_callback_type directly. This competence uses the + * CRTP pattern, so while the competence is instantiated, the mesh (\a TUnderlying) is still an incomplete type. + * A data member of type \ref mesh::interpolate_callback_type, would require \c TUnderlying::element_class, + * which is not available for the incomplete type. Therefore we use this index based callback type for storage + * without the need for element_class. Using \ref set_interpolate_callback, we move the element_class lookup + * to the call site. + * \param [in] mesh_old The old mesh that is adapted from. + * \param [in,out] mesh_new The new mesh constructed from \a mesh_old. + * \param [in] refine -1 if a family in the old mesh got coarsened, 0 if the element was not touched, + * 1 if the element got refined. + * \param [in] num_old The number of outgoing elements. + * \param [in] first_old The local mesh handle index of the first outgoing element in the old mesh. + * \param [in] num_new The number of incoming elements. + * \param [in] first_new The local mesh handle index of the first incoming element in the new mesh. + + */ + using internal_interpolate_callback_type = std::function; - // TODO: with userdata - - /** TODO + /** Register a user callback to interpolate the element data after adaptation. + * Note that data can only be interpolated for a level difference of at most one. + * Please use the type \ref mesh::interpolate_callback_type for the callback. + * \see mesh::interpolate_callback_type for the expected callback shape and the meaning of its arguments. + * \note This function is templated on purpose: it is only instantiated at the call site, where the mesh is a + * complete type and \ref mesh::element_class is nameable. The element class is needed in the definition + * of the interpolate_callback_type. You do not have to provide this template, it is normally auto deduced. + * \note This function is templated on purpose: it is only instantiated at the call site, where the mesh is a + * complete type and \ref mesh::element_class (needed to name \ref mesh::interpolate_callback_type) is + * nameable, which it is not inside this competence. + * The template parameter is deduced from the passed callback, so you do not have to provide it explicitly! + * \tparam TInterpolateCallback The user callback type \ref mesh::interpolate_callback_type. + * \param [in] interpolate_callback The span based interpolation callback. + * */ + template void - set_interpolate_callback (interpolate_callback_type&& interpolate_callback) + set_interpolate_callback (TInterpolateCallback&& interpolate_callback) { - m_interpolate_callback = std::forward (interpolate_callback); + /* We wrap the user defined, span based callback using \ref detail::to_replace_callback to the index based callback + * type \ref internal_interpolate_callback_type to be able to store the callback without the need of knowing + * \ref mesh::element_class.*/ + m_interpolate_callback + = detail::to_replace_callback (std::forward (interpolate_callback)); } protected: + /** Decide whether \ref mesh::set_partition has been requested for the upcoming \ref mesh::commit. + * With the interpolation competence the partition step is postponed so that it runs after the element data has been + * interpolated onto the new mesh; \ref mesh::set_partition therefore records its choice in + * \ref m_partition_for_coarsening instead of building the partitioned forest directly. + * \return true if \ref mesh::set_partition has been called (i.e. \ref m_partition_for_coarsening holds a value), + * false otherwise. + */ bool set_partition_called () { - return m_partition_set_for_coarsening.has_value (); + return m_partition_for_coarsening.has_value (); } - interpolate_callback_type m_interpolate_callback; - std::optional m_partition_set_for_coarsening; + internal_interpolate_callback_type + m_interpolate_callback; /**< The wrapped element-index based interpolation callback, + * applied on the next \ref mesh::commit. */ + std::optional + m_partition_for_coarsening; /**< Postponed \ref mesh::set_partition request: a value means partition on the next + * commit (with value passed to \ref mesh::set_partition); no value means do not partition. */ }; } // namespace t8_mesh_handle diff --git a/mesh_handle/internal/interpolate.hxx b/mesh_handle/internal/interpolate.hxx index 4f51996d0a..a206af8698 100644 --- a/mesh_handle/internal/interpolate.hxx +++ b/mesh_handle/internal/interpolate.hxx @@ -58,14 +58,14 @@ struct mesh_interpolate_context_base virtual ~mesh_interpolate_context_base () = default; /** Pure virtual callback to interpolate the element data of one set of old elements onto a set of new elements. - * The indices refer to the flat, process local element numbering of the mesh handle (across all local trees), + * The indices refer to the flat, process local element numbering of the mesh handle (mesh handle id), * not to the tree local numbering used by the forest. The conversion is done in \ref mesh_replace_callback_wrapper. * \param [in] refine -1 if a family in the old mesh got coarsened, 0 if the element was not touched, * 1 if the element got refined. * \param [in] num_old The number of outgoing (old) elements. - * \param [in] first_old The local flat element ID of the first outgoing element in the old mesh. + * \param [in] first_old The local mesh handle id of the first outgoing element in the old mesh. * \param [in] num_new The number of incoming (new) elements. - * \param [in] first_new The local flat element ID of the first incoming element in the new mesh. + * \param [in] first_new The local mesh handle id of the first incoming element in the new mesh. */ virtual void interpolate (const int refine, const int num_old, const t8_locidx_t first_old, const int num_new, @@ -83,7 +83,8 @@ struct mesh_interpolate_context_base template struct mesh_interpolate_context final: mesh_interpolate_context_base { - using callback_type = typename TMesh::interpolate_callback_type; /**< The user defined interpolate callback type. */ + using callback_type = + typename TMesh::internal_interpolate_callback_type; /**< The user defined interpolate callback type. */ /** Constructor of the context with the old and new mesh handle and the user defined callback. * \param [in] mesh_old The old mesh that is being adapted. Only read from during interpolation. @@ -99,9 +100,9 @@ struct mesh_interpolate_context final: mesh_interpolate_context_base * This function is called by \ref mesh_replace_callback_wrapper for each group. * \param [in] refine -1 if a family got coarsened, 0 if the element was not touched, 1 if it got refined. * \param [in] num_old The number of outgoing (old) elements. - * \param [in] first_old The local flat element ID of the first outgoing element in the old mesh. + * \param [in] first_old The local mesh handle id of the first outgoing element in the old mesh. * \param [in] num_new The number of incoming (new) elements. - * \param [in] first_new The local flat element ID of the first incoming element in the new mesh. + * \param [in] first_new The local mesh handle id of the first incoming element in the new mesh. */ void interpolate (const int refine, const int num_old, const t8_locidx_t first_old, const int num_new, @@ -210,7 +211,8 @@ mesh_replace_callback_wrapper (t8_forest_t forest_old, t8_forest_t forest_new, t return; } - // Convert the tree local indices reported by the forest to the flat, process local indices used in the mesh handle. + // Convert the tree local indices reported by the forest to the flat, process local indices used in the mesh handle + // (the mesh handle id). const t8_locidx_t first_old_global = t8_forest_get_tree_element_offset (forest_old, which_tree) + first_outgoing; const t8_locidx_t first_new_global = t8_forest_get_tree_element_offset (forest_new, which_tree) + first_incoming; // Call the actual interpolate callback stored in the context. diff --git a/mesh_handle/mesh.hxx b/mesh_handle/mesh.hxx index f1668ac2a9..eee25ec131 100644 --- a/mesh_handle/mesh.hxx +++ b/mesh_handle/mesh.hxx @@ -79,6 +79,7 @@ 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. */ + // --- Definition of callback types. --- /** Callback function prototype to decide for refining and coarsening of a family of elements * or one element in a mesh handle. * If \a elements contains more than one element, they must form a family and we decide whether this family should be @@ -112,6 +113,48 @@ class mesh: public TMeshCompetencePack::template apply elements, TUserDataType user_data)>; + /** Callback function prototype to interpolate the element data after refining or coarsening. + * \note You need to include \ref interpolate_element_data_mesh_competence to you competences to be able to + * interpolate. The best way to do this is via the predefined pack \ref interpolate_data_mesh_competence_pack + * defined in \ref competence_pack.hxx. + * + * For each group of elements that changed during adaption, the outgoing elements of the old mesh are passed in + * \a old_elements and the incoming elements of the new mesh in \a new_elements; the callback reads the old data + * and writes the interpolated data onto the new elements. \a refine is the value \ref adapt_callback_type returned + * for this group. + * \see set_interpolate_callback for the usage of this callback. + * \param [in] mesh_old The old mesh that is adapted from. + * \param [in,out] mesh_new The new mesh constructed from \a mesh_old. + * \param [in] refine -1 if the family \a old_elements got coarsened, 0 if the element was not touched, + * 1 if the element got refined. Same convention as the return of \ref adapt_callback_type. + * \param [in] old_elements Span over the outgoing elements: the whole family on coarsening, + * a single element if refined or untouched. + * \param [in,out] new_elements Span over the incoming elements to write the interpolated data to: the children on + * refinement, a single element if coarsened or untouched. + */ + using interpolate_callback_type + = std::function old_elements, std::span new_elements)>; + + /** Templated callback function prototype to interpolate the element data after refining or coarsening, + * including user data. + * See the version without user_data \ref interpolate_callback_type for more details! + * Use \ref mesh_interpolate_callback_wrapper to convert this type into \ref interpolate_callback_type + * to be able to pass the callback to \ref set_interpolate_callback (see \ref element_data_competence.hxx). + * \tparam TUserDataType The type of the user data to be passed to the callback. + * \param [in] mesh_old The old mesh that is adapted from. + * \param [in,out] mesh_new The new mesh constructed from \a mesh_old. + * \param [in] refine -1 if the family got coarsened, 0 if the element was not touched, 1 if it got refined. + * \param [in] old_elements Span over the outgoing elements from \a mesh_old. + * \param [in,out] new_elements Span over the incoming elements to write the interpolated data to from \a mesh_new. + * \param [in] user_data The user data to be used during the interpolation. + */ + template + using interpolate_callback_type_with_userdata = std::function old_elements, + std::span new_elements, TUserDataType user_data)>; + + // --- Constructor and destructor. --- /** * Constructor for a mesh of the handle. * \param [in] forest The forest from which the mesh should be created. @@ -277,6 +320,27 @@ class mesh: public TMeshCompetencePack::template apply + static interpolate_callback_type + mesh_interpolate_callback_wrapper ( + interpolate_callback_type_with_userdata interpolate_callback_with_userdata, + const TUserDataType& user_data) + { + return [=] (const SelfType& mesh_old, SelfType& mesh_new, const int refine, + std::span old_elements, std::span new_elements) { + return interpolate_callback_with_userdata (mesh_old, mesh_new, refine, old_elements, new_elements, user_data); + }; + } + /** 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. * This is required to pass an adapt callback with user data to \ref set_adapt. @@ -334,7 +398,7 @@ class mesh: public TMeshCompetencePack::template applym_partition_set_for_coarsening = set_for_coarsening; + this->m_partition_for_coarsening = set_for_coarsening; return; } if (!m_uncommitted_forest.has_value ()) { @@ -431,7 +495,7 @@ class mesh: public TMeshCompetencePack::template applyset_partition_called ()) { t8_forest_init (&m_forest); t8_forest_set_partition (m_forest, m_uncommitted_forest.value (), - this->m_partition_set_for_coarsening.value ()); + this->m_partition_for_coarsening.value ()); if (t8_forest_get_num_ghosts (m_uncommitted_forest.value ()) > 0) { t8_forest_set_ghost (m_forest, true, T8_GHOST_FACES); } @@ -453,7 +517,7 @@ class mesh: public TMeshCompetencePack::template apply @@ -36,32 +36,54 @@ along with t8code; if not, write to the Free Software Foundation, Inc., #include #include #include +#include -// TODO: Vector of elements instead of numbers? +/** Dummy user data for the interpolation. */ +struct interpolate_user_data +{ + int level_step; /**< Levels added when refining and subtracted when coarsening (1 in the standard case). + This is to be applied to the level entry of the dummy user data. */ +}; + +/** Interpolation callback for the mesh handle, using user data. + * The function header fits the definition of \ref TMesh::interpolate_callback_type_with_userdata. + * Copies the data of untouched elements, averages the parent volume over the children on refinement, and sums the + * children's volume onto the parent on coarsening. The level changes by \a user_data.level_step per step. + * \tparam TMeshClass The mesh handle class. + * \param [in] mesh_old The old mesh that is adapted from. + * \param [in,out] mesh_new The new mesh constructed from \a mesh_old. + * \param [in] refine -1 if the family got coarsened, 0 if the element was not touched, 1 if it got refined. + * \param [in] old_elements Span over the outgoing elements from \a mesh_old. + * \param [in,out] new_elements Span over the incoming elements to write the interpolated data to from \a mesh_new. + * \param [in] user_data The user data to be used during the interpolation. + */ template void -interpolate_callback (const TMeshClass& mesh_old, TMeshClass& mesh_new, const int refine, const int num_old, - const t8_locidx_t first_old, const int num_new, const t8_locidx_t first_new) +interpolate_callback ([[maybe_unused]] const TMeshClass& mesh_old, [[maybe_unused]] TMeshClass& mesh_new, + const int refine, std::span old_elements, + std::span new_elements, + const interpolate_user_data& user_data) { - /* Do not adapt or coarsen. */ + /* Untouched: copy data. */ if (refine == 0) { - mesh_new[first_new].set_element_data (mesh_old[first_old].get_element_data ()); + new_elements[0].set_element_data (old_elements[0].get_element_data ()); } - /* The old element is refined. Volume data is averaged for the children. */ + /* Refined: children share the parent volume equally, level increases by user_data.level_step. */ else if (refine == 1) { - for (t8_locidx_t i = 0; i < num_new; i++) { - mesh_new[first_new + i].set_element_data (data_per_element { - mesh_old[first_old].get_element_data ().level + 1, mesh_old[first_old].get_element_data ().volume / num_new }); + const auto& parent_data = old_elements[0].get_element_data (); + for (auto& child : new_elements) { + child.set_element_data ( + data_per_element { parent_data.level + user_data.level_step, parent_data.volume / new_elements.size () }); } } - /* Old element is coarsened. */ + /* Coarsened: parent volume is the sum of the children, level decreases by user_data.level_step. */ else if (refine == -1) { double tmp_volume = 0; - for (t8_locidx_t i = 0; i < num_old; i++) { - tmp_volume += mesh_old[first_old + i].get_element_data ().volume; + for (const auto& child : old_elements) { + tmp_volume += child.get_element_data ().volume; } - mesh_new[first_new].set_element_data ( - data_per_element { mesh_old[first_old].get_element_data ().level - 1, tmp_volume }); + new_elements[0].set_element_data ( + data_per_element { old_elements[0].get_element_data ().level - user_data.level_step, tmp_volume }); } } @@ -69,14 +91,15 @@ TEST (t8_gtest_handle_data, test_interpolate_data) { const int level = 2; using mesh_class = t8_mesh_handle::mesh>; + t8_mesh_handle::interpolate_data_mesh_competence_pack>; auto mesh = t8_mesh_handle::handle_hypercube_hybrid_uniform_default (level, sc_MPI_COMM_WORLD); - dummy_user_data user_data { + dummy_user_data user_data_adapt { t8_3D_vec { 0.5, 0.5, 1 }, /**< Midpoints of the sphere. */ 0.2, /**< Refine if inside this radius. */ 0.4 /**< Coarsen if outside this radius. */ }; + interpolate_user_data dummy_interpolate_user_data { 1 }; /**< Level changes by one per adaptation step. */ // Create element data for all local mesh elements and set via mesh competence. std::vector element_data; @@ -85,11 +108,15 @@ TEST (t8_gtest_handle_data, test_interpolate_data) } mesh->set_element_data (std::move (element_data)); + // Adapt the mesh and set all options. mesh->set_adapt ( - mesh_class::mesh_adapt_callback_wrapper (adapt_callback_test, user_data)); + mesh_class::mesh_adapt_callback_wrapper (adapt_callback_test, user_data_adapt)); mesh->set_balance (); - mesh->set_interpolate_callback (interpolate_callback); + mesh->set_interpolate_callback (mesh_class::mesh_interpolate_callback_wrapper ( + interpolate_callback, dummy_interpolate_user_data)); mesh->commit (); + + // Test interpolation. bool tested_something = false; for (auto& elem : *mesh) { if (!tested_something && (elem.get_level () != level)) { @@ -97,7 +124,7 @@ TEST (t8_gtest_handle_data, test_interpolate_data) } EXPECT_EQ (elem.get_level (), elem.get_element_data ().level); if (!(elem.get_element_data ().level > level)) { - // For refined elements, the volume is averaged and thus not exact. + // For refined elements, the volume is averaged and thus not exact. Only test coarsening. EXPECT_EQ (elem.get_volume (), elem.get_element_data ().volume); } } diff --git a/test/mesh_handle/t8_gtest_mesh_handle.cxx b/test/mesh_handle/t8_gtest_mesh_handle.cxx index 565557b4b8..8bb9870b4b 100644 --- a/test/mesh_handle/t8_gtest_mesh_handle.cxx +++ b/test/mesh_handle/t8_gtest_mesh_handle.cxx @@ -204,7 +204,7 @@ TEST (t8_mesh_handle_test, test_union_mesh_competence_pack) using mesh_class = mesh, - 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; From b2c576de13dcc53269f2846a64a0f0c17af4b77d Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Wed, 29 Jul 2026 16:33:15 +0200 Subject: [PATCH 17/20] Add repartition of data --- .../competences/element_data_competences.hxx | 38 +++++++++++++++++-- mesh_handle/mesh.hxx | 5 ++- test/mesh_handle/t8_gtest_interpolate.cxx | 34 ++++++++++------- 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/mesh_handle/competences/element_data_competences.hxx b/mesh_handle/competences/element_data_competences.hxx index 10676a5e53..2580f999f8 100644 --- a/mesh_handle/competences/element_data_competences.hxx +++ b/mesh_handle/competences/element_data_competences.hxx @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -132,12 +133,12 @@ class element_data_mesh_competence_impl: public t8_crtp_basic { void exchange_ghost_data () { - // t8_forest_ghost_exchange_data expects an sc_array, so we need to wrap our data array to one. - sc_array* sc_array_wrapper; + // Extend element data array to hold also the ghost elements. const auto num_local_elements = this->underlying ().get_num_local_elements (); const auto num_ghosts = this->underlying ().get_num_ghosts (); m_element_data.resize (num_local_elements + num_ghosts); - sc_array_wrapper + // t8_forest_ghost_exchange_data expects an sc_array, so we need to wrap our data array to one. + sc_array* sc_array_wrapper = sc_array_new_data (m_element_data.data (), sizeof (ElementDataType), num_local_elements + num_ghosts); // Data exchange: entries with indices > num_local_elements will get overwritten. @@ -292,6 +293,37 @@ class interpolate_element_data_mesh_competence: return m_partition_for_coarsening.has_value (); } + /** Repartition the element data so it follows a newly partitioned forest. + * The element data currently belongs to \a forest_from; this moves it to the layout of \a forest_to, which must + * have been created by partitioning \a forest_from. Analogous to \ref exchange_ghost_data, but using + * \ref t8_forest_partition_data. This function is called from \ref mesh::commit after the interpolated data + * has been produced and the partitioned forest has been committed. + * \param [in] forest_from The (committed) forest the current element data belongs to. + * \param [in] forest_to The committed forest that was partitioned from \a forest_from. + * \note Both forests could also be accessed directly (by this->underlying()) but this requires that the function is + * called on the exact right states of m_forest and m_uncommitted_forest. + * Providing the variables is the saver implementation. + */ + void + repartition_element_data (t8_forest_t forest_from, t8_forest_t forest_to) + { + using element_data_type = typename TUnderlying::ElementDataType; + // Take ownership of old data and wrap into sc_array. This is because the forest functions expect an sc_array. + std::vector old_data = this->underlying ().take_element_data (); + sc_array* data_in = sc_array_new_data (old_data.data (), sizeof (element_data_type), old_data.size ()); + const t8_locidx_t num_new_local = t8_forest_get_local_num_leaf_elements (forest_to); + // Define vector for the new data and wrap it. + std::vector partitioned_data (num_new_local); + sc_array* data_out = sc_array_new_data (partitioned_data.data (), sizeof (element_data_type), num_new_local); + // Partition magic using the forest function. + t8_forest_partition_data (forest_from, forest_to, data_in, data_out); + // Clean up. + sc_array_destroy (data_in); + sc_array_destroy (data_out); + // Set the partitioned element data to the mesh. + this->underlying ().set_element_data (std::move (partitioned_data)); + } + internal_interpolate_callback_type m_interpolate_callback; /**< The wrapped element-index based interpolation callback, * applied on the next \ref mesh::commit. */ diff --git a/mesh_handle/mesh.hxx b/mesh_handle/mesh.hxx index eee25ec131..6cc222fea8 100644 --- a/mesh_handle/mesh.hxx +++ b/mesh_handle/mesh.hxx @@ -500,7 +500,10 @@ class mesh: public TMeshCompetencePack::template applyrepartition_element_data (m_uncommitted_forest.value (), m_forest); } else { // Update underlying forest of the mesh for the case where we do not repartition. diff --git a/test/mesh_handle/t8_gtest_interpolate.cxx b/test/mesh_handle/t8_gtest_interpolate.cxx index 33c77b3343..36f58eef79 100644 --- a/test/mesh_handle/t8_gtest_interpolate.cxx +++ b/test/mesh_handle/t8_gtest_interpolate.cxx @@ -35,6 +35,7 @@ along with t8code; if not, write to the Free Software Foundation, Inc., #include #include #include +#include #include #include @@ -89,15 +90,15 @@ interpolate_callback ([[maybe_unused]] const TMeshClass& mesh_old, [[maybe_unuse TEST (t8_gtest_handle_data, test_interpolate_data) { - const int level = 2; + const int level = 3; using mesh_class = t8_mesh_handle::mesh>; - auto mesh = t8_mesh_handle::handle_hypercube_hybrid_uniform_default (level, sc_MPI_COMM_WORLD); + auto mesh = t8_mesh_handle::handle_hypercube_uniform_default (T8_ECLASS_HEX, level, sc_MPI_COMM_WORLD); dummy_user_data user_data_adapt { - t8_3D_vec { 0.5, 0.5, 1 }, /**< Midpoints of the sphere. */ - 0.2, /**< Refine if inside this radius. */ - 0.4 /**< Coarsen if outside this radius. */ + t8_3D_vec { 0.5, 0.5, 0.5 }, /**< Midpoints of the sphere. */ + 0.2, /**< Refine if inside this radius. */ + 0.4 /**< Coarsen if outside this radius. */ }; interpolate_user_data dummy_interpolate_user_data { 1 }; /**< Level changes by one per adaptation step. */ @@ -112,21 +113,28 @@ TEST (t8_gtest_handle_data, test_interpolate_data) mesh->set_adapt ( mesh_class::mesh_adapt_callback_wrapper (adapt_callback_test, user_data_adapt)); mesh->set_balance (); + mesh->set_partition (); mesh->set_interpolate_callback (mesh_class::mesh_interpolate_callback_wrapper ( interpolate_callback, dummy_interpolate_user_data)); mesh->commit (); // Test interpolation. - bool tested_something = false; + // Variables to demonstrate that we tested interpolation for coarsening and for refinement. + bool found_refined = false; + bool found_coarsened = false; for (auto& elem : *mesh) { - if (!tested_something && (elem.get_level () != level)) { - tested_something = true; + if (!found_refined && (elem.get_level () > level)) { + found_refined = true; } - EXPECT_EQ (elem.get_level (), elem.get_element_data ().level); - if (!(elem.get_element_data ().level > level)) { - // For refined elements, the volume is averaged and thus not exact. Only test coarsening. - EXPECT_EQ (elem.get_volume (), elem.get_element_data ().volume); + if (!found_coarsened && (elem.get_level () < level)) { + found_coarsened = true; } + + // Check that element data and actual geometric data match. + EXPECT_EQ (elem.get_level (), elem.get_element_data ().level); + // For hexes, our interpolation method for volume is accurate. + EXPECT_NEAR (elem.get_volume (), elem.get_element_data ().volume, T8_PRECISION_SQRT_EPS); } - EXPECT_TRUE (tested_something); + EXPECT_TRUE (found_refined); + EXPECT_TRUE (found_coarsened); } From 4415d6a1a0ec3f8b4dc98450c612ba158fb43f56 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Wed, 29 Jul 2026 16:59:37 +0200 Subject: [PATCH 18/20] resolve doxygen errors --- mesh_handle/competences/element_data_competences.hxx | 11 ++++++----- mesh_handle/mesh.hxx | 10 ++++++---- test/mesh_handle/t8_gtest_common.hxx | 9 +++++---- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/mesh_handle/competences/element_data_competences.hxx b/mesh_handle/competences/element_data_competences.hxx index 2580f999f8..d040bf4634 100644 --- a/mesh_handle/competences/element_data_competences.hxx +++ b/mesh_handle/competences/element_data_competences.hxx @@ -50,7 +50,7 @@ namespace detail /** Helper function to wrap a span based interpolation callback (see \ref mesh::interpolate_callback_type) into * the element-index based \ref interpolate_element_data_mesh_competence::internal_interpolate_callback_type. * The returned wrapper receives the index/count pairs, builds the element spans, and forwards them to \a callback. - * The spans are built here and not in \ref interpolate_mesh_competence because \a TMesh is complete, + * The spans are built here and not in \ref interpolate_element_data_mesh_competence because \a TMesh is complete, * so element_class is nameable — which it is not inside the competence (see note on * \ref interpolate_element_data_mesh_competence::internal_interpolate_callback_type). * This is used in \ref interpolate_element_data_mesh_competence::set_interpolate_callback @@ -221,7 +221,7 @@ struct element_data_element_competence: public t8_crtp_operator @@ -295,9 +295,10 @@ class interpolate_element_data_mesh_competence: /** Repartition the element data so it follows a newly partitioned forest. * The element data currently belongs to \a forest_from; this moves it to the layout of \a forest_to, which must - * have been created by partitioning \a forest_from. Analogous to \ref exchange_ghost_data, but using - * \ref t8_forest_partition_data. This function is called from \ref mesh::commit after the interpolated data - * has been produced and the partitioned forest has been committed. + * have been created by partitioning \a forest_from. + * Analogous to \ref element_data_mesh_competence_impl::exchange_ghost_data, but using \ref t8_forest_partition_data. + * This function is called from \ref mesh::commit after the interpolated data has been produced and the partitioned + * forest has been committed. * \param [in] forest_from The (committed) forest the current element data belongs to. * \param [in] forest_to The committed forest that was partitioned from \a forest_from. * \note Both forests could also be accessed directly (by this->underlying()) but this requires that the function is diff --git a/mesh_handle/mesh.hxx b/mesh_handle/mesh.hxx index 6cc222fea8..04d2b89de5 100644 --- a/mesh_handle/mesh.hxx +++ b/mesh_handle/mesh.hxx @@ -122,7 +122,7 @@ class mesh: public TMeshCompetencePack::template apply static interpolate_callback_type diff --git a/test/mesh_handle/t8_gtest_common.hxx b/test/mesh_handle/t8_gtest_common.hxx index 6af725fcce..5d5c56a9da 100644 --- a/test/mesh_handle/t8_gtest_common.hxx +++ b/test/mesh_handle/t8_gtest_common.hxx @@ -43,16 +43,17 @@ struct dummy_user_data /** Dummy element data taken from a tutorial for test purposes. */ struct data_per_element { - int level; - double volume; + int level; /**< The level of the element. */ + double volume; /**< The volume of the element. */ + /** Comparison operator to check if two data entries are the same. */ bool operator== (const data_per_element &) const = default; }; /** Callback function for the mesh handle to decide for refining or coarsening of (a family of) elements. - * The function header fits the definition of \ref TMesh::adapt_callback_type_with_userdata. + * The function header fits the definition of \ref t8_mesh_handle::mesh::adapt_callback_type_with_userdata. * \tparam TMeshClass The mesh handle class. * \param [in] mesh The mesh that should be adapted. * \param [in] elements One element or a family of elements to consider for adaptation. @@ -79,7 +80,7 @@ adapt_callback_test ([[maybe_unused]] const TMeshClass &mesh, } /** Adapt callback implementation for a forest. - * This callback defines the same adaptation rules as \ref adapt_callback_test, + * This callback defines the same adaptation rules as adapt_callback_test defined above, * but it is used for the forest instead of the mesh handle. */ int From e56d6ef11cb6963520b3e2c941f7d3fc0cd521fd Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Thu, 6 Aug 2026 12:16:51 +0200 Subject: [PATCH 19/20] Adapted test to be valid for each process number --- .../competences/element_data_competences.hxx | 3 +- mesh_handle/mesh.hxx | 15 ++- .../competences/t8_gtest_dg_competences.cxx | 26 +---- .../t8_gtest_adapt_partition_balance.cxx | 50 +--------- test/mesh_handle/t8_gtest_common.hxx | 99 ++++++++++++++++--- test/mesh_handle/t8_gtest_interpolate.cxx | 40 +++++--- 6 files changed, 132 insertions(+), 101 deletions(-) diff --git a/mesh_handle/competences/element_data_competences.hxx b/mesh_handle/competences/element_data_competences.hxx index d040bf4634..5ed7bf8ae7 100644 --- a/mesh_handle/competences/element_data_competences.hxx +++ b/mesh_handle/competences/element_data_competences.hxx @@ -311,7 +311,8 @@ class interpolate_element_data_mesh_competence: using element_data_type = typename TUnderlying::ElementDataType; // Take ownership of old data and wrap into sc_array. This is because the forest functions expect an sc_array. std::vector old_data = this->underlying ().take_element_data (); - sc_array* data_in = sc_array_new_data (old_data.data (), sizeof (element_data_type), old_data.size ()); + sc_array* data_in = sc_array_new_data (old_data.data (), sizeof (element_data_type), + t8_forest_get_local_num_leaf_elements (forest_from)); const t8_locidx_t num_new_local = t8_forest_get_local_num_leaf_elements (forest_to); // Define vector for the new data and wrap it. std::vector partitioned_data (num_new_local); diff --git a/mesh_handle/mesh.hxx b/mesh_handle/mesh.hxx index 04d2b89de5..e9fbfd6c04 100644 --- a/mesh_handle/mesh.hxx +++ b/mesh_handle/mesh.hxx @@ -187,6 +187,16 @@ class mesh: public TMeshCompetencePack::template applym_partition_for_coarsening.value ()); - if (t8_forest_get_num_ghosts (m_uncommitted_forest.value ()) > 0) { - t8_forest_set_ghost (m_forest, true, T8_GHOST_FACES); + const t8_ghost_type_t ghost_type = m_uncommitted_forest.value ()->ghost_type; + if (ghost_type != T8_GHOST_NONE) { + t8_forest_set_ghost (m_forest, true, ghost_type); } t8_forest_commit (m_forest); diff --git a/test/mesh_handle/competences/t8_gtest_dg_competences.cxx b/test/mesh_handle/competences/t8_gtest_dg_competences.cxx index a9f89c9815..cd239faab0 100644 --- a/test/mesh_handle/competences/t8_gtest_dg_competences.cxx +++ b/test/mesh_handle/competences/t8_gtest_dg_competences.cxx @@ -26,6 +26,7 @@ along with t8code; if not, write to the Free Software Foundation, Inc., */ #include #include +#include #include #include @@ -38,27 +39,6 @@ struct rank_data_per_element int rank; ///< Rank of the element. }; -/** Callback function for the mesh handle to decide for refining or coarsening of (a family of) elements. - * The adaptation criterion is to refine every element with even id. - * The function header fits the definition of \ref TMesh::adapt_callback_type. - * \tparam TMeshClass The mesh handle class. - * \param [in] mesh The mesh that should be adapted. - * \param [in] elements One element or a family of elements to consider for adaptation. - * \return 1 if the first entry in \a elements should be refined, - * -1 if the family \a elements shall be coarsened, - * 0 else. - */ -template -int -mesh_adapt_callback_test_refine_second ([[maybe_unused]] const TMeshClass& mesh, - std::span elements) -{ - if ((elements[0].get_element_handle_id ()) % 2 == 0) { - return 1; - } - return 0; -} - /** Check the competence remote_ranks_mesh_competence for correctness. The ranks are set as data first, exchanged for * ghost elements and then checked against the competence functionality. */ @@ -70,7 +50,7 @@ TEST (t8_gtest_dg_competences, remote_ranks) union_competence_packs_type, data_mesh_competences_basic>>; auto mesh = handle_hypercube_hybrid_uniform_default (level, sc_MPI_COMM_WORLD, true, false); - mesh->set_adapt (mesh_adapt_callback_test_refine_second); + mesh->set_adapt (adapt_callback_refine_second); mesh->set_partition (); mesh->set_ghost (); mesh->commit (); @@ -111,7 +91,7 @@ TEST (t8_gtest_dg_competences, face_vector_mesh_competence) using namespace t8_mesh_handle; using mesh_class = mesh, dg_mesh_competences>; auto mesh = handle_hypercube_hybrid_uniform_default (level, sc_MPI_COMM_WORLD, true, false); - mesh->set_adapt (mesh_adapt_callback_test_refine_second); + mesh->set_adapt (adapt_callback_refine_second); mesh->set_partition (); mesh->set_ghost (); mesh->commit (); diff --git a/test/mesh_handle/t8_gtest_adapt_partition_balance.cxx b/test/mesh_handle/t8_gtest_adapt_partition_balance.cxx index 765a965345..3edf845561 100644 --- a/test/mesh_handle/t8_gtest_adapt_partition_balance.cxx +++ b/test/mesh_handle/t8_gtest_adapt_partition_balance.cxx @@ -24,7 +24,6 @@ along with t8code; if not, write to the Free Software Foundation, Inc., * \file t8_gtest_adapt_partition_balance.cxx * Tests for the adapt, partition and balance routines of mesh handle. */ -#include "t8_types/t8_vec.h" #include #include #include "t8_gtest_common.hxx" @@ -35,45 +34,6 @@ along with t8code; if not, write to the Free Software Foundation, Inc., #include #include -//--- Second callback type for testing purpose: Refine every second element. --- -/** Callback function for the mesh handle to decide for refining or coarsening of (a family of) elements. - * The adaptation criterion is to refine every element with even id. - * The function header fits the definition of \ref TMesh::adapt_callback_type. - * \tparam TMeshClass The mesh handle class. - * \param [in] mesh The mesh that should be adapted. - * \param [in] elements One element or a family of elements to consider for adaptation. - * \return 1 if the first entry in \a elements should be refined, - * -1 if the family \a elements shall be coarsened, - * 0 else. - */ -template -int -mesh_adapt_callback_test_refine_second ([[maybe_unused]] const TMeshClass &mesh, - std::span elements) -{ - if ((elements[0].get_element_handle_id ()) % 2 == 0) { - return 1; - } - return 0; -} - -/** Adapt callback implementation for a forest. The adaptation criterion is to refine every element with even id. - * This callback defines the same adaptation rules as \ref mesh_adapt_callback_test_refine_second, - * but it is used for the forest instead of the mesh handle. - */ -int -forest_adapt_callback_refine_second ([[maybe_unused]] t8_forest_t forest, [[maybe_unused]] t8_forest_t forest_from, - t8_locidx_t which_tree, [[maybe_unused]] t8_eclass_t tree_class, - t8_locidx_t lelement_id, [[maybe_unused]] const t8_scheme *scheme, - [[maybe_unused]] const int is_family, [[maybe_unused]] const int num_elements, - [[maybe_unused]] t8_element_t *elements[]) -{ - if ((t8_forest_get_tree_element_offset (forest_from, which_tree) + lelement_id) % 2 == 0) { - return 1; - } - return 0; -} - /** Test the adapt, partition and balance routines of a mesh handle. * The test compares the results of the mesh handle to a forest adapted with the same criterion and balanced and partitioned similarly. * Therefore, the check is based on the assumption that the forest functionality works as intended and is tested elsewhere. @@ -100,10 +60,10 @@ TEST (t8_gtest_handle_adapt, compare_with_forest) // Adapt mesh handle. mesh_handle.set_adapt ( - mesh_class::mesh_adapt_callback_wrapper (adapt_callback_test, user_data)); + mesh_class::mesh_adapt_callback_wrapper (adapt_callback_sphere, user_data)); mesh_handle.commit (); // Adapt forest classically. - forest = t8_forest_new_adapt (forest, forest_adapt_callback_example, 0, 0, &user_data); + forest = t8_forest_new_adapt (forest, forest_adapt_callback_sphere, 0, 0, &user_data); // Compare results. EXPECT_TRUE (t8_forest_is_equal (mesh_handle.get_forest (), forest)); @@ -112,7 +72,7 @@ TEST (t8_gtest_handle_adapt, compare_with_forest) mesh_handle.set_balance (); mesh_handle.set_partition (); mesh_handle.set_adapt ( - mesh_class::mesh_adapt_callback_wrapper (adapt_callback_test, user_data)); + mesh_class::mesh_adapt_callback_wrapper (adapt_callback_sphere, user_data)); mesh_handle.commit (); EXPECT_TRUE (mesh_handle.is_balanced ()); @@ -120,14 +80,14 @@ TEST (t8_gtest_handle_adapt, compare_with_forest) t8_forest_t forest_compare; t8_forest_init (&forest_compare); t8_forest_set_user_data (forest_compare, &user_data); - t8_forest_set_adapt (forest_compare, forest, forest_adapt_callback_example, false); + t8_forest_set_adapt (forest_compare, forest, forest_adapt_callback_sphere, false); t8_forest_set_partition (forest_compare, NULL, false); t8_forest_set_balance (forest_compare, NULL, false); t8_forest_commit (forest_compare); EXPECT_TRUE (t8_forest_is_equal (mesh_handle.get_forest (), forest_compare)); // Adapt again with the second callback. - mesh_handle.set_adapt (mesh_adapt_callback_test_refine_second); + mesh_handle.set_adapt (adapt_callback_refine_second); mesh_handle.commit (); t8_forest_t forest_refine; diff --git a/test/mesh_handle/t8_gtest_common.hxx b/test/mesh_handle/t8_gtest_common.hxx index 5d5c56a9da..9466d4f0da 100644 --- a/test/mesh_handle/t8_gtest_common.hxx +++ b/test/mesh_handle/t8_gtest_common.hxx @@ -19,6 +19,11 @@ You should have received a copy of the GNU General Public License along with t8code; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ + +/** + * \file t8_gtest_common.hxx + * Collection of data types and callbacks for mesh handle meshes and forests to be used in the mesh handle test cases. + */ #pragma once #include @@ -32,14 +37,6 @@ along with t8code; if not, write to the Free Software Foundation, Inc., #include #include -/** Dummy user data taken from tutorial for test purposes. */ -struct dummy_user_data -{ - t8_3D_vec midpoint; /**< The midpoint of our sphere. */ - double refine_if_inside_radius; /**< If an element's center is smaller than this value, we refine the element. */ - double coarsen_if_outside_radius; /**< If an element's center is larger this value, we coarsen its family. */ -}; - /** Dummy element data taken from a tutorial for test purposes. */ struct data_per_element { @@ -52,6 +49,78 @@ struct data_per_element = default; }; +/** Adapt callback for a mesh handle hypercube that exercises all possible operations (coarsen, refine, nothing). + * Adaption with this callback produces an unbalanced mesh (if the initial refinement level fits). + * The callback coarsens the left half (x < 0.5), refines a band 0.5 <= x < 0.75 and leaves the rest untouched. + * The centroid is used to determine the x-coordinate. + * \tparam TMeshClass The mesh handle class. + * \param [in] mesh Unused. + * \param [in] elements One element, or a family of elements to consider for adaptation. + * \return -1 to coarsen the family, 1 to refine the first element, 0 otherwise. + */ +template +int +adapt_callback_coarsen_left_refine_middle ([[maybe_unused]] const TMeshClass &mesh, + std::span elements) +{ + const double x_centroid = elements[0].get_centroid ()[0]; + if ((elements.size () > 1) && (x_centroid < 0.5)) { + return -1; // Coarsen the left half. + } + if (x_centroid >= 0.5 && x_centroid < 0.75) { + return 1; // Refine band 0.5 <= x < 0.75. + } + return 0; // Untouched right band. +} + +//--- Callback to refine every second element. --- +/** Callback function for the mesh handle to decide for refining or coarsening of (a family of) elements. + * The adaptation criterion is to refine every element with even id. + * The function header fits the definition of \ref TMesh::adapt_callback_type. + * \tparam TMeshClass The mesh handle class. + * \param [in] mesh The mesh that should be adapted. + * \param [in] elements One element or a family of elements to consider for adaptation. + * \return 1 if the first entry in \a elements should be refined, + * -1 if the family \a elements shall be coarsened, + * 0 else. + */ +template +int +adapt_callback_refine_second ([[maybe_unused]] const TMeshClass &mesh, + std::span elements) +{ + if ((elements[0].get_element_handle_id ()) % 2 == 0) { + return 1; + } + return 0; +} + +/** Adapt callback implementation for a forest. The adaptation criterion is to refine every element with even id. + * This callback defines the same adaptation rules as \ref adapt_callback_refine_second, + * but it is used for the forest instead of the mesh handle. + */ +int +forest_adapt_callback_refine_second ([[maybe_unused]] t8_forest_t forest, [[maybe_unused]] t8_forest_t forest_from, + t8_locidx_t which_tree, [[maybe_unused]] t8_eclass_t tree_class, + t8_locidx_t lelement_id, [[maybe_unused]] const t8_scheme *scheme, + [[maybe_unused]] const int is_family, [[maybe_unused]] const int num_elements, + [[maybe_unused]] t8_element_t *elements[]) +{ + if ((t8_forest_get_tree_element_offset (forest_from, which_tree) + lelement_id) % 2 == 0) { + return 1; + } + return 0; +} + +//--- Define callbacks to adapt according to a sphere-based criterion. --- +/** Dummy user data taken from tutorial for test purposes. This user data type is used in the sphere adapt callbacks. */ +struct dummy_user_data +{ + t8_3D_vec midpoint; /**< The midpoint of our sphere. */ + double refine_if_inside_radius; /**< If an element's center is smaller than this value, we refine the element. */ + double coarsen_if_outside_radius; /**< If an element's center is larger this value, we coarsen its family. */ +}; + /** Callback function for the mesh handle to decide for refining or coarsening of (a family of) elements. * The function header fits the definition of \ref t8_mesh_handle::mesh::adapt_callback_type_with_userdata. * \tparam TMeshClass The mesh handle class. @@ -64,8 +133,8 @@ struct data_per_element */ template int -adapt_callback_test ([[maybe_unused]] const TMeshClass &mesh, - std::span elements, const dummy_user_data &user_data) +adapt_callback_sphere ([[maybe_unused]] const TMeshClass &mesh, + std::span elements, const dummy_user_data &user_data) { auto element_centroid = elements[0].get_centroid (); double dist = t8_dist (element_centroid, user_data.midpoint); @@ -80,14 +149,14 @@ adapt_callback_test ([[maybe_unused]] const TMeshClass &mesh, } /** Adapt callback implementation for a forest. - * This callback defines the same adaptation rules as adapt_callback_test defined above, + * This callback defines the same adaptation rules as adapt_callback_sphere defined above, * but it is used for the forest instead of the mesh handle. */ int -forest_adapt_callback_example (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t which_tree, - [[maybe_unused]] t8_eclass_t tree_class, [[maybe_unused]] t8_locidx_t lelement_id, - [[maybe_unused]] const t8_scheme *scheme, const int is_family, - [[maybe_unused]] const int num_elements, t8_element_t *elements[]) +forest_adapt_callback_sphere (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t which_tree, + [[maybe_unused]] t8_eclass_t tree_class, [[maybe_unused]] t8_locidx_t lelement_id, + [[maybe_unused]] const t8_scheme *scheme, const int is_family, + [[maybe_unused]] const int num_elements, t8_element_t *elements[]) { const struct dummy_user_data *adapt_data = (const struct dummy_user_data *) t8_forest_get_user_data (forest); t8_3D_vec centroid; diff --git a/test/mesh_handle/t8_gtest_interpolate.cxx b/test/mesh_handle/t8_gtest_interpolate.cxx index 36f58eef79..1d2e46ac7c 100644 --- a/test/mesh_handle/t8_gtest_interpolate.cxx +++ b/test/mesh_handle/t8_gtest_interpolate.cxx @@ -88,19 +88,16 @@ interpolate_callback ([[maybe_unused]] const TMeshClass& mesh_old, [[maybe_unuse } } -TEST (t8_gtest_handle_data, test_interpolate_data) +/** Test for the data interpolation of the mesh handle using the interpolate_callback. */ +TEST (t8_gtest_handle_interpolate, test_interpolate_data) { - const int level = 3; + const int level + = 3; ///< 3 is the minimum level to have each case of refine, coarsen, copy but still include balance. using mesh_class = t8_mesh_handle::mesh>; auto mesh = t8_mesh_handle::handle_hypercube_uniform_default (T8_ECLASS_HEX, level, sc_MPI_COMM_WORLD); - dummy_user_data user_data_adapt { - t8_3D_vec { 0.5, 0.5, 0.5 }, /**< Midpoints of the sphere. */ - 0.2, /**< Refine if inside this radius. */ - 0.4 /**< Coarsen if outside this radius. */ - }; - interpolate_user_data dummy_interpolate_user_data { 1 }; /**< Level changes by one per adaptation step. */ + interpolate_user_data dummy_interpolate_user_data { 1 }; ///< Level changes by one per adaptation step. // Create element data for all local mesh elements and set via mesh competence. std::vector element_data; @@ -110,18 +107,29 @@ TEST (t8_gtest_handle_data, test_interpolate_data) mesh->set_element_data (std::move (element_data)); // Adapt the mesh and set all options. - mesh->set_adapt ( - mesh_class::mesh_adapt_callback_wrapper (adapt_callback_test, user_data_adapt)); + mesh->set_adapt (adapt_callback_coarsen_left_refine_middle); mesh->set_balance (); mesh->set_partition (); + mesh->set_ghost (); mesh->set_interpolate_callback (mesh_class::mesh_interpolate_callback_wrapper ( interpolate_callback, dummy_interpolate_user_data)); mesh->commit (); + // Check basics. + EXPECT_TRUE (mesh->is_balanced ()); + EXPECT_TRUE (mesh->get_num_ghosts () > 0); + // Ensure partitioned. + int mpi_size = 0; + int mpiret = sc_MPI_Comm_size (sc_MPI_COMM_WORLD, &mpi_size); + SC_CHECK_MPI (mpiret); + int num_global_elements_averaged = (int) (mesh->get_num_global_elements () / mpi_size); + EXPECT_LE (mesh->get_num_local_elements (), num_global_elements_averaged + 1); + EXPECT_GE (mesh->get_num_local_elements (), num_global_elements_averaged - 1); + // Test interpolation. // Variables to demonstrate that we tested interpolation for coarsening and for refinement. - bool found_refined = false; - bool found_coarsened = false; + int found_refined = false; + int found_coarsened = false; for (auto& elem : *mesh) { if (!found_refined && (elem.get_level () > level)) { found_refined = true; @@ -129,12 +137,14 @@ TEST (t8_gtest_handle_data, test_interpolate_data) if (!found_coarsened && (elem.get_level () < level)) { found_coarsened = true; } - // Check that element data and actual geometric data match. EXPECT_EQ (elem.get_level (), elem.get_element_data ().level); // For hexes, our interpolation method for volume is accurate. EXPECT_NEAR (elem.get_volume (), elem.get_element_data ().volume, T8_PRECISION_SQRT_EPS); } - EXPECT_TRUE (found_refined); - EXPECT_TRUE (found_coarsened); + int global_refined = 0, global_coarsened = 0; + sc_MPI_Allreduce (&found_refined, &global_refined, 1, sc_MPI_INT, sc_MPI_LOR, sc_MPI_COMM_WORLD); + sc_MPI_Allreduce (&found_coarsened, &global_coarsened, 1, sc_MPI_INT, sc_MPI_LOR, sc_MPI_COMM_WORLD); + EXPECT_TRUE (global_refined); + EXPECT_TRUE (global_coarsened); } From a7bdf99993b6de012ca60433499b4105270cae7d Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Thu, 6 Aug 2026 13:50:41 +0200 Subject: [PATCH 20/20] resolve doxygen error --- test/mesh_handle/t8_gtest_common.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/mesh_handle/t8_gtest_common.hxx b/test/mesh_handle/t8_gtest_common.hxx index 9466d4f0da..5405cf0fdd 100644 --- a/test/mesh_handle/t8_gtest_common.hxx +++ b/test/mesh_handle/t8_gtest_common.hxx @@ -76,7 +76,7 @@ adapt_callback_coarsen_left_refine_middle ([[maybe_unused]] const TMeshClass &me //--- Callback to refine every second element. --- /** Callback function for the mesh handle to decide for refining or coarsening of (a family of) elements. * The adaptation criterion is to refine every element with even id. - * The function header fits the definition of \ref TMesh::adapt_callback_type. + * The function header fits the definition of \ref t8_mesh_handle::mesh::adapt_callback_type. * \tparam TMeshClass The mesh handle class. * \param [in] mesh The mesh that should be adapted. * \param [in] elements One element or a family of elements to consider for adaptation.