Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions mesh_handle/adapt.hxx
Original file line number Diff line number Diff line change
@@ -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 <t8.h>
#include <t8_forest/t8_forest_general.h>
#include "mesh.hxx"
#include <vector>
#include <memory>

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 <typename TMesh>
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<typename TMesh::element_class> 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<MeshAdaptContextBase> 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 ();
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<t8_forest_t, std::unique_ptr<MeshAdaptContextBase>>&
get_map ()
{
static std::unordered_map<t8_forest_t, std::unique_ptr<MeshAdaptContextBase>> 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
122 changes: 98 additions & 24 deletions mesh_handle/mesh.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@
#pragma once

#include <t8.h>
#include <t8_forest/t8_forest_general.h>
#include "element.hxx"
#include "competence_pack.hxx"
#include "adapt.hxx"
#include <t8_forest/t8_forest_general.h>
#include <t8_forest/t8_forest_ghost.h>
#include <vector>
#include <type_traits>
#include <functional>
#include <memory>

namespace t8_mesh_handle
{
Expand Down Expand Up @@ -69,6 +72,20 @@ class mesh {
using mesh_iterator =
typename std::vector<element_class>::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 more 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<int (const SelfType& mesh, const std::vector<element_class>& elements)>;

/**
* Constructor for a mesh of the handle.
* \param [in] forest The forest from which the mesh should be created.
Expand All @@ -90,6 +107,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.
Expand Down Expand Up @@ -120,6 +138,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.
Expand Down Expand Up @@ -191,16 +220,72 @@ class mesh {
return const_cast<element_class&> (static_cast<const mesh*> (this)->operator[] (local_index));
}

/**
* Getter for the forest the mesh is defined for.
* \return The forest the mesh is defined for.
// --- Methods to change the mesh, e.g. adapt, partition, balance, ... ---
/** 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.
*/
t8_forest_t
get_forest () const
void
set_adapt (adapt_callback_type adapt_callback, bool recursive)
{
return m_forest;
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<detail::MeshAdaptContext<SelfType>> (*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);
}

/** 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.
*/
void
commit ()
{
if (!std::is_void<TUserDataType>::value) {
t8_forest_set_user_data (m_uncommitted_forest.value (), t8_forest_get_user_data (m_forest));
}
t8_forest_commit (m_uncommitted_forest.value ());
detail::AdaptRegistry::unregister_context (m_uncommitted_forest.value ());
if (!std::is_void<TElementDataType>::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 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.
Expand Down Expand Up @@ -275,23 +360,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<TElementDataType>::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.
*/
Expand All @@ -316,6 +384,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);
Expand All @@ -333,6 +405,8 @@ class mesh {
std::vector<element_class> m_ghosts; /**< Vector storing the (local) ghost elements. */
std::conditional_t<!std::is_void_v<TElementDataType>, std::vector<TElementDataType>, std::nullptr_t>
m_element_data; /**< Vector storing the (local) element data. */
std::optional<t8_forest_t>
m_uncommitted_forest; /**< Forest in which the set flags are set for a new forest before committing. */
};

} // namespace t8_mesh_handle
Loading