diff --git a/mesh_handle/adapt.hxx b/mesh_handle/adapt.hxx new file mode 100644 index 0000000000..04b2fb5d44 --- /dev/null +++ b/mesh_handle/adapt.hxx @@ -0,0 +1,211 @@ +/* + 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 adapt.hxx + * This file provides helper functionality to adapt a \ref t8_mesh_handle::mesh + * according to a user defined callback. + */ + +#pragma once + +#include +#include +#include "mesh.hxx" +#include +#include + +namespace t8_mesh_handle +{ + +/** Namespace detail to hide implementation details from the user. */ +namespace detail +{ + +/** Virtual base class for mesh adaptation contexts. + * We need this base class and not only \ref MeshAdaptContext for the \ref AdaptRegistry. + * AdaptRegistry should not be templated because we need to access registered contexts in \ref mesh_adapt_callback_wrapper, + * where we do not know the type of the mesh. Therefore, we work with a map of forests to instances of this base class to remain template free. + */ +struct MeshAdaptContextBase +{ + /** Virtual destructor for safe polymorphic deletion. + */ + virtual ~MeshAdaptContextBase () = default; + + /** Pure virtual callback for mesh adaptation. + * \param[in] lelement_handle_id Local element ID in the mesh handle. + * \param [in] is_family If 1, the entries in \a elements form a family. If 0, they do not. + * \param [in] num_elements The number of entries in \a elements. + * \param [in] elements Pointers to members of a family or, if \a is_family is zero, pointer to one element. + * \return 1 if the first entry in \a elements should be refined, + * -1 if the family \a elements shall be coarsened, + * 0 else. + */ + virtual int + adapt_mesh (const t8_locidx_t lelement_handle_id, const int is_family, const int num_elements, + t8_element_t* elements[]) + = 0; +}; + +/** Templated mesh adaptation context holding the mesh handle and the user defined callback. + * Class inherits from \ref MeshAdaptContextBase and implements the virtual adapt callback using the mesh and the callback. + * \tparam TMesh The mesh handle class. + */ +template +struct MeshAdaptContext final: MeshAdaptContextBase +{ + /** Constructor of the context with the mesh handle and the user defined callback. + * \param [in] mesh_handle The mesh handle to adapt. + * \param [in] adapt_callback The adapt callback. + */ + MeshAdaptContext (TMesh& mesh_handle, typename TMesh::adapt_callback_type adapt_callback) + : m_mesh_handle (mesh_handle), m_adapt_callback (std::move (adapt_callback)) + { + } + + /** Callback for mesh adaptation using the user defined adapt callback. + * \param [in] lelement_handle_id Local flat element ID in the mesh handle. + * \param [in] is_family If 1, the entries in \a elements form a family. If 0, they do not. + * \param [in] num_elements The number of entries in \a elements. + * \param [in] elements Pointers to members of a family or, if \a is_family is zero, pointer to one element. + * \return 1 if the first entry in \a elements should be refined, + * -1 if the family \a elements shall be coarsened, + * 0 else. + */ + int + adapt_mesh (const t8_locidx_t lelement_handle_id, const int is_family, const int num_elements, + t8_element_t* elements[]) override + { + // Check if adapt callback is set and call it using the correct mesh handle function arguments. + T8_ASSERTF (m_adapt_callback, "No adapt callback set."); + std::vector element_vec; + if (is_family) { + for (int i = 0; i < num_elements; i++) { + element_vec.push_back (m_mesh_handle[lelement_handle_id + i]); + } + } + else { + element_vec.push_back (m_mesh_handle[lelement_handle_id]); + } + return m_adapt_callback (m_mesh_handle, element_vec); + } + + private: + TMesh& m_mesh_handle; /**< The mesh handle to adapt. */ + 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. + * This globally accessible static class is required to get the handle and the callback in the forest callback, + * as the predefined header permits to give these as function arguments. + */ +class AdaptRegistry { + public: + /** Static function to register \a context using \a forest as identifier. + * This makes the context publicly available using the Registry. + * \param [in] forest The forest identifier. In our case, this is the forest to be adapted + * and not the forest from which we adapt. + * \param [in] context The context to register. Use unique pointer to ensure proper memory management and ownership. + */ + 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) { + throw std::logic_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 forest to be adapted. + */ + static void + unregister_context (t8_forest_t forest) + { + auto& map = get_map (); + [[maybe_unused]] const auto erased = map.erase (forest); + 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 forest to be adapted. + * \return Pointer to the context registered with the id \a forest if found, nullptr otherwise. + */ + static MeshAdaptContextBase* + get (t8_forest_t forest) + { + auto& map = get_map (); + auto it = map.find (forest); + return it != map.end () ? it->second.get () : nullptr; + } + + private: + /** Get the static map associating t8_forest_t with MeshAdaptContextBase 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 MeshAdaptContextBase references. + */ + static std::unordered_map>& + get_map () + { + static std::unordered_map> map; + return map; + } +}; + +/** Wrapper around the mesh handle adapt functionality to be able to pass the callback to the classic adapt routine of a forest. + * The function header fits the definition of \ref t8_forest_adapt_t. + * \param [in] forest Unused; forest to which the new elements belong. + * \param [in] forest_from Forest that is adapted. + * \param [in] which_tree Local tree containing \a elements. + * \param [in] tree_class Unused; eclass of \a which_tree. + * \param [in] lelement_id The local element id in the tree of the first element in elements. + * \param [in] scheme Unused; scheme of the forest. + * \param [in] is_family If 1, the entries in \a elements form a family. If 0, they do not. + * \param [in] num_elements The number of entries in \a elements. + * \param [in] elements Pointers to a family or, if \a is_family is zero, pointer to one element. + * \return 1 if the first entry in \a elements should be refined, + * -1 if the family \a elements shall be coarsened, + * 0 else. + */ +int +mesh_adapt_callback_wrapper ([[maybe_unused]] t8_forest_t forest, 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, const int is_family, const int num_elements, + t8_element_t* elements[]) +{ + // Get static adapt context from the registry. + // Via this, we can access the mesh handle and the user defined adapt callback that uses mesh handle functionality. + auto* context = AdaptRegistry::get (forest); + if (!context) { + t8_global_infof ( + "Something went wrong while registering the adaptation callbacks. Please check your implementation."); + return 0; // No adaptation as default. + } + // Convert to index used in the mesh handle. + const t8_locidx_t mesh_index = t8_forest_get_tree_element_offset (forest_from, which_tree) + lelement_id; + // Call the actual adapt callback stored in the context. + return context->adapt_mesh (mesh_index, is_family, num_elements, elements); +} + +} // namespace detail +} // namespace t8_mesh_handle diff --git a/mesh_handle/mesh.hxx b/mesh_handle/mesh.hxx index 36dfec261c..8fda61347f 100644 --- a/mesh_handle/mesh.hxx +++ b/mesh_handle/mesh.hxx @@ -27,12 +27,15 @@ #pragma once #include -#include #include "element.hxx" #include "competence_pack.hxx" +#include "adapt.hxx" +#include #include #include #include +#include +#include namespace t8_mesh_handle { @@ -48,19 +51,16 @@ concept T8MPISafeType * \tparam TCompetencePack The competences you want to add to the default functionality of the mesh. * \see element for more details on the choice of the template parameter. * \note Please pack your competences using the \ref competence_pack class. - * \tparam TUserDataType The user data type you want to associate with the mesh. Use void (this is also the default) if you do not want to set user data. * \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. * Use void (this is also the default) if you do not want to set element data. */ -template , typename TUserDataType = void, - T8MPISafeType TElementDataType = void> +template , T8MPISafeType TElementDataType = void> class mesh { public: - using SelfType = mesh; /**< Type of the current class with all template parameters specified. */ - using UserDataType = TUserDataType; /**< Make Type of the user data accessible. */ - using ElementDataType = TElementDataType; /**< Make Type of the element data accessible. */ + using SelfType + = mesh; /**< Type of the current class with all template parameters specified. */ + using ElementDataType = TElementDataType; /**< Make Type of the element data accessible. */ using element_class = TCompetencePack::template apply; /**< The element class of the mesh with given competences. */ friend element_class; /**< Element class as friend such that private members (e.g. the forest) can be accessed. */ @@ -69,6 +69,37 @@ class mesh { using mesh_iterator = typename std::vector::iterator; /**< Non-const iterator type for the mesh elements. */ + /** 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 coarsened + * or only the first element should be refined. + * Family means multiple elements that can be coarsened into one parent element. + * \see set_adapt for the usage of this callback. + * \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. + */ + using adapt_callback_type = std::function& elements)>; + + /** Templated callback function prototype to decide for refining and coarsening of a family of elements + * or one element in a mesh handle including user data. + * See the version without user_data \ref adapt_callback_type for more details. + * Use \ref mesh_adapt_callback_wrapper to convert this type into \ref adapt_callback_type + * to be able to pass the callback to \ref set_adapt. + * \tparam TUserDataType The type of the user data to be passed to the callback. + * \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 + using adapt_callback_type_with_userdata + = std::function& elements, TUserDataType user_data)>; + /** * Constructor for a mesh of the handle. * \param [in] forest The forest from which the mesh should be created. @@ -90,6 +121,7 @@ class mesh { t8_forest_unref (&m_forest); } + // --- Getter for mesh related information. --- /** * Getter for the number of local elements in the mesh. * \return Number of local elements in the mesh. @@ -120,6 +152,17 @@ class mesh { return t8_forest_get_dimension (m_forest); } + /** + * Getter for the forest the mesh is defined for. + * \return The forest the mesh is defined for. + */ + t8_forest_t + get_forest () const + { + return m_forest; + } + + // --- Methods to access elements. --- /** * Returns a constant iterator to the first (local) mesh element. * \return Constant iterator to the first (local) mesh element. @@ -191,38 +234,92 @@ class mesh { return const_cast (static_cast (this)->operator[] (local_index)); } - /** - * Getter for the forest the mesh is defined for. - * \return The forest the mesh is defined for. - */ - t8_forest_t - get_forest () const + // --- 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. + * This is required to pass an adapt callback with user data to \ref set_adapt. + * \tparam TUserDataType The type of the user data to be passed to the callback. + * \param [in] adapt_callback_with_userdata The adapt callback including user data. + * \param [in] user_data The user data to be used during the adaptation process. + * \return An adapt callback without user data parameter that can be passed to \ref set_adapt. + */ + template + static adapt_callback_type + mesh_adapt_callback_wrapper (adapt_callback_type_with_userdata adapt_callback_with_userdata, + const TUserDataType& user_data) { - return m_forest; + return [=] (const SelfType& mesh, const std::vector& elements) { + return adapt_callback_with_userdata (mesh, elements, user_data); + }; } - /** - * Set the user data of the mesh. This can i.e. be used to pass user defined arguments to the adapt routine. - * \param [in] data The user data of class TUserDataType. Data will never be touched by mesh handling routines. + /** Set an adapt function to be used to adapt the mesh on committing. + * \param [in] adapt_callback The adapt callback used on committing. + * \param [in] recursive Specifying whether adaptation is to be done recursively or not. + * \note The adaptation is carried out only when \ref commit is called. + * \note This setting can be combined with set_partition and set_balance. The order in which + * these operations are executed is always 1) Adapt 2) Partition 3) Balance. */ - template ::value>> void - set_user_data (const UserDataType& data) + set_adapt (adapt_callback_type adapt_callback, bool recursive) { - t8_forest_set_user_data (m_forest, data); + 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::AdaptRegistry::register_context ( + m_uncommitted_forest.value (), + 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); } - /** - * Get the user data of the mesh. - * \return The user data previously set using \ref set_user_data. + /** Enable or disable the creation of a layer of ghost elements. + * \param [in] do_ghost If true a ghost layer will be created. + * \param [in] ghost_type Controls which neighbors count as ghost elements, + * currently only T8_GHOST_FACES is supported. This value + * is ignored if \a do_ghost = false. + */ + void + set_ghost (bool do_ghost = true, t8_ghost_type_t ghost_type = T8_GHOST_FACES) + { + if (!m_uncommitted_forest.has_value ()) { + t8_forest_t new_forest; + t8_forest_init (&new_forest); + m_uncommitted_forest = new_forest; + } + t8_forest_set_ghost (m_uncommitted_forest.value (), do_ghost, ghost_type); + } + + /** After allocating and adding properties to the mesh, commit the changes. + * This call updates the internal state of the mesh. + * The forest used to define the mesh handle is replaced in this function. + * The previous forest is unreferenced. Call \ref t8_forest_ref before if you want to keep it alive. + * Specialize the update with calls like \ref set_adapt first. */ - template ::value>> - const UserDataType& - get_user_data () const + void + commit () { - return *static_cast (t8_forest_get_user_data (m_forest)); + if (!m_uncommitted_forest.has_value ()) { + t8_forest_t new_forest; + t8_forest_init (&new_forest); + m_uncommitted_forest = new_forest; + } + t8_forest_commit (m_uncommitted_forest.value ()); + detail::AdaptRegistry::unregister_context (m_uncommitted_forest.value ()); + if (!std::is_void::value) { + t8_global_infof ("Please note that the element data is not interpolated automatically during adaptation. Use the " + "function set_element_data() to provide new adapted element data.\n"); + } + m_forest = m_uncommitted_forest.value (); + m_uncommitted_forest = std::nullopt; + update_elements (); } + // --- Methods to set and get user and element data and exchange data between processes. --- /** * Set the element data vector. The vector should have the length of num_local_elements. * \param [in] element_data The element data vector to set with one entry of class TElementDataType @@ -275,23 +372,6 @@ class mesh { } private: - /** - * Setter for the forest. - * \param [in] input_forest The forest from which the mesh should be a wrapper. - */ - void - set_forest (t8_forest_t input_forest) - { - T8_ASSERT (t8_forest_is_committed (input_forest)); - m_forest = input_forest; - update_elements (); - if constexpr (!std::is_void::value) { - t8_global_infof ("The forest of the mesh handle has been updated. Please note that the element data in the mesh " - "has to be updated accordingly. Use the function set_element_data() to provide element data " - "fitting to the new forest.\n"); - } - } - /** * Update the storage of the mesh elements according to the current forest. */ @@ -316,6 +396,10 @@ class mesh { void update_ghost_elements () { + if (get_num_ghosts () == 0) { + m_ghosts.clear (); + return; + } m_ghosts.clear (); m_ghosts.reserve (get_num_ghosts ()); t8_locidx_t num_loc_trees = t8_forest_get_num_local_trees (m_forest); @@ -333,6 +417,8 @@ class mesh { std::vector m_ghosts; /**< Vector storing the (local) ghost elements. */ std::conditional_t, std::vector, std::nullptr_t> m_element_data; /**< Vector storing the (local) element data. */ + std::optional + m_uncommitted_forest; /**< Forest in which the set flags are set for a new forest before committing. */ }; } // namespace t8_mesh_handle diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 65e73dbd5a..77b3c56d99 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -217,6 +217,7 @@ if( T8CODE_BUILD_MESH_HANDLE ) add_t8_cpp_test( NAME t8_gtest_custom_competence_serial SOURCES mesh_handle/t8_gtest_custom_competence.cxx ) add_t8_cpp_test( NAME t8_gtest_cache_competence_serial SOURCES mesh_handle/t8_gtest_cache_competence.cxx ) add_t8_cpp_test( NAME t8_gtest_handle_data_parallel SOURCES mesh_handle/t8_gtest_handle_data.cxx ) + add_t8_cpp_test( NAME t8_gtest_handle_adapt_serial SOURCES mesh_handle/t8_gtest_adapt.cxx ) endif() copy_test_file( test_cube_unstructured_1.inp ) diff --git a/test/mesh_handle/t8_gtest_adapt.cxx b/test/mesh_handle/t8_gtest_adapt.cxx new file mode 100644 index 0000000000..5739c790a1 --- /dev/null +++ b/test/mesh_handle/t8_gtest_adapt.cxx @@ -0,0 +1,134 @@ +/* +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_adapt.cxx + * Tests for the adapt routines of mesh handles. + * This tests uses the callback and user data of tutorial step 3 as example. + * The adaptation criterion is to look at the midpoint coordinates of the current element and if + * they are inside a sphere around a given midpoint we refine, if they are outside, we coarsen. + */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** Dummy user data taken from tutorial for test purposes. */ +struct dummy_user_data +{ + t8_3D_point 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, + const std::vector &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_point 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 routine of a mesh handle. + * We compare the result to a classically adapted forest with similar callback. + */ +TEST (t8_gtest_handle_adapt, compare_adapt_with_forest) +{ + // Define forest, a mesh handle and user data. + const int level = 3; + t8_cmesh_t cmesh = t8_cmesh_new_hypercube_hybrid (sc_MPI_COMM_WORLD, 0, 0); + const t8_scheme *init_scheme = t8_scheme_new_default (); + t8_forest_t forest = t8_forest_new_uniform (cmesh, init_scheme, level, 0, sc_MPI_COMM_WORLD); + using mesh_class = t8_mesh_handle::mesh, dummy_user_data>; + mesh_class mesh_handle = mesh_class (forest); + struct dummy_user_data user_data = { + t8_3D_point ({ 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. + t8_forest_ref (forest); + + // Adapt mesh handle. + mesh_handle.set_adapt ( + mesh_class::mesh_adapt_callback_wrapper (adapt_callback_test, user_data), false); + mesh_handle.commit (); + // Adapt forest classically. + forest = t8_forest_new_adapt (forest, forest_adapt_callback_example, 0, 1, &user_data); + + // Compare results. + EXPECT_TRUE (t8_forest_is_equal (mesh_handle.get_forest (), forest)); + + // Clean up. + t8_forest_unref (&forest); +} diff --git a/test/mesh_handle/t8_gtest_handle_data.cxx b/test/mesh_handle/t8_gtest_handle_data.cxx index e03ae80190..6d8c7d9062 100644 --- a/test/mesh_handle/t8_gtest_handle_data.cxx +++ b/test/mesh_handle/t8_gtest_handle_data.cxx @@ -22,7 +22,7 @@ along with t8code; if not, write to the Free Software Foundation, Inc., /** * \file t8_gtest_handle_data.cxx - * Tests to check that \ref t8_mesh_handle::mesh user data and element data functionality works as intended. + * Tests to check that the element data functionality of the \ref t8_mesh_handle::mesh works as intended. */ #include @@ -30,50 +30,11 @@ along with t8code; if not, write to the Free Software Foundation, Inc., #include #include -#include -#include +#include #include -#include #include #include -// --- Test for user data. --- -// Dummy user data taken from a tutorial for test purposes. -struct dummy_user_data -{ - t8_3D_point 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. */ -}; - -/** Check that user data can be set and accesses for the handle. - */ -TEST (t8_gtest_handle_data, set_and_get_user_data) -{ - // Define forest and mesh handle. - const int level = 2; - t8_cmesh_t cmesh = t8_cmesh_new_hypercube_hybrid (sc_MPI_COMM_WORLD, 0, 0); - const t8_scheme *init_scheme = t8_scheme_new_default (); - t8_forest_t forest = t8_forest_new_uniform (cmesh, init_scheme, level, 0, sc_MPI_COMM_WORLD); - - using mesh_class = t8_mesh_handle::mesh, dummy_user_data>; - mesh_class mesh (forest); - - struct dummy_user_data user_data = { - t8_3D_point ({ 41, 42, 43 }), /* Midpoints of the sphere. */ - 0.2, /* Refine if inside this radius. */ - 0.4 /* Coarsen if outside this radius. */ - }; - - // Set user data for the mesh handle and check that the getter returns the same data. - mesh.set_user_data (&user_data); - auto mesh_user_data = mesh.get_user_data (); - EXPECT_EQ (mesh_user_data.midpoint, user_data.midpoint); - EXPECT_EQ (mesh_user_data.refine_if_inside_radius, user_data.refine_if_inside_radius); - EXPECT_EQ (mesh_user_data.coarsen_if_outside_radius, user_data.coarsen_if_outside_radius); -} - -// --- Test for element data. --- /** Dummy element data taken from a tutorial for test purposes. */ struct data_per_element { @@ -86,62 +47,60 @@ struct data_per_element */ TEST (t8_gtest_handle_data, set_and_get_element_data) { - // Define forest and mesh handle. const int level = 2; - t8_cmesh_t cmesh = t8_cmesh_new_hypercube_hybrid (sc_MPI_COMM_WORLD, 0, 0); - const t8_scheme *init_scheme = t8_scheme_new_default (); - t8_forest_t forest = t8_forest_new_uniform (cmesh, init_scheme, level, 1, sc_MPI_COMM_WORLD); + using mesh_class = t8_mesh_handle::mesh, data_per_element>; + auto mesh = t8_mesh_handle::handle_hypercube_hybrid_uniform_default (level, sc_MPI_COMM_WORLD, false, + true, false); + auto forest = mesh->get_forest (); - using mesh_class = t8_mesh_handle::mesh, void, data_per_element>; - mesh_class mesh (forest); - if ((mesh.get_dimension () > 1) && (mesh.get_num_local_elements () > 1)) { + if ((mesh->get_dimension () > 1) && (mesh->get_num_local_elements () > 1)) { // Ensure that we actually test with ghost elements. - EXPECT_GT (mesh.get_num_ghosts (), 0); + EXPECT_GT (mesh->get_num_ghosts (), 0); } // Create element data for all local mesh elements. std::vector element_data; - for (const auto &elem : mesh) { + for (const auto &elem : *mesh) { element_data.push_back ({ elem.get_level (), elem.get_volume () }); } - mesh.set_element_data (std::move (element_data)); + mesh->set_element_data (std::move (element_data)); // Get element data 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++) { + 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++) { EXPECT_EQ (mesh_element_data[ielem].level, level) << "ielem = " << ielem; - EXPECT_EQ (mesh_element_data[ielem].volume, mesh[ielem].get_volume ()) << "ielem = " << ielem; + EXPECT_EQ (mesh_element_data[ielem].volume, (*mesh)[ielem].get_volume ()) << "ielem = " << ielem; } t8_gloidx_t barrier = t8_forest_get_num_global_trees (forest) / 2.0; const int newlevel = 42; const double newvolume = 42.42; - for (auto &elem : mesh) { + for (auto &elem : *mesh) { if (t8_forest_global_tree_id (forest, elem.get_local_tree_id ()) < barrier) { elem.set_element_data ({ newlevel, newvolume }); } } - mesh.exchange_ghost_data (); - for (t8_locidx_t ielem = 0; ielem < mesh.get_num_local_elements (); ielem++) { - if (t8_forest_global_tree_id (forest, mesh[ielem].get_local_tree_id ()) < barrier) { - EXPECT_EQ (mesh[ielem].get_element_data ().level, newlevel) << "ielem = " << ielem; - EXPECT_EQ (mesh[ielem].get_element_data ().volume, newvolume) << "ielem = " << ielem; + mesh->exchange_ghost_data (); + for (auto &elem : *mesh) { + if (t8_forest_global_tree_id (forest, elem.get_local_tree_id ()) < barrier) { + EXPECT_EQ (elem.get_element_data ().level, newlevel); + EXPECT_EQ (elem.get_element_data ().volume, newvolume); } else { - EXPECT_EQ (mesh[ielem].get_element_data ().level, level) << "ielem = " << ielem; - EXPECT_EQ (mesh[ielem].get_element_data ().volume, mesh[ielem].get_volume ()) << "ielem = " << ielem; + EXPECT_EQ (elem.get_element_data ().level, level); + EXPECT_EQ (elem.get_element_data ().volume, elem.get_volume ()); } } - for (t8_locidx_t ighost = mesh.get_num_local_elements (); - ighost < mesh.get_num_local_elements () + mesh.get_num_ghosts (); ighost++) { - if (t8_forest_ghost_get_global_treeid (forest, - mesh[ighost].get_local_tree_id () - t8_forest_get_num_local_trees (forest)) + for (t8_locidx_t ighost = mesh->get_num_local_elements (); + ighost < mesh->get_num_local_elements () + mesh->get_num_ghosts (); ighost++) { + if (t8_forest_ghost_get_global_treeid ( + forest, (*mesh)[ighost].get_local_tree_id () - t8_forest_get_num_local_trees (forest)) < barrier) { - EXPECT_EQ (mesh[ighost].get_element_data ().level, newlevel); - EXPECT_EQ (mesh[ighost].get_element_data ().volume, newvolume); + EXPECT_EQ ((*mesh)[ighost].get_element_data ().level, newlevel); + EXPECT_EQ ((*mesh)[ighost].get_element_data ().volume, newvolume); } else { - EXPECT_EQ (mesh[ighost].get_element_data ().level, level); - EXPECT_EQ (mesh[ighost].get_element_data ().volume, mesh[ighost].get_volume ()); + EXPECT_EQ ((*mesh)[ighost].get_element_data ().level, level); + EXPECT_EQ ((*mesh)[ighost].get_element_data ().volume, (*mesh)[ighost].get_volume ()); } } }