From 0b677ddddaeef61268854e538fe6dd3b0ce9937f Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Mon, 11 May 2026 07:25:12 +0200 Subject: [PATCH 01/28] add example --- example/CMakeLists.txt | 2 + .../subelements/t8_quads_hanging_nodes.cxx | 196 ++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 example/subelements/t8_quads_hanging_nodes.cxx diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index e41e3148c3..1b83c49a8c 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -93,6 +93,8 @@ add_t8_example( NAME t8_example_spheres SOURCES remove/t8_exampl add_t8_example( NAME t8_example_gauss_blob SOURCES remove/t8_example_gauss_blob.cxx ) add_t8_example( NAME t8_example_empty_trees SOURCES remove/t8_example_empty_trees.cxx ) +add_t8_example( NAME t8_example_hanging_nodes SOURCES subelements/t8_quads_hanging_nodes.cxx ) + add_t8_example( NAME t8_version SOURCES version/t8_version.cxx ) # NOTE: The following examples are (currently) deprecated and no longer compiled. diff --git a/example/subelements/t8_quads_hanging_nodes.cxx b/example/subelements/t8_quads_hanging_nodes.cxx new file mode 100644 index 0000000000..f82923c1bd --- /dev/null +++ b/example/subelements/t8_quads_hanging_nodes.cxx @@ -0,0 +1,196 @@ +/* + 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_quads_hanging_nodes.cxx + * This is an example to demonstrate hanging node resolution for quads. + */ + +#include /* General t8code header, always include this. */ +#include /* cmesh definition and basic interface. */ +#include /* A collection of exemplary cmeshes */ +#include /* forest definition and basic interface. */ +#include /* save forest */ +#include /* geometrical information of the forest */ +#include /* default refinement scheme. */ +#include /* Basic operations on 3D vectors. */ + +struct t8_adapt_data +{ + double midpoint[3]; /* 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. */ +}; + +/** The adaptation callback function. + * \param [in] forest The current forest that is in construction. + * \param [in] forest_from The forest from which we adapt the current forest (in our case, the uniform forest) + * \param [in] which_tree The process local id of the current tree. + * \param [in] tree_class The eclass of \a which_tree. + * \param [in] lelement_id The tree local index of the current element (or the first of the family). + * \param [in] scheme The refinement scheme for this tree's element class. + * \param [in] is_family If 1, the first \a num_elements entries in \a elements form a family. If 0, they do not. + * \param [in] num_elements The number of entries in \a elements elements that are defined. + * \param [in] elements The element or family of elements to consider for refinement/coarsening. + */ +int +t8_adapt_callback (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[]) +{ + /* Our 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. */ + const struct t8_adapt_data *adapt_data = (const struct t8_adapt_data *) t8_forest_get_user_data (forest); + T8_ASSERT (adapt_data != NULL); + + /* Compute the element's centroid coordinates. */ + double centroid[3]; + t8_forest_element_centroid (forest_from, which_tree, elements[0], centroid); + + /* Compute the distance to our sphere midpoint. */ + 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; +} + +/* This is the adapt function, called for each element in a balanced forest during transition. + * We refine an element into a suitable transition cell if it has at most one hanging face */ +int +t8_remove_hanging_nodes_callback (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, + const t8_scheme *scheme, const int is_family, [[maybe_unused]] const int num_elements, + t8_element_t *elements[]) +{ + int subelement_type = 0; + /* We use a binary encoding (depending on the face enumeration), to determine which subelement type to use. + * Every face has a flag parameter, which is set to 1, if there is a neighbour with a higher level + * and to 0, if the level of the neighbour is at most the level of the element. + * + * f0 1 + * x - - x - - x x - - x - - x + * | | | \ | / | + * | | | \ | / | | f3 | f2 | f1 | f0 | + * f3 x | f2 --> 1 x - - x | 0 --> binary code (according to the face enumeration): | 1 | 0 | 0 | 1 | = 9 in base 10 + * | | | / \ | + * | elem | | / \ | + * x - - - - - x x - - - - - x + * f1 0 + * + */ + const int num_faces = scheme->element_get_num_faces (tree_class, elements[0]); + for (int iface = 0; iface < num_faces; iface++) { + const t8_element_t **neighbors; /**< Neighboring elements. */ + int *dual_faces_internal; /**< Face indices of the neighbor elements. */ + int num_neighbors; /**< Number of neighboring elements. */ + t8_locidx_t *neighids; /**< Neighboring elements ids. */ + t8_eclass_t neigh_class; /**< Neighboring elements tree class. */ + + t8_forest_leaf_face_neighbors (forest, which_tree, elements[0], &neighbors, iface, &dual_faces_internal, + &num_neighbors, &neighids, &neigh_class); + + if (num_neighbors > 1) { + subelement_type += 1 << ((num_faces - 1) - iface); + } + /* clean-up */ + if (num_neighbors > 0) { + // Free allocated memory. + T8_FREE (neighbors); + T8_FREE (dual_faces_internal); + T8_FREE (neighids); + } + } + + /* returning the right subelement types */ + if (subelement_type == 0) { /* in this case, there are no hanging nodes and we do not need to do anything */ + return 0; + } + else if (subelement_type == 15) { /* Normal 1:8 refinement */ + return 1; + } + else { /* use subelements and add 1 to every type, to avoid refine = 1 */ + return subelement_type + 1; + } +} + +/** Adapt forest according to callback. */ +t8_forest_t +t8_adapt_forest (t8_forest_t forest) +{ + t8_forest_t forest_adapt; + struct t8_adapt_data adapt_data = { + { 0.5, 0.5, 1 }, /* Midpoints of the sphere. */ + 0.2, /* Refine if inside this radius. */ + 0.4 /* Coarsen if outside this radius. */ + }; + forest_adapt = t8_forest_new_adapt (forest, t8_adapt_callback, 0, 0, &adapt_data); + return forest_adapt; +} + +/** Adapt forest according to callback. */ +t8_forest_t +t8_remove_hanging_nodes (t8_forest_t forest) +{ + t8_forest_t forest_adapt = t8_forest_new_adapt (forest, t8_remove_hanging_nodes_callback, 0, 0, NULL); + return forest_adapt; +} + +/** Entry point of the program. */ +int +main (int argc, char **argv) +{ + /* The uniform refinement level of the forest. */ + const int level = 3; + + int mpiret = sc_MPI_Init (&argc, &argv); + SC_CHECK_MPI (mpiret); + sc_init (sc_MPI_COMM_WORLD, 1, 1, NULL, SC_LP_ESSENTIAL); + t8_init (SC_LP_PRODUCTION); + + /* We will use MPI_COMM_WORLD as a communicator. */ + sc_MPI_Comm comm = sc_MPI_COMM_WORLD; + + /* ---Setup. Build cmesh and uniform forest.--- */ + /* Build a cube cmesh with tet, hex, and prism trees. */ + t8_cmesh_t cmesh = t8_cmesh_new_hypercube (T8_ECLASS_QUAD, comm, 0, 0, 0); + t8_forest_t forest = t8_forest_new_uniform (cmesh, t8_scheme_new_default (), level, 0, comm); // TODO: New scheme + + /* --- Adapt the forest. --- */ + forest = t8_adapt_forest (forest); + + // --- Remove hanging nodes via adapting again. --- + forest = t8_remove_hanging_nodes (forest); + //TODO: permit forest_from->incomplete_trees + + // --- Cleanup. --- + t8_forest_unref (&forest); + + sc_finalize (); + mpiret = sc_MPI_Finalize (); + SC_CHECK_MPI (mpiret); + + return 0; +} From 2ce7d915e3ac7348996b6f0d7e0e8511bfdff93a Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Tue, 19 May 2026 16:51:13 +0200 Subject: [PATCH 02/28] begin to construct subelement scheme --- .../subelements/t8_quads_hanging_nodes.cxx | 13 +- src/CMakeLists.txt | 1 + src/t8_schemes/t8_scheme.hxx | 6 +- .../t8_scheme_implementation.hxx | 1135 +++++++++++++++++ .../t8_subelement/t8_subelement.cxx | 71 ++ .../t8_subelement/t8_subelement.hxx | 41 + .../t8_subelement/t8_subelement_type.hxx | 43 + 7 files changed, 1303 insertions(+), 7 deletions(-) create mode 100644 src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx create mode 100644 src/t8_schemes/t8_subelement/t8_subelement.cxx create mode 100644 src/t8_schemes/t8_subelement/t8_subelement.hxx create mode 100644 src/t8_schemes/t8_subelement/t8_subelement_type.hxx diff --git a/example/subelements/t8_quads_hanging_nodes.cxx b/example/subelements/t8_quads_hanging_nodes.cxx index f82923c1bd..37db429d31 100644 --- a/example/subelements/t8_quads_hanging_nodes.cxx +++ b/example/subelements/t8_quads_hanging_nodes.cxx @@ -24,6 +24,7 @@ * This is an example to demonstrate hanging node resolution for quads. */ +#include "t8_schemes/t8_subelement/t8_subelement.hxx" #include /* General t8code header, always include this. */ #include /* cmesh definition and basic interface. */ #include /* A collection of exemplary cmeshes */ @@ -80,10 +81,10 @@ t8_adapt_callback (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t whic /* This is the adapt function, called for each element in a balanced forest during transition. * We refine an element into a suitable transition cell if it has at most one hanging face */ int -t8_remove_hanging_nodes_callback (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t which_tree, +t8_remove_hanging_nodes_callback (t8_forest_t forest, [[maybe_unused]] t8_forest_t forest_from, t8_locidx_t which_tree, [[maybe_unused]] t8_eclass_t tree_class, [[maybe_unused]] t8_locidx_t lelement_id, - const t8_scheme *scheme, const int is_family, [[maybe_unused]] const int num_elements, - t8_element_t *elements[]) + const t8_scheme *scheme, [[maybe_unused]] const int is_family, + [[maybe_unused]] const int num_elements, t8_element_t *elements[]) { int subelement_type = 0; /* We use a binary encoding (depending on the face enumeration), to determine which subelement type to use. @@ -176,15 +177,15 @@ main (int argc, char **argv) /* ---Setup. Build cmesh and uniform forest.--- */ /* Build a cube cmesh with tet, hex, and prism trees. */ t8_cmesh_t cmesh = t8_cmesh_new_hypercube (T8_ECLASS_QUAD, comm, 0, 0, 0); - t8_forest_t forest = t8_forest_new_uniform (cmesh, t8_scheme_new_default (), level, 0, comm); // TODO: New scheme + t8_forest_t forest = t8_forest_new_uniform (cmesh, t8_scheme_new_subelement (), level, 0, comm); // TODO: New scheme /* --- Adapt the forest. --- */ forest = t8_adapt_forest (forest); // --- Remove hanging nodes via adapting again. --- - forest = t8_remove_hanging_nodes (forest); + // forest = t8_remove_hanging_nodes (forest); //TODO: permit forest_from->incomplete_trees - + std::cout << "Scheme : " << t8_element_get_element_size (t8_forest_get_scheme (forest), T8_ECLASS_QUAD) << "\n"; // --- Cleanup. --- t8_forest_unref (&forest); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 53a710f213..36fb71efb0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -208,6 +208,7 @@ target_sources( T8 PRIVATE t8_schemes/t8_default/t8_default_vertex/t8_default_vertex.cxx t8_types/t8_vec.cxx t8_schemes/t8_standalone/t8_standalone.cxx + t8_schemes/t8_subelement/t8_subelement.cxx t8_vtk/t8_vtk.c t8_vtk/t8_vtk_writer.cxx t8_vtk/t8_vtk_write_ASCII.cxx diff --git a/src/t8_schemes/t8_scheme.hxx b/src/t8_schemes/t8_scheme.hxx index fdaf9510c4..a7ff8dee8f 100644 --- a/src/t8_schemes/t8_scheme.hxx +++ b/src/t8_schemes/t8_scheme.hxx @@ -43,6 +43,7 @@ #include #include #include +#include #include #if T8_ENABLE_DEBUG // Only needed for t8_debug_print_type @@ -96,10 +97,13 @@ struct t8_scheme t8_default_scheme_tet, t8_default_scheme_prism, t8_default_scheme_pyramid, + /* Standalone schemes */ t8_standalone_scheme, t8_standalone_scheme, t8_standalone_scheme, - t8_standalone_scheme + t8_standalone_scheme, + /* Subelement schemes */ + t8_subelementquad_scheme >; /* clang-format on */ diff --git a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx new file mode 100644 index 0000000000..adbb2f0f8c --- /dev/null +++ b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx @@ -0,0 +1,1135 @@ +/* + 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 + + 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_scheme_implementation.hxx + * An implementation for the class \ref t8_scheme in \ref t8_scheme.hxx. + */ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** A templated implementation of the scheme interface based on cutting planes. */ +struct t8_subelementquad_scheme: public t8_scheme_helpers +{ + public: + /** Constructor + */ + t8_subelementquad_scheme () noexcept + : m_element_size (sizeof (t8_subelement_element)), m_scheme_context (sc_mempool_new (m_element_size)) {}; + + protected: + // I am not sure why i even need this both variables. + size_t m_element_size; /**< The size in bytes of an element of class \a eclass */ + void *m_scheme_context; /**< Anonymous implementation context. */ + + public: + /** Destructor for all default schemes */ + ~t8_subelementquad_scheme () + { + T8_ASSERT (m_scheme_context != NULL); + SC_ASSERT (((sc_mempool_t *) m_scheme_context)->elem_count == 0); + sc_mempool_destroy ((sc_mempool_t *) m_scheme_context); + } + + /** Move constructor */ + t8_subelementquad_scheme (t8_subelementquad_scheme &&other) noexcept + : m_element_size (other.m_element_size), m_scheme_context (std::exchange (other.m_scheme_context, nullptr)) + { + } + + /** Move assignment operator */ + t8_subelementquad_scheme & + operator= (t8_subelementquad_scheme &&other) noexcept + { + if (this != &other) { + // Free existing resources of moved-to object + if (m_scheme_context) { + sc_mempool_destroy ((sc_mempool_t *) m_scheme_context); + } + + // Transfer ownership of resources + m_element_size = other.m_element_size; + m_scheme_context = other.m_scheme_context; + + // Leave the source object in a valid state + other.m_scheme_context = nullptr; + } + return *this; + } + + /** Copy constructor */ + t8_subelementquad_scheme (const t8_subelementquad_scheme &other) + : m_element_size (other.m_element_size), m_scheme_context (sc_mempool_new (other.m_element_size)) {}; + + /** Copy assignment operator */ + t8_subelementquad_scheme & + operator= (const t8_subelementquad_scheme &other) + { + if (this != &other) { + // Free existing resources of assigned-to object + if (m_scheme_context) { + sc_mempool_destroy ((sc_mempool_t *) m_scheme_context); + } + + // Copy the values from the source object + m_element_size = other.m_element_size; + m_scheme_context = sc_mempool_new (other.m_element_size); + } + return *this; + } + + // ################################################____GENERAL INFO____################################################ + + /** Return the size of any element of a given class. + * \return The size of an element. + */ + constexpr size_t + get_element_size (void) const noexcept + { + return (sizeof (t8_subelement_element)); + } + + /** Returns true, if there is one element in the tree, that does not refine into 2^dim children. + * Returns false otherwise. + * \return non-zero if there is one element in the tree that does not refine into 2^dim children. + */ + int + refines_irregular (void) const noexcept + { + return true; // Potentially there are subelements. + } + + /** Return the maximum allowed level for any element of a given class. + * \return The maximum allowed level for elements of class \b ts. + */ + int + get_maxlevel (void) const noexcept + { + return t8_standalone_scheme::get_maxlevel (); + } + + // ################################################____SHAPE INFORMATION____################################################ + + /** Compute the number of corners of a given element. + * \param [in] elem The element. + * \return The number of corners of \a elem. + */ + int + element_get_num_corners ([[maybe_unused]] const t8_element_t *elem) const noexcept + { + const t8_subelement_element *subelement = (const t8_subelement_element *) elem; + T8_ASSERT (element_is_valid (elem)); + if (subelement->subelement_type == 0) { + return t8_standalone_scheme::element_get_num_corners ( + (const t8_element_t *) &subelement->element); + } + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Compute the number of faces of a given element. + * \param [in] elem The element. + * \return The number of faces of \a elem. + */ + int + element_get_num_faces ([[maybe_unused]] const t8_element_t *elem) const noexcept + { + const t8_subelement_element *subelement = (const t8_subelement_element *) elem; + T8_ASSERT (element_is_valid (elem)); + if (subelement->subelement_type == 0) { + return t8_standalone_scheme::element_get_num_faces ((const t8_element_t *) &subelement->element); + } + return T8_SUBELEMENT_FACES; + } + + /** Compute the maximum number of faces of a given element and all of its + * descendants. + * \param [in] elem The element. + * \return The maximum number of faces of \a elem and its descendants. + */ + int + element_get_max_num_faces ([[maybe_unused]] const t8_element_t *elem) const noexcept + { + const t8_subelement_element *subelement = (const t8_subelement_element *) elem; + + T8_ASSERT (element_is_valid (elem)); + + if (subelement->subelement_type != 0) { + return T8_SUBELEMENT_FACES; + } + return t8_standalone_scheme::element_get_max_num_faces ( + (const t8_element_t *) &subelement->element); + } + + /** Return the shape of an allocated element according its type. + * For example, a child of an element can be an element of a different shape + * and has to be handled differently - according to its shape. + * \param [in] elem The element to be considered + * \return The shape of the element as an eclass + */ + t8_element_shape_t + element_get_shape ([[maybe_unused]] const t8_element_t *elem) const noexcept + { + const t8_subelement_element *subelement = (const t8_subelement_element *) elem; + + T8_ASSERT (element_is_valid (elem)); + + if (subelement->subelement_type == 0) { + return t8_standalone_scheme::element_get_shape ((const t8_element_t *) &subelement->element); + } + return T8_ECLASS_TRIANGLE; + } + + /** Return the corner number of an element's face corner. + * Example quad: 2 x --- x 3 + * | | + * | | face 1 + * 0 x --- x 1 + * Thus for face = 1 the output is: corner=0 : 1, corner=1: 3 + * + * \param [in] element The element. + * \param [in] face A face index for \a element. + * \param [in] corner A corner index for the face 0 <= \a corner < num_face_corners. + * \return The corner number of the \a corner-th vertex of \a face. + * + * The order in which the corners must be given is determined by the eclass of \a element: + * LINE/QUAD/TRIANGLE: No specific order. + * HEX : In Z-order of the face starting with the lowest corner number. + * TET : Starting with the lowest corner number counterclockwise as seen from + * 'outside' of the element. + */ + int + element_get_face_corner ([[maybe_unused]] const t8_element_t *element, const int face, + const int corner) const noexcept + { + const t8_subelement_element *subelement = (const t8_subelement_element *) element; + + T8_ASSERT (element_is_valid (element)); + + if (subelement->subelement_type == 0) { + return t8_standalone_scheme::element_get_face_corner ((const t8_element_t *) &subelement->element, + face, corner); + } + int t8_face_corners_subelement[3][2] = { { 0, 1 }, { 1, 2 }, { 2, 0 } }; + /* + * + * x - - - - - x 1 + * | \ f0 / | + * | \ 0 / | + * x - - x el | f1 + * | / \ | + * | / f2 \ | + * x - - x - - x 2 + * + * The vertices of a subelement are enumerated clockwise, starting with the center vertex of the transition cell. + */ + + T8_ASSERT (0 <= face && face < T8_SUBELEMENT_FACES); + T8_ASSERT (0 <= corner && corner < 3); + + return t8_face_corners_subelement[face][corner]; + } + + /** Return the face numbers of the faces sharing an element's corner. + * Example quad: 2 x --- x 3 + * | | + * | | face 1 + * 0 x --- x 1 + * face 2 + * Thus for corner = 1 the output is: face=0 : 2, face=1: 1 + * \param [in] element The element. + * \param [in] corner A corner index for the face. + * \param [in] face A face index for \a corner. + * \return The face number of the \a face-th face at \a corner. + */ + int + element_get_corner_face (const t8_element_t *element, const int corner, const int face) const noexcept + { + const t8_subelement_element *subelement = (const t8_subelement_element *) element; + T8_ASSERT (element_is_valid (element)); + if (subelement->subelement_type == 0) { + return t8_standalone_scheme::element_get_corner_face ((const t8_element_t *) &subelement->element, + corner, face); + } + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Compute the shape of the face of an element. + * \param [in] elem The element. + * \param [in] face A face of \a elem. + * \return The element shape of the face. + * I.e. T8_ECLASS_LINE for quads, T8_ECLASS_TRIANGLE for tets + * and depending on the face number either T8_ECLASS_QUAD or + * T8_ECLASS_TRIANGLE for prisms. + */ + t8_element_shape_t + element_get_face_shape ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int face) const noexcept + { + T8_ASSERT (element_is_valid (elem)); + return T8_ECLASS_LINE; + } + + // ################################################____GENERAL HELPER____################################################ + + /** Copy all entries of \b source to \b dest. \b dest must be an existing + * element. No memory is allocated by this function. + * \param [in] source The element whose entries will be copied to \b dest. + * \param [in,out] dest This element's entries will be overwrite with the + * entries of \b source. + * \note \a source and \a dest may point to the same element. + */ + void + element_copy ([[maybe_unused]] const t8_element_t *source, [[maybe_unused]] t8_element_t *dest) const noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Check if two elements are equal. + * \param [in] elem1 The first element. + * \param [in] elem2 The second element. + * \return true if the elements are equal, false if they are not equal + */ + int + element_is_equal (const t8_element_t *elem1, const t8_element_t *elem2) const noexcept + { + T8_ASSERT (element_is_valid (elem1)); + T8_ASSERT (element_is_valid (elem2)); + + const t8_subelement_element *el1 = (const t8_subelement_element *) elem1; + const t8_subelement_element *el2 = (const t8_subelement_element *) elem2; + if (!t8_standalone_scheme::element_is_equal ((const t8_element_t *) &el1->element, + (const t8_element_t *) &el2->element)) + return 0; + if (el1->subelement_type != el2->subelement_type) { + return 0; + } + if (el1->subelement_id != el2->subelement_id) { + return 0; + } + return 1; + } + + // ################################################____ACCESSOR____################################################ + + /** Return the level of a particular element. + * \param [in] elem The element whose level should be returned. + * \return The level of \b elem. + */ + int + element_get_level ([[maybe_unused]] const t8_element_t *elem) const noexcept + { + T8_ASSERT (element_is_valid (elem)); + return ((t8_subelement_element *) elem)->element.level; + } + + // ################################################____REFINEMENT____################################################ + + /** create the root element + * \param [in,out] elem The element that is filled with the root + */ + void + set_to_root (t8_element_t *elem) const noexcept + { + t8_subelement_element *el = (t8_subelement_element *) elem; + t8_standalone_scheme::set_to_root ((t8_element_t *) &el->element); + return; + } + + /** Compute the parent of a given element \b elem and store it in \b parent. + * \b parent needs to be an existing element. No memory is allocated by this function. + * \b elem and \b parent can point to the same element, then the entries of + * \b elem are overwritten by the ones of its parent. + * \param [in] elem The element whose parent will be computed. + * \param [in,out] parent This element's entries will be overwritten by those + * of \b elem's parent. + * The storage for this element must exist + * and match the element class of the parent. + * For a pyramid, for example, it may be either a + * tetrahedron or a pyramid depending on \b elem's childid. + */ + static void + element_get_parent ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] t8_element_t *parent) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Compute the number of siblings of an element. That is the number of + * elements with the same parent (if available). + * \param [in] elem The element. + * \return The number of siblings of \a element. + * Note that this number is >= 1, since we count the element itself as a sibling. + * Note that the number of siblings is 1 for the root element. + */ + int + element_get_num_siblings (const t8_element_t *elem) const noexcept + { + const t8_subelement_element *subelement = (const t8_subelement_element *) elem; + T8_ASSERT (element_is_valid (elem)); + if (subelement->subelement_type == 0) { + return t8_standalone_scheme::element_get_num_siblings ( + (const t8_element_t *) &subelement->element); + } + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Compute a specific sibling of a given element \b elem and store it in \b sibling. + * \b sibling needs to be an existing element. No memory is allocated by this function. + * \b elem and \b sibling can point to the same element, then the entries of + * \b elem are overwritten by the ones of its sibid-th sibling. + * \param [in] elem The element whose sibling will be computed. + * \param [in] sibid The id of the sibling computed. + * \param [in,out] sibling This element's entries will be overwritten by those + * of \b elem's sibid-th sibling. + * The storage for this element must exist + * and match the element class of the sibling. + */ + static void + element_get_sibling ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int sibid, + [[maybe_unused]] t8_element_t *sibling) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Construct the child element of a given number. + * \param [in] elem This must be a valid element, bigger than maxlevel. + * \param [in] childid The number of the child to construct. + * \param [in,out] child The storage for this element must exist + * and match the element class of the child. + * For a pyramid, for example, it may be either a + * tetrahedron or a pyramid depending on \a childid. + * This can be checked by \a t8_element_child_eclass. + * On output, a valid element. + * It is valid to call this function with elem = child. + * \see t8_element_child_eclass + */ + static void + element_get_child ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int childid, + [[maybe_unused]] t8_element_t *child) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Return the number of children of an element when it is refined. + * \param [in] elem The element whose number of children is returned. + * \return The number of children of \a elem if it is to be refined. + */ + static int + element_get_num_children ([[maybe_unused]] const t8_element_t *elem) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Return the max number of children of an eclass. + * \return The max number of children of \a element. + */ + static int + get_max_num_children () noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** + * Indicates if an element is refinable. Possible reasons for being not refinable could be + * that the element has reached its max level. + * \param [in] elem The element to check. + * \return True if the element is refinable. + */ + static bool + element_is_refinable ([[maybe_unused]] const t8_element_t *elem) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Construct all children of a given element. + * \param [in] elem This must be a valid element, bigger than maxlevel. + * \param [in] length The length of the output array \a c must match + * the number of children. + * \param [in,out] c The storage for these \a length elements must exist + * and match the element class in the children's ordering. + * On output, all children are valid. + * It is valid to call this function with elem = c[0]. + * \see t8_element_num_children + * \see t8_element_child_eclass + */ + static void + element_get_children ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int length, + [[maybe_unused]] t8_element_t *c[]) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Compute the child id of an element. + * \param [in] elem This must be a valid element. + * \return The child id of elem. + */ + static int + element_get_child_id ([[maybe_unused]] const t8_element_t *elem) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Compute the ancestor id of an element, that is the child id + * at a given level. + * \param [in] elem This must be a valid element. + * \param [in] level A refinement level. Must satisfy \a level < elem.level + * \return The child_id of \a elem in regard to its \a level ancestor. + */ + static int + element_get_ancestor_id ([[maybe_unused]] const t8_element_t *elem, + [[maybe_unused]] const t8_element_level level) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Query whether a given set of elements is a family or not. + * \param [in] fam An array of as many elements as an element of class + * \b ts has siblings. + * \return Zero if \b fam is not a family, nonzero if it is. + * \note level 0 elements do not form a family. + */ + static int + elements_are_family ([[maybe_unused]] t8_element_t *const *fam) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + // Note to devs: element_is_ancestor currently cannot be static + // since it uses the non-static function element_new + /** Query whether element A is an ancestor of the element B. + * An element A is ancestor of an element B if A == B or if B can + * be obtained from A via successive refinement. + * \param [in] element_A An element of class \a eclass in scheme \a scheme. + * \param [in] element_B An element of class \a eclass in scheme \a scheme. + * \return True if and only if \a element_A is an ancestor of \a element_B. + */ + bool + element_is_ancestor ([[maybe_unused]] const t8_element_t *element_A, + [[maybe_unused]] const t8_element_t *element_B) const noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Compute the nearest common ancestor of two elements. That is, + * the element with highest level that still has both given elements as + * descendants. + * \param [in] elem1 The first of the two input elements. + * \param [in] elem2 The second of the two input elements. + * \param [in,out] nca The storage for this element must exist + * and match the element class of the child. + * On output the unique nearest common ancestor of + * \b elem1 and \b elem2. + */ + static void + element_get_nca ([[maybe_unused]] const t8_element_t *elem1, [[maybe_unused]] const t8_element_t *elem2, + [[maybe_unused]] t8_element_t *nca) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Compute the first descendant of a given element. + * \param [in] elem The element whose descendant is computed. + * \param [out] desc The first element in a uniform refinement of \a elem + * of the given level. + * \param [in] level The level, at which the descendant is computed. + */ + static void + element_get_first_descendant ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] t8_element_t *desc, + [[maybe_unused]] const t8_element_level level) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Compute the last descendant of a given element. + * \param [in] elem The element whose descendant is computed. + * \param [out] desc The last element in a uniform refinement of \a elem + * of the given level. + * \param [in] level The level, at which the descendant is computed. + */ + static void + element_get_last_descendant ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] t8_element_t *desc, + [[maybe_unused]] const t8_element_level level) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + // ################################################____FACE REFINEMENT____################################################ + + /** Return the number of children of an element's face when the element is refined. + * \param [in] elem The element whose face is considered. + * \param [in] face A face of \a elem. + * \return The number of children of \a face if \a elem is to be refined. + */ + static int + element_get_num_face_children ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int face) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Given an element and a face of the element, compute all children of + * the element that touch the face. + * \param [in] elem The element. + * \param [in] face A face of \a elem. + * \param [in,out] children Allocated elements, in which the children of \a elem + * that share a face with \a face are stored. + * They will be stored in order of their linear id. + * \param [in] num_children The number of elements in \a children. Must match + * the number of children that touch \a face. + * \ref element_get_num_face_children + * \param [in,out] child_indices If not NULL, an array of num_children integers must be given, + * on output its i-th entry is the child_id of the i-th face_child. + * It is valid to call this function with elem = children[0]. + */ + static void + element_get_children_at_face ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int face, + [[maybe_unused]] t8_element_t *children[], [[maybe_unused]] const int num_children, + [[maybe_unused]] int *child_indices) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Given a face of an element and a child number of a child of that face, return the face number + * of the child of the element that matches the child face. + * \verbatim + x ---- x x x x ---- x + | | | | | | | <-- f + | | | x | x--x + | | | | | + x ---- x x x ---- x + elem face face_child Returns the face number f + \endverbatim + + * \param [in] elem The element. + * \param [in] face Then number of the face. + * \param [in] face_child A number 0 <= \a face_child < num_face_children, + * specifying a child of \a elem that shares a face with \a face. + * These children are counted in linear order. This coincides with + * the order of children from a call to \ref element_get_children_at_face. + * \return The face number of the face of a child of \a elem + * that coincides with \a face_child. + */ + static int + element_face_get_child_face ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int face, + [[maybe_unused]] const int face_child) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Given a face of an element return the face number + * of the parent of the element that matches the element's face. Or return -1 if + * no face of the parent matches the face. + + * \param [in] elem The element. + * \param [in] face Then number of the face. + * \return If \a face of \a elem is also a face of \a elem's parent, + * the face number of this face. Otherwise -1. + * \note For the root element this function always returns \a face. + */ + static int + element_face_get_parent_face ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int face) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Construct the first descendant of an element at a given level that touches a given face. + * \param [in] elem The input element. + * \param [in] face A face of \a elem. + * \param [in, out] first_desc An allocated element. This element's data will be + * filled with the data of the first descendant of \a elem + * that shares a face with \a face. + * \param [in] level The level, at which the first descendant is constructed + */ + static void + element_get_first_descendant_face ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int face, + [[maybe_unused]] t8_element_t *first_desc, + [[maybe_unused]] const t8_element_level level) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Construct the last descendant of an element at a given level that touches a given face. + * \param [in] elem The input element. + * \param [in] face A face of \a elem. + * \param [in, out] last_desc An allocated element. This element's data will be + * filled with the data of the last descendant of \a elem + * that shares a face with \a face. + * \param [in] level The level, at which the last descendant is constructed + */ + static void + element_get_last_descendant_face ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int face, + [[maybe_unused]] t8_element_t *last_desc, + [[maybe_unused]] const t8_element_level level) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + // ################################################____FACE NEIGHBOR____################################################ + + /** Compute whether a given element shares a given face with its root tree. + * \param [in] elem The input element. + * \param [in] face A face of \a elem. + * \return True if \a face is a subface of the element's root element. + * \note You can compute the corresponding face number of the tree via \ref element_get_tree_face. + */ + static int + element_is_root_boundary ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int face) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Given an element and a face of this element. If the face lies on the + * tree boundary, return the face number of the tree face. + * If not the return value is arbitrary. + * You can call \ref t8_element_is_root_boundary to query whether the face is + * at the tree boundary. + * \param [in] elem The element. + * \param [in] face The index of a face of \a elem. + * \return The index of the tree face that \a face is a subface of, if + * \a face is on a tree boundary. + * Any arbitrary integer if \a is not at a tree boundary. + * \warning The return value may look like a valid face of the tree even if + * the element does not lie on the root boundary. + */ + static int + element_get_tree_face ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int face) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Construct the face neighbor of a given element if this face neighbor + * is inside the root tree. Return 0 otherwise. + * \param [in] elem The element to be considered. + * \param [in,out] neigh If the face neighbor of \a elem along \a face is inside + * the root tree, this element's data is filled with the + * data of the face neighbor. Otherwise the data can be modified + * arbitrarily. + * \param [in] face The number of the face along which the neighbor should be + * constructed. + * \param [out] neigh_face The number of \a face as viewed from \a neigh. + * An arbitrary value, if the neighbor is not inside the root tree. + * \return True if \a neigh is inside the root tree. + * False if not. In this case \a neigh's data can be arbitrary + * on output. + */ + static int + element_get_face_neighbor_inside ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] t8_element_t *neigh, + [[maybe_unused]] const int face, [[maybe_unused]] int *neigh_face) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + // ################################################____TREE FACE TRANSFORMATION____################################################ */ + + /** Suppose we have two trees that share a common face f. + * Given an element e that is a subface of f in one of the trees + * and given the orientation of the tree connection, construct the face + * element of the respective tree neighbor that logically coincides with e + * but lies in the coordinate system of the neighbor tree. + * \param [in] elem1 The face element. + * \param [in,out] elem2 On return the face element \a elem1 with respective + * to the coordinate system of the other tree. + * \param [in] orientation The orientation of the tree-tree connection. + * \see t8_cmesh_set_join + * \param [in] sign Depending on the topological orientation of the two tree faces, + * either 0 (both faces have opposite orientation) + * or 1 (both faces have the same top. orientation). + * \ref t8_eclass_face_orientation + * \param [in] is_smaller_face Flag to declare whether \a elem1 belongs to + * the smaller face. A face f of tree T is smaller than + * f' of T' if either the eclass of T is smaller or if + * the classes are equal and fsubelement_type = 0; + subelement->subelement_id = 0; + t8_standalone_scheme::element_set_linear_id ((t8_element_t *) &subelement->element, level, id); + } + + /** Compute the linear id of a given element in a hypothetical uniform + * refinement of a given level. + * \param [in] elem The element whose id we compute. + * \param [in] level The level of the uniform refinement to consider. + * \return The linear id of the element. + */ + static t8_linearidx_t + element_get_linear_id ([[maybe_unused]] const t8_element_t *elem, + [[maybe_unused]] const t8_element_level level) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Construct the successor in a uniform refinement of a given element. + * \param [in] elem1 The element whose successor should be constructed. + * \param [in,out] elem2 The element whose entries will be set. + */ + static void + element_construct_successor ([[maybe_unused]] const t8_element_t *elem1, + [[maybe_unused]] t8_element_t *elem2) noexcept + { + const t8_subelement_element *subelement1 = (const t8_subelement_element *) elem1; + T8_ASSERT (element_is_valid (elem1)); + if (subelement1->subelement_type == 0) { + t8_subelement_element *subelement2 = (t8_subelement_element *) elem2; + t8_standalone_scheme::element_construct_successor ((const t8_element_t *) &subelement1->element, + (t8_element_t *) &subelement2->element); + } + SC_ABORT ("SUBELEMENTS: This function is not implemented yet.\n"); + } + + /** Count how many leaf descendants of a given uniform level an element would produce. + * \param [in] elem The element to be checked. + * \param [in] level A refinement level. + * \return Suppose \a elem is uniformly refined up to level \a level. The return value + * is the resulting number of elements (of the given level). + * If \a level < t8_element_level(t), the return value should be 0. + * + * Example: If \a elem is a line element that refines into 2 line elements on each level, + * then the return value is max(0, 2^{\a level - level(\a t)}). + * Thus, if \a elem's level is 0, and \a level = 3, the return value is 2^3 = 8. + */ + t8_gloidx_t + element_count_leaves (const t8_element_t *elem, const t8_element_level level) const noexcept + { + const t8_subelement_element *subelement = (const t8_subelement_element *) elem; + T8_ASSERT (element_is_valid (elem)); + if (subelement->subelement_type == 0) { + return t8_standalone_scheme::element_count_leaves ((const t8_element_t *) &subelement->element, + level); + } + SC_ABORT ("SUBELEMENTS: This function is not implemented yet.\n"); + } + + /** Count how many leaf descendants of a given uniform level the root element will produce. + * \param [in] level A refinement level. + * \return The value of \ref t8_element_count_leaves if the input element + * is the root (level 0) element. + * + * This is a convenience function, and can be implemented via + * \ref t8_element_count_leaves. + */ + static t8_gloidx_t + count_leaves_from_root (const t8_element_level level) noexcept + { + return t8_standalone_scheme::count_leaves_from_root (level); + } + + /** Compare two elements. + * \param [in] elem1 The first element. + * \param [in] elem2 The second element. + * \return negative if elem1 < elem2, zero if elem1 equals elem2 + * and positive if elem1 > elem2. + * If elem2 is a copy of elem1 then the elements are equal. + */ + static int + element_compare ([[maybe_unused]] const t8_element_t *elem1, [[maybe_unused]] const t8_element_t *elem2) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + // ################################################____VISUALIZATION____################################################ + + /** Compute the coordinates of a given element vertex inside a reference tree + * that is embedded into [0,1]^d (d = dimension). + * \param [in] elem The element to be considered. + * \param [in] vertex The id of the vertex whose coordinates shall be computed. + * \param [out] coords An array of at least as many doubles as the element's dimension + * whose entries will be filled with the coordinates of \a vertex. + */ + static void + element_get_vertex_reference_coords ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int vertex, + [[maybe_unused]] double coords[]) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Convert a point in the reference space of an element to a point in the + * reference space of the tree. + * + * \param [in] elem The element. + * \param [in] ref_coords The coordinates of the point in the reference space of the element. + * \param [in] num_coords The number of coordinates to evaluate. + * \param [out] out_coords The coordinates of the point in the reference space of the tree. + */ + static void + element_get_reference_coords ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const double *ref_coords, + [[maybe_unused]] const size_t num_coords, [[maybe_unused]] double *out_coords) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + // ################################################____MEMORY____################################################ + + /** Allocate memory for an array of elements of a given class and initialize them. + * \param [in] length The number of elements to be allocated. + * \param [in,out] elems On input an array of \b length many unallocated + * element pointers. + * On output all these pointers will point to an allocated + * and initialized element. + * \note Not every element that is created in t8code will be created by a call + * to this function. However, if an element is not created using \ref element_new, + * then it is guaranteed that \ref element_init is called on it. + * \note In debugging mode, an element that was created with \ref element_new + * must pass \ref element_is_valid. + * \note If an element was created by \ref element_new then \ref element_init + * may not be called for it. Thus, \ref element_new should initialize an element + * in the same way as a call to \ref element_init would. + * \see element_init + * \see element_is_valid + */ + /* TODO: would it be better to directly allocate an array of elements, + * not element pointers? */ + void + element_new ([[maybe_unused]] const int length, [[maybe_unused]] t8_element_t **elems) const noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Initialize an array of allocated elements. + * \param [in] length The number of elements to be initialized. + * \param [in,out] elems On input an array of \b length many allocated + * elements. + * \note In debugging mode, an element that was passed to \ref element_init + * must pass \ref element_is_valid. + * \note If an element was created by \ref element_new then \ref element_init + * may not be called for it. Thus, \ref element_new should initialize an element + * in the same way as a call to \ref element_init would. + * \see element_new + * \see element_is_valid + */ + static inline void + element_init (const int length, t8_element_t *elems) noexcept + { + t8_subelement_element *subelements = (t8_subelement_element *) elems; + for (int i = 0; i < length; i++) { + subelements[i].subelement_type = 0; + subelements[i].subelement_id = 0; + t8_standalone_scheme::element_init (1, (t8_element_t *) &subelements[i].element); + } + } + + /** Deinitialize an array of allocated elements. + * \param [in] length The number of elements to be deinitialized. + * \param [in,out] elems On input an array of \a length many allocated + * and initialized elements, on output an array of + * \a length many allocated, but not initialized elements. + * \note Call this function if you called element_init on the element pointers. + * \see element_init + */ + static void + element_deinit ([[maybe_unused]] const int length, [[maybe_unused]] t8_element_t *elems) noexcept + { + } + + /** Deallocate an array of elements. + * \param [in] length The number of elements in the array. + * \param [in,out] elems On input an array of \b length many allocated + * element pointers. + * On output all these pointers will be freed. + * \b elems itself will not be freed by this function. + */ + void + element_destroy ([[maybe_unused]] const int length, [[maybe_unused]] t8_element_t **elems) const noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + // ################################################____DEBUG____################################################ + +#if T8_ENABLE_DEBUG + /** Query whether a given element can be considered as 'valid' and it is + * safe to perform any of the above algorithms on it. + * For example this could mean that all coordinates are in valid ranges + * and other membervariables do have meaningful values. + * \param [in] elem The element to be checked. + * \return True if \a elem is safe to use. False otherwise. + * \note An element that is constructed with \ref element_new + * must pass this test. + * \note An element for which \ref element_init was called must pass + * this test. + * \note This function is used for debugging to catch certain errors. + * These can for example occur when an element points to a region + * of memory which should not be interpreted as an element. + * \note We recommend to use the assertion T8_ASSERT (element_is_valid (elem)) + * in the implementation of each of the functions in this file. + */ + static int + element_is_valid ([[maybe_unused]] const t8_element_t *elem) noexcept + { + const t8_subelement_element *subelement = (const t8_subelement_element *) elem; + int element_valid + = t8_standalone_scheme::element_is_valid ((const t8_element_t *) &subelement->element); + if (subelement->subelement_type == 0) { + return element_valid; + } + + bool subelement_valid = (subelement->subelement_type >= T8_SUB_QUAD_MIN_SUBELEMENT_TYPE + && subelement->subelement_type <= T8_SUB_QUAD_MAX_SUBELEMENT_TYPE) + && (subelement->subelement_id >= T8_SUB_QUAD_MIN_SUBELEMENT_ID + && subelement->subelement_id <= T8_SUB_QUAD_MAX_SUBELEMENT_ID); + + return subelement_valid && element_valid; + } + + /** + * Print a given element. For a example for a triangle print the coordinates + * and the level of the triangle. This function is only available in the + * debugging configuration. + * + * \param [in] elem The element to print + */ + static void + element_debug_print ([[maybe_unused]] const t8_element_t *elem) noexcept + { + + SC_ABORT ("This function is not implemented yet.\n"); + } + +#endif + /** + * Fill a string with readable information about the element + * \param[in] elem The element to translate into human-readable information + * \param[in, out] debug_string The string to fill. + * \param[in] string_size Buffer size of c-string + */ + static void + element_to_string ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] char *debug_string, + [[maybe_unused]] const int string_size) noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + // ################################################____MPI____################################################ + + /** Pack multiple elements into contiguous memory, so they can be sent via MPI. + * \param [in] elements Array of elements that are to be packed + * \param [in] count Number of elements to pack + * \param [in,out] send_buffer Buffer in which to pack the elements + * \param [in] buffer_size size of the buffer (in order to check that we don't access out of range) + * \param [in, out] position the position of the first byte that is not already packed + * \param [in] comm MPI Communicator + */ + void + element_MPI_Pack ([[maybe_unused]] t8_element_t **const elements, [[maybe_unused]] const unsigned int count, + [[maybe_unused]] void *send_buffer, [[maybe_unused]] const int buffer_size, + [[maybe_unused]] int *position, [[maybe_unused]] sc_MPI_Comm comm) const noexcept + + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Determine an upper bound for the size of the packed message of \a count elements + * \param [in] count Number of elements to pack + * \param [in] comm MPI Communicator + * \param [out] pack_size upper bound on the message size + */ + void + element_MPI_Pack_size ([[maybe_unused]] const unsigned int count, [[maybe_unused]] sc_MPI_Comm comm, + [[maybe_unused]] int *pack_size) const noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } + + /** Unpack multiple elements from contiguous memory that was received via MPI. + * \param [in] recvbuf Buffer from which to unpack the elements + * \param [in] buffer_size size of the buffer (in order to check that we don't access out of range) + * \param [in, out] position the position of the first byte that is not already packed + * \param [in] elements Array of initialised elements that is to be filled from the message + * \param [in] count Number of elements to unpack + * \param [in] comm MPI Communicator + */ + void + element_MPI_Unpack ([[maybe_unused]] void *recvbuf, [[maybe_unused]] const int buffer_size, + [[maybe_unused]] int *position, [[maybe_unused]] t8_element_t **elements, + [[maybe_unused]] const unsigned int count, [[maybe_unused]] sc_MPI_Comm comm) const noexcept + { + SC_ABORT ("This function is not implemented yet.\n"); + } +}; diff --git a/src/t8_schemes/t8_subelement/t8_subelement.cxx b/src/t8_schemes/t8_subelement/t8_subelement.cxx new file mode 100644 index 0000000000..04859db400 --- /dev/null +++ b/src/t8_schemes/t8_subelement/t8_subelement.cxx @@ -0,0 +1,71 @@ +/* + 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 + + 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_subelement.cxx + * Implements functions declared in \ref t8_subelement.hxx. + */ + +#include +#include "t8_scheme_implementation.hxx" +#include + +const t8_scheme * +t8_scheme_new_subelement (void) +{ + t8_scheme_builder builder; + + builder.add_eclass_scheme> (); + builder.add_eclass_scheme> (); + builder.add_eclass_scheme (); + builder.add_eclass_scheme (); + builder.add_eclass_scheme> (); + builder.add_eclass_scheme (); + builder.add_eclass_scheme (); + builder.add_eclass_scheme (); + return builder.build_scheme (); +} + +int +t8_eclass_scheme_is_subelement (const t8_scheme *scheme, const t8_eclass_t eclass) +{ + switch (eclass) { + case T8_ECLASS_VERTEX: + return scheme->check_eclass_scheme_type> (T8_ECLASS_VERTEX); + case T8_ECLASS_LINE: + return scheme->check_eclass_scheme_type> (T8_ECLASS_LINE); + // case T8_ECLASS_QUAD: + // return scheme->check_eclass_scheme_type> (T8_ECLASS_QUAD); + // // case T8_ECLASS_TRIANGLE: + // // return scheme->check_eclass_scheme_type> (T8_ECLASS_TRIANGLE); + // case T8_ECLASS_HEX: + // return scheme->check_eclass_scheme_type> (T8_ECLASS_HEX); + // case T8_ECLASS_TET: + // return scheme->check_eclass_scheme_type> (T8_ECLASS_TET); + // case T8_ECLASS_PRISM: + // return scheme->check_eclass_scheme_type> (T8_ECLASS_PRISM); + // case T8_ECLASS_PYRAMID: + // return scheme->check_eclass_scheme_type> (T8_ECLASS_PYRAMID); + default: + SC_ABORT_NOT_REACHED (); + } + return 0; /* Default return value false */ +} diff --git a/src/t8_schemes/t8_subelement/t8_subelement.hxx b/src/t8_schemes/t8_subelement/t8_subelement.hxx new file mode 100644 index 0000000000..edba038bb8 --- /dev/null +++ b/src/t8_schemes/t8_subelement/t8_subelement.hxx @@ -0,0 +1,41 @@ +/* + 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_subelement.hxx + * Define the subelement scheme interface. + */ + +#pragma once + +#include + +/** Return the subelement implementation of t8code. */ +const t8_scheme * +t8_scheme_new_subelement (void); + +/** Check whether a given eclass_scheme is one of the subelement schemes. + * \param [in] scheme A (pointer to a) scheme. + * \param [in] eclass The eclass to check. + * \return True if \a scheme is one of the subelement schemes, false otherwise. + */ +bool +t8_eclass_scheme_is_subelement (const t8_scheme *scheme, const t8_eclass_t eclass); diff --git a/src/t8_schemes/t8_subelement/t8_subelement_type.hxx b/src/t8_schemes/t8_subelement/t8_subelement_type.hxx new file mode 100644 index 0000000000..989f1f43c5 --- /dev/null +++ b/src/t8_schemes/t8_subelement/t8_subelement_type.hxx @@ -0,0 +1,43 @@ +/* + 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 + + 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_subelement.hxx + * + */ + +#pragma once +#include +#include + +#define T8_SUBELEMENT_FACES 3 +#define T8_SUB_QUAD_MAX_SUBELEMENT_TYPE 15 +#define T8_SUB_QUAD_MIN_SUBELEMENT_TYPE 1 +#define T8_SUB_QUAD_MAX_SUBELEMENT_ID 7 +#define T8_SUB_QUAD_MIN_SUBELEMENT_ID 0 + +struct t8_subelement_element +{ + t8_standalone_element element; + int + subelement_type; /* saves the information, which type of transition cell a subelement is associated to (default is 0, meaning no subelement). */ + int subelement_id; /* saves the information, what children subelement the given element is (default is 0) */ +}; From b5f187aacb5e39bbd9547e00e582238a7a51f491 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Wed, 20 May 2026 16:08:16 +0200 Subject: [PATCH 03/28] Got SEGV --- .../t8_scheme_implementation.hxx | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx index adbb2f0f8c..724cd33c3a 100644 --- a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx +++ b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx @@ -306,7 +306,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type == 0) { + t8_standalone_scheme::element_get_first_descendant ( + (const t8_element_t *) &subelement->element, (t8_element_t *) &descsubelement->element, level); + return; + } + SC_ABORT ("SUBELEMENTS: This function is not implemented yet.\n"); + + // TODO: reset subelem values } /** Compute the last descendant of a given element. @@ -576,7 +586,15 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type == 0) { + t8_standalone_scheme::element_get_last_descendant ( + (const t8_element_t *) &subelement->element, (t8_element_t *) &descsubelement->element, level); + return; + } + SC_ABORT ("SUBELEMENTS: This function is not implemented yet.\n"); } // ################################################____FACE REFINEMENT____################################################ @@ -860,6 +878,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers::element_construct_successor ((const t8_element_t *) &subelement1->element, (t8_element_t *) &subelement2->element); + return; } SC_ABORT ("SUBELEMENTS: This function is not implemented yet.\n"); } @@ -966,10 +985,15 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers::element_init (1, (t8_element_t *) &subelements[i].element); + } } /** Initialize an array of allocated elements. From c4982fc19a71d9f7546d4e8540cd73e6703b8db6 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Thu, 21 May 2026 09:57:09 +0200 Subject: [PATCH 04/28] Approach starting from standalone implementation --- src/t8_schemes/t8_scheme.hxx | 2 +- .../t8_scheme_implementation.hxx | 1540 +++++++++++++---- .../t8_subelement/t8_subelement.cxx | 2 +- 3 files changed, 1225 insertions(+), 319 deletions(-) diff --git a/src/t8_schemes/t8_scheme.hxx b/src/t8_schemes/t8_scheme.hxx index a7ff8dee8f..864fdc5229 100644 --- a/src/t8_schemes/t8_scheme.hxx +++ b/src/t8_schemes/t8_scheme.hxx @@ -103,7 +103,7 @@ struct t8_scheme t8_standalone_scheme, t8_standalone_scheme, /* Subelement schemes */ - t8_subelementquad_scheme + t8_subelementquad_scheme<> >; /* clang-format on */ diff --git a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx index 724cd33c3a..1301c85f68 100644 --- a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx +++ b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx @@ -29,39 +29,41 @@ #include #include #include -#include #include +#include #include #include #include #include -/** A templated implementation of the scheme interface based on cutting planes. */ -struct t8_subelementquad_scheme: public t8_scheme_helpers +/** TODO. */ +template +struct t8_subelementquad_scheme: public t8_scheme_helpers> { public: + using standalone_scheme = t8_standalone_scheme; /** Constructor */ t8_subelementquad_scheme () noexcept - : m_element_size (sizeof (t8_subelement_element)), m_scheme_context (sc_mempool_new (m_element_size)) {}; + : element_size (sizeof (t8_standalone_element)), scheme_context (sc_mempool_new (element_size)) {}; protected: - // I am not sure why i even need this both variables. - size_t m_element_size; /**< The size in bytes of an element of class \a eclass */ - void *m_scheme_context; /**< Anonymous implementation context. */ + // What do i need this for? + size_t element_size; /**< The size in bytes of an element of class \a eclass */ + void *scheme_context; /**< Anonymous implementation context. */ public: /** Destructor for all default schemes */ ~t8_subelementquad_scheme () { - T8_ASSERT (m_scheme_context != NULL); - SC_ASSERT (((sc_mempool_t *) m_scheme_context)->elem_count == 0); - sc_mempool_destroy ((sc_mempool_t *) m_scheme_context); + T8_ASSERT (scheme_context != NULL); + SC_ASSERT (((sc_mempool_t *) scheme_context)->elem_count == 0); + sc_mempool_destroy ((sc_mempool_t *) scheme_context); } /** Move constructor */ t8_subelementquad_scheme (t8_subelementquad_scheme &&other) noexcept - : m_element_size (other.m_element_size), m_scheme_context (std::exchange (other.m_scheme_context, nullptr)) + : element_size (other.element_size), scheme_context (std::exchange (other.scheme_context, nullptr)) { } @@ -71,23 +73,23 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers); } /** Returns true, if there is one element in the tree, that does not refine into 2^dim children. * Returns false otherwise. * \return non-zero if there is one element in the tree that does not refine into 2^dim children. */ - int - refines_irregular (void) const noexcept + static constexpr int + refines_irregular (void) noexcept { - return true; // Potentially there are subelements. + if constexpr (TEclass == T8_ECLASS_PYRAMID) { + return 1; + } + return 0; } /** Return the maximum allowed level for any element of a given class. * \return The maximum allowed level for elements of class \b ts. */ - int - get_maxlevel (void) const noexcept + static constexpr int + get_maxlevel (void) noexcept { - return t8_standalone_scheme::get_maxlevel (); + return T8_ELEMENT_MAXLEVEL[TEclass]; } // ################################################____SHAPE INFORMATION____################################################ @@ -142,31 +147,24 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type == 0) { - return t8_standalone_scheme::element_get_num_corners ( - (const t8_element_t *) &subelement->element); - } - SC_ABORT ("This function is not implemented yet.\n"); + + return T8_ELEMENT_NUM_CORNERS[TEclass]; } /** Compute the number of faces of a given element. * \param [in] elem The element. * \return The number of faces of \a elem. */ - int - element_get_num_faces ([[maybe_unused]] const t8_element_t *elem) const noexcept + static constexpr int + element_get_num_faces ([[maybe_unused]] const t8_element_t *elem) noexcept { - const t8_subelement_element *subelement = (const t8_subelement_element *) elem; T8_ASSERT (element_is_valid (elem)); - if (subelement->subelement_type == 0) { - return t8_standalone_scheme::element_get_num_faces ((const t8_element_t *) &subelement->element); - } - return T8_SUBELEMENT_FACES; + /* Note: With the introduction of pyramids the implementation will be adjusted. */ + return T8_ELEMENT_NUM_FACES[TEclass]; } /** Compute the maximum number of faces of a given element and all of its @@ -174,18 +172,11 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type != 0) { - return T8_SUBELEMENT_FACES; - } - return t8_standalone_scheme::element_get_max_num_faces ( - (const t8_element_t *) &subelement->element); + return T8_ELEMENT_NUM_FACES[TEclass]; } /** Return the shape of an allocated element according its type. @@ -194,17 +185,11 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type == 0) { - return t8_standalone_scheme::element_get_shape ((const t8_element_t *) &subelement->element); - } - return T8_ECLASS_TRIANGLE; + return TEclass; } /** Return the corner number of an element's face corner. @@ -225,36 +210,15 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type == 0) { - return t8_standalone_scheme::element_get_face_corner ((const t8_element_t *) &subelement->element, - face, corner); - } - int t8_face_corners_subelement[3][2] = { { 0, 1 }, { 1, 2 }, { 2, 0 } }; - /* - * - * x - - - - - x 1 - * | \ f0 / | - * | \ 0 / | - * x - - x el | f1 - * | / \ | - * | / f2 \ | - * x - - x - - x 2 - * - * The vertices of a subelement are enumerated clockwise, starting with the center vertex of the transition cell. - */ - - T8_ASSERT (0 <= face && face < T8_SUBELEMENT_FACES); - T8_ASSERT (0 <= corner && corner < 3); - - return t8_face_corners_subelement[face][corner]; + T8_ASSERT (0 <= face && face < T8_ELEMENT_NUM_FACES[TEclass]); + T8_ASSERT (0 <= corner && corner < T8_ELEMENT_NUM_CORNERS[TEclass]); + const int face_sign = face % 2; + const int face_dim = face / 2; + return get_hypercube_face_corner_index (face_dim, face_sign, corner); } /** Return the face numbers of the faces sharing an element's corner. @@ -269,16 +233,13 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type == 0) { - return t8_standalone_scheme::element_get_corner_face ((const t8_element_t *) &subelement->element, - corner, face); - } - SC_ABORT ("This function is not implemented yet.\n"); + T8_ASSERT (0 <= face && face < T8_ELEMENT_NUM_FACES[TEclass]); + T8_ASSERT (0 <= corner && corner < T8_ELEMENT_NUM_CORNERS[TEclass]); + return (corner >> face & 1) + 2 * face; } /** Compute the shape of the face of an element. @@ -289,11 +250,23 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *) dest, (const t8_standalone_element *) source, + sizeof (t8_standalone_element)); + T8_ASSERT (element_is_valid (dest)); } /** Check if two elements are equal. @@ -316,23 +294,22 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers::element_is_equal ((const t8_element_t *) &el1->element, - (const t8_element_t *) &el2->element)) - return 0; - if (el1->subelement_type != el2->subelement_type) { - return 0; - } - if (el1->subelement_id != el2->subelement_id) { + const t8_standalone_element *el1 = (const t8_standalone_element *) elem1; + const t8_standalone_element *el2 = (const t8_standalone_element *) elem2; + if (el1->level != el2->level) return 0; + for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + if (el1->coords[idim] != el2->coords[idim]) + return 0; } + /* return el1->type == el2->type; + ToDo-Type */ return 1; } @@ -342,11 +319,11 @@ struct t8_subelementquad_scheme: public t8_scheme_helperselement.level; + return ((const t8_standalone_element *) elem)->level; } // ################################################____REFINEMENT____################################################ @@ -354,11 +331,16 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers::set_to_root ((t8_element_t *) &el->element); + t8_standalone_element *el = (t8_standalone_element *) elem; + el->level = 0; + for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + el->coords[idim] = 0; + } + /* el->type = 0; + ToDo-Type */ return; } @@ -374,10 +356,27 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; + t8_standalone_element *parent_elem = (t8_standalone_element *) parent; + + T8_ASSERT (el->level > 0); + + if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + + const t8_element_coord length = element_get_len ((el->level)); + set_coords_at_level_to_zero (el, parent_elem, length); + + parent_elem->level = el->level - 1; + T8_ASSERT (parent_elem->level >= 0); + + T8_ASSERT (element_is_valid (parent)); } /** Compute the number of siblings of an element. That is the number of @@ -387,16 +386,22 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers= 1, since we count the element itself as a sibling. * Note that the number of siblings is 1 for the root element. */ - int - element_get_num_siblings (const t8_element_t *elem) const noexcept + static constexpr int + element_get_num_siblings (const t8_element_t *elem) noexcept { - const t8_subelement_element *subelement = (const t8_subelement_element *) elem; T8_ASSERT (element_is_valid (elem)); - if (subelement->subelement_type == 0) { - return t8_standalone_scheme::element_get_num_siblings ( - (const t8_element_t *) &subelement->element); + + const t8_standalone_element *el = (const t8_standalone_element *) elem; + if (el->level == 0) + return 1; + T8_ASSERT (0 < el->level && el->level <= T8_ELEMENT_MAXLEVEL[TEclass]); + /* To get the number siblings, we first get the parent and then get the number of children of that parent*/ + if constexpr (refines_irregular ()) { + SC_ABORT ("This function is not implemented yet.\n"); + } + else { + return T8_ELEMENT_NUM_CHILDREN[TEclass]; } - SC_ABORT ("This function is not implemented yet.\n"); } /** Compute a specific sibling of a given element \b elem and store it in \b sibling. @@ -410,7 +415,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; + t8_standalone_element *c = (t8_standalone_element *) child; + + T8_ASSERT (0 <= childid && childid < T8_ELEMENT_NUM_CHILDREN[TEclass]); + T8_ASSERT (0 <= el->level && el->level <= T8_ELEMENT_MAXLEVEL[TEclass]); + + /* Compute the cube id and shift the coordinates accordingly */ + t8_cube_id cube_id; + if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + else { + cube_id = childid; + } + + const t8_element_coord length = element_get_len (el->level + 1); + + put_cube_id_at_level (el, c, length, cube_id); + + c->level = el->level + 1; + + T8_ASSERT (element_is_valid (child)); } /** Return the number of children of an element when it is refined. * \param [in] elem The element whose number of children is returned. * \return The number of children of \a elem if it is to be refined. */ - static int + static constexpr int element_get_num_children ([[maybe_unused]] const t8_element_t *elem) noexcept { - SC_ABORT ("This function is not implemented yet.\n"); + T8_ASSERT (element_is_valid (elem)); + + return T8_ELEMENT_NUM_CHILDREN[TEclass]; } /** Return the max number of children of an eclass. * \return The max number of children of \a element. */ - static int + static constexpr int get_max_num_children () noexcept { - SC_ABORT ("This function is not implemented yet.\n"); + return T8_ELEMENT_NUM_CHILDREN[TEclass]; } /** @@ -461,10 +492,12 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; + T8_ASSERT (0 <= el->level && el->level <= T8_ELEMENT_MAXLEVEL[TEclass]); + + const int num_children = length; + T8_ASSERT (length == element_get_num_children ((const t8_element_t *) el)); + + for (int ichild = num_children - 1; ichild >= 0; ichild--) { + element_get_child ((const t8_element_t *) el, ichild, c[ichild]); + T8_ASSERT (element_is_valid (c[ichild])); + } } /** Compute the child id of an element. * \param [in] elem This must be a valid element. * \return The child id of elem. */ - static int - element_get_child_id ([[maybe_unused]] const t8_element_t *elem) noexcept + static constexpr int + element_get_child_id (const t8_element_t *elem) noexcept { - SC_ABORT ("This function is not implemented yet.\n"); + T8_ASSERT (element_is_valid (elem)); + + const t8_standalone_element *el = (const t8_standalone_element *) elem; + T8_ASSERT (el->level >= 0); + if (el->level == 0) { + return -1; + } + const t8_cube_id cube_id = compute_cubeid (el, el->level); + t8_child_id child_id; + if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + else { + child_id = cube_id; + } + return child_id; } /** Compute the ancestor id of an element, that is the child id @@ -501,11 +559,18 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; + t8_standalone_element ancestor; + T8_ASSERT (0 <= el->level && el->level <= T8_ELEMENT_MAXLEVEL[TEclass]); + + element_get_ancestor (el, level, &ancestor); + return element_get_child_id ((const t8_element_t *) &ancestor); } /** Query whether a given set of elements is a family or not. @@ -514,10 +579,36 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers parent, compare; + /* Take the parent of the first element as baseline to compare against */ + element_get_parent ((const t8_element_t *) fam[0], (t8_element_t *) &parent); + const int num_children = element_get_num_children ((const t8_element_t *) &parent); + for (int childid = 0; childid < num_children; childid++) { + /* check whether each element has the same parent */ + element_get_parent ((const t8_element_t *) fam[childid], (t8_element_t *) &compare); + if (element_compare ((const t8_element_t *) &parent, (const t8_element_t *) &compare)) { + return 0; + } + + /* check whether each element is the correct child of the collective parent */ + /* Could be replaced by type comparison as level is already checked in parent comparison */ + element_get_child ((const t8_element_t *) &parent, childid, (t8_element_t *) &compare); + + if (element_compare ((const t8_element_t *) fam[childid], (const t8_element_t *) &compare)) { + return 0; + } + } + return 1; } // Note to devs: element_is_ancestor currently cannot be static @@ -530,10 +621,42 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers level(B) then A cannot be an ancestor. + - Otherwise compute the ancestor of B at level(A) + - Compare the computed ancestor with A. + */ + T8_ASSERT (element_is_valid (element_A)); + T8_ASSERT (element_is_valid (element_B)); + + const t8_standalone_element *el_B = (const t8_standalone_element *) element_B; + + const int level_A = element_get_level (element_A); + const int level_B = element_get_level (element_B); + + if (level_A > level_B) { + // A is finer than B and thus cannot be an ancestor. + return false; + } + + // Compute the ancestor of B at level_A and compare it with A + t8_element_t *ancestor; + element_new (1, &ancestor); + + t8_standalone_element *ancestor_casted = (t8_standalone_element *) ancestor; + + element_get_ancestor (el_B, level_A, ancestor_casted); + + const bool is_ancestor = element_is_equal (ancestor, element_A); + + element_destroy (1, &ancestor); + + // Return true if A == ancestor + // Return false if A != ancestor + return is_ancestor; } /** Compute the nearest common ancestor of two elements. That is, @@ -546,11 +669,26 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el1 = (const t8_standalone_element *) elem1; + const t8_standalone_element *el2 = (const t8_standalone_element *) elem2; + /* get the first possible level of the nca*/ + int cube_ancestor_level = element_get_cube_nca_level (el1, el2); + int real_level; + if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + else { + real_level = cube_ancestor_level; + } + /* get the ancestor at the calculated level*/ + element_get_ancestor (el1, real_level, (t8_standalone_element *) nca); + T8_ASSERT (element_is_valid (nca)); } /** Compute the first descendant of a given element. @@ -559,21 +697,22 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type == 0) { - t8_standalone_scheme::element_get_first_descendant ( - (const t8_element_t *) &subelement->element, (t8_element_t *) &descsubelement->element, level); - return; - } - SC_ABORT ("SUBELEMENTS: This function is not implemented yet.\n"); - // TODO: reset subelem values + const t8_standalone_element *el = (const t8_standalone_element *) elem; + t8_standalone_element *d = (t8_standalone_element *) desc; + + T8_ASSERT (level >= el->level); + T8_ASSERT (0 <= level && level <= T8_ELEMENT_MAXLEVEL[TEclass]); + + /* The first descendant of an element has the same anchor coords and type, but another level */ + element_copy ((const t8_element_t *) el, (t8_element_t *) d); + d->level = level; + + T8_ASSERT (element_is_valid ((t8_element_t *) d)); } /** Compute the last descendant of a given element. @@ -582,19 +721,28 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type == 0) { - t8_standalone_scheme::element_get_last_descendant ( - (const t8_element_t *) &subelement->element, (t8_element_t *) &descsubelement->element, level); - return; + + const t8_standalone_element *el = (const t8_standalone_element *) elem; + t8_standalone_element *d = (t8_standalone_element *) desc; + + T8_ASSERT (level >= el->level); + T8_ASSERT (0 <= level && level <= T8_ELEMENT_MAXLEVEL[TEclass]); + + element_copy ((const t8_element_t *) el, (t8_element_t *) d); + d->level = level; + + /* Shift the coords to the eighth cube. The type of the last descendant + * is the type of the input element */ + t8_element_coord coord_offset = element_get_len (el->level) - element_get_len (level); + for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + d->coords[idim] |= coord_offset; } - SC_ABORT ("SUBELEMENTS: This function is not implemented yet.\n"); + + T8_ASSERT (element_is_valid (desc)); } // ################################################____FACE REFINEMENT____################################################ @@ -604,10 +752,13 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; + t8_standalone_element **children_els = (t8_standalone_element **) children; + int local_indices[T8_ELEMENT_NUM_CHILDREN[TEclass]]; + + if (child_indices == NULL) { + child_indices = local_indices; + } + const int face_sign = face % 2; + const int face_dim = face / 2; + for (int ifacechild = 0; ifacechild < num_children; ifacechild++) { + child_indices[ifacechild] = get_hypercube_face_corner_index (face_dim, face_sign, ifacechild); + } + for (int ifacechild = num_children - 1; ifacechild >= 0; ifacechild--) { + element_get_child ((const t8_element_t *) el, child_indices[ifacechild], + (t8_element_t *) children_els[ifacechild]); + } } /** Given a face of an element and a child number of a child of that face, return the face number @@ -652,11 +819,11 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; + if (el->level == 0) + return -1; + if constexpr (!T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + /* Check if the least significant bit of the face normal coord is equal to the face_sign bit to get a valid parent face.*/ + const int least_significant_bit = ((el->coords[face / 2]) >> (T8_ELEMENT_MAXLEVEL[TEclass] - el->level)) % 2; + const bool invalid_parent_face = (element_face_is_1_boundary (el, face) != least_significant_bit); + if (invalid_parent_face) { + return -1; + } + return face; + } + else { + SC_ABORT ("Only implemented for hypercubes.\n"); + return 0; + } } /** Construct the first descendant of an element at a given level that touches a given face. @@ -683,12 +867,31 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; + t8_standalone_element *first_descendant = (t8_standalone_element *) first_desc; + + first_descendant->level = level; + if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + std::copy (el->coords.begin (), el->coords.end (), first_descendant->coords.begin ()); + + const bool face_is_1_boundary = face % 2; + + if (face_is_1_boundary) { //the face is a xi=1 boundary + const int facenormal_dim = face / 2; + + const t8_element_coord coord_offset = element_get_len (el->level) - element_get_len (level); + + first_descendant->coords[facenormal_dim] += coord_offset; + } } /** Construct the last descendant of an element at a given level that touches a given face. @@ -699,12 +902,27 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; + t8_standalone_element *last_descendant = (t8_standalone_element *) last_desc; + + last_descendant->level = level; + const t8_element_coord coord_offset = element_get_len (el->level) - element_get_len (level); + + if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + + for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + const int multiplier = (idim == face / 2) ? face % 2 : 1; + last_descendant->coords[idim] = el->coords[idim] + multiplier * coord_offset; + } } // ################################################____FACE NEIGHBOR____################################################ @@ -715,10 +933,50 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; + + if (!element_is_face_internal (el, face)) { + const int dim = element_face_normal_dim (el, face); + if (element_face_is_1_boundary (el, face)) { + // a_d must be full of 1s up to level l + const t8_element_coord coord_offset = get_root_len () - element_get_len (el->level); + if (el->coords[dim] != coord_offset) { + return 0; + } + // all edges containing dim must be fulfilled with x_d-a_d >= x_j-a_j or x_j-a_j <= x_d-a_d + if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + } + else { + //zeroboundary + // x_d must be full of 0s up to level l + if (el->coords[dim] != 0) { + return 0; + } + // all edges containing dimid must be fulfilled with x_d-a_d <= x_j-a_j or x_j-a_j >= x_d-a_d + if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + } + } + else { + // internalface + // get graph edge e (or ieq) = (xi,xj) + // ai = aj is necessary and sufficient + if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + else { + SC_ABORT ("Cubes should not have internal faces!\n"); + } + } + return 1; } /** Given an element and a face of this element. If the face lies on the @@ -734,10 +992,12 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers= 0); + element_copy (elem, neigh); + + const t8_standalone_element *el = (const t8_standalone_element *) elem; + t8_standalone_element *neighbor = (t8_standalone_element *) neigh; + + if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + const int facenormal_dim = face / 2; + const int sign = face % 2 ? 1 : -1; + + /**Adapt coordinates*/ + const t8_element_coord length = element_get_len (el->level); + + neighbor->coords[facenormal_dim] += length * sign; + + *neigh_face = face ^ 1; + + T8_ASSERT (element_is_valid ((t8_element_t *) neighbor)); + /**check inside root*/ + return element_is_inside_root (neighbor); } // ################################################____TREE FACE TRANSFORMATION____################################################ */ @@ -807,11 +1090,11 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem, root_face, (t8_standalone_element *) boundary); + return; + case T8_ECLASS_LINE: + compute_boundary_face (elem, root_face, (t8_standalone_element *) boundary); + return; + case T8_ECLASS_QUAD: + compute_boundary_face (elem, root_face, (t8_standalone_element *) boundary); + return; + default: + if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + return; + } } // ################################################____LINEAR ID____################################################ @@ -841,14 +1144,59 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type = 0; - subelement->subelement_id = 0; - t8_standalone_scheme::element_set_linear_id ((t8_element_t *) &subelement->element, level, id); + t8_standalone_element *el = (t8_standalone_element *) elem; + + set_to_root ((t8_element_t *) el); + + /* There is only one element at level 0, so it must be root */ + if (level == 0) { + T8_ASSERT (id == 0); + return; + } + + T8_ASSERT (id < (size_t) element_count_leaves (elem, level)); + T8_ASSERT (1 <= level && level <= T8_ELEMENT_MAXLEVEL[TEclass]); + t8_standalone_element child; + + while (el->level < level) { + /* Shortcut if we need the first descendant of the subtree*/ + if (id == 0) { + element_get_first_descendant ((const t8_element_t *) el, (t8_element_t *) el, level); + return; + } + + t8_linearidx_t sum_descendants_of_children_before; + t8_linearidx_t sum_descendants_of_children_until_current = 0; + int childindex = -1; + + /* Find the first child id so that the sum of descendants of previous child and the own number of descendants is greater than id */ + do { + /* Go to the next child */ + sum_descendants_of_children_before = sum_descendants_of_children_until_current; + childindex++; + T8_ASSERT (childindex < element_get_num_children ((const t8_element_t *) el)); + + element_get_num_children ((const t8_element_t *) el); + + element_get_child ((const t8_element_t *) el, childindex, (t8_element_t *) &child); + const t8_linearidx_t num_descendants_of_child = element_count_leaves ((t8_element_t *) &child, level); + + /* Add number of descendant of current child to cumulative sum */ + sum_descendants_of_children_until_current = sum_descendants_of_children_before + num_descendants_of_child; + + } while (sum_descendants_of_children_until_current <= id); + + /* Replace el by child to go into next iteration at finer level*/ + element_get_child ((const t8_element_t *) el, childindex, (t8_element_t *) el); + /* get id in subtree of child */ + id -= sum_descendants_of_children_before; + } + T8_ASSERT (id == 0); + return; } /** Compute the linear id of a given element in a hypothetical uniform @@ -857,30 +1205,74 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; + t8_standalone_element ancestor; + + /* Determine the starting element for the iterative linear id computation. */ + if (level < el->level) { + /* Throw away child ids up to the coarser level */ + element_get_ancestor (el, level, &ancestor); + } + else { + /* Start with the input element. + Copy to have a mutable element. */ + element_copy ((const t8_element_t *) el, (t8_element_t *) &ancestor); + } + + t8_linearidx_t id = 0; + t8_standalone_element child; + + while (ancestor.level != 0) { + const t8_child_id childid = element_get_child_id ((t8_element_t *) &ancestor); + element_get_parent ((t8_element_t *) &ancestor, (t8_element_t *) &ancestor); + t8_linearidx_t parent_id = 0; + + for (int ichild = 0; ichild < childid; ichild++) { + /* el is now parent, so compute child to get sibling of previous el */ + + element_get_child ((const t8_element_t *) &ancestor, ichild, (t8_element_t *) &child); + const t8_linearidx_t num_child_descendants = element_count_leaves ((t8_element_t *) &child, level); + parent_id += num_child_descendants; + } + id += parent_id; + } + T8_ASSERT (id < (size_t) element_count_leaves ((t8_element_t *) &ancestor, level)); + return id; } /** Construct the successor in a uniform refinement of a given element. * \param [in] elem1 The element whose successor should be constructed. * \param [in,out] elem2 The element whose entries will be set. */ - static void - element_construct_successor ([[maybe_unused]] const t8_element_t *elem1, - [[maybe_unused]] t8_element_t *elem2) noexcept + static constexpr void + element_construct_successor (const t8_element_t *elem1, t8_element_t *elem2) noexcept { - const t8_subelement_element *subelement1 = (const t8_subelement_element *) elem1; T8_ASSERT (element_is_valid (elem1)); - if (subelement1->subelement_type == 0) { - t8_subelement_element *subelement2 = (t8_subelement_element *) elem2; - t8_standalone_scheme::element_construct_successor ((const t8_element_t *) &subelement1->element, - (t8_element_t *) &subelement2->element); - return; + + const t8_standalone_element *elem = (const t8_standalone_element *) elem1; + t8_standalone_element *succ = (t8_standalone_element *) elem2; + + element_copy ((const t8_element_t *) elem, (t8_element_t *) succ); + + const t8_child_id child_id = element_get_child_id ((const t8_element_t *) elem); + const int num_siblings = element_get_num_siblings ((const t8_element_t *) elem); + T8_ASSERT (0 <= child_id && child_id < num_siblings); + /* If the element is the last child of the parent, we need to go to the parent's successor (go to a coarser level)*/ + if (child_id == num_siblings - 1) { + element_get_parent ((const t8_element_t *) succ, (t8_element_t *) succ); + element_construct_successor ((const t8_element_t *) succ, (t8_element_t *) succ); + element_get_child ((const t8_element_t *) succ, 0, (t8_element_t *) succ); } - SC_ABORT ("SUBELEMENTS: This function is not implemented yet.\n"); + else { + element_get_parent ((const t8_element_t *) succ, (t8_element_t *) succ); + element_get_child ((const t8_element_t *) succ, child_id + 1, (t8_element_t *) succ); + } + + T8_ASSERT (element_is_valid (elem2)); } /** Count how many leaf descendants of a given uniform level an element would produce. @@ -894,16 +1286,17 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type == 0) { - return t8_standalone_scheme::element_count_leaves ((const t8_element_t *) &subelement->element, - level); + T8_ASSERT (0 <= level && level <= T8_ELEMENT_MAXLEVEL[TEclass]); + if (level < element_get_level (elem)) { + return 0; + } + else { + return num_descendants_at_leveldiff (elem, level - element_get_level (elem)); } - SC_ABORT ("SUBELEMENTS: This function is not implemented yet.\n"); } /** Count how many leaf descendants of a given uniform level the root element will produce. @@ -914,10 +1307,15 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers::count_leaves_from_root (level); + T8_ASSERT (level <= T8_ELEMENT_MAXLEVEL[TEclass]); + T8_ASSERT (level >= 0); + if constexpr (TEclass == T8_ECLASS_PYRAMID) { + SC_ABORT ("Not implemented yet.\n"); + } + return 1LL << (level * T8_ELEMENT_DIM[TEclass]); } /** Compare two elements. @@ -927,10 +1325,28 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers elem2. * If elem2 is a copy of elem1 then the elements are equal. */ - static int - element_compare ([[maybe_unused]] const t8_element_t *elem1, [[maybe_unused]] const t8_element_t *elem2) noexcept + static constexpr int + element_compare (const t8_element_t *elem1, const t8_element_t *elem2) noexcept { - SC_ABORT ("This function is not implemented yet.\n"); + T8_ASSERT (element_is_valid (elem1)); + T8_ASSERT (element_is_valid (elem2)); + + const t8_standalone_element *e1 = (const t8_standalone_element *) elem1; + const t8_standalone_element *e2 = (const t8_standalone_element *) elem2; + + const int maxlvl = SC_MAX (e1->level, e2->level); + + const t8_linearidx_t id1 = element_get_linear_id ((const t8_element_t *) e1, maxlvl); + const t8_linearidx_t id2 = element_get_linear_id ((const t8_element_t *) e2, maxlvl); + if (id1 == id2) { + if (e1->level == e2->level) { + return 0; + } + else { + return e1->level - e2->level; + } + } + return id1 < id2 ? -1 : 1; } // ################################################____VISUALIZATION____################################################ @@ -942,11 +1358,23 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; + if constexpr (TEclass == T8_ECLASS_VERTEX) { + return; + } + else { + int coords_int[T8_ELEMENT_DIM[TEclass]]; + T8_ASSERT (0 <= vertex && vertex < T8_ELEMENT_NUM_CORNERS[TEclass]); + element_compute_coords (el, vertex, coords_int); + for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + coords[idim] = coords_int[idim] / (double) get_root_len (); + } + } } /** Convert a point in the reference space of an element to a point in the @@ -957,11 +1385,25 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *) elem)->coords[dim] + current_ref_coords[dim] * length; + + current_out_coords[dim] /= (double) get_root_len (); + } + + current_ref_coords += T8_ECLASS_MAX_DIM; + current_out_coords += T8_ELEMENT_DIM[TEclass]; + } } // ################################################____MEMORY____################################################ @@ -985,15 +1427,26 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers::element_init (1, (t8_element_t *) &subelements[i].element); + /* allocate memory */ + T8_ASSERT (this->scheme_context != NULL); + T8_ASSERT (0 <= length); + T8_ASSERT (elems != NULL); + + for (int i = 0; i < length; ++i) { + elems[i] = (t8_element_t *) sc_mempool_alloc ((sc_mempool_t *) this->scheme_context); } + +/* in debug mode, set sensible default values. */ +#if T8_ENABLE_DEBUG + { + for (int i = 0; i < length; i++) { + element_init (1, elems[i]); + } + } +#endif } /** Initialize an array of allocated elements. @@ -1009,14 +1462,16 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers::element_init (1, (t8_element_t *) &subelements[i].element); +#if T8_ENABLE_DEBUG + t8_standalone_element *el = (t8_standalone_element *) elems; + /* Set all values to 0 */ + for (int ielem = 0; ielem < length; ielem++) { + element_set_linear_id ((t8_element_t *) (el + ielem), 0, 0); + T8_ASSERT (element_is_valid ((t8_element_t *) (el + ielem))); } +#endif } /** Deinitialize an array of allocated elements. @@ -1027,7 +1482,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpersscheme_context != NULL); + T8_ASSERT (0 <= length); + T8_ASSERT (elems != NULL); + for (int i = 0; i < length; ++i) { + sc_mempool_free ((sc_mempool_t *) scheme_context, elems[i]); + } } // ################################################____DEBUG____################################################ @@ -1064,22 +1524,22 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers::element_is_valid ((const t8_element_t *) &subelement->element); - if (subelement->subelement_type == 0) { - return element_valid; - } + T8_ASSERT (elem != NULL); + + const t8_standalone_element *el = (const t8_standalone_element *) elem; + const t8_element_coord max_coord = 2LL * get_root_len () - 1; - bool subelement_valid = (subelement->subelement_type >= T8_SUB_QUAD_MIN_SUBELEMENT_TYPE - && subelement->subelement_type <= T8_SUB_QUAD_MAX_SUBELEMENT_TYPE) - && (subelement->subelement_id >= T8_SUB_QUAD_MIN_SUBELEMENT_ID - && subelement->subelement_id <= T8_SUB_QUAD_MAX_SUBELEMENT_ID); + /* Check the level */ + int is_valid = 0 <= el->level && el->level <= T8_ELEMENT_MAXLEVEL[TEclass]; + /* Check coordinates, we allow a boundary layer around the root-element */ + for (int i = 0; i < T8_ELEMENT_DIM[TEclass]; i++) { + is_valid = is_valid && -(int64_t) get_root_len () <= el->coords[i] && el->coords[i] <= max_coord; + } - return subelement_valid && element_valid; + return is_valid; } /** @@ -1089,11 +1549,20 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; + + t8_debugf ("level: %i\n", el->level); + for (int i = 0; i < T8_ELEMENT_DIM[TEclass]; i++) { + t8_debugf ("x_%i: %i \n", i, el->coords[i]); + } + /** for (int e = 0; e < T8_ELEMENT_NUM_EQUATIONS[TEclass]; e++) { + * t8_debugf ("t_%i: %i \n", e, el->type[e]); + *} + * ToDo-Type */ } #endif @@ -1103,11 +1572,15 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; + int offset = 0; + offset += snprintf (debug_string + offset, string_size - offset, "level: %i\n", el->level); + for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + offset += snprintf (debug_string + offset, string_size - offset, "x_%i: %i \n", idim, el->coords[idim]); + } } // ################################################____MPI____################################################ @@ -1120,13 +1593,22 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers **els = (t8_standalone_element **) elements; + + for (unsigned int ielem = 0; ielem < count; ielem++) { + for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + mpiret = sc_MPI_Pack (&(els[ielem]->coords[idim]), 1, sc_MPI_INT, send_buffer, buffer_size, position, comm); + SC_CHECK_MPI (mpiret); + } + mpiret = sc_MPI_Pack (&els[ielem]->level, 1, sc_MPI_INT8_T, send_buffer, buffer_size, position, comm); + SC_CHECK_MPI (mpiret); + } } /** Determine an upper bound for the size of the packed message of \a count elements @@ -1134,11 +1616,24 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers **els = (t8_standalone_element **) elements; + + for (unsigned int ielem = 0; ielem < count; ielem++) { + for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + mpiret = sc_MPI_Unpack (recvbuf, buffer_size, position, &(els[ielem]->coords[idim]), 1, sc_MPI_INT, comm); + SC_CHECK_MPI (mpiret); + } + mpiret = sc_MPI_Unpack (recvbuf, buffer_size, position, &(els[ielem]->level), 1, sc_MPI_INT8_T, comm); + SC_CHECK_MPI (mpiret); + } + } + + private: + // ################################################____HELPER____################################################ + + /** The length of a element at a given level in integer coordinates + * \param[in] level Level of the element + */ + static constexpr t8_element_coord + element_get_len (const t8_element_level level) noexcept + { + return 1 << (T8_ELEMENT_MAXLEVEL[TEclass] - (level)); + } + + /** Compute the cube id of an element + * \param[in] elem Input element + * \param[in] level The refinement level + */ + static constexpr t8_cube_id + compute_cubeid (const t8_standalone_element *elem, const t8_element_level level) noexcept + { + t8_cube_id cube_id = 0; + + T8_ASSERT (0 <= elem->level && elem->level <= T8_ELEMENT_MAXLEVEL[TEclass]); + const t8_element_coord h = element_get_len (level); + + /* The cube id of the root element is 0.*/ + if (level != 0) { + for (int i = 0; i < T8_ELEMENT_DIM[TEclass]; i++) { + cube_id |= ((elem->coords[i] & h) ? 1 << i : 0); + } + } + return cube_id; + } + + /** + * Compute the ancestor of \a el at a given level via the equation properties + * + * \param[in] elem Input element + * \param[in] level Level of the ancestor to compute + * \param[in, out] ancestor Allocated element that will be filled with the data of the ancestor. + */ + static constexpr void + element_get_ancestor (const t8_standalone_element *elem, const t8_element_level level, + t8_standalone_element *ancestor) noexcept + { + T8_ASSERT (element_is_valid ((t8_element_t *) elem)); + T8_ASSERT (0 <= level && level <= elem->level); + if (elem != ancestor) { + element_copy ((const t8_element_t *) elem, (t8_element_t *) ancestor); + } + if (elem->level == level) { + return; + } + + /* Set type */ + if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + + /* The coordinates and the type of the ancestor are defined by the level. */ + element_cut_coordinates (ancestor, T8_ELEMENT_MAXLEVEL[TEclass] - level); + + ancestor->level = level; + } + + /** Use the number of zero bits on the left to detrime the level of the nearest common ancestor of two elements. + * \param[in] elem1 First input element + * \param[in] elem2 Second input element + * \return The level of the nearest common ancestor of the two elements + */ + static constexpr t8_element_level + element_get_cube_nca_level (const t8_standalone_element *elem1, + const t8_standalone_element *elem2) noexcept + { + /* XOR all coordinates. The number of zeros on the left determines the level needed, so that the coordinates equal. + OR over all these bit representations. The number of zeros on the left in this new number equals the coarses of all of these levels. + Therefore this is the level needed so that all coordinates equal.*/ + t8_element_coord maxexclor = 0; + + for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + maxexclor |= (elem1->coords[idim] ^ elem2->coords[idim]); + } + + const int num_zeros = number_of_leading_zeros (maxexclor); + /* If one element already is the ancestor of the other element num_zeros evaluates to maxlevel, in that case return the coarser of both levels*/ + return SC_MIN (num_zeros, (int) SC_MIN (elem1->level, elem2->level)); + } + + /** Compute the number of zero bits on the left side of coords. + * \param[in] coordinates Input coordinates + * \return Number of leading zeros + */ + static constexpr int + number_of_leading_zeros (const t8_element_coord coordinates) noexcept + { + const int num_of_active_bits_used = SC_LOG2_32 (coordinates) + 1; + T8_ASSERT (num_of_active_bits_used <= T8_ELEMENT_MAXLEVEL[TEclass]); + + return T8_ELEMENT_MAXLEVEL[TEclass] - num_of_active_bits_used; + } + + /** + * Set the \a shift last bits of every coordinate to zero. + * + * \param[in, out] elem Input element + * \param[in] shift Number of bits to set to zero + */ + static constexpr void + element_cut_coordinates (t8_standalone_element *elem, const int shift) noexcept + { + T8_ASSERT (0 <= shift && shift <= T8_ELEMENT_MAXLEVEL[TEclass]); + for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + elem->coords[idim] = (elem->coords[idim] >> shift) << shift; + } + } + + /** + * Set the least significant coordinates bits to zero. + * + * \param[in] elem Input element + * \param[in, out] parent_elem Parent element + * \param[in] length int that is 1 at the level of the input element + * Note length is used as additional input to avoid recomputation. + */ + static constexpr void + set_coords_at_level_to_zero (const t8_standalone_element *elem, t8_standalone_element *parent_elem, + const t8_element_coord length) noexcept + { + for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + parent_elem->coords[idim] = elem->coords[idim] & ~length; + } + } + + /** + * Adjust the coordinates based on the cube ID. + * + * \param[in] parent Input element + * \param[in, out] child Output element + * \param[in] length int that is 1 at the level of the child element + * \param[in] cube_id Cube ID for bitwise operation + * Note length is used as additional input to avoid recomputation. + */ + static constexpr void + put_cube_id_at_level (const t8_standalone_element *parent, t8_standalone_element *child, + const t8_element_coord length, const t8_cube_id cube_id) noexcept + { + for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + child->coords[idim] = parent->coords[idim] + ((cube_id & (1 << idim)) ? length : 0); + } + } + + /** Compute the length of the root element of the current TEclass. The length for a Vertex root element is always 0. + * \return The length of the root element + */ + + static constexpr t8_element_coord + get_root_len () noexcept + { + if constexpr (TEclass == T8_ECLASS_VERTEX) { + return 0; + } + else { + return 1 << T8_ELEMENT_MAXLEVEL[TEclass]; + } + } + + /** Get the number of descendants of an element at a given leveldiff. + * \param[in] elem Input element + * \param[in] leveldiff Difference between the level of the element + * \return number of descendants + * Note Caller is responsible for taking the absolute value of leveldiff + */ + static constexpr t8_linearidx_t + num_descendants_at_leveldiff ([[maybe_unused]] const t8_element_t *elem, const t8_element_level leveldiff) noexcept + { + T8_ASSERT (leveldiff <= get_maxlevel ()); + if constexpr (TEclass == T8_ECLASS_PYRAMID) { + SC_ABORT ("Not implemented yet.\n"); + } + return 1LL << (T8_ELEMENT_DIM[TEclass] * leveldiff); + } + + /** Compute the coordinates of a vertex of an element. + * \param [in] elem Input element. + * \param [in] vertex The number of the vertex. + * \param [out] coords An array of 3 t8_element_coord that + * will be filled with the coordinates of the vertex. + */ + static constexpr void + element_compute_coords (const t8_standalone_element *elem, const int vertex, int coords[]) noexcept + { + T8_ASSERT (0 <= vertex && vertex < element_get_num_corners ((const t8_element_t *) elem)); + + if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + else { + //Hypercubes + for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + coords[idim] = elem->coords[idim] + ((vertex & (1 << idim)) >> idim) * element_get_len (elem->level); + } + } + } + + /** Check if the element is inside the root tree + * \param [in] elem The input element. + * \return 1 if the element is inside the root tree, 0 otherwise. + */ + static inline int + element_is_inside_root ([[maybe_unused]] const t8_standalone_element *elem) noexcept + { + SC_ABORT ("Not implemented yet."); + } + + /** Check if the face is an internal face + * \param [in] elem The input element. + * \param [in] face The input face. + * \return 1 if the face is internal, 0 otherwise. + */ + static constexpr int + element_is_face_internal ([[maybe_unused]] const t8_standalone_element *elem, + [[maybe_unused]] const int face) noexcept + { + T8_ASSERT (element_is_valid ((const t8_element_t *) elem)); + T8_ASSERT (0 <= face && face < T8_ELEMENT_NUM_FACES[TEclass]); + if constexpr (!T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + return 0; + } + else { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + } + + /** Get the normal dim of the face + * \param [in] elem The input element. + * \param [in] face The input face. + * \return The normal dimension of the face. + */ + static constexpr int + element_face_normal_dim ([[maybe_unused]] const t8_standalone_element *elem, const int face) noexcept + { + T8_ASSERT (element_is_valid ((const t8_element_t *) elem)); + T8_ASSERT (0 <= face && face < T8_ELEMENT_NUM_FACES[TEclass]); + if constexpr (!T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + return face / 2; + } + else { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + } + + /** Given a face of an element that is also a hypercube face, determine if it is the boundary x_i == 1. + * \param [in] elem The input element. + * \param [in] face The input face. Needs to be a face of the hypercube the element is embedded in. + * \return 1 if the face is the boundary x_i == 1, 0 otherwise. + */ + static constexpr int + element_face_is_1_boundary ([[maybe_unused]] const t8_standalone_element *elem, const int face) noexcept + { + T8_ASSERT (element_is_valid ((const t8_element_t *) elem)); + T8_ASSERT (0 <= face && face < T8_ELEMENT_NUM_FACES[TEclass]); + if constexpr (!T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + return face % 2; + } + else { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + } + + /** Given a root_face that is also a hypercube face, determine if it is the boundary x_i == 1. + * \param [in] root_face The root_face. Needs to be a face of the hypercube the element is embedded in. + * \return 1 if the root_face is the boundary x_i == 1, 0 otherwise. + */ + static constexpr int + root_face_is_1_boundary (const int root_face) noexcept + { + if constexpr (!T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + return root_face % 2; + } + else { + /* Get root element or type*/ + SC_ABORT ("Only implemented for hypercubes.\n"); + } + } + + /** Get the eclass of the face for the element eclass + * \return The eclass of the face. + * Note: Only implemented for hypercubes + */ + static constexpr t8_eclass_t + get_face_eclass () noexcept + { + switch (TEclass) { + case T8_ECLASS_VERTEX: + SC_ABORT_NOT_REACHED (); + return T8_ECLASS_INVALID; + case T8_ECLASS_LINE: + return T8_ECLASS_VERTEX; + case T8_ECLASS_QUAD: + return T8_ECLASS_LINE; + case T8_ECLASS_HEX: + return T8_ECLASS_QUAD; + default: + if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + return T8_ECLASS_INVALID; + } + } + + /** Construct the boundary element at a specific face. + * \param [in] elem The input element. + * \param [in] root_face The index of the face of the root tree in which \a face + * lies. + * \param [in,out] boundary An allocated element of dimension of \a element + * minus 1. The entries will be filled with the entries + * of the face of \a element. + */ + template + static constexpr void + compute_boundary_face (const t8_element_t *elem, const int root_face, + t8_standalone_element *boundary) noexcept + { + T8_ASSERT (element_is_valid (elem)); + T8_ASSERT (0 <= root_face && root_face < T8_ELEMENT_NUM_FACES[TEclass]); + const t8_standalone_element *el = (const t8_standalone_element *) elem; + + /* Avoid problems for unneeded instantiations*/ + if constexpr (T8_ELEMENT_DIM[TFaceEclass] >= T8_ELEMENT_DIM[TEclass]) { + return; + } + + else { + boundary->level = el->level; + /* Delete the coordinate orthogonal to the given face and combine the remaining coordinates*/ + for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + const int ifacedim = get_facedim (idim, root_face); + + if (ifacedim != -1) { + /** Currently this part of the code is also compiled for vertices and faces of higher dim than the element. + * This leads to invalid shift inputs.*/ + if constexpr (TFaceEclass != T8_ECLASS_VERTEX) { + /** Set the boundary coordinates to the corresponding coordinates of the element, + * adjusted to the maxlevel of the face-scheme*/ + boundary->coords[ifacedim] = el->coords[idim] + << (T8_ELEMENT_MAXLEVEL[TFaceEclass] - T8_ELEMENT_MAXLEVEL[TEclass]); + } + else { + SC_ABORT_NOT_REACHED (); + } + } + } + } + if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + T8_ASSERT (t8_standalone_scheme::element_is_valid ((t8_element_t *) boundary)); + } + + /* Compute the index of the corner of a face + \ref element_get_face_corner + */ + static constexpr int + get_hypercube_face_corner_index (const int face_dim, const int face_sign, const int corner) noexcept + { + /** Bitoperation to put the face_sign bit at the face_dim position in the binary representation of corner. + * Example with the binary representation shown as aaaabb: + * corner = aaaabb, face_sign = x, then element_corner = aaaaxbb */ + const t8_element_coord first_part = (corner >> face_dim) << (face_dim + 1); + const t8_element_coord last_part = corner & ((1 << face_dim) - 1); + const t8_element_coord face_part = face_sign << face_dim; + return first_part + face_part + last_part; + } + + /** Delete the coordinate orthogonal to the given face and combine the remaining coordinates + * \param [in] idim The input coordinate index. + * \param [in] root_face The root_face + * \return The facedim + */ + static inline int + get_facedim (const int idim, const int root_face) noexcept + { + if constexpr (!T8_ELEMENT_NUM_EQUATIONS[TEclass]) { + const int facenormal_dim = root_face / 2; + if (idim == facenormal_dim) { + /* Coordinate direction is orthogonal to face, therefore it does not influence boundary face representation.*/ + return -1; + } + else if (idim > facenormal_dim) { + /* Coordinate direction is after the face normal direction, therefore we need to shift the coordinate index.*/ + return idim - 1; + } + else { + /* Coordinate direction is before the face normal direction, therefore we keep the coordinate index.*/ + return idim; + } + } + else { + SC_ABORT ("Only implemented for hypercubes.\n"); + } + return 0; } }; diff --git a/src/t8_schemes/t8_subelement/t8_subelement.cxx b/src/t8_schemes/t8_subelement/t8_subelement.cxx index 04859db400..787cddd87f 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement.cxx +++ b/src/t8_schemes/t8_subelement/t8_subelement.cxx @@ -35,7 +35,7 @@ t8_scheme_new_subelement (void) builder.add_eclass_scheme> (); builder.add_eclass_scheme> (); - builder.add_eclass_scheme (); + builder.add_eclass_scheme> (); builder.add_eclass_scheme (); builder.add_eclass_scheme> (); builder.add_eclass_scheme (); From 8ca7cbfc3250bde41de0e048c32b9dab64ea8eba Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Thu, 21 May 2026 16:20:08 +0200 Subject: [PATCH 05/28] Implement several functions --- .../subelements/t8_quads_hanging_nodes.cxx | 24 +- src/t8_schemes/t8_scheme.hxx | 2 +- .../t8_scheme_implementation.hxx | 1080 ++++------------- .../t8_subelement/t8_subelement.cxx | 2 +- .../t8_subelement/t8_subelement_type.hxx | 10 +- 5 files changed, 250 insertions(+), 868 deletions(-) diff --git a/example/subelements/t8_quads_hanging_nodes.cxx b/example/subelements/t8_quads_hanging_nodes.cxx index 37db429d31..b27eef28e4 100644 --- a/example/subelements/t8_quads_hanging_nodes.cxx +++ b/example/subelements/t8_quads_hanging_nodes.cxx @@ -53,28 +53,14 @@ struct t8_adapt_data * \param [in] elements The element or family of elements to consider for refinement/coarsening. */ int -t8_adapt_callback (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[]) +t8_adapt_callback ([[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[]) { - /* Our 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. */ - const struct t8_adapt_data *adapt_data = (const struct t8_adapt_data *) t8_forest_get_user_data (forest); - T8_ASSERT (adapt_data != NULL); - - /* Compute the element's centroid coordinates. */ - double centroid[3]; - t8_forest_element_centroid (forest_from, which_tree, elements[0], centroid); - - /* Compute the distance to our sphere midpoint. */ - double dist = t8_dist (centroid, adapt_data->midpoint); - if (dist < adapt_data->refine_if_inside_radius) { + if ((t8_forest_get_tree_element_offset (forest_from, which_tree) + lelement_id) % 2 == 0) { return 1; } - else if (is_family && dist > adapt_data->coarsen_if_outside_radius) { - return -1; - } return 0; } diff --git a/src/t8_schemes/t8_scheme.hxx b/src/t8_schemes/t8_scheme.hxx index 864fdc5229..a7ff8dee8f 100644 --- a/src/t8_schemes/t8_scheme.hxx +++ b/src/t8_schemes/t8_scheme.hxx @@ -103,7 +103,7 @@ struct t8_scheme t8_standalone_scheme, t8_standalone_scheme, /* Subelement schemes */ - t8_subelementquad_scheme<> + t8_subelementquad_scheme >; /* clang-format on */ diff --git a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx index 1301c85f68..d85cef4855 100644 --- a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx +++ b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx @@ -37,15 +37,14 @@ #include /** TODO. */ -template -struct t8_subelementquad_scheme: public t8_scheme_helpers> +struct t8_subelementquad_scheme: public t8_scheme_helpers { public: using standalone_scheme = t8_standalone_scheme; /** Constructor */ t8_subelementquad_scheme () noexcept - : element_size (sizeof (t8_standalone_element)), scheme_context (sc_mempool_new (element_size)) {}; + : element_size (sizeof (t8_subelement_element)), scheme_context (sc_mempool_new (element_size)) {}; protected: // What do i need this for? @@ -116,7 +115,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers); + return sizeof (t8_subelement_element); } /** Returns true, if there is one element in the tree, that does not refine into 2^dim children. @@ -126,10 +125,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type == 0) { + return standalone_scheme::element_get_num_corners (subelement_to_element (subelement)); + } - return T8_ELEMENT_NUM_CORNERS[TEclass]; + return T8_ELEMENT_NUM_CORNERS[T8_ECLASS_TRIANGLE]; } /** Compute the number of faces of a given element. * \param [in] elem The element. * \return The number of faces of \a elem. */ - static constexpr int - element_get_num_faces ([[maybe_unused]] const t8_element_t *elem) noexcept + static int + element_get_num_faces (const t8_element_t *elem) noexcept { T8_ASSERT (element_is_valid (elem)); - /* Note: With the introduction of pyramids the implementation will be adjusted. */ - return T8_ELEMENT_NUM_FACES[TEclass]; + const t8_subelement_element *subelement = (const t8_subelement_element *) elem; + if (subelement->subelement_type == 0) { + return standalone_scheme::element_get_num_faces (subelement_to_element (subelement)); + } + + return T8_ELEMENT_NUM_FACES[T8_ECLASS_TRIANGLE]; } /** Compute the maximum number of faces of a given element and all of its @@ -172,11 +176,13 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers> face & 1) + 2 * face; + SC_ABORT ("element_get_face_corner is not implemented for subelements yet.\n"); } /** Compute the shape of the face of an element. @@ -254,19 +257,13 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *) dest, (const t8_standalone_element *) source, - sizeof (t8_standalone_element)); + memcpy ((t8_subelement_element *) dest, (const t8_subelement_element *) source, sizeof (t8_subelement_element)); T8_ASSERT (element_is_valid (dest)); } @@ -300,17 +296,15 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el1 = (const t8_standalone_element *) elem1; - const t8_standalone_element *el2 = (const t8_standalone_element *) elem2; - if (el1->level != el2->level) + const t8_subelement_element *el1 = (const t8_subelement_element *) elem1; + const t8_subelement_element *el2 = (const t8_subelement_element *) elem2; + if (el1->subelement_type != el2->subelement_type) { + return 0; + } + if (el1->subelement_id != el2->subelement_id) { return 0; - for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { - if (el1->coords[idim] != el2->coords[idim]) - return 0; } - /* return el1->type == el2->type; - ToDo-Type */ - return 1; + return standalone_scheme::element_is_equal (subelement_to_element (el1), subelement_to_element (el2)); } // ################################################____ACCESSOR____################################################ @@ -323,7 +317,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *) elem)->level; + return standalone_scheme::element_get_level (element_to_element (elem)); } // ################################################____REFINEMENT____################################################ @@ -334,14 +328,9 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (t8_standalone_element *) elem; - el->level = 0; - for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { - el->coords[idim] = 0; - } - /* el->type = 0; - ToDo-Type */ - return; + t8_subelement_element *subelement = (t8_subelement_element *) elem; + reset_subelement_values (subelement); + standalone_scheme::set_to_root (subelement_to_element (subelement)); } /** Compute the parent of a given element \b elem and store it in \b parent. @@ -361,22 +350,15 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - t8_standalone_element *parent_elem = (t8_standalone_element *) parent; - - T8_ASSERT (el->level > 0); - - if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - SC_ABORT ("Only implemented for hypercubes.\n"); + const t8_subelement_element *el = (const t8_subelement_element *) elem; + t8_subelement_element *parent_elem = (t8_subelement_element *) parent; + reset_subelement_values (parent_elem); + if (element_is_subelement (elem)) { + // For subelements, the parent is the element from which they are refined. + standalone_scheme::element_copy (subelement_to_element (el), subelement_to_element (parent_elem)); + return; } - - const t8_element_coord length = element_get_len ((el->level)); - set_coords_at_level_to_zero (el, parent_elem, length); - - parent_elem->level = el->level - 1; - T8_ASSERT (parent_elem->level >= 0); - - T8_ASSERT (element_is_valid (parent)); + standalone_scheme::element_get_parent (subelement_to_element (el), subelement_to_element (parent_elem)); } /** Compute the number of siblings of an element. That is the number of @@ -390,18 +372,16 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - if (el->level == 0) - return 1; - T8_ASSERT (0 < el->level && el->level <= T8_ELEMENT_MAXLEVEL[TEclass]); - /* To get the number siblings, we first get the parent and then get the number of children of that parent*/ - if constexpr (refines_irregular ()) { - SC_ABORT ("This function is not implemented yet.\n"); + const t8_subelement_element *subelement = (const t8_subelement_element *) elem; + if (!element_is_subelement (elem)) { + return standalone_scheme::element_get_num_siblings (subelement_to_element (subelement)); } - else { - return T8_ELEMENT_NUM_CHILDREN[TEclass]; + int num_hanging_faces = 0; + // For subelements, the siblings are the other subelements of the same parent element. + for (int i = 0; i < T8_ELEMENT_NUM_FACES[T8_ECLASS_QUAD]; ++i) { + num_hanging_faces += (subelement->subelement_type & (1 << i)) >> i; } + return T8_ELEMENT_NUM_FACES[T8_ECLASS_QUAD] + num_hanging_faces; } /** Compute a specific sibling of a given element \b elem and store it in \b sibling. @@ -422,7 +402,8 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - t8_standalone_element *c = (t8_standalone_element *) child; - - T8_ASSERT (0 <= childid && childid < T8_ELEMENT_NUM_CHILDREN[TEclass]); - T8_ASSERT (0 <= el->level && el->level <= T8_ELEMENT_MAXLEVEL[TEclass]); - - /* Compute the cube id and shift the coordinates accordingly */ - t8_cube_id cube_id; - if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - SC_ABORT ("Only implemented for hypercubes.\n"); - } - else { - cube_id = childid; - } - - const t8_element_coord length = element_get_len (el->level + 1); - - put_cube_id_at_level (el, c, length, cube_id); - - c->level = el->level + 1; - + T8_ASSERT (!element_is_subelement (elem)); + T8_ASSERT (element_is_refinable (elem)); + standalone_scheme::element_get_child (element_to_element (elem), childid, element_to_element (child)); T8_ASSERT (element_is_valid (child)); } @@ -472,9 +431,10 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - T8_ASSERT (0 <= el->level && el->level <= T8_ELEMENT_MAXLEVEL[TEclass]); + t8_standalone_element *standalone_children[]; - const int num_children = length; - T8_ASSERT (length == element_get_num_children ((const t8_element_t *) el)); + if (element_is_subelement (elem)) { + t8_subelement_element *parent; + element_get_parent (elem, parent); + standalone_scheme::element_get_children (subelement_to_element (parent), length, standalone_children); + } + else { + standalone_scheme::element_get_children (subelement_to_element (elem), length, standalone_children); + } - for (int ichild = num_children - 1; ichild >= 0; ichild--) { - element_get_child ((const t8_element_t *) el, ichild, c[ichild]); - T8_ASSERT (element_is_valid (c[ichild])); + for (int ichild = 0; i < length; ++i) { + c[ichild].element = standalone_children[ichild]; + reset_subelement_values (c[ichild]); } } @@ -536,21 +507,12 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - T8_ASSERT (el->level >= 0); - if (el->level == 0) { - return -1; + const t8_subelement_element *subelement = (const t8_subelement_element *) elem; + if (element_is_subelement (elem)) { + // For subelements, the child id is the subelement id. + return subelement->subelement_id; } - const t8_cube_id cube_id = compute_cubeid (el, el->level); - t8_child_id child_id; - if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - SC_ABORT ("Only implemented for hypercubes.\n"); - } - else { - child_id = cube_id; - } - return child_id; + return standalone_scheme::element_get_child_id (subelement_to_element (subelement)); } /** Compute the ancestor id of an element, that is the child id @@ -562,15 +524,8 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - t8_standalone_element ancestor; - T8_ASSERT (0 <= el->level && el->level <= T8_ELEMENT_MAXLEVEL[TEclass]); - - element_get_ancestor (el, level, &ancestor); - return element_get_child_id ((const t8_element_t *) &ancestor); + SC_CHECK_ABORT (!element_is_subelement (elem), "element_get_ancestor_id is not implemented for subelements yet.\n"); + return standalone_scheme::element_get_ancestor_id (element_to_element (elem), level); } /** Query whether a given set of elements is a family or not. @@ -588,27 +543,26 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers parent, compare; - /* Take the parent of the first element as baseline to compare against */ - element_get_parent ((const t8_element_t *) fam[0], (t8_element_t *) &parent); - const int num_children = element_get_num_children ((const t8_element_t *) &parent); - for (int childid = 0; childid < num_children; childid++) { - /* check whether each element has the same parent */ - element_get_parent ((const t8_element_t *) fam[childid], (t8_element_t *) &compare); - if (element_compare ((const t8_element_t *) &parent, (const t8_element_t *) &compare)) { - return 0; + /* If the first element is a subelement, the remaining elements also have to be subelements and the elements must be equal. */ + if (element_is_subelement (fam[0])) { + auto element_0 = element_to_element (fam[0]); + for (int isib = 1; isib < element_get_num_siblings (fam[0]); ++isib) { + if (!element_is_subelement (fam[isib]) + || !standalone_scheme::element_is_equal (element_0, element_to_element ([isib]))) { + return 0; + } } - - /* check whether each element is the correct child of the collective parent */ - /* Could be replaced by type comparison as level is already checked in parent comparison */ - element_get_child ((const t8_element_t *) &parent, childid, (t8_element_t *) &compare); - - if (element_compare ((const t8_element_t *) fam[childid], (const t8_element_t *) &compare)) { + return 1; + } + /* If the first element is no subelement, the remaining elements also have to be no subelements and they must form a family. */ + t8_standalone_element *standalone_children[]; + for (int isib = 0; isib < element_get_num_siblings (fam[0]); ++isib) { + if (element_is_subelement (fam[isib])) { return 0; } + standalone_children[isib] = element_to_element (fam[isib]); } - return 1; + return standalone_scheme::elements_are_family (standalone_children); } // Note to devs: element_is_ancestor currently cannot be static @@ -620,43 +574,20 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers level(B) then A cannot be an ancestor. - - Otherwise compute the ancestor of B at level(A) - - Compare the computed ancestor with A. - */ + static bool + element_is_ancestor (const t8_element_t *element_A, const t8_element_t *element_B) noexcept + { T8_ASSERT (element_is_valid (element_A)); T8_ASSERT (element_is_valid (element_B)); - - const t8_standalone_element *el_B = (const t8_standalone_element *) element_B; - - const int level_A = element_get_level (element_A); - const int level_B = element_get_level (element_B); - - if (level_A > level_B) { - // A is finer than B and thus cannot be an ancestor. + if (element_is_equal (element_A, element_B)) { + return true; + } + if (element_is_subelement (element_A)) { + // Subelements are not ancestors of any element, as they are discarded for the next adaptation cycle. + // B could be a subelement if the underlying element is an ancestor of A. return false; } - - // Compute the ancestor of B at level_A and compare it with A - t8_element_t *ancestor; - element_new (1, &ancestor); - - t8_standalone_element *ancestor_casted = (t8_standalone_element *) ancestor; - - element_get_ancestor (el_B, level_A, ancestor_casted); - - const bool is_ancestor = element_is_equal (ancestor, element_A); - - element_destroy (1, &ancestor); - - // Return true if A == ancestor - // Return false if A != ancestor - return is_ancestor; + return standalone_scheme::element_is_ancestor (element_to_element (element_A, element_to_element (element_B))); } /** Compute the nearest common ancestor of two elements. That is, @@ -670,25 +601,10 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el1 = (const t8_standalone_element *) elem1; - const t8_standalone_element *el2 = (const t8_standalone_element *) elem2; - /* get the first possible level of the nca*/ - int cube_ancestor_level = element_get_cube_nca_level (el1, el2); - int real_level; - if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - SC_ABORT ("Only implemented for hypercubes.\n"); - } - else { - real_level = cube_ancestor_level; - } - /* get the ancestor at the calculated level*/ - element_get_ancestor (el1, real_level, (t8_standalone_element *) nca); - T8_ASSERT (element_is_valid (nca)); + SC_ABORT ("This function is not implemented yet.\n"); } /** Compute the first descendant of a given element. @@ -700,19 +616,10 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - t8_standalone_element *d = (t8_standalone_element *) desc; - - T8_ASSERT (level >= el->level); - T8_ASSERT (0 <= level && level <= T8_ELEMENT_MAXLEVEL[TEclass]); - - /* The first descendant of an element has the same anchor coords and type, but another level */ - element_copy ((const t8_element_t *) el, (t8_element_t *) d); - d->level = level; - - T8_ASSERT (element_is_valid ((t8_element_t *) d)); + SC_CHECK_ABORT (element_is_subelement (elem), + "element_get_first_descendant is not implemented for subelements yet.\n"); + return standalone_scheme::element_get_first_descendant (element_to_element (elem), element_to_element (desc), + level); } /** Compute the last descendant of a given element. @@ -724,25 +631,9 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - t8_standalone_element *d = (t8_standalone_element *) desc; - - T8_ASSERT (level >= el->level); - T8_ASSERT (0 <= level && level <= T8_ELEMENT_MAXLEVEL[TEclass]); - - element_copy ((const t8_element_t *) el, (t8_element_t *) d); - d->level = level; - - /* Shift the coords to the eighth cube. The type of the last descendant - * is the type of the input element */ - t8_element_coord coord_offset = element_get_len (el->level) - element_get_len (level); - for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { - d->coords[idim] |= coord_offset; - } - - T8_ASSERT (element_is_valid (desc)); + SC_CHECK_ABORT (element_is_subelement (elem), + "element_get_last_descendant is not implemented for subelements yet.\n"); + return standalone_scheme::element_get_last_descendant (element_to_element (elem), element_to_element (desc), level); } // ################################################____FACE REFINEMENT____################################################ @@ -755,10 +646,9 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - t8_standalone_element **children_els = (t8_standalone_element **) children; - int local_indices[T8_ELEMENT_NUM_CHILDREN[TEclass]]; - - if (child_indices == NULL) { - child_indices = local_indices; - } - const int face_sign = face % 2; - const int face_dim = face / 2; - for (int ifacechild = 0; ifacechild < num_children; ifacechild++) { - child_indices[ifacechild] = get_hypercube_face_corner_index (face_dim, face_sign, ifacechild); - } - for (int ifacechild = num_children - 1; ifacechild >= 0; ifacechild--) { - element_get_child ((const t8_element_t *) el, child_indices[ifacechild], - (t8_element_t *) children_els[ifacechild]); + SC_CHECK_ABORT (element_is_subelement (elem), + "element_get_children_at_face is not implemented for subelements yet.\n"); + t8_standalone_element *standalone_children[]; + standalone_scheme::element_get_children_at_face (element_to_element (elem), face, standalone_children, num_children, + child_indices); + for (int ichild = 0; ichild < num_children; ++ichild) { + children[ichild].element = standalone_children[ichild]; + reset_subelement_values (children[ichild]); } } @@ -823,7 +704,9 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - if (el->level == 0) - return -1; - if constexpr (!T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - /* Check if the least significant bit of the face normal coord is equal to the face_sign bit to get a valid parent face.*/ - const int least_significant_bit = ((el->coords[face / 2]) >> (T8_ELEMENT_MAXLEVEL[TEclass] - el->level)) % 2; - const bool invalid_parent_face = (element_face_is_1_boundary (el, face) != least_significant_bit); - if (invalid_parent_face) { - return -1; - } - return face; - } - else { - SC_ABORT ("Only implemented for hypercubes.\n"); - return 0; - } + SC_CHECK_ABORT (element_is_subelement (elem), + "element_face_get_parent_face is not implemented for subelements yet.\n"); + return standalone_scheme::element_face_get_parent_face (element_to_element (elem), face); } /** Construct the first descendant of an element at a given level that touches a given face. @@ -871,27 +739,10 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - t8_standalone_element *first_descendant = (t8_standalone_element *) first_desc; - - first_descendant->level = level; - if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - SC_ABORT ("Only implemented for hypercubes.\n"); - } - std::copy (el->coords.begin (), el->coords.end (), first_descendant->coords.begin ()); - - const bool face_is_1_boundary = face % 2; - - if (face_is_1_boundary) { //the face is a xi=1 boundary - const int facenormal_dim = face / 2; - - const t8_element_coord coord_offset = element_get_len (el->level) - element_get_len (level); - - first_descendant->coords[facenormal_dim] += coord_offset; - } + SC_CHECK_ABORT (element_is_subelement (elem), + "element_get_first_descendant_face is not implemented for subelements yet.\n"); + return standalone_scheme::element_get_first_descendant_face (element_to_element (elem), face, + element_to_element (first_desc), level); } /** Construct the last descendant of an element at a given level that touches a given face. @@ -906,23 +757,10 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - t8_standalone_element *last_descendant = (t8_standalone_element *) last_desc; - - last_descendant->level = level; - const t8_element_coord coord_offset = element_get_len (el->level) - element_get_len (level); - - if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - SC_ABORT ("Only implemented for hypercubes.\n"); - } - - for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { - const int multiplier = (idim == face / 2) ? face % 2 : 1; - last_descendant->coords[idim] = el->coords[idim] + multiplier * coord_offset; - } + SC_CHECK_ABORT (element_is_subelement (elem), + "element_get_last_descendant_face is not implemented for subelements yet.\n"); + return standalone_scheme::element_get_last_descendant_face (element_to_element (elem), face, + element_to_element (last_desc), level); } // ################################################____FACE NEIGHBOR____################################################ @@ -934,49 +772,9 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - - if (!element_is_face_internal (el, face)) { - const int dim = element_face_normal_dim (el, face); - if (element_face_is_1_boundary (el, face)) { - // a_d must be full of 1s up to level l - const t8_element_coord coord_offset = get_root_len () - element_get_len (el->level); - if (el->coords[dim] != coord_offset) { - return 0; - } - // all edges containing dim must be fulfilled with x_d-a_d >= x_j-a_j or x_j-a_j <= x_d-a_d - if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - SC_ABORT ("Only implemented for hypercubes.\n"); - } - } - else { - //zeroboundary - // x_d must be full of 0s up to level l - if (el->coords[dim] != 0) { - return 0; - } - // all edges containing dimid must be fulfilled with x_d-a_d <= x_j-a_j or x_j-a_j >= x_d-a_d - if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - SC_ABORT ("Only implemented for hypercubes.\n"); - } - } - } - else { - // internalface - // get graph edge e (or ieq) = (xi,xj) - // ai = aj is necessary and sufficient - if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - SC_ABORT ("Only implemented for hypercubes.\n"); - } - else { - SC_ABORT ("Cubes should not have internal faces!\n"); - } - } - return 1; + SC_ABORT ("This function is not implemented yet.\n"); } /** Given an element and a face of this element. If the face lies on the @@ -993,11 +791,9 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers= 0); - element_copy (elem, neigh); - - const t8_standalone_element *el = (const t8_standalone_element *) elem; - t8_standalone_element *neighbor = (t8_standalone_element *) neigh; - - if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - SC_ABORT ("Only implemented for hypercubes.\n"); - } - const int facenormal_dim = face / 2; - const int sign = face % 2 ? 1 : -1; - - /**Adapt coordinates*/ - const t8_element_coord length = element_get_len (el->level); - - neighbor->coords[facenormal_dim] += length * sign; - - *neigh_face = face ^ 1; - - T8_ASSERT (element_is_valid ((t8_element_t *) neighbor)); - /**check inside root*/ - return element_is_inside_root (neighbor); + SC_ABORT ("This function is not implemented yet.\n"); } // ################################################____TREE FACE TRANSFORMATION____################################################ */ @@ -1109,30 +882,10 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem, root_face, (t8_standalone_element *) boundary); - return; - case T8_ECLASS_LINE: - compute_boundary_face (elem, root_face, (t8_standalone_element *) boundary); - return; - case T8_ECLASS_QUAD: - compute_boundary_face (elem, root_face, (t8_standalone_element *) boundary); - return; - default: - if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - SC_ABORT ("Only implemented for hypercubes.\n"); - } - return; - } + SC_ABORT ("This function is not implemented yet.\n"); } // ################################################____LINEAR ID____################################################ @@ -1148,7 +901,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (t8_standalone_element *) elem; + t8_standalone_element *el = (t8_standalone_element *) elem; set_to_root ((t8_element_t *) el); @@ -1159,8 +912,8 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers child; + T8_ASSERT (1 <= level && level <= T8_ELEMENT_MAXLEVEL[T8_ECLASS_QUAD]); + t8_standalone_element child; while (el->level < level) { /* Shortcut if we need the first descendant of the subtree*/ @@ -1209,8 +962,8 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - t8_standalone_element ancestor; + const t8_standalone_element *el = (const t8_standalone_element *) elem; + t8_standalone_element ancestor; /* Determine the starting element for the iterative linear id computation. */ if (level < el->level) { @@ -1224,7 +977,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers child; + t8_standalone_element child; while (ancestor.level != 0) { const t8_child_id childid = element_get_child_id ((t8_element_t *) &ancestor); @@ -1253,8 +1006,8 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *elem = (const t8_standalone_element *) elem1; - t8_standalone_element *succ = (t8_standalone_element *) elem2; + const t8_standalone_element *elem = (const t8_standalone_element *) elem1; + t8_standalone_element *succ = (t8_standalone_element *) elem2; element_copy ((const t8_element_t *) elem, (t8_element_t *) succ); @@ -1290,7 +1043,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers= 0); - if constexpr (TEclass == T8_ECLASS_PYRAMID) { + if constexpr (T8_ECLASS_QUAD == T8_ECLASS_PYRAMID) { SC_ABORT ("Not implemented yet.\n"); } - return 1LL << (level * T8_ELEMENT_DIM[TEclass]); + return 1LL << (level * T8_ELEMENT_DIM[T8_ECLASS_QUAD]); } /** Compare two elements. @@ -1331,8 +1084,8 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *e1 = (const t8_standalone_element *) elem1; - const t8_standalone_element *e2 = (const t8_standalone_element *) elem2; + const t8_standalone_element *e1 = (const t8_standalone_element *) elem1; + const t8_standalone_element *e2 = (const t8_standalone_element *) elem2; const int maxlvl = SC_MAX (e1->level, e2->level); @@ -1363,15 +1116,15 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - if constexpr (TEclass == T8_ECLASS_VERTEX) { + const t8_standalone_element *el = (const t8_standalone_element *) elem; + if constexpr (T8_ECLASS_QUAD == T8_ECLASS_VERTEX) { return; } else { - int coords_int[T8_ELEMENT_DIM[TEclass]]; - T8_ASSERT (0 <= vertex && vertex < T8_ELEMENT_NUM_CORNERS[TEclass]); + int coords_int[T8_ELEMENT_DIM[T8_ECLASS_QUAD]]; + T8_ASSERT (0 <= vertex && vertex < T8_ELEMENT_NUM_CORNERS[T8_ECLASS_QUAD]); element_compute_coords (el, vertex, coords_int); - for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + for (int idim = 0; idim < T8_ELEMENT_DIM[T8_ECLASS_QUAD]; idim++) { coords[idim] = coords_int[idim] / (double) get_root_len (); } } @@ -1394,15 +1147,15 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *) elem)->coords[dim] + current_ref_coords[dim] * length; + = ((t8_standalone_element *) elem)->coords[dim] + current_ref_coords[dim] * length; current_out_coords[dim] /= (double) get_root_len (); } current_ref_coords += T8_ECLASS_MAX_DIM; - current_out_coords += T8_ELEMENT_DIM[TEclass]; + current_out_coords += T8_ELEMENT_DIM[T8_ECLASS_QUAD]; } } @@ -1465,7 +1218,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (t8_standalone_element *) elems; + t8_standalone_element *el = (t8_standalone_element *) elems; /* Set all values to 0 */ for (int ielem = 0; ielem < length; ielem++) { element_set_linear_id ((t8_element_t *) (el + ielem), 0, 0); @@ -1529,13 +1282,13 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; + const t8_standalone_element *el = (const t8_standalone_element *) elem; const t8_element_coord max_coord = 2LL * get_root_len () - 1; /* Check the level */ - int is_valid = 0 <= el->level && el->level <= T8_ELEMENT_MAXLEVEL[TEclass]; + int is_valid = 0 <= el->level && el->level <= T8_ELEMENT_MAXLEVEL[T8_ECLASS_QUAD]; /* Check coordinates, we allow a boundary layer around the root-element */ - for (int i = 0; i < T8_ELEMENT_DIM[TEclass]; i++) { + for (int i = 0; i < T8_ELEMENT_DIM[T8_ECLASS_QUAD]; i++) { is_valid = is_valid && -(int64_t) get_root_len () <= el->coords[i] && el->coords[i] <= max_coord; } @@ -1553,13 +1306,13 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; + const t8_standalone_element *el = (const t8_standalone_element *) elem; t8_debugf ("level: %i\n", el->level); - for (int i = 0; i < T8_ELEMENT_DIM[TEclass]; i++) { + for (int i = 0; i < T8_ELEMENT_DIM[T8_ECLASS_QUAD]; i++) { t8_debugf ("x_%i: %i \n", i, el->coords[i]); } - /** for (int e = 0; e < T8_ELEMENT_NUM_EQUATIONS[TEclass]; e++) { + /** for (int e = 0; e < T8_ELEMENT_NUM_EQUATIONS[T8_ECLASS_QUAD]; e++) { * t8_debugf ("t_%i: %i \n", e, el->type[e]); *} * ToDo-Type */ @@ -1575,10 +1328,10 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; + const t8_standalone_element *el = (const t8_standalone_element *) elem; int offset = 0; offset += snprintf (debug_string + offset, string_size - offset, "level: %i\n", el->level); - for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + for (int idim = 0; idim < T8_ELEMENT_DIM[T8_ECLASS_QUAD]; idim++) { offset += snprintf (debug_string + offset, string_size - offset, "x_%i: %i \n", idim, el->coords[idim]); } } @@ -1599,10 +1352,10 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers **els = (t8_standalone_element **) elements; + t8_standalone_element **els = (t8_standalone_element **) elements; for (unsigned int ielem = 0; ielem < count; ielem++) { - for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + for (int idim = 0; idim < T8_ELEMENT_DIM[T8_ECLASS_QUAD]; idim++) { mpiret = sc_MPI_Pack (&(els[ielem]->coords[idim]), 1, sc_MPI_INT, send_buffer, buffer_size, position, comm); SC_CHECK_MPI (mpiret); } @@ -1626,7 +1379,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers **els = (t8_standalone_element **) elements; + t8_standalone_element **els = (t8_standalone_element **) elements; for (unsigned int ielem = 0; ielem < count; ielem++) { - for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { + for (int idim = 0; idim < T8_ELEMENT_DIM[T8_ECLASS_QUAD]; idim++) { mpiret = sc_MPI_Unpack (recvbuf, buffer_size, position, &(els[ielem]->coords[idim]), 1, sc_MPI_INT, comm); SC_CHECK_MPI (mpiret); } @@ -1662,404 +1415,47 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *elem, const t8_element_level level) noexcept - { - t8_cube_id cube_id = 0; - - T8_ASSERT (0 <= elem->level && elem->level <= T8_ELEMENT_MAXLEVEL[TEclass]); - const t8_element_coord h = element_get_len (level); - - /* The cube id of the root element is 0.*/ - if (level != 0) { - for (int i = 0; i < T8_ELEMENT_DIM[TEclass]; i++) { - cube_id |= ((elem->coords[i] & h) ? 1 << i : 0); - } - } - return cube_id; - } - - /** - * Compute the ancestor of \a el at a given level via the equation properties - * - * \param[in] elem Input element - * \param[in] level Level of the ancestor to compute - * \param[in, out] ancestor Allocated element that will be filled with the data of the ancestor. - */ - static constexpr void - element_get_ancestor (const t8_standalone_element *elem, const t8_element_level level, - t8_standalone_element *ancestor) noexcept - { - T8_ASSERT (element_is_valid ((t8_element_t *) elem)); - T8_ASSERT (0 <= level && level <= elem->level); - if (elem != ancestor) { - element_copy ((const t8_element_t *) elem, (t8_element_t *) ancestor); - } - if (elem->level == level) { - return; - } - - /* Set type */ - if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - SC_ABORT ("Only implemented for hypercubes.\n"); - } - - /* The coordinates and the type of the ancestor are defined by the level. */ - element_cut_coordinates (ancestor, T8_ELEMENT_MAXLEVEL[TEclass] - level); - - ancestor->level = level; - } - - /** Use the number of zero bits on the left to detrime the level of the nearest common ancestor of two elements. - * \param[in] elem1 First input element - * \param[in] elem2 Second input element - * \return The level of the nearest common ancestor of the two elements - */ - static constexpr t8_element_level - element_get_cube_nca_level (const t8_standalone_element *elem1, - const t8_standalone_element *elem2) noexcept - { - /* XOR all coordinates. The number of zeros on the left determines the level needed, so that the coordinates equal. - OR over all these bit representations. The number of zeros on the left in this new number equals the coarses of all of these levels. - Therefore this is the level needed so that all coordinates equal.*/ - t8_element_coord maxexclor = 0; - - for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { - maxexclor |= (elem1->coords[idim] ^ elem2->coords[idim]); - } - - const int num_zeros = number_of_leading_zeros (maxexclor); - /* If one element already is the ancestor of the other element num_zeros evaluates to maxlevel, in that case return the coarser of both levels*/ - return SC_MIN (num_zeros, (int) SC_MIN (elem1->level, elem2->level)); - } - - /** Compute the number of zero bits on the left side of coords. - * \param[in] coordinates Input coordinates - * \return Number of leading zeros - */ - static constexpr int - number_of_leading_zeros (const t8_element_coord coordinates) noexcept - { - const int num_of_active_bits_used = SC_LOG2_32 (coordinates) + 1; - T8_ASSERT (num_of_active_bits_used <= T8_ELEMENT_MAXLEVEL[TEclass]); - - return T8_ELEMENT_MAXLEVEL[TEclass] - num_of_active_bits_used; - } - - /** - * Set the \a shift last bits of every coordinate to zero. - * - * \param[in, out] elem Input element - * \param[in] shift Number of bits to set to zero - */ - static constexpr void - element_cut_coordinates (t8_standalone_element *elem, const int shift) noexcept - { - T8_ASSERT (0 <= shift && shift <= T8_ELEMENT_MAXLEVEL[TEclass]); - for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { - elem->coords[idim] = (elem->coords[idim] >> shift) << shift; - } - } - - /** - * Set the least significant coordinates bits to zero. - * - * \param[in] elem Input element - * \param[in, out] parent_elem Parent element - * \param[in] length int that is 1 at the level of the input element - * Note length is used as additional input to avoid recomputation. - */ - static constexpr void - set_coords_at_level_to_zero (const t8_standalone_element *elem, t8_standalone_element *parent_elem, - const t8_element_coord length) noexcept - { - for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { - parent_elem->coords[idim] = elem->coords[idim] & ~length; - } - } - - /** - * Adjust the coordinates based on the cube ID. - * - * \param[in] parent Input element - * \param[in, out] child Output element - * \param[in] length int that is 1 at the level of the child element - * \param[in] cube_id Cube ID for bitwise operation - * Note length is used as additional input to avoid recomputation. - */ - static constexpr void - put_cube_id_at_level (const t8_standalone_element *parent, t8_standalone_element *child, - const t8_element_coord length, const t8_cube_id cube_id) noexcept - { - for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { - child->coords[idim] = parent->coords[idim] + ((cube_id & (1 << idim)) ? length : 0); - } - } - - /** Compute the length of the root element of the current TEclass. The length for a Vertex root element is always 0. - * \return The length of the root element - */ - - static constexpr t8_element_coord - get_root_len () noexcept - { - if constexpr (TEclass == T8_ECLASS_VERTEX) { - return 0; - } - else { - return 1 << T8_ELEMENT_MAXLEVEL[TEclass]; - } - } - - /** Get the number of descendants of an element at a given leveldiff. - * \param[in] elem Input element - * \param[in] leveldiff Difference between the level of the element - * \return number of descendants - * Note Caller is responsible for taking the absolute value of leveldiff - */ - static constexpr t8_linearidx_t - num_descendants_at_leveldiff ([[maybe_unused]] const t8_element_t *elem, const t8_element_level leveldiff) noexcept + // PRIVATE HELPER + static constexpr const t8_element_t * + subelement_to_element (const t8_subelement_element *subelement) noexcept { - T8_ASSERT (leveldiff <= get_maxlevel ()); - if constexpr (TEclass == T8_ECLASS_PYRAMID) { - SC_ABORT ("Not implemented yet.\n"); - } - return 1LL << (T8_ELEMENT_DIM[TEclass] * leveldiff); + return (const t8_element_t *) &subelement->element; } - /** Compute the coordinates of a vertex of an element. - * \param [in] elem Input element. - * \param [in] vertex The number of the vertex. - * \param [out] coords An array of 3 t8_element_coord that - * will be filled with the coordinates of the vertex. - */ - static constexpr void - element_compute_coords (const t8_standalone_element *elem, const int vertex, int coords[]) noexcept + static constexpr t8_element_t * + subelement_to_element (t8_subelement_element *subelement) noexcept { - T8_ASSERT (0 <= vertex && vertex < element_get_num_corners ((const t8_element_t *) elem)); - - if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - SC_ABORT ("Only implemented for hypercubes.\n"); - } - else { - //Hypercubes - for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { - coords[idim] = elem->coords[idim] + ((vertex & (1 << idim)) >> idim) * element_get_len (elem->level); - } - } + return (t8_element_t *) &subelement->element; } - /** Check if the element is inside the root tree - * \param [in] elem The input element. - * \return 1 if the element is inside the root tree, 0 otherwise. - */ - static inline int - element_is_inside_root ([[maybe_unused]] const t8_standalone_element *elem) noexcept + static constexpr const t8_element_t * + element_to_element (const t8_element_t *element) noexcept { - SC_ABORT ("Not implemented yet."); - } - - /** Check if the face is an internal face - * \param [in] elem The input element. - * \param [in] face The input face. - * \return 1 if the face is internal, 0 otherwise. - */ - static constexpr int - element_is_face_internal ([[maybe_unused]] const t8_standalone_element *elem, - [[maybe_unused]] const int face) noexcept - { - T8_ASSERT (element_is_valid ((const t8_element_t *) elem)); - T8_ASSERT (0 <= face && face < T8_ELEMENT_NUM_FACES[TEclass]); - if constexpr (!T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - return 0; - } - else { - SC_ABORT ("Only implemented for hypercubes.\n"); - } + const t8_subelement_element *subelement = (const t8_subelement_element *) element; + return subelement_to_element (subelement); } - /** Get the normal dim of the face - * \param [in] elem The input element. - * \param [in] face The input face. - * \return The normal dimension of the face. - */ - static constexpr int - element_face_normal_dim ([[maybe_unused]] const t8_standalone_element *elem, const int face) noexcept + static constexpr t8_element_t * + element_to_element (t8_element_t *element) noexcept { - T8_ASSERT (element_is_valid ((const t8_element_t *) elem)); - T8_ASSERT (0 <= face && face < T8_ELEMENT_NUM_FACES[TEclass]); - if constexpr (!T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - return face / 2; - } - else { - SC_ABORT ("Only implemented for hypercubes.\n"); - } + t8_subelement_element *subelement = (t8_subelement_element *) element; + return subelement_to_element (subelement); } - /** Given a face of an element that is also a hypercube face, determine if it is the boundary x_i == 1. - * \param [in] elem The input element. - * \param [in] face The input face. Needs to be a face of the hypercube the element is embedded in. - * \return 1 if the face is the boundary x_i == 1, 0 otherwise. - */ - static constexpr int - element_face_is_1_boundary ([[maybe_unused]] const t8_standalone_element *elem, const int face) noexcept + static bool + element_is_subelement (const t8_element_t *elem) noexcept { - T8_ASSERT (element_is_valid ((const t8_element_t *) elem)); - T8_ASSERT (0 <= face && face < T8_ELEMENT_NUM_FACES[TEclass]); - if constexpr (!T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - return face % 2; - } - else { - SC_ABORT ("Only implemented for hypercubes.\n"); - } + const t8_subelement_element *subelement = (const t8_subelement_element *) elem; + return (subelement->subelement_type != 0); } - /** Given a root_face that is also a hypercube face, determine if it is the boundary x_i == 1. - * \param [in] root_face The root_face. Needs to be a face of the hypercube the element is embedded in. - * \return 1 if the root_face is the boundary x_i == 1, 0 otherwise. - */ - static constexpr int - root_face_is_1_boundary (const int root_face) noexcept - { - if constexpr (!T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - return root_face % 2; - } - else { - /* Get root element or type*/ - SC_ABORT ("Only implemented for hypercubes.\n"); - } - } - - /** Get the eclass of the face for the element eclass - * \return The eclass of the face. - * Note: Only implemented for hypercubes - */ - static constexpr t8_eclass_t - get_face_eclass () noexcept - { - switch (TEclass) { - case T8_ECLASS_VERTEX: - SC_ABORT_NOT_REACHED (); - return T8_ECLASS_INVALID; - case T8_ECLASS_LINE: - return T8_ECLASS_VERTEX; - case T8_ECLASS_QUAD: - return T8_ECLASS_LINE; - case T8_ECLASS_HEX: - return T8_ECLASS_QUAD; - default: - if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - SC_ABORT ("Only implemented for hypercubes.\n"); - } - return T8_ECLASS_INVALID; - } - } - - /** Construct the boundary element at a specific face. - * \param [in] elem The input element. - * \param [in] root_face The index of the face of the root tree in which \a face - * lies. - * \param [in,out] boundary An allocated element of dimension of \a element - * minus 1. The entries will be filled with the entries - * of the face of \a element. + /** create the root element + * \param [in,out] elem The element that is filled with the root */ - template - static constexpr void - compute_boundary_face (const t8_element_t *elem, const int root_face, - t8_standalone_element *boundary) noexcept + static void + reset_subelement_values (t8_subelement_element *subelement) noexcept { - T8_ASSERT (element_is_valid (elem)); - T8_ASSERT (0 <= root_face && root_face < T8_ELEMENT_NUM_FACES[TEclass]); - const t8_standalone_element *el = (const t8_standalone_element *) elem; - - /* Avoid problems for unneeded instantiations*/ - if constexpr (T8_ELEMENT_DIM[TFaceEclass] >= T8_ELEMENT_DIM[TEclass]) { - return; - } - - else { - boundary->level = el->level; - /* Delete the coordinate orthogonal to the given face and combine the remaining coordinates*/ - for (int idim = 0; idim < T8_ELEMENT_DIM[TEclass]; idim++) { - const int ifacedim = get_facedim (idim, root_face); - - if (ifacedim != -1) { - /** Currently this part of the code is also compiled for vertices and faces of higher dim than the element. - * This leads to invalid shift inputs.*/ - if constexpr (TFaceEclass != T8_ECLASS_VERTEX) { - /** Set the boundary coordinates to the corresponding coordinates of the element, - * adjusted to the maxlevel of the face-scheme*/ - boundary->coords[ifacedim] = el->coords[idim] - << (T8_ELEMENT_MAXLEVEL[TFaceEclass] - T8_ELEMENT_MAXLEVEL[TEclass]); - } - else { - SC_ABORT_NOT_REACHED (); - } - } - } - } - if constexpr (T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - SC_ABORT ("Only implemented for hypercubes.\n"); - } - T8_ASSERT (t8_standalone_scheme::element_is_valid ((t8_element_t *) boundary)); - } - - /* Compute the index of the corner of a face - \ref element_get_face_corner - */ - static constexpr int - get_hypercube_face_corner_index (const int face_dim, const int face_sign, const int corner) noexcept - { - /** Bitoperation to put the face_sign bit at the face_dim position in the binary representation of corner. - * Example with the binary representation shown as aaaabb: - * corner = aaaabb, face_sign = x, then element_corner = aaaaxbb */ - const t8_element_coord first_part = (corner >> face_dim) << (face_dim + 1); - const t8_element_coord last_part = corner & ((1 << face_dim) - 1); - const t8_element_coord face_part = face_sign << face_dim; - return first_part + face_part + last_part; - } - - /** Delete the coordinate orthogonal to the given face and combine the remaining coordinates - * \param [in] idim The input coordinate index. - * \param [in] root_face The root_face - * \return The facedim - */ - static inline int - get_facedim (const int idim, const int root_face) noexcept - { - if constexpr (!T8_ELEMENT_NUM_EQUATIONS[TEclass]) { - const int facenormal_dim = root_face / 2; - if (idim == facenormal_dim) { - /* Coordinate direction is orthogonal to face, therefore it does not influence boundary face representation.*/ - return -1; - } - else if (idim > facenormal_dim) { - /* Coordinate direction is after the face normal direction, therefore we need to shift the coordinate index.*/ - return idim - 1; - } - else { - /* Coordinate direction is before the face normal direction, therefore we keep the coordinate index.*/ - return idim; - } - } - else { - SC_ABORT ("Only implemented for hypercubes.\n"); - } - return 0; + subelement->subelement_type = 0; + subelement->subelement_id = 0; } }; diff --git a/src/t8_schemes/t8_subelement/t8_subelement.cxx b/src/t8_schemes/t8_subelement/t8_subelement.cxx index 787cddd87f..04859db400 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement.cxx +++ b/src/t8_schemes/t8_subelement/t8_subelement.cxx @@ -35,7 +35,7 @@ t8_scheme_new_subelement (void) builder.add_eclass_scheme> (); builder.add_eclass_scheme> (); - builder.add_eclass_scheme> (); + builder.add_eclass_scheme (); builder.add_eclass_scheme (); builder.add_eclass_scheme> (); builder.add_eclass_scheme (); diff --git a/src/t8_schemes/t8_subelement/t8_subelement_type.hxx b/src/t8_schemes/t8_subelement/t8_subelement_type.hxx index 989f1f43c5..c0f25bf512 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement_type.hxx +++ b/src/t8_schemes/t8_subelement/t8_subelement_type.hxx @@ -29,15 +29,15 @@ #include #define T8_SUBELEMENT_FACES 3 -#define T8_SUB_QUAD_MAX_SUBELEMENT_TYPE 15 +#define T8_SUB_QUAD_MAX_SUBELEMENT_TYPE 14 #define T8_SUB_QUAD_MIN_SUBELEMENT_TYPE 1 #define T8_SUB_QUAD_MAX_SUBELEMENT_ID 7 #define T8_SUB_QUAD_MIN_SUBELEMENT_ID 0 struct t8_subelement_element { - t8_standalone_element element; - int - subelement_type; /* saves the information, which type of transition cell a subelement is associated to (default is 0, meaning no subelement). */ - int subelement_id; /* saves the information, what children subelement the given element is (default is 0) */ + t8_standalone_element* element; + int subelement_type + = 0; /* saves the information, which type of transition cell a subelement is associated to (default is 0, meaning no subelement). */ + int subelement_id = 0; /* saves the information, what children subelement the given element is (default is 0) */ }; From 1866bbec57791d871822074f4902116d170f218a Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Fri, 22 May 2026 12:47:34 +0200 Subject: [PATCH 06/28] remaining functions --- .../t8_scheme_implementation.hxx | 295 ++++-------------- 1 file changed, 63 insertions(+), 232 deletions(-) diff --git a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx index d85cef4855..10ec5327cb 100644 --- a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx +++ b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx @@ -900,56 +900,8 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (t8_standalone_element *) elem; - - set_to_root ((t8_element_t *) el); - - /* There is only one element at level 0, so it must be root */ - if (level == 0) { - T8_ASSERT (id == 0); - return; - } - - T8_ASSERT (id < (size_t) element_count_leaves (elem, level)); - T8_ASSERT (1 <= level && level <= T8_ELEMENT_MAXLEVEL[T8_ECLASS_QUAD]); - t8_standalone_element child; - - while (el->level < level) { - /* Shortcut if we need the first descendant of the subtree*/ - if (id == 0) { - element_get_first_descendant ((const t8_element_t *) el, (t8_element_t *) el, level); - return; - } - - t8_linearidx_t sum_descendants_of_children_before; - t8_linearidx_t sum_descendants_of_children_until_current = 0; - int childindex = -1; - - /* Find the first child id so that the sum of descendants of previous child and the own number of descendants is greater than id */ - do { - /* Go to the next child */ - sum_descendants_of_children_before = sum_descendants_of_children_until_current; - childindex++; - T8_ASSERT (childindex < element_get_num_children ((const t8_element_t *) el)); - - element_get_num_children ((const t8_element_t *) el); - - element_get_child ((const t8_element_t *) el, childindex, (t8_element_t *) &child); - const t8_linearidx_t num_descendants_of_child = element_count_leaves ((t8_element_t *) &child, level); - - /* Add number of descendant of current child to cumulative sum */ - sum_descendants_of_children_until_current = sum_descendants_of_children_before + num_descendants_of_child; - - } while (sum_descendants_of_children_until_current <= id); - - /* Replace el by child to go into next iteration at finer level*/ - element_get_child ((const t8_element_t *) el, childindex, (t8_element_t *) el); - /* get id in subtree of child */ - id -= sum_descendants_of_children_before; - } - T8_ASSERT (id == 0); - return; + SC_CHECK_ABORT (element_is_subelement (elem), "element_set_linear_id is not implemented for subelements yet.\n"); + standalone_scheme::element_set_linear_id (element_to_element (elem), level, id); } /** Compute the linear id of a given element in a hypothetical uniform @@ -961,40 +913,8 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - t8_standalone_element ancestor; - - /* Determine the starting element for the iterative linear id computation. */ - if (level < el->level) { - /* Throw away child ids up to the coarser level */ - element_get_ancestor (el, level, &ancestor); - } - else { - /* Start with the input element. - Copy to have a mutable element. */ - element_copy ((const t8_element_t *) el, (t8_element_t *) &ancestor); - } - - t8_linearidx_t id = 0; - t8_standalone_element child; - - while (ancestor.level != 0) { - const t8_child_id childid = element_get_child_id ((t8_element_t *) &ancestor); - element_get_parent ((t8_element_t *) &ancestor, (t8_element_t *) &ancestor); - t8_linearidx_t parent_id = 0; - - for (int ichild = 0; ichild < childid; ichild++) { - /* el is now parent, so compute child to get sibling of previous el */ - - element_get_child ((const t8_element_t *) &ancestor, ichild, (t8_element_t *) &child); - const t8_linearidx_t num_child_descendants = element_count_leaves ((t8_element_t *) &child, level); - parent_id += num_child_descendants; - } - id += parent_id; - } - T8_ASSERT (id < (size_t) element_count_leaves ((t8_element_t *) &ancestor, level)); - return id; + SC_CHECK_ABORT (element_is_subelement (elem), "element_get_linear_id is not implemented for subelements yet.\n"); + return standalone_scheme::element_get_linear_id (element_to_element (elem), level); } /** Construct the successor in a uniform refinement of a given element. @@ -1004,28 +924,9 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *elem = (const t8_standalone_element *) elem1; - t8_standalone_element *succ = (t8_standalone_element *) elem2; - - element_copy ((const t8_element_t *) elem, (t8_element_t *) succ); - - const t8_child_id child_id = element_get_child_id ((const t8_element_t *) elem); - const int num_siblings = element_get_num_siblings ((const t8_element_t *) elem); - T8_ASSERT (0 <= child_id && child_id < num_siblings); - /* If the element is the last child of the parent, we need to go to the parent's successor (go to a coarser level)*/ - if (child_id == num_siblings - 1) { - element_get_parent ((const t8_element_t *) succ, (t8_element_t *) succ); - element_construct_successor ((const t8_element_t *) succ, (t8_element_t *) succ); - element_get_child ((const t8_element_t *) succ, 0, (t8_element_t *) succ); - } - else { - element_get_parent ((const t8_element_t *) succ, (t8_element_t *) succ); - element_get_child ((const t8_element_t *) succ, child_id + 1, (t8_element_t *) succ); - } - - T8_ASSERT (element_is_valid (elem2)); + SC_CHECK_ABORT (element_is_subelement (elem1), + "element_construct_successor is not implemented for subelements yet.\n"); + return standalone_scheme::element_construct_successor (element_to_element (elem1), element_to_element (elem2)); } /** Count how many leaf descendants of a given uniform level an element would produce. @@ -1042,14 +943,8 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers= 0); - if constexpr (T8_ECLASS_QUAD == T8_ECLASS_PYRAMID) { - SC_ABORT ("Not implemented yet.\n"); - } - return 1LL << (level * T8_ELEMENT_DIM[T8_ECLASS_QUAD]); + return standalone_scheme::count_leaves_from_root (level); } /** Compare two elements. @@ -1081,25 +971,9 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *e1 = (const t8_standalone_element *) elem1; - const t8_standalone_element *e2 = (const t8_standalone_element *) elem2; - - const int maxlvl = SC_MAX (e1->level, e2->level); - - const t8_linearidx_t id1 = element_get_linear_id ((const t8_element_t *) e1, maxlvl); - const t8_linearidx_t id2 = element_get_linear_id ((const t8_element_t *) e2, maxlvl); - if (id1 == id2) { - if (e1->level == e2->level) { - return 0; - } - else { - return e1->level - e2->level; - } - } - return id1 < id2 ? -1 : 1; + SC_CHECK_ABORT (element_is_subelement (elem1) || element_is_subelement (elem2), + "element_compare is not implemented for subelements yet.\n"); + return standalone_scheme::element_compare (element_to_element (elem1), element_to_element (elem2)); } // ################################################____VISUALIZATION____################################################ @@ -1114,20 +988,9 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - if constexpr (T8_ECLASS_QUAD == T8_ECLASS_VERTEX) { - return; - } - else { - int coords_int[T8_ELEMENT_DIM[T8_ECLASS_QUAD]]; - T8_ASSERT (0 <= vertex && vertex < T8_ELEMENT_NUM_CORNERS[T8_ECLASS_QUAD]); - element_compute_coords (el, vertex, coords_int); - for (int idim = 0; idim < T8_ELEMENT_DIM[T8_ECLASS_QUAD]; idim++) { - coords[idim] = coords_int[idim] / (double) get_root_len (); - } - } + SC_CHECK_ABORT (element_is_subelement (elem), + "element_get_vertex_reference_coords is not implemented for subelements yet.\n"); + return standalone_scheme::element_get_vertex_reference_coords (element_to_element (elem), vertex, coords); } /** Convert a point in the reference space of an element to a point in the @@ -1142,21 +1005,10 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *) elem)->coords[dim] + current_ref_coords[dim] * length; - - current_out_coords[dim] /= (double) get_root_len (); - } - - current_ref_coords += T8_ECLASS_MAX_DIM; - current_out_coords += T8_ELEMENT_DIM[T8_ECLASS_QUAD]; - } + SC_CHECK_ABORT (element_is_subelement (elem), + "element_get_reference_coords is not implemented for subelements yet.\n"); + return standalone_scheme::element_get_vertex_reference_coords (element_to_element (elem), ref_coords, num_coords, + out_coords); } // ################################################____MEMORY____################################################ @@ -1218,11 +1070,11 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (t8_standalone_element *) elems; - /* Set all values to 0 */ + t8_subelement_element *subelement = (t8_subelement_element *) elem; for (int ielem = 0; ielem < length; ielem++) { - element_set_linear_id ((t8_element_t *) (el + ielem), 0, 0); - T8_ASSERT (element_is_valid ((t8_element_t *) (el + ielem))); + reset_subelement_values (subelement + ielem); + standalone_scheme::element_init (subelement_to_element (subelement + ielem)); + T8_ASSERT (element_is_valid ((t8_element_t *) (subelement + ielem))); } #endif } @@ -1282,40 +1134,30 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - const t8_element_coord max_coord = 2LL * get_root_len () - 1; - - /* Check the level */ - int is_valid = 0 <= el->level && el->level <= T8_ELEMENT_MAXLEVEL[T8_ECLASS_QUAD]; - /* Check coordinates, we allow a boundary layer around the root-element */ - for (int i = 0; i < T8_ELEMENT_DIM[T8_ECLASS_QUAD]; i++) { - is_valid = is_valid && -(int64_t) get_root_len () <= el->coords[i] && el->coords[i] <= max_coord; + const t8_subelement_element *subelement = (const t8_subelement_element *) elem; + int element_valid = standalone_scheme::element_is_valid (subelement_to_element (subelement)); + if (element_is_subelement (elem)) { + return element_valid; } + bool subelement_valid = (subelement->subelement_type >= T8_SUB_QUAD_MIN_SUBELEMENT_TYPE + && subelement->subelement_type <= T8_SUB_QUAD_MAX_SUBELEMENT_TYPE) + && (subelement->subelement_id >= T8_SUB_QUAD_MIN_SUBELEMENT_ID + && subelement->subelement_id <= T8_SUB_QUAD_MAX_SUBELEMENT_ID); - return is_valid; + return subelement_valid && element_valid; } /** - * Print a given element. For a example for a triangle print the coordinates - * and the level of the triangle. This function is only available in the - * debugging configuration. - * - * \param [in] elem The element to print - */ + * Print a given element. For a example for a triangle print the coordinates + * and the level of the triangle. This function is only available in the + * debugging configuration. + * + * \param [in] elem The element to print + */ static constexpr void - element_debug_print (const t8_element_t *elem) noexcept + element_debug_print ([[maybe_unused]] const t8_element_t *elem) noexcept { - - const t8_standalone_element *el = (const t8_standalone_element *) elem; - - t8_debugf ("level: %i\n", el->level); - for (int i = 0; i < T8_ELEMENT_DIM[T8_ECLASS_QUAD]; i++) { - t8_debugf ("x_%i: %i \n", i, el->coords[i]); - } - /** for (int e = 0; e < T8_ELEMENT_NUM_EQUATIONS[T8_ECLASS_QUAD]; e++) { - * t8_debugf ("t_%i: %i \n", e, el->type[e]); - *} - * ToDo-Type */ + SC_ABORT ("Not implemented."); } #endif @@ -1326,14 +1168,10 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *el = (const t8_standalone_element *) elem; - int offset = 0; - offset += snprintf (debug_string + offset, string_size - offset, "level: %i\n", el->level); - for (int idim = 0; idim < T8_ELEMENT_DIM[T8_ECLASS_QUAD]; idim++) { - offset += snprintf (debug_string + offset, string_size - offset, "x_%i: %i \n", idim, el->coords[idim]); - } + SC_ABORT ("Not implemented."); } // ################################################____MPI____################################################ @@ -1351,15 +1189,13 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers **els = (t8_standalone_element **) elements; - + t8_subelement_element **els = (t8_subelement_element **) elements; for (unsigned int ielem = 0; ielem < count; ielem++) { - for (int idim = 0; idim < T8_ELEMENT_DIM[T8_ECLASS_QUAD]; idim++) { - mpiret = sc_MPI_Pack (&(els[ielem]->coords[idim]), 1, sc_MPI_INT, send_buffer, buffer_size, position, comm); - SC_CHECK_MPI (mpiret); - } - mpiret = sc_MPI_Pack (&els[ielem]->level, 1, sc_MPI_INT8_T, send_buffer, buffer_size, position, comm); + standalone_scheme::element_MPI_Pack (subelement_to_element (els[ielem]), 1, send_buffer, buffer_size, position, + comm); + int mpiret = sc_MPI_Pack (&els[ielem]->subelement_type, 1, sc_MPI_INT, send_buffer, buffer_size, position, comm); + SC_CHECK_MPI (mpiret); + mpiret = sc_MPI_Pack (&els[ielem]->subelement_id, 1, sc_MPI_INT, send_buffer, buffer_size, position, comm); SC_CHECK_MPI (mpiret); } } @@ -1372,19 +1208,15 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers **els = (t8_standalone_element **) elements; + t8_subelement_element **els = (t8_subelement_element **) elements; for (unsigned int ielem = 0; ielem < count; ielem++) { - for (int idim = 0; idim < T8_ELEMENT_DIM[T8_ECLASS_QUAD]; idim++) { - mpiret = sc_MPI_Unpack (recvbuf, buffer_size, position, &(els[ielem]->coords[idim]), 1, sc_MPI_INT, comm); - SC_CHECK_MPI (mpiret); - } - mpiret = sc_MPI_Unpack (recvbuf, buffer_size, position, &(els[ielem]->level), 1, sc_MPI_INT8_T, comm); + standalone_scheme::element_MPI_Unpack (recvbuf, buffer_size, position, subelement_to_element (els[ielem]), 1, + comm); + int mpiret = sc_MPI_Unpack (recvbuf, buffer_size, position, &els[ielem]->subelement_type, 1, sc_MPI_INT, comm); + SC_CHECK_MPI (mpiret); + mpiret = sc_MPI_Unpack (recvbuf, buffer_size, position, &els[ielem]->subelement_id, 1, sc_MPI_INT, comm); SC_CHECK_MPI (mpiret); } } From bdaec0b9ab167fb7e15505cf67c56d6c7c6bbeb5 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Tue, 26 May 2026 16:47:32 +0200 Subject: [PATCH 07/28] working version of adapting normally --- .../subelements/t8_quads_hanging_nodes.cxx | 8 +- .../t8_scheme_implementation.hxx | 209 +++++++++--------- .../t8_subelement/t8_subelement_type.hxx | 4 +- 3 files changed, 115 insertions(+), 106 deletions(-) diff --git a/example/subelements/t8_quads_hanging_nodes.cxx b/example/subelements/t8_quads_hanging_nodes.cxx index b27eef28e4..049b32159e 100644 --- a/example/subelements/t8_quads_hanging_nodes.cxx +++ b/example/subelements/t8_quads_hanging_nodes.cxx @@ -170,8 +170,12 @@ main (int argc, char **argv) // --- Remove hanging nodes via adapting again. --- // forest = t8_remove_hanging_nodes (forest); - //TODO: permit forest_from->incomplete_trees - std::cout << "Scheme : " << t8_element_get_element_size (t8_forest_get_scheme (forest), T8_ECLASS_QUAD) << "\n"; + + // Now output to vtk. + const char *prefix_with_hanging_nodes = "t8_with_hanging_nodes"; + t8_forest_write_vtk (forest, prefix_with_hanging_nodes); + t8_global_productionf (" [subelements] Wrote adapted forest with hanging nodes to vtu files: %s*\n", + prefix_with_hanging_nodes); // --- Cleanup. --- t8_forest_unref (&forest); diff --git a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx index 10ec5327cb..1c207a3466 100644 --- a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx +++ b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx @@ -220,7 +220,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers= 1, since we count the element itself as a sibling. * Note that the number of siblings is 1 for the root element. */ - static constexpr int + static int element_get_num_siblings (const t8_element_t *elem) noexcept { T8_ASSERT (element_is_valid (elem)); @@ -395,7 +396,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *standalone_children[]; + t8_element_t *standalone_children_ptrs[T8_ELEMENT_NUM_CHILDREN[T8_ECLASS_QUAD]]; + for (int ichild = 0; ichild < length; ++ichild) { + standalone_children_ptrs[ichild] = subelement_to_element ((t8_subelement_element *) c[ichild]); + } if (element_is_subelement (elem)) { - t8_subelement_element *parent; - element_get_parent (elem, parent); - standalone_scheme::element_get_children (subelement_to_element (parent), length, standalone_children); + t8_subelement_element parent_storage; + element_get_parent (elem, (t8_element_t *) &parent_storage); + standalone_scheme::element_get_children (subelement_to_element (&parent_storage), length, + standalone_children_ptrs); } else { - standalone_scheme::element_get_children (subelement_to_element (elem), length, standalone_children); + standalone_scheme::element_get_children (subelement_to_element (subelement), length, standalone_children_ptrs); } - - for (int ichild = 0; i < length; ++i) { - c[ichild].element = standalone_children[ichild]; - reset_subelement_values (c[ichild]); + for (int ichild = 0; ichild < length; ++ichild) { + reset_subelement_values ((t8_subelement_element *) c[ichild]); } } @@ -503,7 +505,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *standalone_children[]; + t8_element_t *standalone_children[T8_ELEMENT_NUM_CHILDREN[T8_ECLASS_QUAD]]; for (int isib = 0; isib < element_get_num_siblings (fam[0]); ++isib) { if (element_is_subelement (fam[isib])) { return 0; @@ -565,8 +567,6 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers *standalone_children[]; - standalone_scheme::element_get_children_at_face (element_to_element (elem), face, standalone_children, num_children, - child_indices); + t8_element_t *standalone_children_ptrs[T8_ELEMENT_NUM_CHILDREN[T8_ECLASS_QUAD]]; for (int ichild = 0; ichild < num_children; ++ichild) { - children[ichild].element = standalone_children[ichild]; - reset_subelement_values (children[ichild]); + standalone_children_ptrs[ichild] = subelement_to_element ((t8_subelement_element *) children[ichild]); + } + standalone_scheme::element_get_children_at_face (element_to_element (elem), face, standalone_children_ptrs, + num_children, child_indices); + for (int ichild = 0; ichild < num_children; ++ichild) { + reset_subelement_values ((t8_subelement_element *) children[ichild]); } } @@ -700,13 +703,13 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers elem2. * If elem2 is a copy of elem1 then the elements are equal. */ - static constexpr int + static int element_compare (const t8_element_t *elem1, const t8_element_t *elem2) noexcept { - SC_CHECK_ABORT (element_is_subelement (elem1) || element_is_subelement (elem2), + SC_CHECK_ABORT (!element_is_subelement (elem1) && !element_is_subelement (elem2), "element_compare is not implemented for subelements yet.\n"); return standalone_scheme::element_compare (element_to_element (elem1), element_to_element (elem2)); } @@ -985,10 +988,10 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type >= T8_SUB_QUAD_MIN_SUBELEMENT_TYPE @@ -1154,7 +1157,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type, 1, sc_MPI_INT, send_buffer, buffer_size, position, comm); SC_CHECK_MPI (mpiret); mpiret = sc_MPI_Pack (&els[ielem]->subelement_id, 1, sc_MPI_INT, send_buffer, buffer_size, position, comm); @@ -1205,11 +1209,12 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type, 1, sc_MPI_INT, comm); SC_CHECK_MPI (mpiret); mpiret = sc_MPI_Unpack (recvbuf, buffer_size, position, &els[ielem]->subelement_id, 1, sc_MPI_INT, comm); @@ -1247,26 +1252,26 @@ struct t8_subelementquad_scheme: public t8_scheme_helperselement; } - static constexpr t8_element_t * + static t8_element_t * subelement_to_element (t8_subelement_element *subelement) noexcept { return (t8_element_t *) &subelement->element; } - static constexpr const t8_element_t * + static const t8_element_t * element_to_element (const t8_element_t *element) noexcept { const t8_subelement_element *subelement = (const t8_subelement_element *) element; return subelement_to_element (subelement); } - static constexpr t8_element_t * + static t8_element_t * element_to_element (t8_element_t *element) noexcept { t8_subelement_element *subelement = (t8_subelement_element *) element; diff --git a/src/t8_schemes/t8_subelement/t8_subelement_type.hxx b/src/t8_schemes/t8_subelement/t8_subelement_type.hxx index c0f25bf512..86393fbf6c 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement_type.hxx +++ b/src/t8_schemes/t8_subelement/t8_subelement_type.hxx @@ -31,12 +31,12 @@ #define T8_SUBELEMENT_FACES 3 #define T8_SUB_QUAD_MAX_SUBELEMENT_TYPE 14 #define T8_SUB_QUAD_MIN_SUBELEMENT_TYPE 1 -#define T8_SUB_QUAD_MAX_SUBELEMENT_ID 7 +#define T8_SUB_QUAD_MAX_SUBELEMENT_ID 6 #define T8_SUB_QUAD_MIN_SUBELEMENT_ID 0 struct t8_subelement_element { - t8_standalone_element* element; + t8_standalone_element element; int subelement_type = 0; /* saves the information, which type of transition cell a subelement is associated to (default is 0, meaning no subelement). */ int subelement_id = 0; /* saves the information, what children subelement the given element is (default is 0) */ From 8de61006a6a581059891ac337a8a40adbc465d1b Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Wed, 27 May 2026 17:01:37 +0200 Subject: [PATCH 08/28] Add to adapt --- .../subelements/t8_quads_hanging_nodes.cxx | 114 +++---------- src/CMakeLists.txt | 1 + src/t8_forest/t8_forest_adapt.cxx | 21 +++ src/t8_forest/t8_forest_subelement.cxx | 152 ++++++++++++++++++ src/t8_forest/t8_forest_subelement.hxx | 41 +++++ .../t8_scheme_implementation.hxx | 113 ++++++++++--- .../t8_subelement/t8_subelement.cxx | 14 +- 7 files changed, 323 insertions(+), 133 deletions(-) create mode 100644 src/t8_forest/t8_forest_subelement.cxx create mode 100644 src/t8_forest/t8_forest_subelement.hxx diff --git a/example/subelements/t8_quads_hanging_nodes.cxx b/example/subelements/t8_quads_hanging_nodes.cxx index 049b32159e..264b74fbdd 100644 --- a/example/subelements/t8_quads_hanging_nodes.cxx +++ b/example/subelements/t8_quads_hanging_nodes.cxx @@ -24,22 +24,15 @@ * This is an example to demonstrate hanging node resolution for quads. */ -#include "t8_schemes/t8_subelement/t8_subelement.hxx" -#include /* General t8code header, always include this. */ -#include /* cmesh definition and basic interface. */ -#include /* A collection of exemplary cmeshes */ -#include /* forest definition and basic interface. */ -#include /* save forest */ -#include /* geometrical information of the forest */ -#include /* default refinement scheme. */ -#include /* Basic operations on 3D vectors. */ - -struct t8_adapt_data -{ - double midpoint[3]; /* 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. */ -}; +#include /* General t8code header, always include this. */ +#include /* cmesh definition and basic interface. */ +#include /* A collection of exemplary cmeshes */ +#include /* forest definition and basic interface. */ +#include /* save forest */ +#include /* geometrical information of the forest */ +#include /* Function for adding subelements. */ +#include /* Subelement refinement scheme. */ +#include /* Basic operations on 3D vectors. */ /** The adaptation callback function. * \param [in] forest The current forest that is in construction. @@ -64,84 +57,12 @@ t8_adapt_callback ([[maybe_unused]] t8_forest_t forest, [[maybe_unused]] t8_fore return 0; } -/* This is the adapt function, called for each element in a balanced forest during transition. - * We refine an element into a suitable transition cell if it has at most one hanging face */ -int -t8_remove_hanging_nodes_callback (t8_forest_t forest, [[maybe_unused]] t8_forest_t forest_from, t8_locidx_t which_tree, - [[maybe_unused]] t8_eclass_t tree_class, [[maybe_unused]] t8_locidx_t lelement_id, - const t8_scheme *scheme, [[maybe_unused]] const int is_family, - [[maybe_unused]] const int num_elements, t8_element_t *elements[]) -{ - int subelement_type = 0; - /* We use a binary encoding (depending on the face enumeration), to determine which subelement type to use. - * Every face has a flag parameter, which is set to 1, if there is a neighbour with a higher level - * and to 0, if the level of the neighbour is at most the level of the element. - * - * f0 1 - * x - - x - - x x - - x - - x - * | | | \ | / | - * | | | \ | / | | f3 | f2 | f1 | f0 | - * f3 x | f2 --> 1 x - - x | 0 --> binary code (according to the face enumeration): | 1 | 0 | 0 | 1 | = 9 in base 10 - * | | | / \ | - * | elem | | / \ | - * x - - - - - x x - - - - - x - * f1 0 - * - */ - const int num_faces = scheme->element_get_num_faces (tree_class, elements[0]); - for (int iface = 0; iface < num_faces; iface++) { - const t8_element_t **neighbors; /**< Neighboring elements. */ - int *dual_faces_internal; /**< Face indices of the neighbor elements. */ - int num_neighbors; /**< Number of neighboring elements. */ - t8_locidx_t *neighids; /**< Neighboring elements ids. */ - t8_eclass_t neigh_class; /**< Neighboring elements tree class. */ - - t8_forest_leaf_face_neighbors (forest, which_tree, elements[0], &neighbors, iface, &dual_faces_internal, - &num_neighbors, &neighids, &neigh_class); - - if (num_neighbors > 1) { - subelement_type += 1 << ((num_faces - 1) - iface); - } - /* clean-up */ - if (num_neighbors > 0) { - // Free allocated memory. - T8_FREE (neighbors); - T8_FREE (dual_faces_internal); - T8_FREE (neighids); - } - } - - /* returning the right subelement types */ - if (subelement_type == 0) { /* in this case, there are no hanging nodes and we do not need to do anything */ - return 0; - } - else if (subelement_type == 15) { /* Normal 1:8 refinement */ - return 1; - } - else { /* use subelements and add 1 to every type, to avoid refine = 1 */ - return subelement_type + 1; - } -} - /** Adapt forest according to callback. */ t8_forest_t t8_adapt_forest (t8_forest_t forest) { t8_forest_t forest_adapt; - struct t8_adapt_data adapt_data = { - { 0.5, 0.5, 1 }, /* Midpoints of the sphere. */ - 0.2, /* Refine if inside this radius. */ - 0.4 /* Coarsen if outside this radius. */ - }; - forest_adapt = t8_forest_new_adapt (forest, t8_adapt_callback, 0, 0, &adapt_data); - return forest_adapt; -} - -/** Adapt forest according to callback. */ -t8_forest_t -t8_remove_hanging_nodes (t8_forest_t forest) -{ - t8_forest_t forest_adapt = t8_forest_new_adapt (forest, t8_remove_hanging_nodes_callback, 0, 0, NULL); + forest_adapt = t8_forest_new_adapt (forest, t8_adapt_callback, 0, 0, NULL); return forest_adapt; } @@ -167,16 +88,17 @@ main (int argc, char **argv) /* --- Adapt the forest. --- */ forest = t8_adapt_forest (forest); + std::cout << "Subelements before removing: " << t8_forest_has_subelements (forest) << std::endl; // --- Remove hanging nodes via adapting again. --- - // forest = t8_remove_hanging_nodes (forest); - + t8_forest_remove_hanging_nodes (forest); + std::cout << "Subelements after removing: " << t8_forest_has_subelements (forest) << std::endl; // Now output to vtk. - const char *prefix_with_hanging_nodes = "t8_with_hanging_nodes"; - t8_forest_write_vtk (forest, prefix_with_hanging_nodes); - t8_global_productionf (" [subelements] Wrote adapted forest with hanging nodes to vtu files: %s*\n", - prefix_with_hanging_nodes); - // --- Cleanup. --- + // const char *prefix_with_hanging_nodes = "t8_with_hanging_nodes"; + // t8_forest_write_vtk (forest, prefix_with_hanging_nodes); + // t8_global_productionf (" [subelements] Wrote adapted forest with hanging nodes to vtu files: %s*\n", + // prefix_with_hanging_nodes); + // // --- Cleanup. --- t8_forest_unref (&forest); sc_finalize (); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 36fb71efb0..7c8cae27af 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -172,6 +172,7 @@ target_sources( T8 PRIVATE t8_forest/t8_forest_ghost.cxx t8_forest/t8_forest_iterate.cxx t8_forest/t8_forest_balance.cxx + t8_forest/t8_forest_subelement.cxx t8_forest/t8_forest_search/t8_forest_search.cxx t8_geometry/t8_geometry.cxx t8_geometry/t8_geometry_helpers.c diff --git a/src/t8_forest/t8_forest_adapt.cxx b/src/t8_forest/t8_forest_adapt.cxx index 1c8e00d382..4690d8a1fc 100644 --- a/src/t8_forest/t8_forest_adapt.cxx +++ b/src/t8_forest/t8_forest_adapt.cxx @@ -23,11 +23,13 @@ * Implements functions declared in \ref t8_forest_adapt.h. */ +#include "t8_eclass/t8_eclass.h" #include #include #include #include #include +#include #include /* We want to export the whole implementation to be callable from "C" */ @@ -431,6 +433,8 @@ t8_forest_adapt (t8_forest_t forest) T8_ASSERT (forest->trees->elem_count == forest_from->trees->elem_count); if (forest->set_adapt_recursive) { + SC_CHECK_ABORT (!t8_eclass_scheme_is_subelement (t8_forest_get_scheme (forest_from), T8_ECLASS_QUAD), + "Recursive adaptation is currently not implemented for subelement schemes."); refine_list = sc_list_new (nullptr); } forest->local_num_leaf_elements = 0; @@ -619,6 +623,23 @@ t8_forest_adapt (t8_forest_t forest) } el_considered++; } + else if (refine > 1) { // Subelement case. + T8_ASSERT (t8_eclass_scheme_is_subelement (t8_forest_get_scheme (forest_from), T8_ECLASS_QUAD)); + const t8_subelementquad_scheme *subelemscheme = (const t8_subelementquad_scheme *) scheme; + /* The subelement-callback function returns refine = subelement_type + 1 to avoid subelement_type = 1. + * We undo this to use the subelement_type-values that match the binary encoding of the neighbour structure. */ + int subelement_type = refine - 1; + + int num_subelements = subelemscheme->element_get_number_of_subelements (subelement_type); + (void) t8_element_array_push_count (telements, num_subelements); + for (int zz = 0; zz < num_subelements; zz++) { + /* TODO: In a future version elements_from[zz] should be const and we should call t8_element_array_index_locidx (the const version). */ + elements[zz] = t8_element_array_index_locidx_mutable (telements, el_inserted + zz); + } + subelemscheme->element_to_subelement (elements_from[0], subelement_type, elements); + el_inserted += (t8_locidx_t) num_subelements; + el_considered++; + } else { /* Remove the element */ T8_ASSERT (refine == -2); diff --git a/src/t8_forest/t8_forest_subelement.cxx b/src/t8_forest/t8_forest_subelement.cxx new file mode 100644 index 0000000000..f80b27a648 --- /dev/null +++ b/src/t8_forest/t8_forest_subelement.cxx @@ -0,0 +1,152 @@ +/* + 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_quads_hanging_nodes.cxx + * This is an example to demonstrate hanging node resolution for quads. + */ + +#include "t8_forest_subelement.hxx" +#include +#include "t8_forest_general.h" +#include "t8_forest_types.h" +#include "t8_forest_private.h" +#include +#include +#include +#include +#include +#include "t8_forest_adapt.h" + +/* This is the adapt function, called for each element in a balanced forest during transition. + * We refine an element into a suitable transition cell if it has at most one hanging face */ +int +discard_subelements_callback ([[maybe_unused]] t8_forest_t forest, [[maybe_unused]] t8_forest_t forest_from, + [[maybe_unused]] t8_locidx_t which_tree, [[maybe_unused]] t8_eclass_t tree_class, + [[maybe_unused]] t8_locidx_t lelement_id, const t8_scheme *scheme, + [[maybe_unused]] const int is_family, [[maybe_unused]] const int num_elements, + t8_element_t *elements[]) +{ + const t8_subelementquad_scheme *subelem_scheme = (const t8_subelementquad_scheme *) scheme; + if (subelem_scheme->element_is_subelement (elements[0])) { + return -1; + } + return 0; +} + +/* This is the adapt function, called for each element in a balanced forest during transition. + * We refine an element into a suitable transition cell if it has at most one hanging face */ +int +t8_remove_hanging_nodes_callback ([[maybe_unused]] 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, + const t8_scheme *scheme, [[maybe_unused]] const int is_family, + [[maybe_unused]] const int num_elements, t8_element_t *elements[]) +{ + int subelement_type = 0; + /* We use a binary encoding (depending on the face enumeration), to determine which subelement type to use. + * Every face has a flag parameter, which is set to 1, if there is a neighbour with a higher level + * and to 0, if the level of the neighbour is at most the level of the element. + * + * f0 1 + * x - - x - - x x - - x - - x + * | | | \ | / | + * | | | \ | / | | f3 | f2 | f1 | f0 | + * f3 x | f2 --> 1 x - - x | 0 --> binary code (according to the face enumeration): | 1 | 0 | 0 | 1 | = 9 in base 10 + * | | | / \ | + * | elem | | / \ | + * x - - - - - x x - - - - - x + * f1 0 + * + */ + const int num_faces = scheme->element_get_num_faces (tree_class, elements[0]); + for (int iface = 0; iface < num_faces; iface++) { + const t8_element_t **neighbors; /**< Neighboring elements. */ + int *dual_faces_internal; /**< Face indices of the neighbor elements. */ + int num_neighbors; /**< Number of neighboring elements. */ + t8_locidx_t *neighids; /**< Neighboring elements ids. */ + t8_eclass_t neigh_class; /**< Neighboring elements tree class. */ + + t8_forest_leaf_face_neighbors (forest_from, which_tree, elements[0], &neighbors, iface, &dual_faces_internal, + &num_neighbors, &neighids, &neigh_class); + + if (num_neighbors > 1) { + subelement_type += 1 << ((num_faces - 1) - iface); + } + /* clean-up */ + if (num_neighbors > 0) { + // Free allocated memory. + T8_FREE (neighbors); + T8_FREE (dual_faces_internal); + T8_FREE (neighids); + } + } + + /* returning the right subelement types */ + if (subelement_type == 0) { /* in this case, there are no hanging nodes and we do not need to do anything */ + return 0; + } + else if (subelement_type == 15) { /* Normal 1:8 refinement */ + return 1; + } + else { /* use subelements and add 1 to every type, to avoid refine = 1 */ + return subelement_type + 1; + } +} + +/** Adapt forest according to callback. */ +bool +t8_forest_has_subelements (t8_forest_t forest) +{ + if (t8_eclass_scheme_is_subelement (t8_forest_get_scheme (forest), T8_ECLASS_QUAD)) { + return false; + } + const t8_subelementquad_scheme *scheme = (const t8_subelementquad_scheme *) t8_forest_get_scheme (forest); + for (t8_locidx_t itree = 0; itree < t8_forest_get_num_local_trees (forest); ++itree) { + + for (t8_locidx_t ielem = 0; ielem < t8_forest_get_tree_num_leaf_elements (forest, itree); ++ielem) { + const t8_element_t *elem = t8_forest_get_leaf_element_in_tree (forest, itree, ielem); + if (scheme->element_is_subelement (elem)) { + return true; + } + } + } + return false; +} + +/** Adapt forest according to callback. */ +void +t8_forest_discard_subelements (t8_forest_t forest) +{ + if (!t8_forest_has_subelements (forest)) { + return; + } + forest = t8_forest_new_adapt (forest, discard_subelements_callback, 0, 0, NULL); +} + +/** Adapt forest according to callback. */ +void +t8_forest_remove_hanging_nodes (t8_forest_t forest) +{ + + t8_global_productionf ("Into t8_forest_remove_hanging_nodes.\n"); + forest = t8_forest_new_adapt (forest, t8_remove_hanging_nodes_callback, 0, 0, NULL); + t8_global_productionf ("Done t8_forest_remove_hanging_nodes.\n"); +} diff --git a/src/t8_forest/t8_forest_subelement.hxx b/src/t8_forest/t8_forest_subelement.hxx new file mode 100644 index 0000000000..cf59e177f4 --- /dev/null +++ b/src/t8_forest/t8_forest_subelement.hxx @@ -0,0 +1,41 @@ +/* + 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 + + 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_forest_subelement.hxx + * TODO + */ +#pragma once + +#include +#include "t8_forest_general.h" +#include +#include +#include + +void +t8_forest_remove_hanging_nodes (t8_forest_t forest); + +bool +t8_forest_has_subelements (t8_forest_t forest); + +void +t8_forest_discard_subelements (t8_forest_t forest); diff --git a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx index 1c207a3466..2371623402 100644 --- a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx +++ b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx @@ -377,12 +377,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type & (1 << i)) >> i; - } - return T8_ELEMENT_NUM_FACES[T8_ECLASS_QUAD] + num_hanging_faces; + return element_get_number_of_subelements (subelement->subelement_type); } /** Compute a specific sibling of a given element \b elem and store it in \b sibling. @@ -632,9 +627,9 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type); + t8_debugf ("Subelementid: %i\n", subelement->subelement_id); + standalone_scheme::element_debug_print (subelement_to_element (subelement)); } #endif @@ -1250,6 +1254,72 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type != 0); + } + static int + element_get_number_of_subelements (int subelement_type) + { + + int num_hanging_faces = 0; + /* Count the number of ones of the binary subelement type. This number equals the number of hanging faces. */ + for (int i = 0; i < T8_ELEMENT_NUM_FACES[T8_ECLASS_QUAD]; ++i) { + num_hanging_faces += (subelement_type & (1 << i)) >> i; + } + return T8_ELEMENT_NUM_FACES[T8_ECLASS_QUAD] + num_hanging_faces; + } + + static void + element_to_subelement (const t8_element_t *elem, int type, t8_element_t *c[]) + { + const t8_subelement_element *element = (const t8_subelement_element *) elem; + t8_subelement_element **subelements = (t8_subelement_element **) c; + + // const p4est_quadrant_t *q = &pquad_w_sub_elem->p4q; + + int num_subelements = element_get_number_of_subelements (type); + + T8_ASSERT (type >= T8_SUB_QUAD_MIN_SUBELEMENT_TYPE && type <= T8_SUB_QUAD_MAX_SUBELEMENT_TYPE); + + T8_ASSERT (!element_is_subelement (elem)); + T8_ASSERT (element_is_valid (elem)); +#if T8_ENABLE_DEBUG + { + for (int j = 0; j < num_subelements; j++) { + T8_ASSERT (element_is_valid (c[j])); + } + } +#endif + + /* Setting the parameter values for different subelements. + * The different subelement types (up to rotation) are: + * + * x - - - - - - x x - - - - - x x - - - - - x x - - - - - x x - - x - - x x - - x - - x + * | | | \ 2 / | | \ / | | \ / | | \ | / | | \ | / | + * | | | 1 \ / | | \ / | | \ / | | \ | / | | \ | / | + * | | --> x - - X 3 | or x - - x | or x - - x - - x or x - - x - - x or x - - x - - x + * | | | 0 / \ | | / | \ | | / \ | | / \ | | / | \ | + * | elem | | / 4 \ | | / | \ | | / \ | | / \ | | / | \ | + * + - - - - - - x x - - - - - x x - - x - - x x - - - - - x x - - - - - x x - - x - - x + * + * Sub_ids are counted clockwise, starting with the (lower) left subelement with id 0. + * Note, that we do not change the underlying quadrant. */ + + for (int sub_id_counter = 0; sub_id_counter < num_subelements; sub_id_counter++) { + + standalone_scheme::element_copy (subelement_to_element (element), + subelement_to_element (subelements[sub_id_counter])); + subelements[sub_id_counter]->subelement_type = type; + subelements[sub_id_counter]->subelement_id = sub_id_counter; + T8_ASSERT (element_is_valid (c[sub_id_counter])); + } + } + private: // PRIVATE HELPER static const t8_element_t * @@ -1278,13 +1348,6 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type != 0); - } - /** create the root element * \param [in,out] elem The element that is filled with the root */ diff --git a/src/t8_schemes/t8_subelement/t8_subelement.cxx b/src/t8_schemes/t8_subelement/t8_subelement.cxx index 04859db400..cc0c3fe33e 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement.cxx +++ b/src/t8_schemes/t8_subelement/t8_subelement.cxx @@ -52,18 +52,8 @@ t8_eclass_scheme_is_subelement (const t8_scheme *scheme, const t8_eclass_t eclas return scheme->check_eclass_scheme_type> (T8_ECLASS_VERTEX); case T8_ECLASS_LINE: return scheme->check_eclass_scheme_type> (T8_ECLASS_LINE); - // case T8_ECLASS_QUAD: - // return scheme->check_eclass_scheme_type> (T8_ECLASS_QUAD); - // // case T8_ECLASS_TRIANGLE: - // // return scheme->check_eclass_scheme_type> (T8_ECLASS_TRIANGLE); - // case T8_ECLASS_HEX: - // return scheme->check_eclass_scheme_type> (T8_ECLASS_HEX); - // case T8_ECLASS_TET: - // return scheme->check_eclass_scheme_type> (T8_ECLASS_TET); - // case T8_ECLASS_PRISM: - // return scheme->check_eclass_scheme_type> (T8_ECLASS_PRISM); - // case T8_ECLASS_PYRAMID: - // return scheme->check_eclass_scheme_type> (T8_ECLASS_PYRAMID); + case T8_ECLASS_QUAD: + return scheme->check_eclass_scheme_type (T8_ECLASS_QUAD); default: SC_ABORT_NOT_REACHED (); } From dc81571c26bf48d40600b45d9aa45e8f939408df Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Thu, 28 May 2026 16:08:44 +0200 Subject: [PATCH 09/28] Fix some problems --- example/subelements/t8_quads_hanging_nodes.cxx | 2 +- src/t8_forest/t8_forest_subelement.cxx | 14 ++++++-------- src/t8_forest/t8_forest_subelement.hxx | 4 ++-- .../t8_subelement/t8_scheme_implementation.hxx | 16 ++++++++-------- 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/example/subelements/t8_quads_hanging_nodes.cxx b/example/subelements/t8_quads_hanging_nodes.cxx index 264b74fbdd..6e862721a3 100644 --- a/example/subelements/t8_quads_hanging_nodes.cxx +++ b/example/subelements/t8_quads_hanging_nodes.cxx @@ -91,7 +91,7 @@ main (int argc, char **argv) std::cout << "Subelements before removing: " << t8_forest_has_subelements (forest) << std::endl; // --- Remove hanging nodes via adapting again. --- - t8_forest_remove_hanging_nodes (forest); + forest = t8_forest_remove_hanging_nodes (forest); std::cout << "Subelements after removing: " << t8_forest_has_subelements (forest) << std::endl; // Now output to vtk. // const char *prefix_with_hanging_nodes = "t8_with_hanging_nodes"; diff --git a/src/t8_forest/t8_forest_subelement.cxx b/src/t8_forest/t8_forest_subelement.cxx index f80b27a648..402e20db1d 100644 --- a/src/t8_forest/t8_forest_subelement.cxx +++ b/src/t8_forest/t8_forest_subelement.cxx @@ -115,7 +115,7 @@ t8_remove_hanging_nodes_callback ([[maybe_unused]] t8_forest_t forest, t8_forest bool t8_forest_has_subelements (t8_forest_t forest) { - if (t8_eclass_scheme_is_subelement (t8_forest_get_scheme (forest), T8_ECLASS_QUAD)) { + if (!t8_eclass_scheme_is_subelement (t8_forest_get_scheme (forest), T8_ECLASS_QUAD)) { return false; } const t8_subelementquad_scheme *scheme = (const t8_subelementquad_scheme *) t8_forest_get_scheme (forest); @@ -131,22 +131,20 @@ t8_forest_has_subelements (t8_forest_t forest) return false; } -/** Adapt forest according to callback. */ -void +t8_forest_t t8_forest_discard_subelements (t8_forest_t forest) { if (!t8_forest_has_subelements (forest)) { - return; + return forest; } - forest = t8_forest_new_adapt (forest, discard_subelements_callback, 0, 0, NULL); + return t8_forest_new_adapt (forest, discard_subelements_callback, 0, 0, NULL); } -/** Adapt forest according to callback. */ -void +t8_forest_t t8_forest_remove_hanging_nodes (t8_forest_t forest) { - t8_global_productionf ("Into t8_forest_remove_hanging_nodes.\n"); forest = t8_forest_new_adapt (forest, t8_remove_hanging_nodes_callback, 0, 0, NULL); t8_global_productionf ("Done t8_forest_remove_hanging_nodes.\n"); + return forest; } diff --git a/src/t8_forest/t8_forest_subelement.hxx b/src/t8_forest/t8_forest_subelement.hxx index cf59e177f4..658550032b 100644 --- a/src/t8_forest/t8_forest_subelement.hxx +++ b/src/t8_forest/t8_forest_subelement.hxx @@ -31,11 +31,11 @@ #include #include -void +t8_forest_t t8_forest_remove_hanging_nodes (t8_forest_t forest); bool t8_forest_has_subelements (t8_forest_t forest); -void +t8_forest_t t8_forest_discard_subelements (t8_forest_t forest); diff --git a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx index 2371623402..9de7dd681d 100644 --- a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx +++ b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx @@ -604,6 +604,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpersp4q; - int num_subelements = element_get_number_of_subelements (type); T8_ASSERT (type >= T8_SUB_QUAD_MIN_SUBELEMENT_TYPE && type <= T8_SUB_QUAD_MAX_SUBELEMENT_TYPE); From 4318e55b6d8f02fad642a3c5711da75ccc5d08cf Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Fri, 29 May 2026 13:56:52 +0200 Subject: [PATCH 10/28] working version!! --- .../subelements/t8_quads_hanging_nodes.cxx | 22 +- .../t8_scheme_implementation.hxx | 275 +++++++++++++++++- 2 files changed, 283 insertions(+), 14 deletions(-) diff --git a/example/subelements/t8_quads_hanging_nodes.cxx b/example/subelements/t8_quads_hanging_nodes.cxx index 6e862721a3..15c20f0226 100644 --- a/example/subelements/t8_quads_hanging_nodes.cxx +++ b/example/subelements/t8_quads_hanging_nodes.cxx @@ -89,16 +89,28 @@ main (int argc, char **argv) /* --- Adapt the forest. --- */ forest = t8_adapt_forest (forest); std::cout << "Subelements before removing: " << t8_forest_has_subelements (forest) << std::endl; + const char *prefix_with_hanging_nodes = "t8_with_hanging_nodes"; + t8_forest_write_vtk (forest, prefix_with_hanging_nodes); + t8_global_productionf (" [subelements] Wrote adapted forest with hanging nodes to vtu files: %s*\n", + prefix_with_hanging_nodes); // --- Remove hanging nodes via adapting again. --- forest = t8_forest_remove_hanging_nodes (forest); std::cout << "Subelements after removing: " << t8_forest_has_subelements (forest) << std::endl; // Now output to vtk. - // const char *prefix_with_hanging_nodes = "t8_with_hanging_nodes"; - // t8_forest_write_vtk (forest, prefix_with_hanging_nodes); - // t8_global_productionf (" [subelements] Wrote adapted forest with hanging nodes to vtu files: %s*\n", - // prefix_with_hanging_nodes); - // // --- Cleanup. --- + const char *prefix_without_hanging_nodes = "t8_without_hanging_nodes"; + t8_forest_write_vtk (forest, prefix_without_hanging_nodes); + t8_global_productionf (" [subelements] Wrote adapted forest without hanging nodes to vtu files: %s*\n", + prefix_without_hanging_nodes); + + forest = t8_forest_discard_subelements (forest); + std::cout << "Subelements removed: " << t8_forest_has_subelements (forest) << std::endl; + // Now output to vtk. + const char *prefix_removed_sub = "t8_removed_sub"; + t8_forest_write_vtk (forest, prefix_removed_sub); + t8_global_productionf (" [subelements] Wrote adapted forest with discarded subelements to vtu files: %s*\n", + prefix_removed_sub); + // --- Cleanup. --- t8_forest_unref (&forest); sc_finalize (); diff --git a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx index 9de7dd681d..1e59b6d189 100644 --- a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx +++ b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx @@ -35,6 +35,7 @@ #include #include #include +#include /** TODO. */ struct t8_subelementquad_scheme: public t8_scheme_helpers @@ -286,7 +287,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type != el2->subelement_type) { return 0; } - if (el1->subelement_id != el2->subelement_id) { - return 0; - } return standalone_scheme::element_is_equal (subelement_to_element (el1), subelement_to_element (el2)); } @@ -996,7 +994,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers n0 + * (1,0) -> n1 + * (1,1) -> n2 + */ + out_coords[coord * 2 + 0] = (1.0 - u) * n0[0] + (u - v) * n1[0] + v * n2[0]; + out_coords[coord * 2 + 1] = (1.0 - u) * n0[1] + (u - v) * n1[1] + v * n2[1]; + } + } + private: // PRIVATE HELPER static const t8_element_t * @@ -1357,4 +1388,230 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type = 0; subelement->subelement_id = 0; } + + static void + vertex_coords_of_subelement (const t8_element_t *elem, int vertex, int coords[]) + { + T8_ASSERT (element_is_valid (elem)); + T8_ASSERT (element_is_subelement (elem)); + const t8_subelement_element *subelement = (const t8_subelement_element *) elem; + + T8_ASSERT (vertex >= 0 && vertex < T8_SUBELEMENT_FACES); /* all subelements are triangles */ + + /* get the length of the current quadrant */ + double len = parent_element_get_len (subelement); + + /* Compute the x and y coordinates of subelement vertices, depending on the subelement type, id and vertex number + * (faces enumerated clockwise, starting at the center of the transition cell): + * + * f1 V1 + * x - - - - - x x + * | \ 2 / | / | + * | 1 \ / 3 | / 3 | + * f0 x - - + - - x f2 --> + - - x + * | 0 / | \ 4 | V0 V2 + * | / 6 | 5 \ | + * x - - x - - x + * f3 + * + * In this example, the below location array would contain the values [2, 1, 1] + * (second face, split, first subelement at this face) */ + + /* get location information of the given subelement */ + int location[3] = {}; + t8_element_get_location_of_subelement (elem, location); + + /* the face number, the subelement is adjacent to */ + int face_number = location[0]; + /* = 1, if the adjacent face is split and = 0, if not */ + int split = location[1]; + /* = 0, if the subelement is the first (of two) subelements, at the adjacent face and = 1 if it is the second */ + int sub_face_id = location[2]; + + /* Check, whether the get_location function provides meaningful location data */ + T8_ASSERT (face_number == 0 || face_number == 1 || face_number == 2 || face_number == 3); + T8_ASSERT ((split == 0 && sub_face_id == 0) || (split == 1 && (sub_face_id == 0 || sub_face_id == 1))); + + coords[0] = subelement->element.coords[0]; + coords[1] = subelement->element.coords[1]; + + /* using the location data to determine vertex coordinates */ + if (vertex == 0) { /* vertex 0 (the first vertex always equals the center of the element) */ + coords[0] += len / 2.; + coords[1] += len / 2.; + } /* end of vertex == 0 */ + else if (vertex == 1) { /* vertex 1 */ + if (face_number == 0) { + if (split && sub_face_id) { + coords[1] += len / 2.; + } + } + else if (face_number == 1) { + coords[1] += len; + if (split && sub_face_id) { + coords[0] += len / 2.; + } + } + else if (face_number == 2) { + coords[0] += len; + coords[1] += len; + if (split && sub_face_id) { + coords[1] -= len / 2.; + } + } + else { + coords[0] += len; + if (split && sub_face_id) { + coords[0] -= len / 2.; + } + } + } /* end of vertex == 1 */ + else if (vertex == 2) { /* vertex 2 */ + if (face_number == 0) { + coords[1] += len; + if (split && (sub_face_id == 0)) { + coords[1] -= len / 2.; + } + } + else if (face_number == 1) { + coords[0] += len; + coords[1] += len; + if (split && (sub_face_id == 0)) { + coords[0] -= len / 2.; + } + } + else if (face_number == 2) { + coords[0] += len; + if (split && (sub_face_id == 0)) { + coords[1] += len / 2.; + } + } + else { + if (split && (sub_face_id == 0)) { + coords[0] += len / 2.; + } + } + } /* end of vertex == 2 */ + } + + static t8_element_coord + parent_element_get_len (const t8_subelement_element *subelement) noexcept + { + return 1 << (T8_ELEMENT_MAXLEVEL[T8_ECLASS_QUAD] + - (standalone_scheme::element_get_level (subelement_to_element (subelement)))); + } + + static void + t8_element_get_location_of_subelement (const t8_element_t *elem, int location[]) + { + const t8_subelement_element *subelement = (const t8_subelement_element *) elem; + + /* this function only works for subelements */ + T8_ASSERT (element_is_subelement (elem)); + + T8_ASSERT (element_is_valid (elem)); + + /* Consider the following subelement of type 13: + * + * f0 1 + * x - - x - - x x - - x - - x + * | | | \ 2 | 3 / | faces: f3 f2 f1 f0 + * | | | 1 \ | / 4 | binary code: 1 1 0 1 (=13) + * f3 x x f2 --> 1 x - - x - - x 1 --> rearrange binaries s.t. the faces are enumerated clockwise: 1 1 1 0 + * | | | 0 / \ 5 | number subelements at face: 2 2 2 1 + * | elem | | / 6 \ | consider sub_id 3: x -> second subelement on the upper face + * + - - - - - x x - - - - - x + * f1 0 + * + * We will use the binary representation to determine the location of the given subelement. + * + * We need to know: + * i) the face number of the first vertex (values: {0,1,2,3}). + * ii) whether this face is split in half (values: {0,1}). + * iii) if the subelement is the first or second subelement at the face (values: {0,1}). + * + * These information are then saved in the location array which will be used by the element_vertex function, + * to automatically determine the vertex coordinates of the given subelement. + * + * The location array for the above example would be {1,1,1} (upper face, split = true, second subelement at the upper face). */ + + /* 1) convert the subelement type from a decimal to a binary representation */ + int type = subelement->subelement_type; + int num_faces_quad = T8_ELEMENT_NUM_CORNERS[T8_ECLASS_QUAD]; + int binary_array[num_faces_quad] = {}; + + for ( + int i = 0; i < num_faces_quad; + i++) { /* need an array with 4 elements to store all subelement types of the quad scheme from 1 to 15 ({0,0,0,1} to {1,1,1,1}) */ + binary_array[(num_faces_quad - 1) - i] = (type & (1 << i)) >> i; + } /* we now got a binary representation of the subelement type, bitwise stored in an array */ + + /* 2) rearrange the binary representation to be in clockwise order */ + int binary_array_temp[num_faces_quad] = {}; + + int j; + + for (j = 0; j < num_faces_quad; j++) { /* copying the binary array */ + binary_array_temp[j] = binary_array[j]; + } + const int subelement_location_to_parent_face[4] = { 0, 3, 1, 2 }; + for (j = 0; j < num_faces_quad; j++) { /* bringing the entries of binary array into clockwise order */ + binary_array[j] = binary_array_temp[subelement_location_to_parent_face[j]]; + } + + /* 3) use the rearranged binary representation, and the sub_id to determine the location of the subelement and store these information in an array */ + /* 3.1) location[0] -> the face_number, the subelement is adjacent to */ + /* 3.2) location[1] -> if the face is split or not */ + /* 3.3) location[2] -> if the subelement is the first or second subelement of the face (always the first, if the face is not split) */ + int num_subelements = element_get_number_of_subelements (subelement->subelement_type); + T8_ASSERT (subelement->subelement_id < num_subelements); + + int sub_id = subelement->subelement_id; + int sub_face_id; + int face_number; + int split; + + int k; + + int cum_neigh_array[num_faces_quad] = {}; + + /* construct a cumulative array of the number of neighbors from face 0 to face 3 */ + cum_neigh_array[0] = binary_array[0] + 1; + cum_neigh_array[1] = cum_neigh_array[0] + binary_array[1] + 1; + cum_neigh_array[2] = cum_neigh_array[1] + binary_array[2] + 1; + cum_neigh_array[3] = cum_neigh_array[2] + binary_array[3] + 1; + + /* 3.1) we can use the cumulative array to determine the face number of the given subelement */ + if (sub_id < cum_neigh_array[0]) { + face_number = 0; + } + else { + for (k = 0; k < num_faces_quad - 1; ++k) { + if (sub_id >= cum_neigh_array[k] && sub_id < cum_neigh_array[k + 1]) { + face_number = k + 1; + break; + } + } + } + + /* 3.2) determine, whether the face is split or not */ + if (binary_array[face_number] == 0) { + split = 0; /* the face is not split */ + } + else { + split = 1; /* the face is split */ + } + + /* 3.3) determine, whether the subelement is the first or the second subelement at the face */ + if (sub_id + 1 == cum_neigh_array[face_number] && split == 1) { + sub_face_id = 1; /* second subelement */ + } + else { + sub_face_id = 0; /* first subelement */ + } + + location[0] = face_number; + location[1] = split; + location[2] = sub_face_id; + } }; From 9be317d3e16ddaff3c4b9053ff232747526c18c3 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Fri, 29 May 2026 15:45:22 +0200 Subject: [PATCH 11/28] begin cleanup --- .../t8_scheme_implementation.hxx | 269 +++++++----------- 1 file changed, 105 insertions(+), 164 deletions(-) diff --git a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx index 1e59b6d189..2562855e65 100644 --- a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx +++ b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx @@ -3,7 +3,7 @@ t8code is a C library to manage a collection (a forest) of multiple connected adaptive space-trees of general element classes in parallel. - Copyright (C) 2025 the developers + Copyright (C) 2026 the developers t8code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,6 +22,7 @@ /** \file t8_scheme_implementation.hxx * An implementation for the class \ref t8_scheme in \ref t8_scheme.hxx. + * This implementation provides a subelement scheme for quads that removes hanging nodes. */ #pragma once @@ -34,26 +35,28 @@ #include #include #include -#include -#include -/** TODO. */ +/** A scheme to resolve hanging nodes in pure quad schemes. This scheme relies on the standalone scheme if the + * current element is not a subelement type. + * Subelements are discarded before the next adaptation cycle and do not have children. + */ struct t8_subelementquad_scheme: public t8_scheme_helpers { public: + /** Standalone scheme used for non-subelement elements. */ using standalone_scheme = t8_standalone_scheme; - /** Constructor - */ + + /** Constructor. */ t8_subelementquad_scheme () noexcept : element_size (sizeof (t8_subelement_element)), scheme_context (sc_mempool_new (element_size)) {}; protected: - // What do i need this for? - size_t element_size; /**< The size in bytes of an element of class \a eclass */ + size_t element_size; /**< The size in bytes of an element. */ void *scheme_context; /**< Anonymous implementation context. */ public: - /** Destructor for all default schemes */ + // #################################____Constructor & Destructor...____############################################### + /** Destructor. */ ~t8_subelementquad_scheme () { T8_ASSERT (scheme_context != NULL); @@ -76,11 +79,9 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type == 0) { - return standalone_scheme::element_get_num_corners (subelement_to_element (subelement)); + if (!element_is_subelement (elem)) { + return standalone_scheme::element_get_num_corners (element_to_element (elem)); } - return T8_ELEMENT_NUM_CORNERS[T8_ECLASS_TRIANGLE]; } @@ -164,16 +161,13 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type == 0) { - return standalone_scheme::element_get_num_faces (subelement_to_element (subelement)); + if (!element_is_subelement (elem)) { + return standalone_scheme::element_get_num_faces (element_to_element (elem)); } - return T8_ELEMENT_NUM_FACES[T8_ECLASS_TRIANGLE]; } - /** Compute the maximum number of faces of a given element and all of its - * descendants. + /** Compute the maximum number of faces of a given element and all of its descendants. * \param [in] elem The element. * \return The maximum number of faces of \a elem and its descendants. */ @@ -181,14 +175,11 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (dest), reinterpret_cast (source), + sizeof (t8_subelement_element)); T8_ASSERT (element_is_valid (dest)); } - /** Check if two elements are equal. For two subelements,it is only checked that the type is equal and not the id!! + /** Check if two elements are equal. + * \note For subelements, it is only checked that the type is equal and not the id!! * \param [in] elem1 The first element. * \param [in] elem2 The second element. * \return true if the elements are equal, false if they are not equal @@ -295,99 +276,69 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem1); + const auto *el2 = reinterpret_cast (elem2); if (el1->subelement_type != el2->subelement_type) { return 0; } return standalone_scheme::element_is_equal (subelement_to_element (el1), subelement_to_element (el2)); } - // ################################################____ACCESSOR____################################################ - - /** Return the level of a particular element. - * \param [in] elem The element whose level should be returned. - * \return The level of \b elem. - */ - static int - element_get_level (const t8_element_t *elem) noexcept - { - T8_ASSERT (element_is_valid (elem)); - return standalone_scheme::element_get_level (element_to_element (elem)); - } - // ################################################____REFINEMENT____################################################ - - /** create the root element - * \param [in,out] elem The element that is filled with the root + /** Create the root element. + * \param [in,out] elem The element that is filled with the root. */ static void set_to_root (t8_element_t *elem) noexcept { - t8_subelement_element *subelement = (t8_subelement_element *) elem; + auto *subelement = reinterpret_cast (elem); reset_subelement_values (subelement); standalone_scheme::set_to_root (subelement_to_element (subelement)); } /** Compute the parent of a given element \b elem and store it in \b parent. - * \b parent needs to be an existing element. No memory is allocated by this function. - * \b elem and \b parent can point to the same element, then the entries of - * \b elem are overwritten by the ones of its parent. + * \b parent needs to be an existing element. No memory is allocated by this function. \b elem and \b parent can + * point to the same element, then the entries of \b elem are overwritten by the ones of its parent. * \param [in] elem The element whose parent will be computed. - * \param [in,out] parent This element's entries will be overwritten by those - * of \b elem's parent. - * The storage for this element must exist - * and match the element class of the parent. - * For a pyramid, for example, it may be either a - * tetrahedron or a pyramid depending on \b elem's childid. + * \param [in,out] parent This element's entries will be overwritten by those of \b elem's parent. + * The storage for this element must exist and match the element class of the parent. */ static void element_get_parent (const t8_element_t *elem, t8_element_t *parent) noexcept { T8_ASSERT (element_is_valid (elem)); - - const t8_subelement_element *el = (const t8_subelement_element *) elem; - t8_subelement_element *parent_elem = (t8_subelement_element *) parent; - reset_subelement_values (parent_elem); + const auto *subelement = reinterpret_cast (elem); + auto *parent_subelement = reinterpret_cast (parent); + reset_subelement_values (parent_subelement); if (element_is_subelement (elem)) { // For subelements, the parent is the element from which they are refined. - standalone_scheme::element_copy (subelement_to_element (el), subelement_to_element (parent_elem)); + standalone_scheme::element_copy (subelement_to_element (subelement), subelement_to_element (parent_subelement)); return; } - standalone_scheme::element_get_parent (subelement_to_element (el), subelement_to_element (parent_elem)); + standalone_scheme::element_get_parent (subelement_to_element (subelement), + subelement_to_element (parent_subelement)); } - /** Compute the number of siblings of an element. That is the number of - * elements with the same parent (if available). + /** Compute the number of siblings of an element. That is the number of elements with the same parent (if available). * \param [in] elem The element. * \return The number of siblings of \a element. - * Note that this number is >= 1, since we count the element itself as a sibling. - * Note that the number of siblings is 1 for the root element. + * Note that this number is >= 1, since we count the element itself as a sibling.. */ static int element_get_num_siblings (const t8_element_t *elem) noexcept { T8_ASSERT (element_is_valid (elem)); - const t8_subelement_element *subelement = (const t8_subelement_element *) elem; if (!element_is_subelement (elem)) { - return standalone_scheme::element_get_num_siblings (subelement_to_element (subelement)); + return standalone_scheme::element_get_num_siblings (element_to_element (elem)); } - return element_get_number_of_subelements (subelement->subelement_type); + return element_get_number_of_subelements (reinterpret_cast (elem)->subelement_type); } - /** Compute a specific sibling of a given element \b elem and store it in \b sibling. - * \b sibling needs to be an existing element. No memory is allocated by this function. - * \b elem and \b sibling can point to the same element, then the entries of - * \b elem are overwritten by the ones of its sibid-th sibling. + /** Not implemented for this scheme * \param [in] elem The element whose sibling will be computed. * \param [in] sibid The id of the sibling computed. - * \param [in,out] sibling This element's entries will be overwritten by those - * of \b elem's sibid-th sibling. - * The storage for this element must exist - * and match the element class of the sibling. + * \param [in,out] sibling This element's entries will be overwritten by those of \b elem's sibid-th sibling. */ static void element_get_sibling ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int sibid, @@ -396,43 +347,32 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem); t8_element_t *standalone_children_ptrs[T8_ELEMENT_NUM_CHILDREN[T8_ECLASS_QUAD]]; for (int ichild = 0; ichild < length; ++ichild) { @@ -502,7 +443,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem); if (element_is_subelement (elem)) { // For subelements, the child id is the subelement id. return subelement->subelement_id; @@ -1140,7 +1081,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem); int element_valid = standalone_scheme::element_is_valid (subelement_to_element (subelement)); if (!element_is_subelement (elem)) { return element_valid; @@ -1163,7 +1104,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem); t8_debugf ("Subelement type: %i\n", subelement->subelement_type); t8_debugf ("Subelementid: %i\n", subelement->subelement_id); standalone_scheme::element_debug_print (subelement_to_element (subelement)); @@ -1261,7 +1202,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem); return (subelement->subelement_type != 0); } static int @@ -1394,12 +1335,12 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem); T8_ASSERT (vertex >= 0 && vertex < T8_SUBELEMENT_FACES); /* all subelements are triangles */ /* get the length of the current quadrant */ - double len = parent_element_get_len (subelement); + int len = parent_element_get_len (subelement); /* Compute the x and y coordinates of subelement vertices, depending on the subelement type, id and vertex number * (faces enumerated clockwise, starting at the center of the transition cell): @@ -1437,32 +1378,32 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem); /* this function only works for subelements */ T8_ASSERT (element_is_subelement (elem)); @@ -1567,9 +1508,9 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_id < num_subelements); int sub_id = subelement->subelement_id; - int sub_face_id; - int face_number; - int split; + int sub_face_id = 0; + int face_number = 0; + int split = 0; int k; From 9df30ff6080dd941d2f07afc26c8dfcf9d63b9a8 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Tue, 2 Jun 2026 12:50:10 +0200 Subject: [PATCH 12/28] documentation --- src/t8_forest/t8_forest_adapt.cxx | 2 +- src/t8_forest/t8_forest_subelement.cxx | 92 ++- src/t8_forest/t8_forest_subelement.hxx | 23 +- .../t8_scheme_implementation.hxx | 685 ++++++++---------- .../t8_subelement/t8_subelement.cxx | 2 +- .../t8_subelement/t8_subelement.hxx | 2 +- .../t8_subelement/t8_subelement_type.hxx | 31 +- 7 files changed, 392 insertions(+), 445 deletions(-) diff --git a/src/t8_forest/t8_forest_adapt.cxx b/src/t8_forest/t8_forest_adapt.cxx index 4690d8a1fc..d585ab5697 100644 --- a/src/t8_forest/t8_forest_adapt.cxx +++ b/src/t8_forest/t8_forest_adapt.cxx @@ -636,7 +636,7 @@ t8_forest_adapt (t8_forest_t forest) /* TODO: In a future version elements_from[zz] should be const and we should call t8_element_array_index_locidx (the const version). */ elements[zz] = t8_element_array_index_locidx_mutable (telements, el_inserted + zz); } - subelemscheme->element_to_subelement (elements_from[0], subelement_type, elements); + subelemscheme->refine_element_in_subelements (elements_from[0], subelement_type, elements); el_inserted += (t8_locidx_t) num_subelements; el_considered++; } diff --git a/src/t8_forest/t8_forest_subelement.cxx b/src/t8_forest/t8_forest_subelement.cxx index 402e20db1d..a8f46253f8 100644 --- a/src/t8_forest/t8_forest_subelement.cxx +++ b/src/t8_forest/t8_forest_subelement.cxx @@ -20,8 +20,8 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** \file t8_quads_hanging_nodes.cxx - * This is an example to demonstrate hanging node resolution for quads. +/** \file t8_forest_subelement.cxx + * Implementation of functionality in \ref t8_forest_subelement.hxx. */ #include "t8_forest_subelement.hxx" @@ -36,8 +36,13 @@ #include #include "t8_forest_adapt.h" -/* This is the adapt function, called for each element in a balanced forest during transition. - * We refine an element into a suitable transition cell if it has at most one hanging face */ +/** Namespace to hide implementation details.*/ +namespace detail +{ + +/** Adapt callback for \ref t8_forest_discard_subelements. All subelements are coarsened such that the mesh using only + * recursive refinement is restored and the subelements are discarded. This is necessary for another adaption cycle. + */ int discard_subelements_callback ([[maybe_unused]] t8_forest_t forest, [[maybe_unused]] t8_forest_t forest_from, [[maybe_unused]] t8_locidx_t which_tree, [[maybe_unused]] t8_eclass_t tree_class, @@ -52,8 +57,14 @@ discard_subelements_callback ([[maybe_unused]] t8_forest_t forest, [[maybe_unuse return 0; } -/* This is the adapt function, called for each element in a balanced forest during transition. - * We refine an element into a suitable transition cell if it has at most one hanging face */ +/* Adapt callback for hanging node resolution. + * We use the face enumeration to determine which subelement type to use for the transition cell. + * Every face has a flag parameter, which is set to 1, if there is a neighbour with a higher level + * and to 0, if the level of the neighbour is at most the level of the element. + * If all faces are hanging, we use the normal 1:8 refinement and return 1. + * Otherwise, we use subelements and add 1 to every type, to avoid refine = 1. + * \return The subelement type + 1 to be used for the transition cell, which is a binary encoding of the hanging faces. + */ int t8_remove_hanging_nodes_callback ([[maybe_unused]] 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, @@ -61,21 +72,6 @@ t8_remove_hanging_nodes_callback ([[maybe_unused]] t8_forest_t forest, t8_forest [[maybe_unused]] const int num_elements, t8_element_t *elements[]) { int subelement_type = 0; - /* We use a binary encoding (depending on the face enumeration), to determine which subelement type to use. - * Every face has a flag parameter, which is set to 1, if there is a neighbour with a higher level - * and to 0, if the level of the neighbour is at most the level of the element. - * - * f0 1 - * x - - x - - x x - - x - - x - * | | | \ | / | - * | | | \ | / | | f3 | f2 | f1 | f0 | - * f3 x | f2 --> 1 x - - x | 0 --> binary code (according to the face enumeration): | 1 | 0 | 0 | 1 | = 9 in base 10 - * | | | / \ | - * | elem | | / \ | - * x - - - - - x x - - - - - x - * f1 0 - * - */ const int num_faces = scheme->element_get_num_faces (tree_class, elements[0]); for (int iface = 0; iface < num_faces; iface++) { const t8_element_t **neighbors; /**< Neighboring elements. */ @@ -86,34 +82,52 @@ t8_remove_hanging_nodes_callback ([[maybe_unused]] t8_forest_t forest, t8_forest t8_forest_leaf_face_neighbors (forest_from, which_tree, elements[0], &neighbors, iface, &dual_faces_internal, &num_neighbors, &neighids, &neigh_class); - if (num_neighbors > 1) { subelement_type += 1 << ((num_faces - 1) - iface); } - /* clean-up */ + + // Free allocated memory. if (num_neighbors > 0) { - // Free allocated memory. T8_FREE (neighbors); T8_FREE (dual_faces_internal); T8_FREE (neighids); } } - /* returning the right subelement types */ - if (subelement_type == 0) { /* in this case, there are no hanging nodes and we do not need to do anything */ + /* Returning the correct subelement type. */ + if (subelement_type == 0) { /* In this case, there are no hanging nodes and we do not need to do anything. */ return 0; } - else if (subelement_type == 15) { /* Normal 1:8 refinement */ + else if (subelement_type == 15) { /* Normal 1:8 refinement. */ return 1; } - else { /* use subelements and add 1 to every type, to avoid refine = 1 */ + else { /* Use subelements and add 1 to every type, to avoid refine = 1. */ return subelement_type + 1; } } -/** Adapt forest according to callback. */ +} // namespace detail + +t8_forest_t +t8_forest_remove_hanging_nodes (t8_forest_t forest) +{ + t8_global_productionf ("Into t8_forest_remove_hanging_nodes.\n"); + forest = t8_forest_new_adapt (forest, detail::t8_remove_hanging_nodes_callback, 0, 0, NULL); + t8_global_productionf ("Done t8_forest_remove_hanging_nodes.\n"); + return forest; +} + +t8_forest_t +t8_forest_discard_subelements (t8_forest_t forest) +{ + if (!t8_forest_has_subelements (forest)) { + return forest; + } + return t8_forest_new_adapt (forest, detail::discard_subelements_callback, 0, 0, NULL); +} + bool -t8_forest_has_subelements (t8_forest_t forest) +t8_forest_has_subelements (const t8_forest_t forest) { if (!t8_eclass_scheme_is_subelement (t8_forest_get_scheme (forest), T8_ECLASS_QUAD)) { return false; @@ -130,21 +144,3 @@ t8_forest_has_subelements (t8_forest_t forest) } return false; } - -t8_forest_t -t8_forest_discard_subelements (t8_forest_t forest) -{ - if (!t8_forest_has_subelements (forest)) { - return forest; - } - return t8_forest_new_adapt (forest, discard_subelements_callback, 0, 0, NULL); -} - -t8_forest_t -t8_forest_remove_hanging_nodes (t8_forest_t forest) -{ - t8_global_productionf ("Into t8_forest_remove_hanging_nodes.\n"); - forest = t8_forest_new_adapt (forest, t8_remove_hanging_nodes_callback, 0, 0, NULL); - t8_global_productionf ("Done t8_forest_remove_hanging_nodes.\n"); - return forest; -} diff --git a/src/t8_forest/t8_forest_subelement.hxx b/src/t8_forest/t8_forest_subelement.hxx index 658550032b..abfa419e65 100644 --- a/src/t8_forest/t8_forest_subelement.hxx +++ b/src/t8_forest/t8_forest_subelement.hxx @@ -21,7 +21,7 @@ */ /** \file t8_forest_subelement.hxx - * TODO + * Functionality to handle subelements in a forest. */ #pragma once @@ -31,11 +31,26 @@ #include #include +/** Remove hanging nodes from the forest by transitioning elements with hanging nodes into subelements. +* \param [in] forest The input forest, which may contain hanging nodes. +* \a forest must be committed before calling this function. Please note that the scheme provided with the +* forest has to be a fitting subelement scheme. +* \return A new forest with the same number of trees and the same connectivity, but conformal without hanging nodes. +*/ t8_forest_t t8_forest_remove_hanging_nodes (t8_forest_t forest); -bool -t8_forest_has_subelements (t8_forest_t forest); - +/** Remove all subelements from a forest. This is required to restore the original mesh using only recursive refinement +* and to be able to adapt again. +* \param [in] forest The input forest which may contain subelements. +* \return A new forest with the same number of trees and the same connectivity, but without subelements. +*/ t8_forest_t t8_forest_discard_subelements (t8_forest_t forest); + +/** Check if a forest contains subelements. +* \param [in] forest The forest to be checked. +* \return true if there are subelements in the forest, false otherwise. +*/ +bool +t8_forest_has_subelements (const t8_forest_t forest); diff --git a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx index 2562855e65..97b4dc1128 100644 --- a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx +++ b/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx @@ -148,7 +148,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (dest), reinterpret_cast (source), - sizeof (t8_subelement_element)); + memcpy (as_subelement (dest), as_subelement (source), sizeof (t8_subelement_element)); T8_ASSERT (element_is_valid (dest)); } @@ -277,12 +276,12 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem1); - const auto *el2 = reinterpret_cast (elem2); + const auto *el1 = as_subelement (elem1); + const auto *el2 = as_subelement (elem2); if (el1->subelement_type != el2->subelement_type) { return 0; } - return standalone_scheme::element_is_equal (subelement_to_element (el1), subelement_to_element (el2)); + return standalone_scheme::element_is_equal (subelement_to_standalone (el1), subelement_to_standalone (el2)); } // ################################################____REFINEMENT____################################################ @@ -292,9 +291,9 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem); + auto *subelement = as_subelement (elem); reset_subelement_values (subelement); - standalone_scheme::set_to_root (subelement_to_element (subelement)); + standalone_scheme::set_to_root (subelement_to_standalone (subelement)); } /** Compute the parent of a given element \b elem and store it in \b parent. @@ -308,16 +307,17 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem); - auto *parent_subelement = reinterpret_cast (parent); + const auto *subelement = as_subelement (elem); + auto *parent_subelement = as_subelement (parent); reset_subelement_values (parent_subelement); if (element_is_subelement (elem)) { // For subelements, the parent is the element from which they are refined. - standalone_scheme::element_copy (subelement_to_element (subelement), subelement_to_element (parent_subelement)); + standalone_scheme::element_copy (subelement_to_standalone (subelement), + subelement_to_standalone (parent_subelement)); return; } - standalone_scheme::element_get_parent (subelement_to_element (subelement), - subelement_to_element (parent_subelement)); + standalone_scheme::element_get_parent (subelement_to_standalone (subelement), + subelement_to_standalone (parent_subelement)); } /** Compute the number of siblings of an element. That is the number of elements with the same parent (if available). @@ -330,9 +330,9 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem)->subelement_type); + return element_get_number_of_subelements (as_subelement (elem)->subelement_type); } /** Not implemented for this scheme @@ -356,7 +356,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem); + const auto *subelement = as_subelement (elem); t8_element_t *standalone_children_ptrs[T8_ELEMENT_NUM_CHILDREN[T8_ECLASS_QUAD]]; for (int ichild = 0; ichild < length; ++ichild) { - standalone_children_ptrs[ichild] = subelement_to_element ((t8_subelement_element *) c[ichild]); - } - - if (element_is_subelement (elem)) { - t8_subelement_element parent_storage; - element_get_parent (elem, (t8_element_t *) &parent_storage); - standalone_scheme::element_get_children (subelement_to_element (&parent_storage), length, - standalone_children_ptrs); - } - else { - standalone_scheme::element_get_children (subelement_to_element (subelement), length, standalone_children_ptrs); - } - for (int ichild = 0; ichild < length; ++ichild) { - reset_subelement_values ((t8_subelement_element *) c[ichild]); + auto *child = as_subelement (c[ichild]); + standalone_children_ptrs[ichild] = subelement_to_standalone (child); + reset_subelement_values (child); } + standalone_scheme::element_get_children (subelement_to_standalone (subelement), length, standalone_children_ptrs); } /** Compute the child id of an element. @@ -443,16 +429,15 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem); + const auto *subelement = as_subelement (elem); if (element_is_subelement (elem)) { // For subelements, the child id is the subelement id. return subelement->subelement_id; } - return standalone_scheme::element_get_child_id (subelement_to_element (subelement)); + return standalone_scheme::element_get_child_id (subelement_to_standalone (subelement)); } - /** Compute the ancestor id of an element, that is the child id - * at a given level. + /** Compute the ancestor id of an element, that is the child id at a given level. * \param [in] elem This must be a valid element. * \param [in] level A refinement level. Must satisfy \a level < elem.level * \return The child_id of \a elem in regard to its \a level ancestor. @@ -461,7 +446,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers elem2. - * If elem2 is a copy of elem1 then the elements are equal. */ static int element_compare (const t8_element_t *elem1, const t8_element_t *elem2) noexcept { SC_CHECK_ABORT (!element_is_subelement (elem1) && !element_is_subelement (elem2), "element_compare is not implemented for subelements yet.\n"); - return standalone_scheme::element_compare (element_to_element (elem1), element_to_element (elem2)); + return standalone_scheme::element_compare (element_to_standalone (elem1), element_to_standalone (elem2)); } - // ################################################____VISUALIZATION____################################################ + // ################################################____VISUALIZATION____############################################## - /** Compute the coordinates of a given element vertex inside a reference tree + /** Compute the coordinates of a given element vertex inside a reference tree * that is embedded into [0,1]^d (d = dimension). + * \note This is not implemented for subelements. * \param [in] elem The element to be considered. * \param [in] vertex The id of the vertex whose coordinates shall be computed. * \param [out] coords An array of at least as many doubles as the element's dimension @@ -935,14 +861,13 @@ struct t8_subelementquad_scheme: public t8_scheme_helpersscheme_context != NULL); T8_ASSERT (0 <= length); T8_ASSERT (elems != NULL); - for (int i = 0; i < length; ++i) { elems[i] = (t8_element_t *) sc_mempool_alloc ((sc_mempool_t *) this->scheme_context); } - -/* in debug mode, set sensible default values. */ +/* In debug mode, set sensible default values. */ #if T8_ENABLE_DEBUG { for (int i = 0; i < length; i++) { @@ -1003,13 +919,10 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem); - int element_valid = standalone_scheme::element_is_valid (subelement_to_element (subelement)); + const auto *subelement = as_subelement (elem); + int element_valid = standalone_scheme::element_is_valid (subelement_to_standalone (subelement)); if (!element_is_subelement (elem)) { return element_valid; } @@ -1094,29 +997,25 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem); + const auto *subelement = as_subelement (elem); t8_debugf ("Subelement type: %i\n", subelement->subelement_type); - t8_debugf ("Subelementid: %i\n", subelement->subelement_id); - standalone_scheme::element_debug_print (subelement_to_element (subelement)); + t8_debugf ("Subelement id: %i\n", subelement->subelement_id); + standalone_scheme::element_debug_print (subelement_to_standalone (subelement)); } #endif - /** - * Fill a string with readable information about the element - * \param[in] elem The element to translate into human-readable information - * \param[in, out] debug_string The string to fill. - * \param[in] string_size Buffer size of c-string - */ + /** Fill a string with readable information about the element + * \param[in] elem The element to translate into human-readable information + * \param[in, out] debug_string The string to fill. + * \param[in] string_size Buffer size of c-string + */ static void element_to_string ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] char *debug_string, [[maybe_unused]] const int string_size) noexcept @@ -1124,16 +1023,15 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type, 1, sc_MPI_INT, send_buffer, buffer_size, position, comm); SC_CHECK_MPI (mpiret); @@ -1151,11 +1049,11 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type, 1, sc_MPI_INT, comm); SC_CHECK_MPI (mpiret); @@ -1197,18 +1095,23 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem); + const auto *subelement = as_subelement (elem); return (subelement->subelement_type != 0); } + + /** Get the number of subelements an element is refined into for a specific type. + * \param [in] subelement_type The subelement type used for refinement. + */ static int element_get_number_of_subelements (int subelement_type) { - int num_hanging_faces = 0; /* Count the number of ones of the binary subelement type. This number equals the number of hanging faces. */ for (int i = 0; i < T8_ELEMENT_NUM_FACES[T8_ECLASS_QUAD]; ++i) { @@ -1217,16 +1120,32 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers x - - X 3 | or x - - x | or x - - x - - x or x - - x - - x + * | | | 0 / \ | | / | \ | | / \ | | / \ | + * | elem | | / 4 \ | | / | \ | | / \ | | / \ | + * + - - - - - - x x - - - - - x x - - x - - x x - - - - - x x - - - - - x + * + * Subelement_ids are counted clockwise, starting with the (lower) left subelement with id 0. + * Note, that we do not change the underlying quadrant. + */ static void - element_to_subelement (const t8_element_t *elem, int type, t8_element_t *c[]) + refine_element_in_subelements (const t8_element_t *elem, int type, t8_element_t *c[]) { const t8_subelement_element *element = (const t8_subelement_element *) elem; t8_subelement_element **subelements = (t8_subelement_element **) c; - - int num_subelements = element_get_number_of_subelements (type); + const int num_subelements = element_get_number_of_subelements (type); T8_ASSERT (type >= T8_SUB_QUAD_MIN_SUBELEMENT_TYPE && type <= T8_SUB_QUAD_MAX_SUBELEMENT_TYPE); - T8_ASSERT (!element_is_subelement (elem)); T8_ASSERT (element_is_valid (elem)); #if T8_ENABLE_DEBUG @@ -1237,30 +1156,64 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers x - - X 3 | or x - - x | or x - - x - - x or x - - x - - x or x - - x - - x - * | | | 0 / \ | | / | \ | | / \ | | / \ | | / | \ | - * | elem | | / 4 \ | | / | \ | | / \ | | / \ | | / | \ | - * + - - - - - - x x - - - - - x x - - x - - x x - - - - - x x - - - - - x x - - x - - x - * - * Sub_ids are counted clockwise, starting with the (lower) left subelement with id 0. - * Note, that we do not change the underlying quadrant. */ - + /* Setting the parameter values for different subelements. */ for (int sub_id_counter = 0; sub_id_counter < num_subelements; sub_id_counter++) { - - standalone_scheme::element_copy (subelement_to_element (element), - subelement_to_element (subelements[sub_id_counter])); + standalone_scheme::element_copy (subelement_to_standalone (element), + subelement_to_standalone (subelements[sub_id_counter])); subelements[sub_id_counter]->subelement_type = type; subelements[sub_id_counter]->subelement_id = sub_id_counter; T8_ASSERT (element_is_valid (c[sub_id_counter])); } } + private: + // PRIVATE HELPER + static const t8_element_t * + subelement_to_standalone (const t8_subelement_element *subelement) noexcept + { + return (const t8_element_t *) &subelement->element; + } + + static t8_element_t * + subelement_to_standalone (t8_subelement_element *subelement) noexcept + { + return (t8_element_t *) &subelement->element; + } + + static const t8_element_t * + element_to_standalone (const t8_element_t *element) noexcept + { + return subelement_to_standalone (as_subelement (element)); + } + + static t8_element_t * + element_to_standalone (t8_element_t *element) noexcept + { + return subelement_to_standalone (as_subelement (element)); + } + + static const t8_subelement_element * + as_subelement (const t8_element_t *element) noexcept + { + return reinterpret_cast (element); + } + + static t8_subelement_element * + as_subelement (t8_element_t *element) noexcept + { + return reinterpret_cast (element); + } + + /** create the root element + * \param [in,out] elem The element that is filled with the root + */ + static void + reset_subelement_values (t8_subelement_element *subelement) noexcept + { + subelement->subelement_type = 0; + subelement->subelement_id = 0; + } + static void subelement_get_reference_coords (const t8_element_t *elem, const double *ref_coords, const size_t num_coords, double *out_coords) noexcept @@ -1292,50 +1245,12 @@ struct t8_subelementquad_scheme: public t8_scheme_helperselement; - } - - static t8_element_t * - subelement_to_element (t8_subelement_element *subelement) noexcept - { - return (t8_element_t *) &subelement->element; - } - - static const t8_element_t * - element_to_element (const t8_element_t *element) noexcept - { - const t8_subelement_element *subelement = (const t8_subelement_element *) element; - return subelement_to_element (subelement); - } - - static t8_element_t * - element_to_element (t8_element_t *element) noexcept - { - t8_subelement_element *subelement = (t8_subelement_element *) element; - return subelement_to_element (subelement); - } - - /** create the root element - * \param [in,out] elem The element that is filled with the root - */ - static void - reset_subelement_values (t8_subelement_element *subelement) noexcept - { - subelement->subelement_type = 0; - subelement->subelement_id = 0; - } - static void vertex_coords_of_subelement (const t8_element_t *elem, int vertex, int coords[]) { T8_ASSERT (element_is_valid (elem)); T8_ASSERT (element_is_subelement (elem)); - const auto *subelement = reinterpret_cast (elem); + const auto *subelement = as_subelement (elem); T8_ASSERT (vertex >= 0 && vertex < T8_SUBELEMENT_FACES); /* all subelements are triangles */ @@ -1360,7 +1275,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (elem); + const auto *subelement = as_subelement (elem); /* this function only works for subelements */ T8_ASSERT (element_is_subelement (elem)); diff --git a/src/t8_schemes/t8_subelement/t8_subelement.cxx b/src/t8_schemes/t8_subelement/t8_subelement.cxx index cc0c3fe33e..207b6a6a92 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement.cxx +++ b/src/t8_schemes/t8_subelement/t8_subelement.cxx @@ -57,5 +57,5 @@ t8_eclass_scheme_is_subelement (const t8_scheme *scheme, const t8_eclass_t eclas default: SC_ABORT_NOT_REACHED (); } - return 0; /* Default return value false */ + return 0; /* Default return value false. */ } diff --git a/src/t8_schemes/t8_subelement/t8_subelement.hxx b/src/t8_schemes/t8_subelement/t8_subelement.hxx index edba038bb8..a759bc424c 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement.hxx +++ b/src/t8_schemes/t8_subelement/t8_subelement.hxx @@ -28,7 +28,7 @@ #include -/** Return the subelement implementation of t8code. */ +/** Return the subelement scheme implementation of t8code. */ const t8_scheme * t8_scheme_new_subelement (void); diff --git a/src/t8_schemes/t8_subelement/t8_subelement_type.hxx b/src/t8_schemes/t8_subelement/t8_subelement_type.hxx index 86393fbf6c..aef4a3426e 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement_type.hxx +++ b/src/t8_schemes/t8_subelement/t8_subelement_type.hxx @@ -20,8 +20,9 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** \file t8_subelement.hxx - * +/** \file t8_subelement_type.hxx + * Definition of the element class of a subelement. A subelement always contains of a standalone element and a + * subelement type and id defining how the standalone element is transitioned into a subelement. */ #pragma once @@ -34,10 +35,30 @@ #define T8_SUB_QUAD_MAX_SUBELEMENT_ID 6 #define T8_SUB_QUAD_MIN_SUBELEMENT_ID 0 +/** Definition of the subelement class. A subelement always has an underlying standalone element. + * With the type, it is defined if the standalone element is further defined into subelements + * (e.g. for hanging node resolution) or not. Type 0 means no subelement and the subelement is just the + * underlying standalone element. For hanging node resolution, the type encodes which faces are hanging and therefore + * the number of subelements in which the element is transitioned. + * Accordingly, the subelement id is between 0 and num_subelement - 1. + + + f0 1 + * x - - x - - x x - - x - - x + * | | | \ | / | + * | | | \ | / | | f3 | f2 | f1 | f0 | + * f3 x | f2 --> 1 x - - x | 0 --> binary code (according to the face enumeration): | 1 | 0 | 0 | 1 | = 9 in base 10 + * | | | / \ | + * | elem | | / \ | + * x - - - - - x x - - - - - x + * f1 0 + + + */ struct t8_subelement_element { - t8_standalone_element element; + t8_standalone_element element; /**< Standalone element of the subelement. */ int subelement_type - = 0; /* saves the information, which type of transition cell a subelement is associated to (default is 0, meaning no subelement). */ - int subelement_id = 0; /* saves the information, what children subelement the given element is (default is 0) */ + = 0; /**< Type of the transition cell a subelement is associated to (default is 0, meaning no subelement). */ + int subelement_id = 0; /**< Id of the children subelement the given element is (default is 0). */ }; From 1193d935696609c319b865015179f1a84d0dc800 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Wed, 3 Jun 2026 16:36:37 +0200 Subject: [PATCH 13/28] Abstract subelement class --- .../subelements/t8_quads_hanging_nodes.cxx | 1 + src/t8_forest/t8_forest_adapt.cxx | 7 +- src/t8_forest/t8_forest_subelement.cxx | 18 +- src/t8_schemes/t8_scheme.cxx | 47 ++ src/t8_schemes/t8_scheme.h | 28 + src/t8_schemes/t8_scheme.hxx | 66 ++- .../specializations/t8_scheme_quads.hxx | 444 +++++++++++++++ .../t8_subelement/t8_subelement.cxx | 27 +- .../t8_subelement/t8_subelement.hxx | 8 + ...mentation.hxx => t8_subelement_scheme.hxx} | 523 ++++-------------- .../t8_subelement/t8_subelement_traits.hxx | 22 + .../t8_subelement/t8_subelement_type.hxx | 41 +- 12 files changed, 779 insertions(+), 453 deletions(-) create mode 100644 src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx rename src/t8_schemes/t8_subelement/{t8_scheme_implementation.hxx => t8_subelement_scheme.hxx} (71%) create mode 100644 src/t8_schemes/t8_subelement/t8_subelement_traits.hxx diff --git a/example/subelements/t8_quads_hanging_nodes.cxx b/example/subelements/t8_quads_hanging_nodes.cxx index 15c20f0226..bfd2c6c454 100644 --- a/example/subelements/t8_quads_hanging_nodes.cxx +++ b/example/subelements/t8_quads_hanging_nodes.cxx @@ -84,6 +84,7 @@ main (int argc, char **argv) /* ---Setup. Build cmesh and uniform forest.--- */ /* Build a cube cmesh with tet, hex, and prism trees. */ t8_cmesh_t cmesh = t8_cmesh_new_hypercube (T8_ECLASS_QUAD, comm, 0, 0, 0); + //t8_cmesh_t cmesh = t8_cmesh_new_periodic_hybrid (comm); t8_forest_t forest = t8_forest_new_uniform (cmesh, t8_scheme_new_subelement (), level, 0, comm); // TODO: New scheme /* --- Adapt the forest. --- */ diff --git a/src/t8_forest/t8_forest_adapt.cxx b/src/t8_forest/t8_forest_adapt.cxx index d585ab5697..1fdb06f7c1 100644 --- a/src/t8_forest/t8_forest_adapt.cxx +++ b/src/t8_forest/t8_forest_adapt.cxx @@ -433,7 +433,7 @@ t8_forest_adapt (t8_forest_t forest) T8_ASSERT (forest->trees->elem_count == forest_from->trees->elem_count); if (forest->set_adapt_recursive) { - SC_CHECK_ABORT (!t8_eclass_scheme_is_subelement (t8_forest_get_scheme (forest_from), T8_ECLASS_QUAD), + SC_CHECK_ABORT (!t8_scheme_has_subelement_scheme (t8_forest_get_scheme (forest_from)), "Recursive adaptation is currently not implemented for subelement schemes."); refine_list = sc_list_new (nullptr); } @@ -625,18 +625,17 @@ t8_forest_adapt (t8_forest_t forest) } else if (refine > 1) { // Subelement case. T8_ASSERT (t8_eclass_scheme_is_subelement (t8_forest_get_scheme (forest_from), T8_ECLASS_QUAD)); - const t8_subelementquad_scheme *subelemscheme = (const t8_subelementquad_scheme *) scheme; /* The subelement-callback function returns refine = subelement_type + 1 to avoid subelement_type = 1. * We undo this to use the subelement_type-values that match the binary encoding of the neighbour structure. */ int subelement_type = refine - 1; - int num_subelements = subelemscheme->element_get_number_of_subelements (subelement_type); + int num_subelements = t8_element_get_number_of_subelements (scheme, tree->eclass, subelement_type); (void) t8_element_array_push_count (telements, num_subelements); for (int zz = 0; zz < num_subelements; zz++) { /* TODO: In a future version elements_from[zz] should be const and we should call t8_element_array_index_locidx (the const version). */ elements[zz] = t8_element_array_index_locidx_mutable (telements, el_inserted + zz); } - subelemscheme->refine_element_in_subelements (elements_from[0], subelement_type, elements); + t8_refine_element_in_subelements (scheme, tree->eclass, elements_from[0], subelement_type, elements); el_inserted += (t8_locidx_t) num_subelements; el_considered++; } diff --git a/src/t8_forest/t8_forest_subelement.cxx b/src/t8_forest/t8_forest_subelement.cxx index a8f46253f8..591ce0fecc 100644 --- a/src/t8_forest/t8_forest_subelement.cxx +++ b/src/t8_forest/t8_forest_subelement.cxx @@ -32,7 +32,6 @@ #include #include #include -#include #include #include "t8_forest_adapt.h" @@ -45,13 +44,13 @@ namespace detail */ int discard_subelements_callback ([[maybe_unused]] t8_forest_t forest, [[maybe_unused]] t8_forest_t forest_from, - [[maybe_unused]] t8_locidx_t which_tree, [[maybe_unused]] t8_eclass_t tree_class, + [[maybe_unused]] t8_locidx_t which_tree, t8_eclass_t tree_class, [[maybe_unused]] t8_locidx_t lelement_id, const t8_scheme *scheme, [[maybe_unused]] const int is_family, [[maybe_unused]] const int num_elements, t8_element_t *elements[]) { - const t8_subelementquad_scheme *subelem_scheme = (const t8_subelementquad_scheme *) scheme; - if (subelem_scheme->element_is_subelement (elements[0])) { + // TODO + if (t8_element_is_subelement (scheme, tree_class, elements[0])) { return -1; } return 0; @@ -129,15 +128,18 @@ t8_forest_discard_subelements (t8_forest_t forest) bool t8_forest_has_subelements (const t8_forest_t forest) { - if (!t8_eclass_scheme_is_subelement (t8_forest_get_scheme (forest), T8_ECLASS_QUAD)) { + auto scheme = t8_forest_get_scheme (forest); + if (!t8_scheme_has_subelement_scheme (scheme)) { return false; } - const t8_subelementquad_scheme *scheme = (const t8_subelementquad_scheme *) t8_forest_get_scheme (forest); for (t8_locidx_t itree = 0; itree < t8_forest_get_num_local_trees (forest); ++itree) { - + auto eclass = t8_forest_get_eclass (forest, itree); + if (!t8_eclass_scheme_is_subelement (scheme, eclass)) { + continue; + } for (t8_locidx_t ielem = 0; ielem < t8_forest_get_tree_num_leaf_elements (forest, itree); ++ielem) { const t8_element_t *elem = t8_forest_get_leaf_element_in_tree (forest, itree, ielem); - if (scheme->element_is_subelement (elem)) { + if (t8_element_is_subelement (scheme, eclass, elem)) { return true; } } diff --git a/src/t8_schemes/t8_scheme.cxx b/src/t8_schemes/t8_scheme.cxx index 8bdbaeae6f..d7560e5e8e 100644 --- a/src/t8_schemes/t8_scheme.cxx +++ b/src/t8_schemes/t8_scheme.cxx @@ -24,10 +24,12 @@ * Implements functions declared in \ref t8_scheme.h. */ +#include "sc.h" #include #include #include #include +#include void t8_scheme_ref (t8_scheme_c *scheme) @@ -435,3 +437,48 @@ t8_element_MPI_Unpack (const t8_scheme_c *scheme, const t8_eclass_t tree_class, { return scheme->element_MPI_Unpack (tree_class, recvbuf, buffer_size, position, elements, count, comm); } + +/** Check if \ref elem is a subelement. + * \param [in] scheme The scheme of the forest. + * \param [in] tree_class The eclass of the current tree. + * \param [in] elem The elem to be checked. + */ +int +t8_element_is_subelement (const t8_scheme_c *scheme, const t8_eclass_t tree_class, const t8_element_t *elem) +{ + SC_CHECK_ABORT (t8_eclass_scheme_is_subelement (scheme, tree_class), + "t8_element_is_subelement was called for a scheme or eclass that does not support subelements.\n"); + return scheme->element_is_subelement (tree_class, elem); +} + +/** Get the number of subelements an element is refined into for a specific type. + * \param [in] scheme The scheme of the forest. + * \param [in] tree_class The eclass of the current tree. + * \param [in] subelement_type The subelement type used for refinement. + */ +int +t8_element_get_number_of_subelements (const t8_scheme_c *scheme, const t8_eclass_t tree_class, int subelement_type) +{ + SC_CHECK_ABORT ( + t8_eclass_scheme_is_subelement (scheme, tree_class), + "t8_element_get_number_of_subelements was called for a scheme or eclass that does not support subelements.\n"); + return scheme->element_get_number_of_subelements (tree_class, subelement_type); +} + +/** This defines how an element is refined in subelements using a specified subelement type. + * \param [in] scheme The scheme of the forest. + * \param [in] tree_class The eclass of the current tree. + * \param [in] elem The element to be refined. + * \param [in] type The subelement type to be used for refinement. + * \param [in, out] c An array of allocated elements that will be filled with the subelements of \a elem. + * The number of subelements is determined by \ref element_get_number_of_subelements. + */ +void +t8_refine_element_in_subelements (const t8_scheme_c *scheme, const t8_eclass_t tree_class, const t8_element_t *elem, + int type, t8_element_t *c[]) +{ + SC_CHECK_ABORT ( + t8_eclass_scheme_is_subelement (scheme, tree_class), + "t8_refine_element_in_subelements was called for a scheme or eclass that does not support subelements.\n"); + scheme->refine_element_in_subelements (tree_class, elem, type, c); +} diff --git a/src/t8_schemes/t8_scheme.h b/src/t8_schemes/t8_scheme.h index 0c3d16ef49..48addd33d4 100644 --- a/src/t8_schemes/t8_scheme.h +++ b/src/t8_schemes/t8_scheme.h @@ -860,6 +860,34 @@ void t8_element_MPI_Unpack (const t8_scheme_c *scheme, const t8_eclass_t tree_class, void *recvbuf, const int buffer_size, int *position, t8_element_t **elements, const unsigned int count, sc_MPI_Comm comm); +/** Check if \ref elem is a subelement. + * \param [in] scheme The scheme of the forest. + * \param [in] tree_class The eclass of the current tree. + * \param [in] elem The elem to be checked. + */ +int +t8_element_is_subelement (const t8_scheme_c *scheme, const t8_eclass_t tree_class, const t8_element_t *elem); + +/** Get the number of subelements an element is refined into for a specific type. + * \param [in] scheme The scheme of the forest. + * \param [in] tree_class The eclass of the current tree. + * \param [in] subelement_type The subelement type used for refinement. + */ +int +t8_element_get_number_of_subelements (const t8_scheme_c *scheme, const t8_eclass_t tree_class, int subelement_type); + +/** This defines how an element is refined in subelements using a specified subelement type. + * \param [in] scheme The scheme of the forest. + * \param [in] tree_class The eclass of the current tree. + * \param [in] elem The element to be refined. + * \param [in] type The subelement type to be used for refinement. + * \param [in, out] c An array of allocated elements that will be filled with the subelements of \a elem. + * The number of subelements is determined by \ref element_get_number_of_subelements. + */ +void +t8_refine_element_in_subelements (const t8_scheme_c *scheme, const t8_eclass_t tree_class, const t8_element_t *elem, + int type, t8_element_t *c[]); + T8_EXTERN_C_END (); #endif /* !T8_SCHEME_H */ diff --git a/src/t8_schemes/t8_scheme.hxx b/src/t8_schemes/t8_scheme.hxx index a7ff8dee8f..408c4328be 100644 --- a/src/t8_schemes/t8_scheme.hxx +++ b/src/t8_schemes/t8_scheme.hxx @@ -43,7 +43,8 @@ #include #include #include -#include +#include +#include #include #if T8_ENABLE_DEBUG // Only needed for t8_debug_print_type @@ -103,7 +104,7 @@ struct t8_scheme t8_standalone_scheme, t8_standalone_scheme, /* Subelement schemes */ - t8_subelementquad_scheme + t8_subelement_scheme_common >; /* clang-format on */ @@ -1192,6 +1193,67 @@ struct t8_scheme [&] (auto &&scheme) { return scheme.element_MPI_Unpack (recvbuf, buffer_size, position, elements, count, comm); }, eclass_schemes[tree_class]); }; + + /** Check if \ref elem is a subelement. + * \param [in] tree_class The eclass of the current tree. + * \param [in] elem The elem to be checked. + */ + inline int + element_is_subelement (const t8_eclass_t tree_class, const t8_element_t *elem) const + { + return std::visit ( + [&] (auto &&scheme) -> int { + if constexpr (requires { scheme.element_is_subelement (elem); }) { + return scheme.element_is_subelement (elem); + } + else { + SC_ABORT ("element_is_subelement not supported by this scheme"); + } + }, + eclass_schemes[tree_class]); + }; + + /** Get the number of subelements an element is refined into for a specific type. + * \param [in] tree_class The eclass of the current tree. + * \param [in] subelement_type The subelement type used for refinement. + */ + inline int + element_get_number_of_subelements (const t8_eclass_t tree_class, int subelement_type) const + { + return std::visit ( + [&] (auto &&scheme) -> int { + if constexpr (requires { scheme.element_get_number_of_subelements (subelement_type); }) { + return scheme.element_get_number_of_subelements (subelement_type); + } + else { + SC_ABORT ("element_get_number_of_subelements not supported by this scheme"); + } + }, + eclass_schemes[tree_class]); + } + + /** This defines how an element is refined in subelements using a specified subelement type. + * \param [in] tree_class The eclass of the current tree. + * \param [in] elem The element to be refined. + * \param [in] type The subelement type to be used for refinement. + * \param [in, out] c An array of allocated elements that will be filled with the subelements of \a elem. + * The number of subelements is determined by \ref element_get_number_of_subelements. + */ + inline void + refine_element_in_subelements (const t8_eclass_t tree_class, const t8_element_t *elem, int type, + t8_element_t *c[]) const + { + std::visit ( + [&] (auto &&scheme) -> void { + if constexpr (requires { scheme.refine_element_in_subelements (elem, type, c); }) { + scheme.refine_element_in_subelements (elem, type, c); + } + else { + SC_ABORT ("refine_element_in_subelements not supported by this scheme"); + } + }, + eclass_schemes[tree_class]); + } }; #endif /* !T8_SCHEME_HXX */ diff --git a/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx b/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx new file mode 100644 index 0000000000..0896d08333 --- /dev/null +++ b/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx @@ -0,0 +1,444 @@ +/* + 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_subelement_type.hxx + * Definition of the element class of a subelement. A subelement always contains of a standalone element and a + * subelement type and id defining how the standalone element is transitioned into a subelement. + */ + +#pragma once +#include +#include +#include +#include +#include +#include + +#define T8_SUB_QUAD_MAX_SUBELEMENT_TYPE 14 + +/** Definition of the subelement class. A subelement always has an underlying standalone element. + * With the type, it is defined if the standalone element is further defined into subelements + * (e.g. for hanging node resolution) or not. Type 0 means no subelement and the subelement is just the + * underlying standalone element. For hanging node resolution, the type encodes which faces are hanging and therefore + * the number of subelements in which the element is transitioned. + * Accordingly, the subelement id is between 0 and num_subelement - 1. + + + f0 1 + * x - - x - - x x - - x - - x + * | | | \ | / | + * | | | \ | / | | f3 | f2 | f1 | f0 | + * f3 x | f2 --> 1 x - - x | 0 --> binary code (according to the face enumeration): | 1 | 0 | 0 | 1 | = 9 in base 10 + * | | | / \ | + * | elem | | / \ | + * x - - - - - x x - - - - - x + * f1 0 +TODO: for this i can separate definition and implementation. + + */ + +struct t8_subelementquad_scheme: public t8_subelement_scheme_common +{ + public: + using TUnderlyingScheme = typename t8_subelement_traits:: + UnderlyingScheme; /**< The used recursive scheme for the underlying elements. Every time we do not need the subelement logic, the scheme calls the functionality of this underlying scheme. */ + using TSubelementType = typename t8_subelement_traits::SubelementType; + using Base = t8_subelement_scheme_common; + + /** Compute the number of corners of an element. + * \param [in] elem The subelement. + * \return The number of corners of \a elem. + */ + static int + subelement_get_num_corners ([[maybe_unused]] const TSubelementType *elem) noexcept + { + return T8_ELEMENT_NUM_CORNERS[T8_ECLASS_TRIANGLE]; + } + + /** Compute the number of faces of a given element. + * \param [in] elem The element. + * \return The number of faces of \a elem. + */ + static int + subelement_get_num_faces ([[maybe_unused]] const TSubelementType *elem) noexcept + { + return T8_ELEMENT_NUM_FACES[T8_ECLASS_TRIANGLE]; + } + + /** Compute the maximum number of faces of a given element and all of its descendants. + * \param [in] elem The element. + * \return The maximum number of faces of \a elem and its descendants. + */ + static int + subelement_get_max_num_faces (const TSubelementType *elem) noexcept + { + return subelement_get_num_faces (elem); + } + + /** Return the shape of an allocated element. + * \param [in] elem The element to be considered + * \return The shape of the element as an eclass + */ + static t8_element_shape_t + subelement_get_shape ([[maybe_unused]] const TSubelementType *elem) noexcept + { + return T8_ECLASS_TRIANGLE; + } + + /** Compute the shape of the face of an element. + * \param [in] elem The element. + * \param [in] face A face of \a elem. + * \return The element shape of the face. As we are in 2D, here always LINE. + */ + static t8_element_shape_t + subelement_get_face_shape ([[maybe_unused]] const TSubelementType *elem, [[maybe_unused]] const int face) noexcept + { + return T8_ECLASS_LINE; + } + + /** Return the max number of children of an subelement. + * \return As an element may be divided in subelements, this is the maximum number of subelements in a quad. + */ + static int + subelement_get_max_num_children () noexcept + { + return 8; + } + + public: + static int + subelement_get_number_of_valid_types () noexcept + { + return T8_SUB_QUAD_MAX_SUBELEMENT_TYPE; + } + + /** Get the number of subelements an element is refined into for a specific type. + * \param [in] subelement_type The subelement type used for refinement. + */ + static int + element_get_number_of_subelements (int subelement_type) + { + int num_hanging_faces = 0; + /* Count the number of ones of the binary subelement type. This number equals the number of hanging faces. */ + for (int i = 0; i < T8_ELEMENT_NUM_FACES[T8_ECLASS_QUAD]; ++i) { + num_hanging_faces += (subelement_type & (1 << i)) >> i; + } + return T8_ELEMENT_NUM_FACES[T8_ECLASS_QUAD] + num_hanging_faces; + } + + /** This defines how an element is refined in subelements using a specified subelement type. + * \param [in] elem The element to be refined. + * \param [in] type The subelement type to be used for refinement. This is a binary encoding of the hanging faces. + * \param [in, out] c An array of allocated elements that will be filled with the subelements of \a elem. + * The number of subelements is determined by \ref element_get_number_of_subelements. + * \note The different subelement types (up to rotation) are: + * + * x - - - - - - x x - - - - - x x - - - - - x x - - - - - x x - - x - - x + * | | | \ 2 / | | \ / | | \ / | | \ | / | + * | | | 1 \ / | | \ / | | \ / | | \ | / | + * | | --> x - - X 3 | or x - - x | or x - - x - - x or x - - x - - x + * | | | 0 / \ | | / | \ | | / \ | | / \ | + * | elem | | / 4 \ | | / | \ | | / \ | | / \ | + * + - - - - - - x x - - - - - x x - - x - - x x - - - - - x x - - - - - x + * + * Subelement_ids are counted clockwise, starting with the (lower) left subelement with id 0. + * Note, that we do not change the underlying quadrant. + */ + static void + refine_element_in_subelements (const t8_element_t *elem, int type, t8_element_t *c[]) + { + const TSubelementType *element = (const TSubelementType *) elem; + TSubelementType **subelements = (TSubelementType **) c; + const int num_subelements = Base::element_get_number_of_subelements (type); + + T8_ASSERT (type >= 1 && type <= T8_SUB_QUAD_MAX_SUBELEMENT_TYPE); + T8_ASSERT (!Base::element_is_subelement (elem)); + T8_ASSERT (Base::element_is_valid (elem)); +#if T8_ENABLE_DEBUG + { + for (int j = 0; j < num_subelements; j++) { + T8_ASSERT (Base::element_is_valid (c[j])); + } + } +#endif + + /* Setting the parameter values for different subelements. */ + for (int sub_id_counter = 0; sub_id_counter < num_subelements; sub_id_counter++) { + TUnderlyingScheme::element_copy (Base::subelement_to_standalone (element), + Base::subelement_to_standalone (subelements[sub_id_counter])); + subelements[sub_id_counter]->subelement_type = type; + subelements[sub_id_counter]->subelement_id = sub_id_counter; + T8_ASSERT (Base::element_is_valid (c[sub_id_counter])); + } + } + + static void + subelement_get_reference_coords (const t8_element_t *elem, const double *ref_coords, const size_t num_coords, + double *out_coords) noexcept + { + + /* Get the 3 integer vertex coords of the subelement triangle */ + int v0[2], v1[2], v2[2]; + vertex_coords_of_subelement (elem, 0, v0); + vertex_coords_of_subelement (elem, 1, v1); + vertex_coords_of_subelement (elem, 2, v2); + + /* Normalize to [0,1] by dividing by root length */ + const double root_len = (1 << T8_ELEMENT_MAXLEVEL[T8_ECLASS_QUAD]); + double n0[2] = { v0[0] / root_len, v0[1] / root_len }; + double n1[2] = { v1[0] / root_len, v1[1] / root_len }; + double n2[2] = { v2[0] / root_len, v2[1] / root_len }; + + for (size_t coord = 0; coord < num_coords; ++coord) { + const double u = ref_coords[coord * 2 + 0]; + const double v = ref_coords[coord * 2 + 1]; + + /* * Mapping verification: + * (0,0) -> n0 + * (1,0) -> n1 + * (1,1) -> n2 + */ + out_coords[coord * 2 + 0] = (1.0 - u) * n0[0] + (u - v) * n1[0] + v * n2[0]; + out_coords[coord * 2 + 1] = (1.0 - u) * n0[1] + (u - v) * n1[1] + v * n2[1]; + } + } + + private: + static void + vertex_coords_of_subelement (const t8_element_t *elem, int vertex, int coords[]) + { + T8_ASSERT (Base::element_is_valid (elem)); + T8_ASSERT (Base::element_is_subelement (elem)); + const auto *subelement = Base::as_subelement (elem); + + T8_ASSERT (vertex >= 0 && vertex < subelement_get_num_faces (subelement)); /* all subelements are triangles */ + + /* get the length of the current quadrant */ + int len = Base::parent_element_get_len (subelement); + + /* Compute the x and y coordinates of subelement vertices, depending on the subelement type, id and vertex number + * (faces enumerated clockwise, starting at the center of the transition cell): + * + * f1 V1 + * x - - - - - x x + * | \ 2 / | / | + * | 1 \ / 3 | / 3 | + * f0 x - - + - - x f2 --> + - - x + * | 0 / | \ 4 | V0 V2 + * | / 6 | 5 \ | + * x - - x - - x + * f3 + * + * In this example, the below location array would contain the values [2, 1, 1] + * (second face, split, first subelement at this face) */ + + /* get location information of the given subelement */ + int location[3] = {}; + element_get_location_of_subelement (elem, location); + + /* the face number, the subelement is adjacent to */ + int face_number = location[0]; + /* = 1, if the adjacent face is split and = 0, if not */ + int split = location[1]; + /* = 0, if the subelement is the first (of two) subelements, at the adjacent face and = 1 if it is the second */ + int sub_face_id = location[2]; + + /* Check, whether the get_location function provides meaningful location data */ + T8_ASSERT (face_number == 0 || face_number == 1 || face_number == 2 || face_number == 3); + T8_ASSERT ((split == 0 && sub_face_id == 0) || (split == 1 && (sub_face_id == 0 || sub_face_id == 1))); + + coords[0] = subelement->element.coords[0]; + coords[1] = subelement->element.coords[1]; + + /* using the location data to determine vertex coordinates */ + if (vertex == 0) { /* vertex 0 (the first vertex always equals the center of the element) */ + coords[0] += len / 2; + coords[1] += len / 2; + } /* end of vertex == 0 */ + else if (vertex == 1) { /* vertex 1 */ + if (face_number == 0) { + if (split && sub_face_id) { + coords[1] += len / 2; + } + } + else if (face_number == 1) { + coords[1] += len; + if (split && sub_face_id) { + coords[0] += len / 2; + } + } + else if (face_number == 2) { + coords[0] += len; + coords[1] += len; + if (split && sub_face_id) { + coords[1] -= len / 2; + } + } + else { + coords[0] += len; + if (split && sub_face_id) { + coords[0] -= len / 2; + } + } + } /* end of vertex == 1 */ + else if (vertex == 2) { /* vertex 2 */ + if (face_number == 0) { + coords[1] += len; + if (split && (sub_face_id == 0)) { + coords[1] -= len / 2; + } + } + else if (face_number == 1) { + coords[0] += len; + coords[1] += len; + if (split && (sub_face_id == 0)) { + coords[0] -= len / 2; + } + } + else if (face_number == 2) { + coords[0] += len; + if (split && (sub_face_id == 0)) { + coords[1] += len / 2; + } + } + else { + if (split && (sub_face_id == 0)) { + coords[0] += len / 2; + } + } + } /* end of vertex == 2 */ + } + + static void + element_get_location_of_subelement (const t8_element_t *elem, int location[]) + { + const auto *subelement = Base::as_subelement (elem); + + /* this function only works for subelements */ + T8_ASSERT (Base::element_is_subelement (elem)); + + T8_ASSERT (Base::element_is_valid (elem)); + + /* Consider the following subelement of type 13: + * + * f0 1 + * x - - x - - x x - - x - - x + * | | | \ 2 | 3 / | faces: f3 f2 f1 f0 + * | | | 1 \ | / 4 | binary code: 1 1 0 1 (=13) + * f3 x x f2 --> 1 x - - x - - x 1 --> rearrange binaries s.t. the faces are enumerated clockwise: 1 1 1 0 + * | | | 0 / \ 5 | number subelements at face: 2 2 2 1 + * | elem | | / 6 \ | consider sub_id 3: x -> second subelement on the upper face + * + - - - - - x x - - - - - x + * f1 0 + * + * We will use the binary representation to determine the location of the given subelement. + * + * We need to know: + * i) the face number of the first vertex (values: {0,1,2,3}). + * ii) whether this face is split in half (values: {0,1}). + * iii) if the subelement is the first or second subelement at the face (values: {0,1}). + * + * These information are then saved in the location array which will be used by the element_vertex function, + * to automatically determine the vertex coordinates of the given subelement. + * + * The location array for the above example would be {1,1,1} (upper face, split = true, second subelement at the upper face). */ + + /* 1) convert the subelement type from a decimal to a binary representation */ + int type = subelement->subelement_type; + int num_faces_quad = T8_ELEMENT_NUM_CORNERS[T8_ECLASS_QUAD]; + int binary_array[num_faces_quad] = {}; + + for ( + int i = 0; i < num_faces_quad; + i++) { /* need an array with 4 elements to store all subelement types of the quad scheme from 1 to 15 ({0,0,0,1} to {1,1,1,1}) */ + binary_array[(num_faces_quad - 1) - i] = (type & (1 << i)) >> i; + } /* we now got a binary representation of the subelement type, bitwise stored in an array */ + + /* 2) rearrange the binary representation to be in clockwise order */ + int binary_array_temp[num_faces_quad] = {}; + + int j; + + for (j = 0; j < num_faces_quad; j++) { /* copying the binary array */ + binary_array_temp[j] = binary_array[j]; + } + const int subelement_location_to_parent_face[4] = { 0, 3, 1, 2 }; + for (j = 0; j < num_faces_quad; j++) { /* bringing the entries of binary array into clockwise order */ + binary_array[j] = binary_array_temp[subelement_location_to_parent_face[j]]; + } + + /* 3) use the rearranged binary representation, and the sub_id to determine the location of the subelement and store these information in an array */ + /* 3.1) location[0] -> the face_number, the subelement is adjacent to */ + /* 3.2) location[1] -> if the face is split or not */ + /* 3.3) location[2] -> if the subelement is the first or second subelement of the face (always the first, if the face is not split) */ + int num_subelements = element_get_number_of_subelements (subelement->subelement_type); + T8_ASSERT (subelement->subelement_id < num_subelements); + + int sub_id = subelement->subelement_id; + int sub_face_id = 0; + int face_number = 0; + int split = 0; + + int k; + + int cum_neigh_array[num_faces_quad] = {}; + + /* construct a cumulative array of the number of neighbors from face 0 to face 3 */ + cum_neigh_array[0] = binary_array[0] + 1; + cum_neigh_array[1] = cum_neigh_array[0] + binary_array[1] + 1; + cum_neigh_array[2] = cum_neigh_array[1] + binary_array[2] + 1; + cum_neigh_array[3] = cum_neigh_array[2] + binary_array[3] + 1; + + /* 3.1) we can use the cumulative array to determine the face number of the given subelement */ + if (sub_id < cum_neigh_array[0]) { + face_number = 0; + } + else { + for (k = 0; k < num_faces_quad - 1; ++k) { + if (sub_id >= cum_neigh_array[k] && sub_id < cum_neigh_array[k + 1]) { + face_number = k + 1; + break; + } + } + } + + /* 3.2) determine, whether the face is split or not */ + if (binary_array[face_number] == 0) { + split = 0; /* the face is not split */ + } + else { + split = 1; /* the face is split */ + } + + /* 3.3) determine, whether the subelement is the first or the second subelement at the face */ + if (sub_id + 1 == cum_neigh_array[face_number] && split == 1) { + sub_face_id = 1; /* second subelement */ + } + else { + sub_face_id = 0; /* first subelement */ + } + + location[0] = face_number; + location[1] = split; + location[2] = sub_face_id; + } +}; diff --git a/src/t8_schemes/t8_subelement/t8_subelement.cxx b/src/t8_schemes/t8_subelement/t8_subelement.cxx index 207b6a6a92..dd026d2537 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement.cxx +++ b/src/t8_schemes/t8_subelement/t8_subelement.cxx @@ -25,7 +25,9 @@ */ #include -#include "t8_scheme_implementation.hxx" +#include "specializations/t8_scheme_quads.hxx" +#include "t8_eclass/t8_eclass.h" +#include "t8_subelement_scheme.hxx" #include const t8_scheme * @@ -35,7 +37,7 @@ t8_scheme_new_subelement (void) builder.add_eclass_scheme> (); builder.add_eclass_scheme> (); - builder.add_eclass_scheme (); + builder.add_eclass_scheme> (); builder.add_eclass_scheme (); builder.add_eclass_scheme> (); builder.add_eclass_scheme (); @@ -48,14 +50,21 @@ int t8_eclass_scheme_is_subelement (const t8_scheme *scheme, const t8_eclass_t eclass) { switch (eclass) { - case T8_ECLASS_VERTEX: - return scheme->check_eclass_scheme_type> (T8_ECLASS_VERTEX); - case T8_ECLASS_LINE: - return scheme->check_eclass_scheme_type> (T8_ECLASS_LINE); case T8_ECLASS_QUAD: - return scheme->check_eclass_scheme_type (T8_ECLASS_QUAD); + return scheme->check_eclass_scheme_type> ( + T8_ECLASS_QUAD); default: - SC_ABORT_NOT_REACHED (); + return 0; /* Default return value false. */ } - return 0; /* Default return value false. */ +} + +bool +t8_scheme_has_subelement_scheme (const t8_scheme *scheme) +{ + for (int ieclass = T8_ECLASS_ZERO; ieclass < T8_ECLASS_COUNT; ++ieclass) { + if (t8_eclass_scheme_is_subelement (scheme, static_cast (ieclass))) { + return true; + } + } + return false; } diff --git a/src/t8_schemes/t8_subelement/t8_subelement.hxx b/src/t8_schemes/t8_subelement/t8_subelement.hxx index a759bc424c..275972c91b 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement.hxx +++ b/src/t8_schemes/t8_subelement/t8_subelement.hxx @@ -39,3 +39,11 @@ t8_scheme_new_subelement (void); */ bool t8_eclass_scheme_is_subelement (const t8_scheme *scheme, const t8_eclass_t eclass); + +/** Check if \a scheme uses a subelement scheme for any eclass. + * This means that it checks if \ref t8_eclass_scheme_is_subelement is true for any eclass. + * \param [in] scheme A (pointer to a) scheme. + * \return True if \a scheme uses a subelement scheme for any eclass, false otherwise. + */ +bool +t8_scheme_has_subelement_scheme (const t8_scheme *scheme); diff --git a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx b/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx similarity index 71% rename from src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx rename to src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx index 97b4dc1128..2223369af0 100644 --- a/src/t8_schemes/t8_subelement/t8_scheme_implementation.hxx +++ b/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx @@ -20,35 +20,40 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** \file t8_scheme_implementation.hxx - * An implementation for the class \ref t8_scheme in \ref t8_scheme.hxx. - * This implementation provides a subelement scheme for quads that removes hanging nodes. +/** \file TODO */ #pragma once #include #include -#include #include -#include -#include -#include #include #include +#include "t8_subelement_traits.hxx" +#include -/** A scheme to resolve hanging nodes in pure quad schemes. This scheme relies on the standalone scheme if the - * current element is not a subelement type. +/** Scheme for the common functionality of all subelements. * Subelements are discarded before the next adaptation cycle and do not have children. + * \tparam TEclass The element class of the underlying elements which we want to define subelements for. + * The subelements themselves could have another eclass. + * \tparam TUnderlyingScheme The used recursive scheme for the underlying elements. Every time we do not need the + * subelement logic, the scheme calls the functionality of this underlying scheme. + * \tparam TSubelementType The type definition of the subelement. See \ref t8_subelement_type.hxx for an example. + * \tparam TSubelementSchemeSpecialization Specialization scheme for the subelements. Every time we need the subelement logic which + * is not equal for all subelements, the scheme calls the functionality of this subelement scheme. + */ -struct t8_subelementquad_scheme: public t8_scheme_helpers +template +struct t8_subelement_scheme_common: + public t8_scheme_helpers> { public: - /** Standalone scheme used for non-subelement elements. */ - using standalone_scheme = t8_standalone_scheme; - + using TUnderlyingScheme = typename t8_subelement_traits:: + UnderlyingScheme; /**< The used recursive scheme for the underlying elements. Every time we do not need the subelement logic, the scheme calls the functionality of this underlying scheme. */ + using TSubelementType = typename t8_subelement_traits::SubelementType; /** Constructor. */ - t8_subelementquad_scheme () noexcept - : element_size (sizeof (t8_subelement_element)), scheme_context (sc_mempool_new (element_size)) {}; + t8_subelement_scheme_common () noexcept + : element_size (sizeof (TSubelementType)), scheme_context (sc_mempool_new (element_size)) {}; protected: size_t element_size; /**< The size in bytes of an element. */ @@ -57,7 +62,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helperselem_count == 0); @@ -65,14 +70,14 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type != el2->subelement_type) { return 0; } - return standalone_scheme::element_is_equal (subelement_to_standalone (el1), subelement_to_standalone (el2)); + return TUnderlyingScheme::element_is_equal (subelement_to_standalone (el1), subelement_to_standalone (el2)); } // ################################################____REFINEMENT____################################################ @@ -293,7 +296,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type); } @@ -356,7 +359,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_id; } - return standalone_scheme::element_get_child_id (subelement_to_standalone (subelement)); + return TUnderlyingScheme::element_get_child_id (subelement_to_standalone (subelement)); } /** Compute the ancestor id of an element, that is the child id at a given level. @@ -446,7 +451,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type >= T8_SUB_QUAD_MIN_SUBELEMENT_TYPE - && subelement->subelement_type <= T8_SUB_QUAD_MAX_SUBELEMENT_TYPE) - && (subelement->subelement_id >= T8_SUB_QUAD_MIN_SUBELEMENT_ID - && subelement->subelement_id <= T8_SUB_QUAD_MAX_SUBELEMENT_ID); + // Subelement type 0 always means no subelement. + bool subelement_valid + = (subelement->subelement_type >= 1 + && subelement->subelement_type <= TSubelementSchemeSpecialization::subelement_get_number_of_valid_types ()) + && (subelement->subelement_id >= 0 + && subelement->subelement_id + < TSubelementSchemeSpecialization::element_get_number_of_subelements (subelement->subelement_type)); return subelement_valid && element_valid; } @@ -1007,7 +1019,7 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type); t8_debugf ("Subelement id: %i\n", subelement->subelement_id); - standalone_scheme::element_debug_print (subelement_to_standalone (subelement)); + TUnderlyingScheme::element_debug_print (subelement_to_standalone (subelement)); } #endif @@ -1037,8 +1049,8 @@ struct t8_subelementquad_scheme: public t8_scheme_helperssubelement_type != 0); @@ -1112,70 +1124,31 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers> i; - } - return T8_ELEMENT_NUM_FACES[T8_ECLASS_QUAD] + num_hanging_faces; + return TSubelementSchemeSpecialization::element_get_number_of_subelements (subelement_type); } /** This defines how an element is refined in subelements using a specified subelement type. * \param [in] elem The element to be refined. - * \param [in] type The subelement type to be used for refinement. This is a binary encoding of the hanging faces. + * \param [in] type The subelement type to be used for refinement. * \param [in, out] c An array of allocated elements that will be filled with the subelements of \a elem. * The number of subelements is determined by \ref element_get_number_of_subelements. - * \note The different subelement types (up to rotation) are: - * - * x - - - - - - x x - - - - - x x - - - - - x x - - - - - x x - - x - - x - * | | | \ 2 / | | \ / | | \ / | | \ | / | - * | | | 1 \ / | | \ / | | \ / | | \ | / | - * | | --> x - - X 3 | or x - - x | or x - - x - - x or x - - x - - x - * | | | 0 / \ | | / | \ | | / \ | | / \ | - * | elem | | / 4 \ | | / | \ | | / \ | | / \ | - * + - - - - - - x x - - - - - x x - - x - - x x - - - - - x x - - - - - x - * - * Subelement_ids are counted clockwise, starting with the (lower) left subelement with id 0. - * Note, that we do not change the underlying quadrant. */ static void refine_element_in_subelements (const t8_element_t *elem, int type, t8_element_t *c[]) { - const t8_subelement_element *element = (const t8_subelement_element *) elem; - t8_subelement_element **subelements = (t8_subelement_element **) c; - const int num_subelements = element_get_number_of_subelements (type); - - T8_ASSERT (type >= T8_SUB_QUAD_MIN_SUBELEMENT_TYPE && type <= T8_SUB_QUAD_MAX_SUBELEMENT_TYPE); - T8_ASSERT (!element_is_subelement (elem)); - T8_ASSERT (element_is_valid (elem)); -#if T8_ENABLE_DEBUG - { - for (int j = 0; j < num_subelements; j++) { - T8_ASSERT (element_is_valid (c[j])); - } - } -#endif - - /* Setting the parameter values for different subelements. */ - for (int sub_id_counter = 0; sub_id_counter < num_subelements; sub_id_counter++) { - standalone_scheme::element_copy (subelement_to_standalone (element), - subelement_to_standalone (subelements[sub_id_counter])); - subelements[sub_id_counter]->subelement_type = type; - subelements[sub_id_counter]->subelement_id = sub_id_counter; - T8_ASSERT (element_is_valid (c[sub_id_counter])); - } + TSubelementSchemeSpecialization::refine_element_in_subelements (elem, type, c); } - private: + protected: // PRIVATE HELPER static const t8_element_t * - subelement_to_standalone (const t8_subelement_element *subelement) noexcept + subelement_to_standalone (const TSubelementType *subelement) noexcept { return (const t8_element_t *) &subelement->element; } static t8_element_t * - subelement_to_standalone (t8_subelement_element *subelement) noexcept + subelement_to_standalone (TSubelementType *subelement) noexcept { return (t8_element_t *) &subelement->element; } @@ -1192,282 +1165,32 @@ struct t8_subelementquad_scheme: public t8_scheme_helpers (element); + return reinterpret_cast (element); } - static t8_subelement_element * + static TSubelementType * as_subelement (t8_element_t *element) noexcept { - return reinterpret_cast (element); + return reinterpret_cast (element); } /** create the root element * \param [in,out] elem The element that is filled with the root */ static void - reset_subelement_values (t8_subelement_element *subelement) noexcept + reset_subelement_values (TSubelementType *subelement) noexcept { subelement->subelement_type = 0; subelement->subelement_id = 0; } - static void - subelement_get_reference_coords (const t8_element_t *elem, const double *ref_coords, const size_t num_coords, - double *out_coords) noexcept - { - - /* Get the 3 integer vertex coords of the subelement triangle */ - int v0[2], v1[2], v2[2]; - vertex_coords_of_subelement (elem, 0, v0); - vertex_coords_of_subelement (elem, 1, v1); - vertex_coords_of_subelement (elem, 2, v2); - - /* Normalize to [0,1] by dividing by root length */ - const double root_len = (1 << T8_ELEMENT_MAXLEVEL[T8_ECLASS_QUAD]); - double n0[2] = { v0[0] / root_len, v0[1] / root_len }; - double n1[2] = { v1[0] / root_len, v1[1] / root_len }; - double n2[2] = { v2[0] / root_len, v2[1] / root_len }; - - for (size_t coord = 0; coord < num_coords; ++coord) { - const double u = ref_coords[coord * 2 + 0]; - const double v = ref_coords[coord * 2 + 1]; - - /* * Mapping verification: - * (0,0) -> n0 - * (1,0) -> n1 - * (1,1) -> n2 - */ - out_coords[coord * 2 + 0] = (1.0 - u) * n0[0] + (u - v) * n1[0] + v * n2[0]; - out_coords[coord * 2 + 1] = (1.0 - u) * n0[1] + (u - v) * n1[1] + v * n2[1]; - } - } - - static void - vertex_coords_of_subelement (const t8_element_t *elem, int vertex, int coords[]) - { - T8_ASSERT (element_is_valid (elem)); - T8_ASSERT (element_is_subelement (elem)); - const auto *subelement = as_subelement (elem); - - T8_ASSERT (vertex >= 0 && vertex < T8_SUBELEMENT_FACES); /* all subelements are triangles */ - - /* get the length of the current quadrant */ - int len = parent_element_get_len (subelement); - - /* Compute the x and y coordinates of subelement vertices, depending on the subelement type, id and vertex number - * (faces enumerated clockwise, starting at the center of the transition cell): - * - * f1 V1 - * x - - - - - x x - * | \ 2 / | / | - * | 1 \ / 3 | / 3 | - * f0 x - - + - - x f2 --> + - - x - * | 0 / | \ 4 | V0 V2 - * | / 6 | 5 \ | - * x - - x - - x - * f3 - * - * In this example, the below location array would contain the values [2, 1, 1] - * (second face, split, first subelement at this face) */ - - /* get location information of the given subelement */ - int location[3] = {}; - element_get_location_of_subelement (elem, location); - - /* the face number, the subelement is adjacent to */ - int face_number = location[0]; - /* = 1, if the adjacent face is split and = 0, if not */ - int split = location[1]; - /* = 0, if the subelement is the first (of two) subelements, at the adjacent face and = 1 if it is the second */ - int sub_face_id = location[2]; - - /* Check, whether the get_location function provides meaningful location data */ - T8_ASSERT (face_number == 0 || face_number == 1 || face_number == 2 || face_number == 3); - T8_ASSERT ((split == 0 && sub_face_id == 0) || (split == 1 && (sub_face_id == 0 || sub_face_id == 1))); - - coords[0] = subelement->element.coords[0]; - coords[1] = subelement->element.coords[1]; - - /* using the location data to determine vertex coordinates */ - if (vertex == 0) { /* vertex 0 (the first vertex always equals the center of the element) */ - coords[0] += len / 2; - coords[1] += len / 2; - } /* end of vertex == 0 */ - else if (vertex == 1) { /* vertex 1 */ - if (face_number == 0) { - if (split && sub_face_id) { - coords[1] += len / 2; - } - } - else if (face_number == 1) { - coords[1] += len; - if (split && sub_face_id) { - coords[0] += len / 2; - } - } - else if (face_number == 2) { - coords[0] += len; - coords[1] += len; - if (split && sub_face_id) { - coords[1] -= len / 2; - } - } - else { - coords[0] += len; - if (split && sub_face_id) { - coords[0] -= len / 2; - } - } - } /* end of vertex == 1 */ - else if (vertex == 2) { /* vertex 2 */ - if (face_number == 0) { - coords[1] += len; - if (split && (sub_face_id == 0)) { - coords[1] -= len / 2; - } - } - else if (face_number == 1) { - coords[0] += len; - coords[1] += len; - if (split && (sub_face_id == 0)) { - coords[0] -= len / 2; - } - } - else if (face_number == 2) { - coords[0] += len; - if (split && (sub_face_id == 0)) { - coords[1] += len / 2; - } - } - else { - if (split && (sub_face_id == 0)) { - coords[0] += len / 2; - } - } - } /* end of vertex == 2 */ - } - static t8_element_coord - parent_element_get_len (const t8_subelement_element *subelement) noexcept + parent_element_get_len (const TSubelementType *subelement) noexcept { - return 1 << (T8_ELEMENT_MAXLEVEL[T8_ECLASS_QUAD] - - (standalone_scheme::element_get_level (subelement_to_standalone (subelement)))); - } - - static void - element_get_location_of_subelement (const t8_element_t *elem, int location[]) - { - const auto *subelement = as_subelement (elem); - - /* this function only works for subelements */ - T8_ASSERT (element_is_subelement (elem)); - - T8_ASSERT (element_is_valid (elem)); - - /* Consider the following subelement of type 13: - * - * f0 1 - * x - - x - - x x - - x - - x - * | | | \ 2 | 3 / | faces: f3 f2 f1 f0 - * | | | 1 \ | / 4 | binary code: 1 1 0 1 (=13) - * f3 x x f2 --> 1 x - - x - - x 1 --> rearrange binaries s.t. the faces are enumerated clockwise: 1 1 1 0 - * | | | 0 / \ 5 | number subelements at face: 2 2 2 1 - * | elem | | / 6 \ | consider sub_id 3: x -> second subelement on the upper face - * + - - - - - x x - - - - - x - * f1 0 - * - * We will use the binary representation to determine the location of the given subelement. - * - * We need to know: - * i) the face number of the first vertex (values: {0,1,2,3}). - * ii) whether this face is split in half (values: {0,1}). - * iii) if the subelement is the first or second subelement at the face (values: {0,1}). - * - * These information are then saved in the location array which will be used by the element_vertex function, - * to automatically determine the vertex coordinates of the given subelement. - * - * The location array for the above example would be {1,1,1} (upper face, split = true, second subelement at the upper face). */ - - /* 1) convert the subelement type from a decimal to a binary representation */ - int type = subelement->subelement_type; - int num_faces_quad = T8_ELEMENT_NUM_CORNERS[T8_ECLASS_QUAD]; - int binary_array[num_faces_quad] = {}; - - for ( - int i = 0; i < num_faces_quad; - i++) { /* need an array with 4 elements to store all subelement types of the quad scheme from 1 to 15 ({0,0,0,1} to {1,1,1,1}) */ - binary_array[(num_faces_quad - 1) - i] = (type & (1 << i)) >> i; - } /* we now got a binary representation of the subelement type, bitwise stored in an array */ - - /* 2) rearrange the binary representation to be in clockwise order */ - int binary_array_temp[num_faces_quad] = {}; - - int j; - - for (j = 0; j < num_faces_quad; j++) { /* copying the binary array */ - binary_array_temp[j] = binary_array[j]; - } - const int subelement_location_to_parent_face[4] = { 0, 3, 1, 2 }; - for (j = 0; j < num_faces_quad; j++) { /* bringing the entries of binary array into clockwise order */ - binary_array[j] = binary_array_temp[subelement_location_to_parent_face[j]]; - } - - /* 3) use the rearranged binary representation, and the sub_id to determine the location of the subelement and store these information in an array */ - /* 3.1) location[0] -> the face_number, the subelement is adjacent to */ - /* 3.2) location[1] -> if the face is split or not */ - /* 3.3) location[2] -> if the subelement is the first or second subelement of the face (always the first, if the face is not split) */ - int num_subelements = element_get_number_of_subelements (subelement->subelement_type); - T8_ASSERT (subelement->subelement_id < num_subelements); - - int sub_id = subelement->subelement_id; - int sub_face_id = 0; - int face_number = 0; - int split = 0; - - int k; - - int cum_neigh_array[num_faces_quad] = {}; - - /* construct a cumulative array of the number of neighbors from face 0 to face 3 */ - cum_neigh_array[0] = binary_array[0] + 1; - cum_neigh_array[1] = cum_neigh_array[0] + binary_array[1] + 1; - cum_neigh_array[2] = cum_neigh_array[1] + binary_array[2] + 1; - cum_neigh_array[3] = cum_neigh_array[2] + binary_array[3] + 1; - - /* 3.1) we can use the cumulative array to determine the face number of the given subelement */ - if (sub_id < cum_neigh_array[0]) { - face_number = 0; - } - else { - for (k = 0; k < num_faces_quad - 1; ++k) { - if (sub_id >= cum_neigh_array[k] && sub_id < cum_neigh_array[k + 1]) { - face_number = k + 1; - break; - } - } - } - - /* 3.2) determine, whether the face is split or not */ - if (binary_array[face_number] == 0) { - split = 0; /* the face is not split */ - } - else { - split = 1; /* the face is split */ - } - - /* 3.3) determine, whether the subelement is the first or the second subelement at the face */ - if (sub_id + 1 == cum_neigh_array[face_number] && split == 1) { - sub_face_id = 1; /* second subelement */ - } - else { - sub_face_id = 0; /* first subelement */ - } - - location[0] = face_number; - location[1] = split; - location[2] = sub_face_id; + return 1 << (T8_ELEMENT_MAXLEVEL[TEclass] + - (TUnderlyingScheme::element_get_level (subelement_to_standalone (subelement)))); } }; diff --git a/src/t8_schemes/t8_subelement/t8_subelement_traits.hxx b/src/t8_schemes/t8_subelement/t8_subelement_traits.hxx new file mode 100644 index 0000000000..d1532266c2 --- /dev/null +++ b/src/t8_schemes/t8_subelement/t8_subelement_traits.hxx @@ -0,0 +1,22 @@ + +#pragma once + +#include +#include +#include +#include + +// Forward declaration reicht hier +struct t8_subelementquad_scheme; + +// Primäres Template (undefiniert — erzeugt klaren Fehler bei fehlendem Trait) +template +struct t8_subelement_traits; + +// Spezialisierung für quad — BEVOR t8_subelementquad_scheme definiert wird +template <> +struct t8_subelement_traits +{ + using UnderlyingScheme = t8_standalone_scheme; + using SubelementType = t8_subelement_element>; +}; diff --git a/src/t8_schemes/t8_subelement/t8_subelement_type.hxx b/src/t8_schemes/t8_subelement/t8_subelement_type.hxx index aef4a3426e..437db706b2 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement_type.hxx +++ b/src/t8_schemes/t8_subelement/t8_subelement_type.hxx @@ -3,7 +3,7 @@ t8code is a C library to manage a collection (a forest) of multiple connected adaptive space-trees of general element classes in parallel. - Copyright (C) 2025 the developers + Copyright (C) 2026 the developers t8code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,43 +21,24 @@ */ /** \file t8_subelement_type.hxx - * Definition of the element class of a subelement. A subelement always contains of a standalone element and a - * subelement type and id defining how the standalone element is transitioned into a subelement. + * Definition of the element class of a subelement. A subelement always contains of an underlying element and + * subelement type and id defining how the underlying element is transitioned into a subelement. */ #pragma once -#include -#include -#define T8_SUBELEMENT_FACES 3 -#define T8_SUB_QUAD_MAX_SUBELEMENT_TYPE 14 -#define T8_SUB_QUAD_MIN_SUBELEMENT_TYPE 1 -#define T8_SUB_QUAD_MAX_SUBELEMENT_ID 6 -#define T8_SUB_QUAD_MIN_SUBELEMENT_ID 0 - -/** Definition of the subelement class. A subelement always has an underlying standalone element. - * With the type, it is defined if the standalone element is further defined into subelements - * (e.g. for hanging node resolution) or not. Type 0 means no subelement and the subelement is just the - * underlying standalone element. For hanging node resolution, the type encodes which faces are hanging and therefore - * the number of subelements in which the element is transitioned. +/** Definition of the subelement class. A subelement always has an underlying element. + * With the type, it is defined if the element is further defined into subelements (e.g. for hanging node resolution). + * Type 0 means no subelement and the subelement is just the underlying element. + * For hanging node resolution, the type encodes which faces are hanging and therefore the number of subelements in + * which the element is transitioned. * Accordingly, the subelement id is between 0 and num_subelement - 1. - - - f0 1 - * x - - x - - x x - - x - - x - * | | | \ | / | - * | | | \ | / | | f3 | f2 | f1 | f0 | - * f3 x | f2 --> 1 x - - x | 0 --> binary code (according to the face enumeration): | 1 | 0 | 0 | 1 | = 9 in base 10 - * | | | / \ | - * | elem | | / \ | - * x - - - - - x x - - - - - x - * f1 0 - - + * \tparam TUnderlyingElement The type of the underlying element. For example a standalone element. */ +template struct t8_subelement_element { - t8_standalone_element element; /**< Standalone element of the subelement. */ + TUnderlyingElement element; /**< Standalone element of the subelement. */ int subelement_type = 0; /**< Type of the transition cell a subelement is associated to (default is 0, meaning no subelement). */ int subelement_id = 0; /**< Id of the children subelement the given element is (default is 0). */ From 80e046095b4b01c2591d3654234b6687d0d2dc2b Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Mon, 8 Jun 2026 07:53:49 +0200 Subject: [PATCH 14/28] begin to add tri scheme --- .../subelements/t8_quads_hanging_nodes.cxx | 4 +- src/t8_forest/t8_forest_subelement.cxx | 2 +- src/t8_schemes/t8_scheme.hxx | 3 +- .../specializations/t8_scheme_tri.hxx | 413 ++++++++++++++++++ .../t8_subelement/t8_subelement.cxx | 6 +- .../t8_subelement/t8_subelement_traits.hxx | 13 +- 6 files changed, 433 insertions(+), 8 deletions(-) create mode 100644 src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx diff --git a/example/subelements/t8_quads_hanging_nodes.cxx b/example/subelements/t8_quads_hanging_nodes.cxx index bfd2c6c454..05dd4df3d9 100644 --- a/example/subelements/t8_quads_hanging_nodes.cxx +++ b/example/subelements/t8_quads_hanging_nodes.cxx @@ -83,8 +83,8 @@ main (int argc, char **argv) /* ---Setup. Build cmesh and uniform forest.--- */ /* Build a cube cmesh with tet, hex, and prism trees. */ - t8_cmesh_t cmesh = t8_cmesh_new_hypercube (T8_ECLASS_QUAD, comm, 0, 0, 0); - //t8_cmesh_t cmesh = t8_cmesh_new_periodic_hybrid (comm); + //t8_cmesh_t cmesh = t8_cmesh_new_hypercube (T8_ECLASS_QUAD, comm, 0, 0, 0); + t8_cmesh_t cmesh = t8_cmesh_new_periodic_hybrid (comm); t8_forest_t forest = t8_forest_new_uniform (cmesh, t8_scheme_new_subelement (), level, 0, comm); // TODO: New scheme /* --- Adapt the forest. --- */ diff --git a/src/t8_forest/t8_forest_subelement.cxx b/src/t8_forest/t8_forest_subelement.cxx index 591ce0fecc..b25ff0d4fc 100644 --- a/src/t8_forest/t8_forest_subelement.cxx +++ b/src/t8_forest/t8_forest_subelement.cxx @@ -56,7 +56,7 @@ discard_subelements_callback ([[maybe_unused]] t8_forest_t forest, [[maybe_unuse return 0; } -/* Adapt callback for hanging node resolution. +/** Adapt callback for hanging node resolution. * We use the face enumeration to determine which subelement type to use for the transition cell. * Every face has a flag parameter, which is set to 1, if there is a neighbour with a higher level * and to 0, if the level of the neighbour is at most the level of the element. diff --git a/src/t8_schemes/t8_scheme.hxx b/src/t8_schemes/t8_scheme.hxx index 408c4328be..8ae4a74feb 100644 --- a/src/t8_schemes/t8_scheme.hxx +++ b/src/t8_schemes/t8_scheme.hxx @@ -104,7 +104,8 @@ struct t8_scheme t8_standalone_scheme, t8_standalone_scheme, /* Subelement schemes */ - t8_subelement_scheme_common + t8_subelement_scheme_common, + t8_subelement_scheme_common >; /* clang-format on */ diff --git a/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx b/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx new file mode 100644 index 0000000000..807469d430 --- /dev/null +++ b/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx @@ -0,0 +1,413 @@ +/* + 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 TODO */ + +#pragma once +#include +#include +#include +#include +#include +#include + +#define T8_TRI_MAX_SUBELEMENT_TYPE 6 + +/** ID is as follow: always start with f0, the 2 with faces sharing f0, first also sharing v1, then v2. + * Then f1, first sharing v0 then v2 ,... + */ + +struct t8_subelementtri_scheme: public t8_subelement_scheme_common +{ + public: + using TUnderlyingScheme = typename t8_subelement_traits:: + UnderlyingScheme; /**< The used recursive scheme for the underlying elements. Every time we do not need the subelement logic, the scheme calls the functionality of this underlying scheme. */ + using TSubelementType = typename t8_subelement_traits::SubelementType; + using Base = t8_subelement_scheme_common; + + /** Compute the number of corners of an element. + * \param [in] elem The subelement. + * \return The number of corners of \a elem. + */ + static int + subelement_get_num_corners ([[maybe_unused]] const TSubelementType *elem) noexcept + { + return T8_ELEMENT_NUM_CORNERS[T8_ECLASS_TRIANGLE]; + } + + /** Compute the number of faces of a given element. + * \param [in] elem The element. + * \return The number of faces of \a elem. + */ + static int + subelement_get_num_faces ([[maybe_unused]] const TSubelementType *elem) noexcept + { + return T8_ELEMENT_NUM_FACES[T8_ECLASS_TRIANGLE]; + } + + /** Compute the maximum number of faces of a given element and all of its descendants. + * \param [in] elem The element. + * \return The maximum number of faces of \a elem and its descendants. + */ + static int + subelement_get_max_num_faces (const TSubelementType *elem) noexcept + { + return subelement_get_num_faces (elem); + } + + /** Return the shape of an allocated element. + * \param [in] elem The element to be considered + * \return The shape of the element as an eclass + */ + static t8_element_shape_t + subelement_get_shape ([[maybe_unused]] const TSubelementType *elem) noexcept + { + return T8_ECLASS_TRIANGLE; + } + + /** Compute the shape of the face of an element. + * \param [in] elem The element. + * \param [in] face A face of \a elem. + * \return The element shape of the face. As we are in 2D, here always LINE. + */ + static t8_element_shape_t + subelement_get_face_shape ([[maybe_unused]] const TSubelementType *elem, [[maybe_unused]] const int face) noexcept + { + return T8_ECLASS_LINE; + } + + /** Return the max number of children of an subelement. + * \return As an element may be divided in subelements, this is the maximum number of subelements in a quad. + */ + static int + subelement_get_max_num_children () noexcept + { + return 3; + } + + public: + static int + subelement_get_number_of_valid_types () noexcept + { + return T8_TRI_MAX_SUBELEMENT_TYPE; + } + + /** Get the number of subelements an element is refined into for a specific type. + * \param [in] subelement_type The subelement type used for refinement. + */ + static int + element_get_number_of_subelements (int subelement_type) + { + int num_hanging_faces = 0; + /* Count the number of ones of the binary subelement type. This number equals the number of hanging faces. */ + for (int i = 0; i < T8_ELEMENT_NUM_FACES[T8_ECLASS_TRIANGLE]; ++i) { + num_hanging_faces += (subelement_type & (1 << i)) >> i; + } + return num_hanging_faces + 1; + } + + /** This defines how an element is refined in subelements using a specified subelement type. + * \param [in] elem The element to be refined. + * \param [in] type The subelement type to be used for refinement. This is a binary encoding of the hanging faces. + * \param [in, out] c An array of allocated elements that will be filled with the subelements of \a elem. + * The number of subelements is determined by \ref element_get_number_of_subelements. + */ + static void + refine_element_in_subelements (const t8_element_t *elem, int type, t8_element_t *c[]) + { + const TSubelementType *element = (const TSubelementType *) elem; + TSubelementType **subelements = (TSubelementType **) c; + const int num_subelements = Base::element_get_number_of_subelements (type); + + T8_ASSERT (type >= 1 && type <= T8_TRI_MAX_SUBELEMENT_TYPE); + T8_ASSERT (!Base::element_is_subelement (elem)); + T8_ASSERT (Base::element_is_valid (elem)); +#if T8_ENABLE_DEBUG + { + for (int j = 0; j < num_subelements; j++) { + T8_ASSERT (Base::element_is_valid (c[j])); + } + } +#endif + + /* Setting the parameter values for different subelements. */ + for (int sub_id_counter = 0; sub_id_counter < num_subelements; sub_id_counter++) { + TUnderlyingScheme::element_copy (Base::subelement_to_standalone (element), + Base::subelement_to_standalone (subelements[sub_id_counter])); + subelements[sub_id_counter]->subelement_type = type; + subelements[sub_id_counter]->subelement_id = sub_id_counter; + T8_ASSERT (Base::element_is_valid (c[sub_id_counter])); + } + } + + //TODO: also the numbering of the subelements in the triangle scheme needs to be defined. + static void + subelement_get_reference_coords (const t8_element_t *elem, const double *ref_coords, const size_t num_coords, + double *out_coords) noexcept + { + + /* Get the 3 integer vertex coords of the subelement triangle */ + int v0[2], v1[2], v2[2]; + vertex_coords_of_subelement (elem, 0, v0); + vertex_coords_of_subelement (elem, 1, v1); + vertex_coords_of_subelement (elem, 2, v2); + + /* Normalize to [0,1] by dividing by root length */ + const double root_len = (1 << T8_ELEMENT_MAXLEVEL[T8_ECLASS_TRIANGLE]); + double n0[2] = { v0[0] / root_len, v0[1] / root_len }; + double n1[2] = { v1[0] / root_len, v1[1] / root_len }; + double n2[2] = { v2[0] / root_len, v2[1] / root_len }; + + for (size_t coord = 0; coord < num_coords; ++coord) { + const double u = ref_coords[coord * 2 + 0]; + const double v = ref_coords[coord * 2 + 1]; + + /* * Mapping verification: + * (0,0) -> n0 + * (1,0) -> n1 + * (1,1) -> n2 + */ + out_coords[coord * 2 + 0] = (1.0 - u) * n0[0] + (u - v) * n1[0] + v * n2[0]; + out_coords[coord * 2 + 1] = (1.0 - u) * n0[1] + (u - v) * n1[1] + v * n2[1]; + } + } + + private: + static void + vertex_coords_of_subelement (const t8_element_t *elem, int vertex, int coords[]) + { + T8_ASSERT (Base::element_is_valid (elem)); + T8_ASSERT (Base::element_is_subelement (elem)); + const auto *subelement = Base::as_subelement (elem); + + T8_ASSERT (vertex >= 0 && vertex < subelement_get_num_faces (subelement)); /* all subelements are triangles */ + + /* get the length of the current quadrant */ + int len = Base::parent_element_get_len (subelement); + + /* Compute the x and y coordinates of subelement vertices, depending on the subelement type, id and vertex number + * (faces enumerated clockwise, starting at the center of the transition cell): + * + * f1 V1 + * x - - - - - x x + * | \ 2 / | / | + * | 1 \ / 3 | / 3 | + * f0 x - - + - - x f2 --> + - - x + * | 0 / | \ 4 | V0 V2 + * | / 6 | 5 \ | + * x - - x - - x + * f3 + * + * In this example, the below location array would contain the values [2, 1, 1] + * (second face, split, first subelement at this face) */ + + /* get location information of the given subelement */ + int location[3] = {}; + element_get_location_of_subelement (elem, location); + + /* the face number, the subelement is adjacent to */ + int face_number = location[0]; + /* = 1, if the adjacent face is split and = 0, if not */ + int split = location[1]; + /* = 0, if the subelement is the first (of two) subelements, at the adjacent face and = 1 if it is the second */ + int sub_face_id = location[2]; + + /* Check, whether the get_location function provides meaningful location data */ + T8_ASSERT (face_number == 0 || face_number == 1 || face_number == 2 || face_number == 3); + T8_ASSERT ((split == 0 && sub_face_id == 0) || (split == 1 && (sub_face_id == 0 || sub_face_id == 1))); + + coords[0] = subelement->element.coords[0]; + coords[1] = subelement->element.coords[1]; + + /* using the location data to determine vertex coordinates */ + if (vertex == 0) { /* vertex 0 (the first vertex always equals the center of the element) */ + coords[0] += len / 2; + coords[1] += len / 2; + } /* end of vertex == 0 */ + else if (vertex == 1) { /* vertex 1 */ + if (face_number == 0) { + if (split && sub_face_id) { + coords[1] += len / 2; + } + } + else if (face_number == 1) { + coords[1] += len; + if (split && sub_face_id) { + coords[0] += len / 2; + } + } + else if (face_number == 2) { + coords[0] += len; + coords[1] += len; + if (split && sub_face_id) { + coords[1] -= len / 2; + } + } + else { + coords[0] += len; + if (split && sub_face_id) { + coords[0] -= len / 2; + } + } + } /* end of vertex == 1 */ + else if (vertex == 2) { /* vertex 2 */ + if (face_number == 0) { + coords[1] += len; + if (split && (sub_face_id == 0)) { + coords[1] -= len / 2; + } + } + else if (face_number == 1) { + coords[0] += len; + coords[1] += len; + if (split && (sub_face_id == 0)) { + coords[0] -= len / 2; + } + } + else if (face_number == 2) { + coords[0] += len; + if (split && (sub_face_id == 0)) { + coords[1] += len / 2; + } + } + else { + if (split && (sub_face_id == 0)) { + coords[0] += len / 2; + } + } + } /* end of vertex == 2 */ + } + + static void + element_get_location_of_subelement (const t8_element_t *elem, int location[]) + { + const auto *subelement = Base::as_subelement (elem); + + /* this function only works for subelements */ + T8_ASSERT (Base::element_is_subelement (elem)); + + T8_ASSERT (Base::element_is_valid (elem)); + + /* Consider the following subelement of type 13: + * + * f0 1 + * x - - x - - x x - - x - - x + * | | | \ 2 | 3 / | faces: f3 f2 f1 f0 + * | | | 1 \ | / 4 | binary code: 1 1 0 1 (=13) + * f3 x x f2 --> 1 x - - x - - x 1 --> rearrange binaries s.t. the faces are enumerated clockwise: 1 1 1 0 + * | | | 0 / \ 5 | number subelements at face: 2 2 2 1 + * | elem | | / 6 \ | consider sub_id 3: x -> second subelement on the upper face + * + - - - - - x x - - - - - x + * f1 0 + * + * We will use the binary representation to determine the location of the given subelement. + * + * We need to know: + * i) the face number of the first vertex (values: {0,1,2,3}). + * ii) whether this face is split in half (values: {0,1}). + * iii) if the subelement is the first or second subelement at the face (values: {0,1}). + * + * These information are then saved in the location array which will be used by the element_vertex function, + * to automatically determine the vertex coordinates of the given subelement. + * + * The location array for the above example would be {1,1,1} (upper face, split = true, second subelement at the upper face). */ + + /* 1) convert the subelement type from a decimal to a binary representation */ + int type = subelement->subelement_type; + int num_faces_quad = T8_ELEMENT_NUM_CORNERS[T8_ECLASS_TRIANGLE]; + int binary_array[num_faces_quad] = {}; + + for ( + int i = 0; i < num_faces_quad; + i++) { /* need an array with 4 elements to store all subelement types of the quad scheme from 1 to 15 ({0,0,0,1} to {1,1,1,1}) */ + binary_array[(num_faces_quad - 1) - i] = (type & (1 << i)) >> i; + } /* we now got a binary representation of the subelement type, bitwise stored in an array */ + + /* 2) rearrange the binary representation to be in clockwise order */ + int binary_array_temp[num_faces_quad] = {}; + + int j; + + for (j = 0; j < num_faces_quad; j++) { /* copying the binary array */ + binary_array_temp[j] = binary_array[j]; + } + const int subelement_location_to_parent_face[4] = { 0, 3, 1, 2 }; + for (j = 0; j < num_faces_quad; j++) { /* bringing the entries of binary array into clockwise order */ + binary_array[j] = binary_array_temp[subelement_location_to_parent_face[j]]; + } + + /* 3) use the rearranged binary representation, and the sub_id to determine the location of the subelement and store these information in an array */ + /* 3.1) location[0] -> the face_number, the subelement is adjacent to */ + /* 3.2) location[1] -> if the face is split or not */ + /* 3.3) location[2] -> if the subelement is the first or second subelement of the face (always the first, if the face is not split) */ + int num_subelements = element_get_number_of_subelements (subelement->subelement_type); + T8_ASSERT (subelement->subelement_id < num_subelements); + + int sub_id = subelement->subelement_id; + int sub_face_id = 0; + int face_number = 0; + int split = 0; + + int k; + + int cum_neigh_array[num_faces_quad] = {}; + + /* construct a cumulative array of the number of neighbors from face 0 to face 3 */ + cum_neigh_array[0] = binary_array[0] + 1; + cum_neigh_array[1] = cum_neigh_array[0] + binary_array[1] + 1; + cum_neigh_array[2] = cum_neigh_array[1] + binary_array[2] + 1; + cum_neigh_array[3] = cum_neigh_array[2] + binary_array[3] + 1; + + /* 3.1) we can use the cumulative array to determine the face number of the given subelement */ + if (sub_id < cum_neigh_array[0]) { + face_number = 0; + } + else { + for (k = 0; k < num_faces_quad - 1; ++k) { + if (sub_id >= cum_neigh_array[k] && sub_id < cum_neigh_array[k + 1]) { + face_number = k + 1; + break; + } + } + } + + /* 3.2) determine, whether the face is split or not */ + if (binary_array[face_number] == 0) { + split = 0; /* the face is not split */ + } + else { + split = 1; /* the face is split */ + } + + /* 3.3) determine, whether the subelement is the first or the second subelement at the face */ + if (sub_id + 1 == cum_neigh_array[face_number] && split == 1) { + sub_face_id = 1; /* second subelement */ + } + else { + sub_face_id = 0; /* first subelement */ + } + + location[0] = face_number; + location[1] = split; + location[2] = sub_face_id; + } +}; diff --git a/src/t8_schemes/t8_subelement/t8_subelement.cxx b/src/t8_schemes/t8_subelement/t8_subelement.cxx index dd026d2537..f27d5f1632 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement.cxx +++ b/src/t8_schemes/t8_subelement/t8_subelement.cxx @@ -26,6 +26,7 @@ #include #include "specializations/t8_scheme_quads.hxx" +#include "specializations/t8_scheme_tri.hxx" #include "t8_eclass/t8_eclass.h" #include "t8_subelement_scheme.hxx" #include @@ -38,7 +39,7 @@ t8_scheme_new_subelement (void) builder.add_eclass_scheme> (); builder.add_eclass_scheme> (); builder.add_eclass_scheme> (); - builder.add_eclass_scheme (); + builder.add_eclass_scheme> (); builder.add_eclass_scheme> (); builder.add_eclass_scheme (); builder.add_eclass_scheme (); @@ -53,6 +54,9 @@ t8_eclass_scheme_is_subelement (const t8_scheme *scheme, const t8_eclass_t eclas case T8_ECLASS_QUAD: return scheme->check_eclass_scheme_type> ( T8_ECLASS_QUAD); + case T8_ECLASS_TRIANGLE: + return scheme->check_eclass_scheme_type> ( + T8_ECLASS_TRIANGLE); default: return 0; /* Default return value false. */ } diff --git a/src/t8_schemes/t8_subelement/t8_subelement_traits.hxx b/src/t8_schemes/t8_subelement/t8_subelement_traits.hxx index d1532266c2..2efbc46ba3 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement_traits.hxx +++ b/src/t8_schemes/t8_subelement/t8_subelement_traits.hxx @@ -3,20 +3,27 @@ #include #include +#include #include #include -// Forward declaration reicht hier struct t8_subelementquad_scheme; -// Primäres Template (undefiniert — erzeugt klaren Fehler bei fehlendem Trait) +struct t8_subelementtri_scheme; + template struct t8_subelement_traits; -// Spezialisierung für quad — BEVOR t8_subelementquad_scheme definiert wird template <> struct t8_subelement_traits { using UnderlyingScheme = t8_standalone_scheme; using SubelementType = t8_subelement_element>; }; + +template <> +struct t8_subelement_traits +{ + using UnderlyingScheme = t8_default_scheme_tri; + using SubelementType = t8_subelement_element; +}; From e8c2a936e4f3615b2901a9cdffc1798612138303 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Wed, 10 Jun 2026 09:07:20 +0200 Subject: [PATCH 15/28] tri --- .../specializations/t8_scheme_quads.hxx | 2 +- .../specializations/t8_scheme_tri.hxx | 275 +++++------------- 2 files changed, 71 insertions(+), 206 deletions(-) diff --git a/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx b/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx index 0896d08333..88f185dcd6 100644 --- a/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx +++ b/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx @@ -364,7 +364,7 @@ struct t8_subelementquad_scheme: public t8_subelement_scheme_commonsubelement_type; - int num_faces_quad = T8_ELEMENT_NUM_CORNERS[T8_ECLASS_QUAD]; + int num_faces_quad = T8_ELEMENT_NUM_FACES[T8_ECLASS_QUAD]; int binary_array[num_faces_quad] = {}; for ( diff --git a/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx b/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx index 807469d430..bd7db552da 100644 --- a/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx +++ b/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx @@ -23,12 +23,15 @@ /** \file TODO */ #pragma once +#include "t8.h" #include #include #include #include #include #include +#include +#include #define T8_TRI_MAX_SUBELEMENT_TYPE 6 @@ -181,11 +184,11 @@ struct t8_subelementtri_scheme: public t8_subelement_scheme_common n0 - * (1,0) -> n1 - * (1,1) -> n2 - */ + /** Mapping verification: + * (0,0) -> n0 + * (1,0) -> n1 + * (1,1) -> n2 + */ out_coords[coord * 2 + 0] = (1.0 - u) * n0[0] + (u - v) * n1[0] + v * n2[0]; out_coords[coord * 2 + 1] = (1.0 - u) * n0[1] + (u - v) * n1[1] + v * n2[1]; } @@ -193,221 +196,83 @@ struct t8_subelementtri_scheme: public t8_subelement_scheme_common, 3> &vertex_coords) { T8_ASSERT (Base::element_is_valid (elem)); T8_ASSERT (Base::element_is_subelement (elem)); const auto *subelement = Base::as_subelement (elem); - T8_ASSERT (vertex >= 0 && vertex < subelement_get_num_faces (subelement)); /* all subelements are triangles */ - /* get the length of the current quadrant */ int len = Base::parent_element_get_len (subelement); - /* Compute the x and y coordinates of subelement vertices, depending on the subelement type, id and vertex number - * (faces enumerated clockwise, starting at the center of the transition cell): - * - * f1 V1 - * x - - - - - x x - * | \ 2 / | / | - * | 1 \ / 3 | / 3 | - * f0 x - - + - - x f2 --> + - - x - * | 0 / | \ 4 | V0 V2 - * | / 6 | 5 \ | - * x - - x - - x - * f3 - * - * In this example, the below location array would contain the values [2, 1, 1] - * (second face, split, first subelement at this face) */ - - /* get location information of the given subelement */ - int location[3] = {}; - element_get_location_of_subelement (elem, location); - - /* the face number, the subelement is adjacent to */ - int face_number = location[0]; - /* = 1, if the adjacent face is split and = 0, if not */ - int split = location[1]; - /* = 0, if the subelement is the first (of two) subelements, at the adjacent face and = 1 if it is the second */ - int sub_face_id = location[2]; - - /* Check, whether the get_location function provides meaningful location data */ - T8_ASSERT (face_number == 0 || face_number == 1 || face_number == 2 || face_number == 3); - T8_ASSERT ((split == 0 && sub_face_id == 0) || (split == 1 && (sub_face_id == 0 || sub_face_id == 1))); - - coords[0] = subelement->element.coords[0]; - coords[1] = subelement->element.coords[1]; - - /* using the location data to determine vertex coordinates */ - if (vertex == 0) { /* vertex 0 (the first vertex always equals the center of the element) */ - coords[0] += len / 2; - coords[1] += len / 2; - } /* end of vertex == 0 */ - else if (vertex == 1) { /* vertex 1 */ - if (face_number == 0) { - if (split && sub_face_id) { - coords[1] += len / 2; - } - } - else if (face_number == 1) { - coords[1] += len; - if (split && sub_face_id) { - coords[0] += len / 2; - } - } - else if (face_number == 2) { - coords[0] += len; - coords[1] += len; - if (split && sub_face_id) { - coords[1] -= len / 2; - } - } - else { - coords[0] += len; - if (split && sub_face_id) { - coords[0] -= len / 2; - } - } - } /* end of vertex == 1 */ - else if (vertex == 2) { /* vertex 2 */ - if (face_number == 0) { - coords[1] += len; - if (split && (sub_face_id == 0)) { - coords[1] -= len / 2; - } - } - else if (face_number == 1) { - coords[0] += len; - coords[1] += len; - if (split && (sub_face_id == 0)) { - coords[0] -= len / 2; - } - } - else if (face_number == 2) { - coords[0] += len; - if (split && (sub_face_id == 0)) { - coords[1] += len / 2; - } - } - else { - if (split && (sub_face_id == 0)) { - coords[0] += len / 2; - } - } - } /* end of vertex == 2 */ - } - - static void - element_get_location_of_subelement (const t8_element_t *elem, int location[]) - { - const auto *subelement = Base::as_subelement (elem); - - /* this function only works for subelements */ - T8_ASSERT (Base::element_is_subelement (elem)); - - T8_ASSERT (Base::element_is_valid (elem)); - - /* Consider the following subelement of type 13: - * - * f0 1 - * x - - x - - x x - - x - - x - * | | | \ 2 | 3 / | faces: f3 f2 f1 f0 - * | | | 1 \ | / 4 | binary code: 1 1 0 1 (=13) - * f3 x x f2 --> 1 x - - x - - x 1 --> rearrange binaries s.t. the faces are enumerated clockwise: 1 1 1 0 - * | | | 0 / \ 5 | number subelements at face: 2 2 2 1 - * | elem | | / 6 \ | consider sub_id 3: x -> second subelement on the upper face - * + - - - - - x x - - - - - x - * f1 0 - * - * We will use the binary representation to determine the location of the given subelement. - * - * We need to know: - * i) the face number of the first vertex (values: {0,1,2,3}). - * ii) whether this face is split in half (values: {0,1}). - * iii) if the subelement is the first or second subelement at the face (values: {0,1}). - * - * These information are then saved in the location array which will be used by the element_vertex function, - * to automatically determine the vertex coordinates of the given subelement. - * - * The location array for the above example would be {1,1,1} (upper face, split = true, second subelement at the upper face). */ - /* 1) convert the subelement type from a decimal to a binary representation */ - int type = subelement->subelement_type; - int num_faces_quad = T8_ELEMENT_NUM_CORNERS[T8_ECLASS_TRIANGLE]; - int binary_array[num_faces_quad] = {}; - - for ( - int i = 0; i < num_faces_quad; - i++) { /* need an array with 4 elements to store all subelement types of the quad scheme from 1 to 15 ({0,0,0,1} to {1,1,1,1}) */ - binary_array[(num_faces_quad - 1) - i] = (type & (1 << i)) >> i; - } /* we now got a binary representation of the subelement type, bitwise stored in an array */ + constexpr int num_faces = T8_ELEMENT_NUM_FACES[T8_ECLASS_TRIANGLE]; - /* 2) rearrange the binary representation to be in clockwise order */ - int binary_array_temp[num_faces_quad] = {}; + const unsigned type = static_cast (subelement->subelement_type); + const unsigned id = static_cast (subelement->subelement_id); - int j; - - for (j = 0; j < num_faces_quad; j++) { /* copying the binary array */ - binary_array_temp[j] = binary_array[j]; - } - const int subelement_location_to_parent_face[4] = { 0, 3, 1, 2 }; - for (j = 0; j < num_faces_quad; j++) { /* bringing the entries of binary array into clockwise order */ - binary_array[j] = binary_array_temp[subelement_location_to_parent_face[j]]; - } + std::array bits {}; - /* 3) use the rearranged binary representation, and the sub_id to determine the location of the subelement and store these information in an array */ - /* 3.1) location[0] -> the face_number, the subelement is adjacent to */ - /* 3.2) location[1] -> if the face is split or not */ - /* 3.3) location[2] -> if the subelement is the first or second subelement of the face (always the first, if the face is not split) */ - int num_subelements = element_get_number_of_subelements (subelement->subelement_type); - T8_ASSERT (subelement->subelement_id < num_subelements); - - int sub_id = subelement->subelement_id; - int sub_face_id = 0; - int face_number = 0; - int split = 0; - - int k; - - int cum_neigh_array[num_faces_quad] = {}; - - /* construct a cumulative array of the number of neighbors from face 0 to face 3 */ - cum_neigh_array[0] = binary_array[0] + 1; - cum_neigh_array[1] = cum_neigh_array[0] + binary_array[1] + 1; - cum_neigh_array[2] = cum_neigh_array[1] + binary_array[2] + 1; - cum_neigh_array[3] = cum_neigh_array[2] + binary_array[3] + 1; + for (int i = 0; i < num_faces; ++i) { + bits[num_faces - 1 - i] = (type >> i) & 1u; + } /* we now got a binary representation of the subelement type, bitwise stored in an array */ - /* 3.1) we can use the cumulative array to determine the face number of the given subelement */ - if (sub_id < cum_neigh_array[0]) { - face_number = 0; - } - else { - for (k = 0; k < num_faces_quad - 1; ++k) { - if (sub_id >= cum_neigh_array[k] && sub_id < cum_neigh_array[k + 1]) { - face_number = k + 1; - break; + const auto num_ones = std::popcount (type); + + T8_ASSERT (num_ones == 1 || num_ones == 2); + + const std::array x0_coords_parent { subelement->element.x, subelement->element.y }; + std::array x1_coords_parent; + TUnderlyingScheme::element_get_vertex_integer_coords (elem, 1, x1_coords_parent.data ()); + std::array x2_coords_parent; + TUnderlyingScheme::element_get_vertex_integer_coords (elem, 2, x2_coords_parent.data ()); + + const auto parent_tri_type = subelement->element.type; + // Just to initialize + std::fill (vertex_coords.begin (), vertex_coords.end (), x0_coords_parent); + /** If we have only one hanging face, we rotate the triangle such that the hanging face is at the bottom and count + * as follows: + * A With order of vertices for T1: B,M,A + * /|\ T2. M,C,A + * / | \ + * / | \ + * /T1 | T2\ + * /____|____\ + * B M C + */ + if (num_ones == 1) { + const int hanging_face = std::counter_zero (type); + switch (hanging_face) { + case 0: + vertex_coords[2][0] = 0.5 * (x1_coords_parent[0] + x2_coords_parent[0]); + vertex_coords[2][1] = 0.5 * (x1_coords_parent[1] + x2_coords_parent[1]); + if (id == 0) { + vertex_coords[1] = x1_coords_parent; + } + if (id == 0) { + vertex_coords[1] = x2_coords_parent; + } + case 1: + vertex_coords[0] = x1_coords_parent; + vertex_coords[2][0] = 0.5 * (x0_coords_parent[0] + x2_coords_parent[0]); + vertex_coords[2][1] = 0.5 * (x0_coords_parent[1] + x2_coords_parent[1]); + if (id == 0) { + vertex_coords[1] = x0_coords_parent; + } + if (id == 0) { + vertex_coords[0] = x2_coords_parent; + } + case 2: + vertex_coords[0] = x2_coords_parent; + vertex_coords[2][0] = 0.5 * (x0_coords_parent[0] + x1_coords_parent[0]); + vertex_coords[2][1] = 0.5 * (x0_coords_parent[1] + x1_coords_parent[1]); + if (id == 0) { + vertex_coords[1] = x0_coords_parent; + } + if (id == 0) { + vertex_coords[0] = x1_coords_parent; } } } - - /* 3.2) determine, whether the face is split or not */ - if (binary_array[face_number] == 0) { - split = 0; /* the face is not split */ - } - else { - split = 1; /* the face is split */ - } - - /* 3.3) determine, whether the subelement is the first or the second subelement at the face */ - if (sub_id + 1 == cum_neigh_array[face_number] && split == 1) { - sub_face_id = 1; /* second subelement */ - } - else { - sub_face_id = 0; /* first subelement */ - } - - location[0] = face_number; - location[1] = split; - location[2] = sub_face_id; } }; From d58003542a04914d00eb28cf45f94ff8341b8c53 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Fri, 12 Jun 2026 15:02:05 +0200 Subject: [PATCH 16/28] Working hybrid mesh --- .typos.toml | 1 + .../subelements/t8_quads_hanging_nodes.cxx | 3 +- src/t8_schemes/t8_scheme.hxx | 5 +- .../specializations/t8_scheme_quads.hxx | 51 ++- .../specializations/t8_scheme_tri.hxx | 156 +++++--- .../t8_subelement/t8_subelement.cxx | 10 +- .../t8_subelement/t8_subelement_scheme.hxx | 355 ++++++++++-------- 7 files changed, 324 insertions(+), 257 deletions(-) diff --git a/.typos.toml b/.typos.toml index b956ef5a00..eb06f3ed0d 100644 --- a/.typos.toml +++ b/.typos.toml @@ -1,6 +1,7 @@ [default.extend-words] eles = "eles" packageid = "packageid" +countr = "countr" [files] extend-exclude = ["scripts/indent.sh", "thirdparty/", "t8code_logo.png", "cmake/FindOpenCASCADE.cmake", "src/t8_misc/t8_with_macro_error.h", "doc/Doxyfile.in"] diff --git a/example/subelements/t8_quads_hanging_nodes.cxx b/example/subelements/t8_quads_hanging_nodes.cxx index 05dd4df3d9..282a889b20 100644 --- a/example/subelements/t8_quads_hanging_nodes.cxx +++ b/example/subelements/t8_quads_hanging_nodes.cxx @@ -24,6 +24,7 @@ * This is an example to demonstrate hanging node resolution for quads. */ +#include "t8_eclass/t8_eclass.h" #include /* General t8code header, always include this. */ #include /* cmesh definition and basic interface. */ #include /* A collection of exemplary cmeshes */ @@ -83,7 +84,7 @@ main (int argc, char **argv) /* ---Setup. Build cmesh and uniform forest.--- */ /* Build a cube cmesh with tet, hex, and prism trees. */ - //t8_cmesh_t cmesh = t8_cmesh_new_hypercube (T8_ECLASS_QUAD, comm, 0, 0, 0); + // t8_cmesh_t cmesh = t8_cmesh_new_hypercube (T8_ECLASS_TRIANGLE, comm, 0, 0, 0); t8_cmesh_t cmesh = t8_cmesh_new_periodic_hybrid (comm); t8_forest_t forest = t8_forest_new_uniform (cmesh, t8_scheme_new_subelement (), level, 0, comm); // TODO: New scheme diff --git a/src/t8_schemes/t8_scheme.hxx b/src/t8_schemes/t8_scheme.hxx index 8ae4a74feb..5374aac6ba 100644 --- a/src/t8_schemes/t8_scheme.hxx +++ b/src/t8_schemes/t8_scheme.hxx @@ -44,7 +44,6 @@ #include #include #include -#include #include #if T8_ENABLE_DEBUG // Only needed for t8_debug_print_type @@ -104,8 +103,8 @@ struct t8_scheme t8_standalone_scheme, t8_standalone_scheme, /* Subelement schemes */ - t8_subelement_scheme_common, - t8_subelement_scheme_common + t8_subelementquad_scheme, + t8_subelementtri_scheme >; /* clang-format on */ diff --git a/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx b/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx index 88f185dcd6..12d46214fc 100644 --- a/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx +++ b/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx @@ -26,12 +26,7 @@ */ #pragma once -#include -#include #include -#include -#include -#include #define T8_SUB_QUAD_MAX_SUBELEMENT_TYPE 14 @@ -64,6 +59,8 @@ struct t8_subelementquad_scheme: public t8_subelement_scheme_common::SubelementType; using Base = t8_subelement_scheme_common; + TUnderlyingScheme underlying_scheme {}; + /** Compute the number of corners of an element. * \param [in] elem The subelement. * \return The number of corners of \a elem. @@ -163,37 +160,37 @@ struct t8_subelementquad_scheme: public t8_subelement_scheme_commonelement_get_number_of_subelements (type); T8_ASSERT (type >= 1 && type <= T8_SUB_QUAD_MAX_SUBELEMENT_TYPE); - T8_ASSERT (!Base::element_is_subelement (elem)); - T8_ASSERT (Base::element_is_valid (elem)); + T8_ASSERT (!this->element_is_subelement (elem)); + T8_ASSERT (this->element_is_valid (elem)); #if T8_ENABLE_DEBUG { for (int j = 0; j < num_subelements; j++) { - T8_ASSERT (Base::element_is_valid (c[j])); + T8_ASSERT (this->element_is_valid (c[j])); } } #endif /* Setting the parameter values for different subelements. */ for (int sub_id_counter = 0; sub_id_counter < num_subelements; sub_id_counter++) { - TUnderlyingScheme::element_copy (Base::subelement_to_standalone (element), - Base::subelement_to_standalone (subelements[sub_id_counter])); + TUnderlyingScheme::element_copy (this->subelement_to_standalone (element), + this->subelement_to_standalone (subelements[sub_id_counter])); subelements[sub_id_counter]->subelement_type = type; subelements[sub_id_counter]->subelement_id = sub_id_counter; - T8_ASSERT (Base::element_is_valid (c[sub_id_counter])); + T8_ASSERT (this->element_is_valid (c[sub_id_counter])); } } - static void + void subelement_get_reference_coords (const t8_element_t *elem, const double *ref_coords, const size_t num_coords, - double *out_coords) noexcept + double *out_coords) const noexcept { /* Get the 3 integer vertex coords of the subelement triangle */ @@ -223,17 +220,17 @@ struct t8_subelementquad_scheme: public t8_subelement_scheme_commonelement_is_valid (elem)); + T8_ASSERT (this->element_is_subelement (elem)); + const auto *subelement = this->as_subelement (elem); T8_ASSERT (vertex >= 0 && vertex < subelement_get_num_faces (subelement)); /* all subelements are triangles */ /* get the length of the current quadrant */ - int len = Base::parent_element_get_len (subelement); + int len = this->parent_element_get_len (subelement); /* Compute the x and y coordinates of subelement vertices, depending on the subelement type, id and vertex number * (faces enumerated clockwise, starting at the center of the transition cell): @@ -328,15 +325,15 @@ struct t8_subelementquad_scheme: public t8_subelement_scheme_commonas_subelement (elem); /* this function only works for subelements */ - T8_ASSERT (Base::element_is_subelement (elem)); + T8_ASSERT (this->element_is_subelement (elem)); - T8_ASSERT (Base::element_is_valid (elem)); + T8_ASSERT (this->element_is_valid (elem)); /* Consider the following subelement of type 13: * diff --git a/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx b/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx index bd7db552da..e54d93f4c6 100644 --- a/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx +++ b/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx @@ -23,13 +23,8 @@ /** \file TODO */ #pragma once -#include "t8.h" -#include -#include + #include -#include -#include -#include #include #include @@ -47,6 +42,8 @@ struct t8_subelementtri_scheme: public t8_subelement_scheme_common::SubelementType; using Base = t8_subelement_scheme_common; + TUnderlyingScheme underlying_scheme {}; + /** Compute the number of corners of an element. * \param [in] elem The subelement. * \return The number of corners of \a elem. @@ -134,51 +131,49 @@ struct t8_subelementtri_scheme: public t8_subelement_scheme_commonelement_get_number_of_subelements (type); T8_ASSERT (type >= 1 && type <= T8_TRI_MAX_SUBELEMENT_TYPE); - T8_ASSERT (!Base::element_is_subelement (elem)); - T8_ASSERT (Base::element_is_valid (elem)); + T8_ASSERT (!this->element_is_subelement (elem)); + T8_ASSERT (this->element_is_valid (elem)); #if T8_ENABLE_DEBUG { for (int j = 0; j < num_subelements; j++) { - T8_ASSERT (Base::element_is_valid (c[j])); + T8_ASSERT (this->element_is_valid (c[j])); } } #endif /* Setting the parameter values for different subelements. */ for (int sub_id_counter = 0; sub_id_counter < num_subelements; sub_id_counter++) { - TUnderlyingScheme::element_copy (Base::subelement_to_standalone (element), - Base::subelement_to_standalone (subelements[sub_id_counter])); + underlying_scheme.element_copy (this->subelement_to_standalone (element), + this->subelement_to_standalone (subelements[sub_id_counter])); subelements[sub_id_counter]->subelement_type = type; subelements[sub_id_counter]->subelement_id = sub_id_counter; - T8_ASSERT (Base::element_is_valid (c[sub_id_counter])); + T8_ASSERT (this->element_is_valid (c[sub_id_counter])); } } //TODO: also the numbering of the subelements in the triangle scheme needs to be defined. - static void + void subelement_get_reference_coords (const t8_element_t *elem, const double *ref_coords, const size_t num_coords, - double *out_coords) noexcept + double *out_coords) const noexcept { /* Get the 3 integer vertex coords of the subelement triangle */ - int v0[2], v1[2], v2[2]; - vertex_coords_of_subelement (elem, 0, v0); - vertex_coords_of_subelement (elem, 1, v1); - vertex_coords_of_subelement (elem, 2, v2); + std::array, 3> vertex_coords; + vertex_coords_of_subelement (elem, vertex_coords); /* Normalize to [0,1] by dividing by root length */ const double root_len = (1 << T8_ELEMENT_MAXLEVEL[T8_ECLASS_TRIANGLE]); - double n0[2] = { v0[0] / root_len, v0[1] / root_len }; - double n1[2] = { v1[0] / root_len, v1[1] / root_len }; - double n2[2] = { v2[0] / root_len, v2[1] / root_len }; + double n0[2] = { vertex_coords[0][0] / root_len, vertex_coords[0][1] / root_len }; + double n1[2] = { vertex_coords[1][0] / root_len, vertex_coords[1][1] / root_len }; + double n2[2] = { vertex_coords[2][0] / root_len, vertex_coords[2][1] / root_len }; for (size_t coord = 0; coord < num_coords; ++coord) { const double u = ref_coords[coord * 2 + 0]; @@ -195,39 +190,30 @@ struct t8_subelementtri_scheme: public t8_subelement_scheme_common, 3> &vertex_coords) + void + vertex_coords_of_subelement (const t8_element_t *elem, + std::array, 3> &vertex_coords) const noexcept { - T8_ASSERT (Base::element_is_valid (elem)); - T8_ASSERT (Base::element_is_subelement (elem)); - const auto *subelement = Base::as_subelement (elem); - - /* get the length of the current quadrant */ - int len = Base::parent_element_get_len (subelement); - - /* 1) convert the subelement type from a decimal to a binary representation */ - constexpr int num_faces = T8_ELEMENT_NUM_FACES[T8_ECLASS_TRIANGLE]; - + T8_ASSERT (this->element_is_valid (elem)); + T8_ASSERT (this->element_is_subelement (elem)); + const auto *subelement = this->as_subelement (elem); const unsigned type = static_cast (subelement->subelement_type); const unsigned id = static_cast (subelement->subelement_id); - std::array bits {}; - - for (int i = 0; i < num_faces; ++i) { - bits[num_faces - 1 - i] = (type >> i) & 1u; - } /* we now got a binary representation of the subelement type, bitwise stored in an array */ - const auto num_ones = std::popcount (type); T8_ASSERT (num_ones == 1 || num_ones == 2); - const std::array x0_coords_parent { subelement->element.x, subelement->element.y }; + std::array x0_coords_parent; + underlying_scheme.element_get_vertex_integer_coords (this->subelement_to_standalone (subelement), 0, + x0_coords_parent.data ()); std::array x1_coords_parent; - TUnderlyingScheme::element_get_vertex_integer_coords (elem, 1, x1_coords_parent.data ()); + underlying_scheme.element_get_vertex_integer_coords (this->subelement_to_standalone (subelement), 1, + x1_coords_parent.data ()); std::array x2_coords_parent; - TUnderlyingScheme::element_get_vertex_integer_coords (elem, 2, x2_coords_parent.data ()); + underlying_scheme.element_get_vertex_integer_coords (this->subelement_to_standalone (subelement), 2, + x2_coords_parent.data ()); - const auto parent_tri_type = subelement->element.type; // Just to initialize std::fill (vertex_coords.begin (), vertex_coords.end (), x0_coords_parent); /** If we have only one hanging face, we rotate the triangle such that the hanging face is at the bottom and count @@ -241,7 +227,9 @@ struct t8_subelementtri_scheme: public t8_subelement_scheme_common> (); builder.add_eclass_scheme> (); - builder.add_eclass_scheme> (); - builder.add_eclass_scheme> (); + builder.add_eclass_scheme (); + builder.add_eclass_scheme (); builder.add_eclass_scheme> (); builder.add_eclass_scheme (); builder.add_eclass_scheme (); @@ -52,11 +52,9 @@ t8_eclass_scheme_is_subelement (const t8_scheme *scheme, const t8_eclass_t eclas { switch (eclass) { case T8_ECLASS_QUAD: - return scheme->check_eclass_scheme_type> ( - T8_ECLASS_QUAD); + return scheme->check_eclass_scheme_type (T8_ECLASS_QUAD); case T8_ECLASS_TRIANGLE: - return scheme->check_eclass_scheme_type> ( - T8_ECLASS_TRIANGLE); + return scheme->check_eclass_scheme_type (T8_ECLASS_TRIANGLE); default: return 0; /* Default return value false. */ } diff --git a/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx b/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx index 2223369af0..7b45accfcb 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx +++ b/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx @@ -36,9 +36,6 @@ * Subelements are discarded before the next adaptation cycle and do not have children. * \tparam TEclass The element class of the underlying elements which we want to define subelements for. * The subelements themselves could have another eclass. - * \tparam TUnderlyingScheme The used recursive scheme for the underlying elements. Every time we do not need the - * subelement logic, the scheme calls the functionality of this underlying scheme. - * \tparam TSubelementType The type definition of the subelement. See \ref t8_subelement_type.hxx for an example. * \tparam TSubelementSchemeSpecialization Specialization scheme for the subelements. Every time we need the subelement logic which * is not equal for all subelements, the scheme calls the functionality of this subelement scheme. @@ -48,9 +45,8 @@ struct t8_subelement_scheme_common: public t8_scheme_helpers> { public: - using TUnderlyingScheme = typename t8_subelement_traits:: - UnderlyingScheme; /**< The used recursive scheme for the underlying elements. Every time we do not need the subelement logic, the scheme calls the functionality of this underlying scheme. */ using TSubelementType = typename t8_subelement_traits::SubelementType; + /** Constructor. */ t8_subelement_scheme_common () noexcept : element_size (sizeof (TSubelementType)), scheme_context (sc_mempool_new (element_size)) {}; @@ -136,10 +132,10 @@ struct t8_subelement_scheme_common: /** Return the maximum allowed level for any element of a given class. * \return The maximum allowed level for elements of class \b ts. */ - static constexpr int - get_maxlevel (void) noexcept + constexpr int + get_maxlevel (void) const noexcept { - return TUnderlyingScheme::get_maxlevel () - 1; // We need to reserve one level for the subelements. + return derived ().underlying_scheme.get_maxlevel () - 1; // We need to reserve one level for the subelements. } // ################################################____SHAPE INFORMATION____########################################## @@ -148,12 +144,12 @@ struct t8_subelement_scheme_common: * \param [in] elem The element. * \return The number of corners of \a elem. */ - static int - element_get_num_corners (const t8_element_t *elem) noexcept + int + element_get_num_corners (const t8_element_t *elem) const noexcept { T8_ASSERT (element_is_valid (elem)); if (!element_is_subelement (elem)) { - return TUnderlyingScheme::element_get_num_corners (element_to_standalone (elem)); + return derived ().underlying_scheme.element_get_num_corners (element_to_standalone (elem)); } return TSubelementSchemeSpecialization::subelement_get_num_corners (as_subelement (elem)); } @@ -162,12 +158,12 @@ struct t8_subelement_scheme_common: * \param [in] elem The element. * \return The number of faces of \a elem. */ - static int - element_get_num_faces (const t8_element_t *elem) noexcept + int + element_get_num_faces (const t8_element_t *elem) const noexcept { T8_ASSERT (element_is_valid (elem)); if (!element_is_subelement (elem)) { - return TUnderlyingScheme::element_get_num_faces (element_to_standalone (elem)); + return derived ().underlying_scheme.element_get_num_faces (element_to_standalone (elem)); } return TSubelementSchemeSpecialization::subelement_get_num_faces (as_subelement (elem)); } @@ -176,11 +172,11 @@ struct t8_subelement_scheme_common: * \param [in] elem The element. * \return The maximum number of faces of \a elem and its descendants. */ - static int - element_get_max_num_faces (const t8_element_t *elem) noexcept + int + element_get_max_num_faces (const t8_element_t *elem) const noexcept { T8_ASSERT (element_is_valid (elem)); - return std::max (TUnderlyingScheme::element_get_max_num_faces (element_to_standalone (elem)), + return std::max (derived ().underlying_scheme.element_get_max_num_faces (element_to_standalone (elem)), TSubelementSchemeSpecialization::subelement_get_max_num_faces (as_subelement (elem))); } @@ -188,12 +184,12 @@ struct t8_subelement_scheme_common: * \param [in] elem The element to be considered * \return The shape of the element as an eclass */ - static t8_element_shape_t - element_get_shape (const t8_element_t *elem) noexcept + t8_element_shape_t + element_get_shape (const t8_element_t *elem) const noexcept { T8_ASSERT (element_is_valid (elem)); if (!element_is_subelement (elem)) { - return TUnderlyingScheme::element_get_shape (element_to_standalone (elem)); + return derived ().underlying_scheme.element_get_shape (element_to_standalone (elem)); } return TSubelementSchemeSpecialization::subelement_get_shape (as_subelement (elem)); } @@ -229,13 +225,13 @@ struct t8_subelement_scheme_common: * \param [in] face A face of \a elem. * \return The element shape of the face. As we are in 2D, here always LINE. */ - static t8_element_shape_t - element_get_face_shape (const t8_element_t *elem, const int face) noexcept + t8_element_shape_t + element_get_face_shape (const t8_element_t *elem, const int face) const noexcept { T8_ASSERT (element_is_valid (elem)); T8_ASSERT (0 <= face && face < element_get_num_faces (elem)); if (!element_is_subelement (elem)) { - return TUnderlyingScheme::element_get_face_shape (element_to_standalone (elem), face); + return derived ().underlying_scheme.element_get_face_shape (element_to_standalone (elem), face); } return TSubelementSchemeSpecialization::subelement_get_face_shape (as_subelement (elem), face); } @@ -244,11 +240,11 @@ struct t8_subelement_scheme_common: * \param [in] elem The element whose level should be returned. * \return The level of \b elem. */ - static int - element_get_level (const t8_element_t *elem) noexcept + int + element_get_level (const t8_element_t *elem) const noexcept { T8_ASSERT (element_is_valid (elem)); - return TUnderlyingScheme::element_get_level (element_to_standalone (elem)); + return derived ().underlying_scheme.element_get_level (element_to_standalone (elem)); } // ################################################____GENERAL HELPER____############################################# @@ -259,8 +255,8 @@ struct t8_subelement_scheme_common: * \param [in,out] dest This element's entries will be overwrite with the entries of \b source. * \note \a source and \a dest may point to the same element. */ - static void - element_copy (const t8_element_t *source, t8_element_t *dest) noexcept + void + element_copy (const t8_element_t *source, t8_element_t *dest) const noexcept { T8_ASSERT (element_is_valid (source)); if (source == dest) @@ -275,8 +271,8 @@ struct t8_subelement_scheme_common: * \param [in] elem2 The second element. * \return true if the elements are equal, false if they are not equal */ - static int - element_is_equal (const t8_element_t *elem1, const t8_element_t *elem2) noexcept + int + element_is_equal (const t8_element_t *elem1, const t8_element_t *elem2) const noexcept { T8_ASSERT (element_is_valid (elem1) && element_is_valid (elem2)); const auto *el1 = as_subelement (elem1); @@ -284,19 +280,20 @@ struct t8_subelement_scheme_common: if (el1->subelement_type != el2->subelement_type) { return 0; } - return TUnderlyingScheme::element_is_equal (subelement_to_standalone (el1), subelement_to_standalone (el2)); + return derived ().underlying_scheme.element_is_equal (subelement_to_standalone (el1), + subelement_to_standalone (el2)); } // ################################################____REFINEMENT____################################################ /** Create the root element. * \param [in,out] elem The element that is filled with the root. */ - static void - set_to_root (t8_element_t *elem) noexcept + void + set_to_root (t8_element_t *elem) const noexcept { auto *subelement = as_subelement (elem); reset_subelement_values (subelement); - TUnderlyingScheme::set_to_root (subelement_to_standalone (subelement)); + derived ().underlying_scheme.set_to_root (subelement_to_standalone (subelement)); } /** Compute the parent of a given element \b elem and store it in \b parent. @@ -306,8 +303,8 @@ struct t8_subelement_scheme_common: * \param [in,out] parent This element's entries will be overwritten by those of \b elem's parent. * The storage for this element must exist and match the element class of the parent. */ - static void - element_get_parent (const t8_element_t *elem, t8_element_t *parent) noexcept + void + element_get_parent (const t8_element_t *elem, t8_element_t *parent) const noexcept { T8_ASSERT (element_is_valid (elem)); const auto *subelement = as_subelement (elem); @@ -315,12 +312,12 @@ struct t8_subelement_scheme_common: reset_subelement_values (parent_subelement); if (element_is_subelement (elem)) { // For subelements, the parent is the element from which they are refined. - TUnderlyingScheme::element_copy (subelement_to_standalone (subelement), - subelement_to_standalone (parent_subelement)); + derived ().underlying_scheme.element_copy (subelement_to_standalone (subelement), + subelement_to_standalone (parent_subelement)); return; } - TUnderlyingScheme::element_get_parent (subelement_to_standalone (subelement), - subelement_to_standalone (parent_subelement)); + derived ().underlying_scheme.element_get_parent (subelement_to_standalone (subelement), + subelement_to_standalone (parent_subelement)); } /** Compute the number of siblings of an element. That is the number of elements with the same parent (if available). @@ -328,12 +325,12 @@ struct t8_subelement_scheme_common: * \return The number of siblings of \a element. * Note that this number is >= 1, since we count the element itself as a sibling.. */ - static int - element_get_num_siblings (const t8_element_t *elem) noexcept + int + element_get_num_siblings (const t8_element_t *elem) const noexcept { T8_ASSERT (element_is_valid (elem)); if (!element_is_subelement (elem)) { - return TUnderlyingScheme::element_get_num_siblings (element_to_standalone (elem)); + return derived ().underlying_scheme.element_get_num_siblings (element_to_standalone (elem)); } return element_get_number_of_subelements (as_subelement (elem)->subelement_type); } @@ -347,7 +344,7 @@ struct t8_subelement_scheme_common: element_get_sibling ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int sibid, [[maybe_unused]] t8_element_t *sibling) noexcept { - SC_ABORT ("This function is not implemented yet.\n"); + SC_ABORT ("element_get_sibling not implemented yet.\n"); } /** As subelements are discarded before the next adaptation cycle, they do not have children. @@ -355,32 +352,33 @@ struct t8_subelement_scheme_common: * \param [in] childid The number of the child to construct. * \param [in,out] child The storage for this element must exist and match the element class of the child. */ - static void - element_get_child (const t8_element_t *elem, const int childid, t8_element_t *child) noexcept + void + element_get_child (const t8_element_t *elem, const int childid, t8_element_t *child) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem), "element_get_child: Cannot construct child of a subelement.\n"); - TUnderlyingScheme::element_get_child (element_to_standalone (elem), childid, element_to_standalone (child)); + derived ().underlying_scheme.element_get_child (element_to_standalone (elem), childid, + element_to_standalone (child)); } /** Return the number of children of an element when it is refined. Not for subelements as they do not have children. * \param [in] elem The element whose number of children is returned. * \return The number of children of \a elem if it is to be refined. */ - static int - element_get_num_children (const t8_element_t *elem) noexcept + int + element_get_num_children (const t8_element_t *elem) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem), "element_get_num_children: Cannot construct child of a subelement.\n"); - return TUnderlyingScheme::element_get_num_children (element_to_standalone (elem)); + return derived ().underlying_scheme.element_get_num_children (element_to_standalone (elem)); } /** Return the max number of children of an eclass. * \return Maximum number of possible children (maximum of normal refinement and subelement refinement). */ - static int - get_max_num_children () noexcept + int + get_max_num_children () const noexcept { - return std::max (TUnderlyingScheme::get_max_num_children (), + return std::max (derived ().underlying_scheme.get_max_num_children (), TSubelementSchemeSpecialization::subelement_get_max_num_children ()); } @@ -390,15 +388,15 @@ struct t8_subelement_scheme_common: * \param [in] elem The element to check. * \return True if the element is refinable. */ - static bool - element_is_refinable (const t8_element_t *elem) noexcept + bool + element_is_refinable (const t8_element_t *elem) const noexcept { T8_ASSERT (element_is_valid (elem)); if (element_is_subelement (elem)) { // Subelements are not refinable, as they are discarded for the next adaptation cycle. return false; } - return TUnderlyingScheme::element_get_level (element_to_standalone (elem)) < get_maxlevel (); + return derived ().underlying_scheme.element_get_level (element_to_standalone (elem)) < get_maxlevel (); } /** Construct all children of a given element. Not possible for subelements as they have no children. @@ -409,8 +407,8 @@ struct t8_subelement_scheme_common: * the children's ordering. On output, all children are valid. * It is valid to call this function with elem = c[0]. */ - static void - element_get_children (const t8_element_t *elem, const int length, t8_element_t *c[]) noexcept + void + element_get_children (const t8_element_t *elem, const int length, t8_element_t *c[]) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem), "element_get_children: Cannot construct child of a subelement.\n"); T8_ASSERT (element_is_valid (elem)); @@ -422,7 +420,8 @@ struct t8_subelement_scheme_common: standalone_children_ptrs[ichild] = subelement_to_standalone (child); reset_subelement_values (child); } - TUnderlyingScheme::element_get_children (subelement_to_standalone (subelement), length, standalone_children_ptrs); + derived ().underlying_scheme.element_get_children (subelement_to_standalone (subelement), length, + standalone_children_ptrs); T8_FREE (standalone_children_ptrs); } @@ -430,8 +429,8 @@ struct t8_subelement_scheme_common: * \param [in] elem This must be a valid element. * \return The child id of elem. */ - static int - element_get_child_id (const t8_element_t *elem) noexcept + int + element_get_child_id (const t8_element_t *elem) const noexcept { T8_ASSERT (element_is_valid (elem)); const auto *subelement = as_subelement (elem); @@ -439,7 +438,7 @@ struct t8_subelement_scheme_common: // For subelements, the child id is the subelement id. return subelement->subelement_id; } - return TUnderlyingScheme::element_get_child_id (subelement_to_standalone (subelement)); + return derived ().underlying_scheme.element_get_child_id (subelement_to_standalone (subelement)); } /** Compute the ancestor id of an element, that is the child id at a given level. @@ -447,11 +446,11 @@ struct t8_subelement_scheme_common: * \param [in] level A refinement level. Must satisfy \a level < elem.level * \return The child_id of \a elem in regard to its \a level ancestor. */ - static int - element_get_ancestor_id (const t8_element_t *elem, const t8_element_level level) noexcept + int + element_get_ancestor_id (const t8_element_t *elem, const t8_element_level level) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem), "element_get_ancestor_id is not implemented for subelements yet.\n"); - return TUnderlyingScheme::element_get_ancestor_id (element_to_standalone (elem), level); + return derived ().underlying_scheme.element_get_ancestor_id (element_to_standalone (elem), level); } /** Query whether a given set of elements is a family or not. @@ -460,8 +459,8 @@ struct t8_subelement_scheme_common: * \return Zero if \b fam is not a family, nonzero if it is. * \note level 0 elements do not form a family. */ - static int - elements_are_family (t8_element_t *const *fam) noexcept + int + elements_are_family (t8_element_t *const *fam) const noexcept { #if T8_ENABLE_DEBUG const int num_siblings = element_get_num_siblings (fam[0]); @@ -475,7 +474,7 @@ struct t8_subelement_scheme_common: auto element_0 = element_to_standalone (fam[0]); for (int isib = 1; isib < element_get_num_siblings (fam[0]); ++isib) { if (!element_is_subelement (fam[isib]) - || !TUnderlyingScheme::element_is_equal (element_0, element_to_standalone (fam[isib]))) { + || !derived ().underlying_scheme.element_is_equal (element_0, element_to_standalone (fam[isib]))) { return 0; } } @@ -491,7 +490,7 @@ struct t8_subelement_scheme_common: standalone_children_ptrs[isib] = element_to_standalone (fam[isib]); } - bool are_family = TUnderlyingScheme::elements_are_family (standalone_children_ptrs); + bool are_family = derived ().underlying_scheme.elements_are_family (standalone_children_ptrs); T8_FREE (standalone_children_ptrs); return are_family; } @@ -503,8 +502,8 @@ struct t8_subelement_scheme_common: * \param [in] element_B An element of class \a eclass in scheme \a scheme. * \return True if and only if \a element_A is an ancestor of \a element_B. */ - static bool - element_is_ancestor (const t8_element_t *element_A, const t8_element_t *element_B) noexcept + bool + element_is_ancestor (const t8_element_t *element_A, const t8_element_t *element_B) const noexcept { T8_ASSERT (element_is_valid (element_A)); T8_ASSERT (element_is_valid (element_B)); @@ -516,8 +515,8 @@ struct t8_subelement_scheme_common: // B could be a subelement if the underlying element is an ancestor of A. return false; } - TUnderlyingScheme tmp {}; - return tmp.element_is_ancestor (element_to_standalone (element_A), element_to_standalone (element_B)); + return derived ().underlying_scheme.element_is_ancestor (element_to_standalone (element_A), + element_to_standalone (element_B)); } /** Compute the nearest common ancestor of two elements. Not implemented yet. @@ -530,7 +529,7 @@ struct t8_subelement_scheme_common: element_get_nca ([[maybe_unused]] const t8_element_t *elem1, [[maybe_unused]] const t8_element_t *elem2, [[maybe_unused]] t8_element_t *nca) noexcept { - SC_ABORT ("This function is not implemented yet.\n"); + SC_ABORT ("element_get_nca not implemented yet.\n"); } /** Compute the first descendant of a given element. @@ -540,10 +539,12 @@ struct t8_subelement_scheme_common: * \param [out] desc The first element in a uniform refinement of \a elem of the given level. * \param [in] level The level, at which the descendant is computed. */ - static void - element_get_first_descendant (const t8_element_t *elem, t8_element_t *desc, const t8_element_level level) noexcept + void + element_get_first_descendant (const t8_element_t *elem, t8_element_t *desc, + const t8_element_level level) const noexcept { - TUnderlyingScheme::element_get_first_descendant (element_to_standalone (elem), element_to_standalone (desc), level); + derived ().underlying_scheme.element_get_first_descendant (element_to_standalone (elem), + element_to_standalone (desc), level); reset_subelement_values ((TSubelementType *) desc); } @@ -554,10 +555,12 @@ struct t8_subelement_scheme_common: * \param [out] desc The last element in a uniform refinement of \a elem of the given level. * \param [in] level The level, at which the descendant is computed. */ - static void - element_get_last_descendant (const t8_element_t *elem, t8_element_t *desc, const t8_element_level level) noexcept + void + element_get_last_descendant (const t8_element_t *elem, t8_element_t *desc, + const t8_element_level level) const noexcept { - TUnderlyingScheme::element_get_last_descendant (element_to_standalone (elem), element_to_standalone (desc), level); + derived ().underlying_scheme.element_get_last_descendant (element_to_standalone (elem), + element_to_standalone (desc), level); reset_subelement_values ((TSubelementType *) desc); } @@ -569,12 +572,13 @@ struct t8_subelement_scheme_common: * \param [in] face A face of \a elem. * \return The number of children of \a face if \a elem is to be refined. */ - static int - element_get_num_face_children ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int face) noexcept + int + element_get_num_face_children ([[maybe_unused]] const t8_element_t *elem, + [[maybe_unused]] const int face) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem), "element_get_num_face_children is not implemented for subelements yet.\n"); - return TUnderlyingScheme::element_get_num_face_children (element_to_standalone (elem), face); + return derived ().underlying_scheme.element_get_num_face_children (element_to_standalone (elem), face); } /** Given an element and a face of the element, compute all children of the element that touch the face. @@ -589,9 +593,9 @@ struct t8_subelement_scheme_common: * on output its i-th entry is the child_id of the i-th face_child. * It is valid to call this function with elem = children[0]. */ - static void + void element_get_children_at_face ([[maybe_unused]] const t8_element_t *elem, const int face, t8_element_t *children[], - const int num_children, [[maybe_unused]] int *child_indices) noexcept + const int num_children, [[maybe_unused]] int *child_indices) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem), "element_get_children_at_face is not implemented for subelements yet.\n"); @@ -601,8 +605,8 @@ struct t8_subelement_scheme_common: standalone_children_ptrs[ichild] = subelement_to_standalone (child); reset_subelement_values (child); } - TUnderlyingScheme::element_get_children_at_face (element_to_standalone (elem), face, standalone_children_ptrs, - num_children, child_indices); + derived ().underlying_scheme.element_get_children_at_face (element_to_standalone (elem), face, + standalone_children_ptrs, num_children, child_indices); T8_FREE (standalone_children_ptrs); } @@ -616,13 +620,13 @@ struct t8_subelement_scheme_common: * This coincides with the order of children from a call to \ref element_get_children_at_face. * \return The face number of the face of a child of \a elem that coincides with \a face_child. */ - static int + int element_face_get_child_face ([[maybe_unused]] const t8_element_t *elem, const int face, - [[maybe_unused]] const int face_child) noexcept + [[maybe_unused]] const int face_child) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem), "element_face_get_child_face is not implemented for subelements yet.\n"); - return TUnderlyingScheme::element_face_get_child_face (element_to_standalone (elem), face, face_child); + return derived ().underlying_scheme.element_face_get_child_face (element_to_standalone (elem), face, face_child); } /** Given a face of an element return the face number of the parent of the element that matches the element's face. @@ -633,12 +637,12 @@ struct t8_subelement_scheme_common: * Otherwise -1. * \note For the root element this function always returns \a face. */ - static int - element_face_get_parent_face ([[maybe_unused]] const t8_element_t *elem, const int face) noexcept + int + element_face_get_parent_face ([[maybe_unused]] const t8_element_t *elem, const int face) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem), "element_face_get_parent_face is not implemented for subelements yet.\n"); - return TUnderlyingScheme::element_face_get_parent_face (element_to_standalone (elem), face); + return derived ().underlying_scheme.element_face_get_parent_face (element_to_standalone (elem), face); } /** Construct the first descendant of an element at a given level that touches a given face. @@ -649,14 +653,14 @@ struct t8_subelement_scheme_common: * descendant of \a elem that shares a face with \a face. * \param [in] level The level, at which the first descendant is constructed */ - static void + void element_get_first_descendant_face (const t8_element_t *elem, const int face, t8_element_t *first_desc, - const t8_element_level level) noexcept + const t8_element_level level) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem), "element_get_first_descendant_face is not implemented for subelements yet.\n"); - TUnderlyingScheme::element_get_first_descendant_face (element_to_standalone (elem), face, - element_to_standalone (first_desc), level); + derived ().underlying_scheme.element_get_first_descendant_face (element_to_standalone (elem), face, + element_to_standalone (first_desc), level); } /** Construct the last descendant of an element at a given level that touches a given face. @@ -667,14 +671,14 @@ struct t8_subelement_scheme_common: * descendant of \a elem that shares a face with \a face. * \param [in] level The level, at which the last descendant is constructed */ - static void + void element_get_last_descendant_face ([[maybe_unused]] const t8_element_t *elem, const int face, t8_element_t *last_desc, - const t8_element_level level) noexcept + const t8_element_level level) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem), "element_get_last_descendant_face is not implemented for subelements yet.\n"); - TUnderlyingScheme::element_get_last_descendant_face (element_to_standalone (elem), face, - element_to_standalone (last_desc), level); + derived ().underlying_scheme.element_get_last_descendant_face (element_to_standalone (elem), face, + element_to_standalone (last_desc), level); } // ################################################____FACE NEIGHBOR____############################################## @@ -686,12 +690,12 @@ struct t8_subelement_scheme_common: * \return True if \a face is a subface of the element's root element. * \note You can compute the corresponding face number of the tree via \ref element_get_tree_face. */ - static int - element_is_root_boundary (const t8_element_t *elem, const int face) noexcept + int + element_is_root_boundary (const t8_element_t *elem, const int face) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem), "element_is_root_boundary is not implemented for subelements yet.\n"); - return TUnderlyingScheme::element_is_root_boundary (element_to_standalone (elem), face); + return derived ().underlying_scheme.element_is_root_boundary (element_to_standalone (elem), face); } /** Given an element and a face of this element. If the face lies on the tree boundary, return the face number @@ -704,11 +708,11 @@ struct t8_subelement_scheme_common: * Any arbitrary integer if \a is not at a tree boundary. * \warning The return value may look like a valid face of the tree even if the element does not lie on the root boundary. */ - static int - element_get_tree_face (const t8_element_t *elem, const int face) noexcept + int + element_get_tree_face (const t8_element_t *elem, const int face) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem), "element_get_tree_face is not implemented for subelements yet.\n"); - return TUnderlyingScheme::element_get_tree_face (element_to_standalone (elem), face); + return derived ().underlying_scheme.element_get_tree_face (element_to_standalone (elem), face); } /** Construct the face neighbor of a given element if this face neighbor is inside the root tree. Return 0 otherwise. @@ -722,14 +726,14 @@ struct t8_subelement_scheme_common: * \return True if \a neigh is inside the root tree. * False if not. In this case \a neigh's data can be arbitrary on output. */ - static int + int element_get_face_neighbor_inside (const t8_element_t *elem, t8_element_t *neigh, const int face, - int *neigh_face) noexcept + int *neigh_face) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem), "element_get_face_neighbor_inside is not implemented for subelements yet.\n"); - return TUnderlyingScheme::element_get_face_neighbor_inside (element_to_standalone (elem), - element_to_standalone (neigh), face, neigh_face); + return derived ().underlying_scheme.element_get_face_neighbor_inside ( + element_to_standalone (elem), element_to_standalone (neigh), face, neigh_face); } // ######################################____TREE FACE TRANSFORMATION____############################################# @@ -755,11 +759,13 @@ struct t8_subelement_scheme_common: * \param [in] root_face * \param [in] scheme */ - static int + int element_extrude_face ([[maybe_unused]] const t8_element_t *face, [[maybe_unused]] t8_element_t *elem, - [[maybe_unused]] const int root_face, [[maybe_unused]] const t8_scheme *scheme) noexcept + [[maybe_unused]] const int root_face, [[maybe_unused]] const t8_scheme *scheme) const noexcept { - SC_ABORT ("This function is not implemented yet."); + SC_CHECK_ABORT (!element_is_subelement (elem), "element_extrude_face is not implemented for subelements yet.\n"); + return derived ().underlying_scheme.element_extrude_face (element_to_standalone (face), + element_to_standalone (elem), root_face, scheme); } /** \note This is not implemented for this scheme. @@ -768,11 +774,15 @@ struct t8_subelement_scheme_common: * \param [in,out] boundary * \param [in] scheme */ - static void + void element_get_boundary_face ([[maybe_unused]] const t8_element_t *elem, [[maybe_unused]] const int face, - [[maybe_unused]] t8_element_t *boundary, [[maybe_unused]] const t8_scheme *scheme) noexcept + [[maybe_unused]] t8_element_t *boundary, + [[maybe_unused]] const t8_scheme *scheme) const noexcept { - SC_ABORT ("This function is not implemented yet.\n"); + SC_CHECK_ABORT (!element_is_subelement (elem), + "element_get_boundary_face is not implemented for subelements yet.\n"); + return derived ().underlying_scheme.element_get_boundary_face (element_to_standalone (elem), face, + element_to_standalone (boundary), scheme); } // ################################################____LINEAR ID____################################################ @@ -783,11 +793,11 @@ struct t8_subelement_scheme_common: * \param [in] level The level of the uniform refinement to consider. * \param [in] id The linear id. id must fulfil 0 <= id < 'number of leaves in the uniform refinement' */ - static void - element_set_linear_id (t8_element_t *elem, const t8_element_level level, t8_linearidx_t id) noexcept + void + element_set_linear_id (t8_element_t *elem, const t8_element_level level, t8_linearidx_t id) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem), "element_set_linear_id is not implemented for subelements yet.\n"); - TUnderlyingScheme::element_set_linear_id (element_to_standalone (elem), level, id); + derived ().underlying_scheme.element_set_linear_id (element_to_standalone (elem), level, id); } /** Compute the linear id of a given element in a hypothetical uniform refinement of a given level. @@ -798,11 +808,11 @@ struct t8_subelement_scheme_common: * \param [in] level The level of the uniform refinement to consider. * \return The linear id of the element. */ - static t8_linearidx_t - element_get_linear_id (const t8_element_t *elem, const t8_element_level level) noexcept + t8_linearidx_t + element_get_linear_id (const t8_element_t *elem, const t8_element_level level) const noexcept { T8_ASSERT (element_is_valid (elem)); - return TUnderlyingScheme::element_get_linear_id (element_to_standalone (elem), level); + return derived ().underlying_scheme.element_get_linear_id (element_to_standalone (elem), level); } /** Construct the successor in a uniform refinement of a given element. @@ -810,13 +820,13 @@ struct t8_subelement_scheme_common: * \param [in] elem1 The element whose successor should be constructed. * \param [in,out] elem2 The element whose entries will be set. */ - static void - element_construct_successor (const t8_element_t *elem1, t8_element_t *elem2) noexcept + void + element_construct_successor (const t8_element_t *elem1, t8_element_t *elem2) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem1), "element_construct_successor is not implemented for subelements yet.\n"); - return TUnderlyingScheme::element_construct_successor (element_to_standalone (elem1), - element_to_standalone (elem2)); + return derived ().underlying_scheme.element_construct_successor (element_to_standalone (elem1), + element_to_standalone (elem2)); } /** Count how many leaf descendants of a given uniform level an element would produce. @@ -824,11 +834,11 @@ struct t8_subelement_scheme_common: * \param [in] elem The element to be checked. * \param [in] level A refinement level. */ - static t8_gloidx_t - element_count_leaves (const t8_element_t *elem, const t8_element_level level) noexcept + t8_gloidx_t + element_count_leaves (const t8_element_t *elem, const t8_element_level level) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem), "element_count_leaves is not implemented for subelements yet.\n"); - return TUnderlyingScheme::element_count_leaves (element_to_standalone (elem), level); + return derived ().underlying_scheme.element_count_leaves (element_to_standalone (elem), level); } /** Count how many leaf descendants of a given uniform level the root element will produce. @@ -836,10 +846,10 @@ struct t8_subelement_scheme_common: * \return The value of \ref t8_element_count_leaves if the input element * is the root (level 0) element. */ - static t8_gloidx_t - count_leaves_from_root (const t8_element_level level) noexcept + t8_gloidx_t + count_leaves_from_root (const t8_element_level level) const noexcept { - return TUnderlyingScheme::count_leaves_from_root (level); + return derived ().underlying_scheme.count_leaves_from_root (level); } /** Compare two elements. @@ -847,12 +857,12 @@ struct t8_subelement_scheme_common: * \param [in] elem1 The first element. * \param [in] elem2 The second element. */ - static int - element_compare (const t8_element_t *elem1, const t8_element_t *elem2) noexcept + int + element_compare (const t8_element_t *elem1, const t8_element_t *elem2) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem1) && !element_is_subelement (elem2), "element_compare is not implemented for subelements yet.\n"); - return TUnderlyingScheme::element_compare (element_to_standalone (elem1), element_to_standalone (elem2)); + return derived ().underlying_scheme.element_compare (element_to_standalone (elem1), element_to_standalone (elem2)); } // ################################################____VISUALIZATION____############################################## @@ -865,12 +875,12 @@ struct t8_subelement_scheme_common: * \param [out] coords An array of at least as many doubles as the element's dimension * whose entries will be filled with the coordinates of \a vertex. */ - static void - element_get_vertex_reference_coords (const t8_element_t *elem, const int vertex, double coords[]) noexcept + void + element_get_vertex_reference_coords (const t8_element_t *elem, const int vertex, double coords[]) const noexcept { SC_CHECK_ABORT (!element_is_subelement (elem), "element_get_vertex_reference_coords is not implemented for subelements yet.\n"); - TUnderlyingScheme::element_get_vertex_reference_coords (element_to_standalone (elem), vertex, coords); + derived ().underlying_scheme.element_get_vertex_reference_coords (element_to_standalone (elem), vertex, coords); } /** Convert a point in the reference space of an element to a point in the reference space of the tree. @@ -880,16 +890,16 @@ struct t8_subelement_scheme_common: * \param [in] num_coords The number of coordinates to evaluate. * \param [out] out_coords The coordinates of the point in the reference space of the tree. */ - static void + void element_get_reference_coords (const t8_element_t *elem, const double *ref_coords, const size_t num_coords, - double *out_coords) noexcept + double *out_coords) const noexcept { if (element_is_subelement (elem)) { - TSubelementSchemeSpecialization::subelement_get_reference_coords (elem, ref_coords, num_coords, out_coords); + derived ().subelement_get_reference_coords (elem, ref_coords, num_coords, out_coords); } else { - TUnderlyingScheme::element_get_reference_coords (element_to_standalone (elem), ref_coords, num_coords, - out_coords); + derived ().underlying_scheme.element_get_reference_coords (element_to_standalone (elem), ref_coords, num_coords, + out_coords); } } @@ -935,14 +945,14 @@ struct t8_subelement_scheme_common: * \see element_new * \see element_is_valid */ - static void - element_init ([[maybe_unused]] const int length, [[maybe_unused]] t8_element_t *elems) noexcept + void + element_init ([[maybe_unused]] const int length, [[maybe_unused]] t8_element_t *elems) const noexcept { #if T8_ENABLE_DEBUG TSubelementType *subelement = (TSubelementType *) elems; for (int ielem = 0; ielem < length; ielem++) { reset_subelement_values (subelement + ielem); - TUnderlyingScheme::element_init (1, subelement_to_standalone (subelement + ielem)); + derived ().underlying_scheme.element_init (1, subelement_to_standalone (subelement + ielem)); T8_ASSERT (element_is_valid ((t8_element_t *) (subelement + ielem))); } #endif @@ -989,12 +999,12 @@ struct t8_subelement_scheme_common: * \note We recommend to use the assertion T8_ASSERT (element_is_valid (elem)) * in the implementation of each of the functions in this file. */ - static int - element_is_valid (const t8_element_t *elem) noexcept + int + element_is_valid (const t8_element_t *elem) const noexcept { T8_ASSERT (elem != NULL); const auto *subelement = as_subelement (elem); - int element_valid = TUnderlyingScheme::element_is_valid (subelement_to_standalone (subelement)); + int element_valid = derived ().underlying_scheme.element_is_valid (subelement_to_standalone (subelement)); if (!element_is_subelement (elem)) { return element_valid; } @@ -1013,13 +1023,13 @@ struct t8_subelement_scheme_common: * This function is only available in the debugging configuration. * \param [in] elem The element to print */ - static void - element_debug_print (const t8_element_t *elem) noexcept + void + element_debug_print (const t8_element_t *elem) const noexcept { const auto *subelement = as_subelement (elem); t8_debugf ("Subelement type: %i\n", subelement->subelement_type); t8_debugf ("Subelement id: %i\n", subelement->subelement_id); - TUnderlyingScheme::element_debug_print (subelement_to_standalone (subelement)); + derived ().underlying_scheme.element_debug_print (subelement_to_standalone (subelement)); } #endif @@ -1050,10 +1060,9 @@ struct t8_subelement_scheme_common: { TSubelementType **els = (TSubelementType **) elements; - TUnderlyingScheme tmp {}; for (unsigned int ielem = 0; ielem < count; ielem++) { t8_element_t *element = subelement_to_standalone (els[ielem]); - tmp.element_MPI_Pack (&element, 1, send_buffer, buffer_size, position, comm); + derived ().underlying_scheme.element_MPI_Pack (&element, 1, send_buffer, buffer_size, position, comm); int mpiret = sc_MPI_Pack (&els[ielem]->subelement_type, 1, sc_MPI_INT, send_buffer, buffer_size, position, comm); SC_CHECK_MPI (mpiret); mpiret = sc_MPI_Pack (&els[ielem]->subelement_id, 1, sc_MPI_INT, send_buffer, buffer_size, position, comm); @@ -1070,8 +1079,7 @@ struct t8_subelement_scheme_common: element_MPI_Pack_size (const unsigned int count, sc_MPI_Comm comm, int *pack_size) const noexcept { // Get single size from standalone scheme. - TUnderlyingScheme tmp {}; - tmp.element_MPI_Pack_size (1, comm, pack_size); + derived ().underlying_scheme.element_MPI_Pack_size (1, comm, pack_size); int singlesize = *pack_size; /* Type and id are both of type int. */ @@ -1096,10 +1104,9 @@ struct t8_subelement_scheme_common: const unsigned int count, sc_MPI_Comm comm) const noexcept { TSubelementType **els = (TSubelementType **) elements; - TUnderlyingScheme tmp {}; for (unsigned int ielem = 0; ielem < count; ielem++) { t8_element_t *single = subelement_to_standalone (els[ielem]); - tmp.element_MPI_Unpack (recvbuf, buffer_size, position, &single, 1, comm); + derived ().underlying_scheme.element_MPI_Unpack (recvbuf, buffer_size, position, &single, 1, comm); int mpiret = sc_MPI_Unpack (recvbuf, buffer_size, position, &els[ielem]->subelement_type, 1, sc_MPI_INT, comm); SC_CHECK_MPI (mpiret); mpiret = sc_MPI_Unpack (recvbuf, buffer_size, position, &els[ielem]->subelement_id, 1, sc_MPI_INT, comm); @@ -1133,10 +1140,10 @@ struct t8_subelement_scheme_common: * \param [in, out] c An array of allocated elements that will be filled with the subelements of \a elem. * The number of subelements is determined by \ref element_get_number_of_subelements. */ - static void - refine_element_in_subelements (const t8_element_t *elem, int type, t8_element_t *c[]) + void + refine_element_in_subelements (const t8_element_t *elem, int type, t8_element_t *c[]) const noexcept { - TSubelementSchemeSpecialization::refine_element_in_subelements (elem, type, c); + derived ().refine_element_in_subelements (elem, type, c); } protected: @@ -1187,10 +1194,28 @@ struct t8_subelement_scheme_common: subelement->subelement_id = 0; } - static t8_element_coord - parent_element_get_len (const TSubelementType *subelement) noexcept + t8_element_coord + parent_element_get_len (const TSubelementType *subelement) const noexcept { return 1 << (T8_ELEMENT_MAXLEVEL[TEclass] - - (TUnderlyingScheme::element_get_level (subelement_to_standalone (subelement)))); + - (derived ().underlying_scheme.element_get_level (subelement_to_standalone (subelement)))); + } + + TSubelementSchemeSpecialization & + derived () noexcept + { + return static_cast (*this); + } + const TSubelementSchemeSpecialization & + derived () const noexcept + { + return static_cast (*this); } }; + +// At the very bottom of t8_subelement_scheme.hxx, AFTER the class definition: +// These must come after the base class definition to break the circular dependency. +// The specializations need the base class complete; the base needs the specializations +// complete only when its methods are instantiated (not when the class is defined). +#include "specializations/t8_scheme_quads.hxx" +#include "specializations/t8_scheme_tri.hxx" From 7c4a26a28e8c30eb2cd78c801b66c7507715c5c0 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Fri, 12 Jun 2026 15:18:34 +0200 Subject: [PATCH 17/28] add non periodic example --- .../subelements/t8_quads_hanging_nodes.cxx | 2 +- src/t8_cmesh/t8_cmesh_examples.cxx | 83 +++++++++++++++++++ src/t8_cmesh/t8_cmesh_examples.h | 7 ++ 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/example/subelements/t8_quads_hanging_nodes.cxx b/example/subelements/t8_quads_hanging_nodes.cxx index 282a889b20..33a230be60 100644 --- a/example/subelements/t8_quads_hanging_nodes.cxx +++ b/example/subelements/t8_quads_hanging_nodes.cxx @@ -85,7 +85,7 @@ main (int argc, char **argv) /* ---Setup. Build cmesh and uniform forest.--- */ /* Build a cube cmesh with tet, hex, and prism trees. */ // t8_cmesh_t cmesh = t8_cmesh_new_hypercube (T8_ECLASS_TRIANGLE, comm, 0, 0, 0); - t8_cmesh_t cmesh = t8_cmesh_new_periodic_hybrid (comm); + t8_cmesh_t cmesh = t8_cmesh_new_2D_hypercube_hybrid (comm); t8_forest_t forest = t8_forest_new_uniform (cmesh, t8_scheme_new_subelement (), level, 0, comm); // TODO: New scheme /* --- Adapt the forest. --- */ diff --git a/src/t8_cmesh/t8_cmesh_examples.cxx b/src/t8_cmesh/t8_cmesh_examples.cxx index 42e6be850b..4fe600d23b 100644 --- a/src/t8_cmesh/t8_cmesh_examples.cxx +++ b/src/t8_cmesh/t8_cmesh_examples.cxx @@ -650,6 +650,89 @@ t8_cmesh_new_hypercube_hybrid (sc_MPI_Comm comm, [[maybe_unused]] int do_partiti return cmesh; } +t8_cmesh_t +t8_cmesh_new_2D_hypercube_hybrid (sc_MPI_Comm comm) +{ + { + /* clang-format off */ + double vertices[60] = { /* Just all vertices of all trees. partly duplicated */ + 0, 0, 0, /* tree 0, triangle */ + 0.5, 0, 0, + 0.5, 0.5, 0, + 0, 0, 0, /* tree 1, triangle */ + 0.5, 0.5, 0, + 0, 0.5, 0, + 0.5, 0, 0, /* tree 2, quad */ + 1, 0, 0, 0.5, + 0.5, 0, 1, 0.5, + 0, 0, 0.5, 0, /* tree 3, quad */ + 0.5, 0.5, 0, + 0, 1, 0, + 0.5, 1, 0, + 0.5, 0.5, 0, /* tree 4, triangle */ + 1, 0.5, 0, + 1, 1, 0, + 0.5, 0.5, 0, /* tree 5, triangle */ + 1, 1, 0, + 0.5, 1, 0 + }; + /* clang-format on */ + + t8_cmesh_t cmesh; + + /* + * This is how the cmesh looks like. The numbers are the tree numbers: + * + * +---+---+ + * | |5 /| + * | 3 | / | + * | |/ 4| + * +---+---+ + * |1 /| | + * | / | 2 | + * |/0 | | + * +---+---+ + */ + + t8_cmesh_init (&cmesh); + /* Use linear geometry */ + t8_cmesh_register_geometry (cmesh); + + t8_cmesh_set_tree_class (cmesh, 0, T8_ECLASS_TRIANGLE); + t8_cmesh_set_tree_class (cmesh, 1, T8_ECLASS_TRIANGLE); + t8_cmesh_set_tree_class (cmesh, 2, T8_ECLASS_QUAD); + t8_cmesh_set_tree_class (cmesh, 3, T8_ECLASS_QUAD); + t8_cmesh_set_tree_class (cmesh, 4, T8_ECLASS_TRIANGLE); + t8_cmesh_set_tree_class (cmesh, 5, T8_ECLASS_TRIANGLE); + + t8_cmesh_set_tree_vertices (cmesh, 0, vertices, 3); + t8_cmesh_set_tree_vertices (cmesh, 1, vertices + 9, 3); + t8_cmesh_set_tree_vertices (cmesh, 2, vertices + 18, 4); + t8_cmesh_set_tree_vertices (cmesh, 3, vertices + 30, 4); + t8_cmesh_set_tree_vertices (cmesh, 4, vertices + 42, 3); + t8_cmesh_set_tree_vertices (cmesh, 5, vertices + 51, 3); + + t8_cmesh_set_join (cmesh, 0, 1, 1, 2, 0); + t8_cmesh_set_join (cmesh, 0, 2, 0, 0, 0); + //t8_cmesh_set_join (cmesh, 0, 3, 2, 3, 0); + + t8_cmesh_set_join (cmesh, 1, 3, 0, 2, 1); + //t8_cmesh_set_join (cmesh, 1, 2, 1, 1, 0); + + t8_cmesh_set_join (cmesh, 2, 4, 3, 2, 0); + //t8_cmesh_set_join (cmesh, 2, 5, 2, 0, 1); + + t8_cmesh_set_join (cmesh, 3, 5, 1, 1, 0); + //t8_cmesh_set_join (cmesh, 3, 4, 0, 0, 0); + + t8_cmesh_set_join (cmesh, 4, 5, 1, 2, 0); + + t8_cmesh_commit (cmesh, comm); + + return cmesh; + } +} + /* The unit cube is constructed from trees of the same eclass. * For triangles the square is divided along the (0,0) -- (1,1) diagonal. * For prisms the front (y=0) and back (y=1) face are divided into triangles diff --git a/src/t8_cmesh/t8_cmesh_examples.h b/src/t8_cmesh/t8_cmesh_examples.h index 66bb1303b8..2026b59992 100644 --- a/src/t8_cmesh/t8_cmesh_examples.h +++ b/src/t8_cmesh/t8_cmesh_examples.h @@ -185,6 +185,13 @@ t8_cmesh_new_hypercube_pad_ext (const t8_eclass_t eclass, sc_MPI_Comm comm, cons t8_cmesh_t t8_cmesh_new_hypercube_hybrid (sc_MPI_Comm comm, int do_partition, int periodic); +/** Construct a unit square of two quads and four triangles. + * \param [in] comm The mpi communicator to use. + * \return A valid cmesh, as if _init and _commit had been called. + */ +t8_cmesh_t +t8_cmesh_new_2D_hypercube_hybrid (sc_MPI_Comm comm); + /** Construct a unit interval/square/cube coarse mesh that is periodic in each direction. * Element class? * Hypercube? From 33dbcbfbc605aebff41554ee411f6d8430e61e39 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Mon, 15 Jun 2026 16:54:25 +0200 Subject: [PATCH 18/28] add shock example --- example/CMakeLists.txt | 3 +- .../subelements/t8_hanging_nodes_shock.cxx | 228 ++++++++++++++++++ .../subelements/t8_quads_hanging_nodes.cxx | 2 +- src/t8_forest/t8_forest_adapt.cxx | 7 +- .../t8_subelement/t8_subelement_scheme.hxx | 9 +- 5 files changed, 242 insertions(+), 7 deletions(-) create mode 100644 example/subelements/t8_hanging_nodes_shock.cxx diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 0dfb8f9462..0fa8d1a70f 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -94,7 +94,8 @@ add_t8_example( NAME t8_example_spheres SOURCES remove/t8_exampl add_t8_example( NAME t8_example_gauss_blob SOURCES remove/t8_example_gauss_blob.cxx ) add_t8_example( NAME t8_example_empty_trees SOURCES remove/t8_example_empty_trees.cxx ) -add_t8_example( NAME t8_example_hanging_nodes SOURCES subelements/t8_quads_hanging_nodes.cxx ) +add_t8_example( NAME t8_hanging_nodes_shock SOURCES subelements/t8_hanging_nodes_shock.cxx ) +add_t8_example( NAME t8_example_hanging_nodes SOURCES subelements/t8_quads_hanging_nodes.cxx ) add_t8_example( NAME t8_version SOURCES version/t8_version.cxx ) diff --git a/example/subelements/t8_hanging_nodes_shock.cxx b/example/subelements/t8_hanging_nodes_shock.cxx new file mode 100644 index 0000000000..ef45318882 --- /dev/null +++ b/example/subelements/t8_hanging_nodes_shock.cxx @@ -0,0 +1,228 @@ +/* + 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_hanging_nodes_shock.cxx + * This is an example to demonstrate hanging node resolution. + */ + +#include "t8_eclass/t8_eclass.h" +#include /* General t8code header, always include this. */ +#include /* cmesh definition and basic interface. */ +#include /* A collection of exemplary cmeshes */ +#include /* forest definition and basic interface. */ +#include /* save forest */ +#include /* geometrical information of the forest */ +#include /* Function for adding subelements. */ +#include /* Subelement refinement scheme. */ +#include /* Basic operations on 3D vectors. */ + +/* This is our own defined data that we will pass on to the + * adaptation callback. */ +struct t8_adapt_data +{ + double midpoint[3]; /* 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. */ + int minlevel; + int maxlevel; +}; + +/** The adaptation callback function. This function will be called once for each element + * and the return value decides whether this element should be refined or not. + * return > 0 -> This element should get refined. + * return = 0 -> This element should not get refined. + * If the current element is the first element of a family (= all level l elements that arise from refining + * the same level l-1 element) then this function is called with the whole family of elements + * as input and the return value additionally decides whether the whole family should get coarsened. + * return > 0 -> The first element should get refined. + * return = 0 -> The first element should not get refined. + * return < 0 -> The whole family should get coarsened. + * + * \param [in] forest The current forest that is in construction. + * \param [in] forest_from The forest from which we adapt the current forest (in our case, the uniform forest) + * \param [in] which_tree The process local id of the current tree. + * \param [in] tree_class The eclass of \a which_tree. + * \param [in] lelement_id The tree local index of the current element (or the first of the family). + * \param [in] scheme The refinement scheme for this tree's element class. + * \param [in] is_family If 1, the first \a num_elements entries in \a elements form a family. If 0, they do not. + * \param [in] num_elements The number of entries in \a elements elements that are defined. + * \param [in] elements The element or family of elements to consider for refinement/coarsening. + */ +int +t8_adapt_callback (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t which_tree, t8_eclass_t tree_class, + [[maybe_unused]] t8_locidx_t lelement_id, const t8_scheme *scheme, const int is_family, + [[maybe_unused]] const int num_elements, t8_element_t *elements[]) +{ + /* Our 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. */ + double centroid[3]; /* Will hold the element midpoint. */ + const auto *adapt_data = (const struct t8_adapt_data *) t8_forest_get_user_data (forest); + double dist; /* Will store the distance of the element's midpoint and the sphere midpoint. */ + + T8_ASSERT (adapt_data != NULL); + + /* Compute the element's centroid coordinates. */ + t8_forest_element_centroid (forest_from, which_tree, elements[0], centroid); + + /* Compute the distance to our sphere midpoint. */ + dist = t8_dist (centroid, adapt_data->midpoint); + const int level = scheme->element_get_level (tree_class, elements[0]); + if ((dist < adapt_data->refine_if_inside_radius) && (level < adapt_data->maxlevel)) { + /* Refine this element. */ + return 1; + } + else if ((is_family && dist > adapt_data->coarsen_if_outside_radius) && (level > adapt_data->minlevel)) { + /* Coarsen this family. Note that we check for is_family before, since returning < 0 + * if we do not have a family as input is illegal. */ + return -1; + } + /* Do not change this element. */ + return 0; +} + +/** Adapt forest according to callback. */ +t8_forest_t +t8_adapt_forest (t8_forest_t forest) +{ + struct t8_adapt_data adapt_data = { + { 0, 1, 0 }, /* Midpoints of the sphere. */ + 0.25, /* Refine if inside this radius. */ + 0.3, /* Coarsen if outside this radius. */ + 3, /* minlevel*/ + 7 /*maxlevel*/ + }; + + t8_forest_t forest_adapt; + forest_adapt = t8_forest_new_adapt (forest, t8_adapt_callback, 1, 0, &adapt_data); + return forest_adapt; +} + +/** Adapt forest according to callback. */ +t8_forest_t +t8_adapt_forest_2and (t8_forest_t forest) +{ + struct t8_adapt_data adapt_data = { + { 0, 1, 0 }, /* Midpoints of the sphere. */ + 0.45, /* Refine if inside this radius. */ + 0.5, /* Coarsen if outside this radius. */ + 3, /* minlevel*/ + 7 /*maxlevel*/ + }; + + t8_forest_t forest_adapt; + forest_adapt = t8_forest_new_adapt (forest, t8_adapt_callback, 1, 0, &adapt_data); + return forest_adapt; +} + +t8_forest_t +t8_forest_balance (t8_forest_t forest) +{ + + t8_forest_t forest_new; + t8_forest_init (&forest_new); + t8_forest_set_balance (forest_new, forest, 0); + t8_forest_commit (forest_new); + return forest_new; +} + +/** Entry point of the program. */ +int +main (int argc, char **argv) +{ + /* The uniform refinement level of the forest. */ + const int level = 3; + + int mpiret = sc_MPI_Init (&argc, &argv); + SC_CHECK_MPI (mpiret); + sc_init (sc_MPI_COMM_WORLD, 1, 1, NULL, SC_LP_ESSENTIAL); + t8_init (SC_LP_PRODUCTION); + + /* We will use MPI_COMM_WORLD as a communicator. */ + sc_MPI_Comm comm = sc_MPI_COMM_WORLD; + + /* ---Setup. Build cmesh and uniform forest.--- */ + /* Build a cube cmesh with tet, hex, and prism trees. */ + // t8_cmesh_t cmesh = t8_cmesh_new_hypercube (T8_ECLASS_TRIANGLE, comm, 0, 0, 0); + t8_cmesh_t cmesh = t8_cmesh_new_2D_hypercube_hybrid (comm); + t8_forest_t forest = t8_forest_new_uniform (cmesh, t8_scheme_new_subelement (), level, 0, comm); + const char *prefix = "t8_uniform"; + t8_forest_write_vtk (forest, prefix); + t8_global_productionf (" [subelements] Uniform forest wrote to file: %s*\n", prefix); + + /* --- Adapt the forest. --- */ + forest = t8_adapt_forest (forest); + std::cout << "Subelements before removing: " << t8_forest_has_subelements (forest) << std::endl; + prefix = "t8_adapted1_"; + t8_forest_write_vtk (forest, prefix); + t8_global_productionf (" [subelements] Wrote adapted forest with hanging nodes to vtu files: %s*\n", prefix); + + // --- Balance the forest. --- + forest = t8_forest_balance (forest); + prefix = "t8_balanced1_"; + t8_forest_write_vtk (forest, prefix); + t8_global_productionf (" [subelements] Balanced and wrote to file: %s*\n", prefix); + + // --- Add subelements to remove hanging nodes. --- + forest = t8_forest_remove_hanging_nodes (forest); + std::cout << "Subelements after removing: " << t8_forest_has_subelements (forest) << std::endl; + // Now output to vtk. + const char *prefix_without_hanging_nodes = "t8_resolved_hanging_nodes1_"; + t8_forest_write_vtk (forest, prefix_without_hanging_nodes); + t8_global_productionf (" [subelements] Wrote adapted forest with resolved hanging nodes to vtu files: %s*\n", + prefix_without_hanging_nodes); + + // --- Discard Subelements. --- + forest = t8_forest_discard_subelements (forest); + std::cout << "Subelements removed: " << t8_forest_has_subelements (forest) << std::endl; + // Output to vtk. + const char *prefix_removed_sub = "t8_discarded_subelements1_"; + t8_forest_write_vtk (forest, prefix_removed_sub); + t8_global_productionf (" [subelements] Wrote adapted forest with discarded subelements to vtu files: %s*\n", + prefix_removed_sub); + + /* --- Adapt the forest again. --- */ + forest = t8_adapt_forest_2and (forest); + prefix = "t8_adapted2_"; + t8_forest_write_vtk (forest, prefix); + t8_global_productionf (" [subelements] Adapted again and wrote to file: %s*\n", prefix); + + // --- Balance again. --- + forest = t8_forest_balance (forest); + prefix = "t8_balanced2_"; + t8_forest_write_vtk (forest, prefix); + t8_global_productionf (" [subelements] Balanced again and wrote to file: %s*\n", prefix); + + // --- Add subelements to remove hanging nodes. --- + forest = t8_forest_remove_hanging_nodes (forest); + prefix = "t8_resolved_hanging_nodes2_"; + t8_forest_write_vtk (forest, prefix); + t8_global_productionf (" [subelements] Removed hanging nodes after second adaptation and wrote to : %s*\n", prefix); + + // --- Cleanup. --- + t8_forest_unref (&forest); + + sc_finalize (); + mpiret = sc_MPI_Finalize (); + SC_CHECK_MPI (mpiret); + + return 0; +} diff --git a/example/subelements/t8_quads_hanging_nodes.cxx b/example/subelements/t8_quads_hanging_nodes.cxx index 33a230be60..92dd9abd2a 100644 --- a/example/subelements/t8_quads_hanging_nodes.cxx +++ b/example/subelements/t8_quads_hanging_nodes.cxx @@ -86,7 +86,7 @@ main (int argc, char **argv) /* Build a cube cmesh with tet, hex, and prism trees. */ // t8_cmesh_t cmesh = t8_cmesh_new_hypercube (T8_ECLASS_TRIANGLE, comm, 0, 0, 0); t8_cmesh_t cmesh = t8_cmesh_new_2D_hypercube_hybrid (comm); - t8_forest_t forest = t8_forest_new_uniform (cmesh, t8_scheme_new_subelement (), level, 0, comm); // TODO: New scheme + t8_forest_t forest = t8_forest_new_uniform (cmesh, t8_scheme_new_subelement (), level, 0, comm); /* --- Adapt the forest. --- */ forest = t8_adapt_forest (forest); diff --git a/src/t8_forest/t8_forest_adapt.cxx b/src/t8_forest/t8_forest_adapt.cxx index 1fdb06f7c1..b7aa49bd29 100644 --- a/src/t8_forest/t8_forest_adapt.cxx +++ b/src/t8_forest/t8_forest_adapt.cxx @@ -31,6 +31,7 @@ #include #include #include +#include "t8_forest_subelement.hxx" /* We want to export the whole implementation to be callable from "C" */ T8_EXTERN_C_BEGIN (); @@ -433,8 +434,10 @@ t8_forest_adapt (t8_forest_t forest) T8_ASSERT (forest->trees->elem_count == forest_from->trees->elem_count); if (forest->set_adapt_recursive) { - SC_CHECK_ABORT (!t8_scheme_has_subelement_scheme (t8_forest_get_scheme (forest_from)), - "Recursive adaptation is currently not implemented for subelement schemes."); + if (t8_scheme_has_subelement_scheme (t8_forest_get_scheme (forest_from))) { + SC_CHECK_ABORT (!t8_forest_has_subelements (forest_from), + "Recursive adaptation is currently not implemented for subelement schemes."); + } refine_list = sc_list_new (nullptr); } forest->local_num_leaf_elements = 0; diff --git a/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx b/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx index 7b45accfcb..97e9e2ee18 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx +++ b/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx @@ -525,11 +525,14 @@ struct t8_subelement_scheme_common: * \param [in,out] nca The storage for this element must exist and match the element class of the child. * On output the unique nearest common ancestor of \b elem1 and \b elem2. */ - static void + void element_get_nca ([[maybe_unused]] const t8_element_t *elem1, [[maybe_unused]] const t8_element_t *elem2, - [[maybe_unused]] t8_element_t *nca) noexcept + [[maybe_unused]] t8_element_t *nca) const noexcept { - SC_ABORT ("element_get_nca not implemented yet.\n"); + SC_CHECK_ABORT ((!element_is_subelement (elem1)) && (!element_is_subelement (elem2)), + "element_get_nca is not implemented for subelements yet.\n"); + derived ().underlying_scheme.element_get_nca (element_to_standalone (elem1), element_to_standalone (elem2), + element_to_standalone (nca)); } /** Compute the first descendant of a given element. From b5dcf75acd720691ee52049914ce44cb5006c2f9 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Thu, 18 Jun 2026 17:08:35 +0200 Subject: [PATCH 19/28] example now with radius --- .../subelements/t8_hanging_nodes_shock.cxx | 49 ++++++++++--------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/example/subelements/t8_hanging_nodes_shock.cxx b/example/subelements/t8_hanging_nodes_shock.cxx index ef45318882..2937f65011 100644 --- a/example/subelements/t8_hanging_nodes_shock.cxx +++ b/example/subelements/t8_hanging_nodes_shock.cxx @@ -39,9 +39,9 @@ * adaptation callback. */ struct t8_adapt_data { - double midpoint[3]; /* 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. */ + double midpoint[3]; /* The midpoint of our sphere. */ + double radius; /* Refined close to radius with midpoint midpoint. */ + double delta; /* How close to refine. */ int minlevel; int maxlevel; }; @@ -76,7 +76,6 @@ t8_adapt_callback (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t whic * they are inside a sphere around a given midpoint we refine, if they are outside, we coarsen. */ double centroid[3]; /* Will hold the element midpoint. */ const auto *adapt_data = (const struct t8_adapt_data *) t8_forest_get_user_data (forest); - double dist; /* Will store the distance of the element's midpoint and the sphere midpoint. */ T8_ASSERT (adapt_data != NULL); @@ -84,13 +83,19 @@ t8_adapt_callback (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t whic t8_forest_element_centroid (forest_from, which_tree, elements[0], centroid); /* Compute the distance to our sphere midpoint. */ - dist = t8_dist (centroid, adapt_data->midpoint); + double radius = t8_dist (centroid, adapt_data->midpoint); + double abs_to_radius = fabs (radius - adapt_data->radius); const int level = scheme->element_get_level (tree_class, elements[0]); - if ((dist < adapt_data->refine_if_inside_radius) && (level < adapt_data->maxlevel)) { + + double alpha = std::min (abs_to_radius / adapt_data->delta, 1.0); + int target_level + = adapt_data->maxlevel - static_cast (std::round (alpha * (adapt_data->maxlevel - adapt_data->minlevel))); + + if ((level < target_level) && (level < adapt_data->maxlevel)) { /* Refine this element. */ return 1; } - else if ((is_family && dist > adapt_data->coarsen_if_outside_radius) && (level > adapt_data->minlevel)) { + else if ((is_family && level > target_level) && (level > adapt_data->minlevel)) { /* Coarsen this family. Note that we check for is_family before, since returning < 0 * if we do not have a family as input is illegal. */ return -1; @@ -105,10 +110,10 @@ t8_adapt_forest (t8_forest_t forest) { struct t8_adapt_data adapt_data = { { 0, 1, 0 }, /* Midpoints of the sphere. */ - 0.25, /* Refine if inside this radius. */ - 0.3, /* Coarsen if outside this radius. */ - 3, /* minlevel*/ - 7 /*maxlevel*/ + 0.45, /* Refine if inside this radius. */ + 0.1, /* Coarsen if outside this radius. */ + 2, /* minlevel*/ + 6 /*maxlevel*/ }; t8_forest_t forest_adapt; @@ -122,10 +127,10 @@ t8_adapt_forest_2and (t8_forest_t forest) { struct t8_adapt_data adapt_data = { { 0, 1, 0 }, /* Midpoints of the sphere. */ - 0.45, /* Refine if inside this radius. */ - 0.5, /* Coarsen if outside this radius. */ - 3, /* minlevel*/ - 7 /*maxlevel*/ + 0.6, /* Refine if inside this radius. */ + 0.1, /* Coarsen if outside this radius. */ + 2, /* minlevel*/ + 6 /*maxlevel*/ }; t8_forest_t forest_adapt; @@ -171,13 +176,13 @@ main (int argc, char **argv) /* --- Adapt the forest. --- */ forest = t8_adapt_forest (forest); std::cout << "Subelements before removing: " << t8_forest_has_subelements (forest) << std::endl; - prefix = "t8_adapted1_"; + prefix = "t8_adapted1"; t8_forest_write_vtk (forest, prefix); t8_global_productionf (" [subelements] Wrote adapted forest with hanging nodes to vtu files: %s*\n", prefix); // --- Balance the forest. --- forest = t8_forest_balance (forest); - prefix = "t8_balanced1_"; + prefix = "t8_balanced1"; t8_forest_write_vtk (forest, prefix); t8_global_productionf (" [subelements] Balanced and wrote to file: %s*\n", prefix); @@ -185,7 +190,7 @@ main (int argc, char **argv) forest = t8_forest_remove_hanging_nodes (forest); std::cout << "Subelements after removing: " << t8_forest_has_subelements (forest) << std::endl; // Now output to vtk. - const char *prefix_without_hanging_nodes = "t8_resolved_hanging_nodes1_"; + const char *prefix_without_hanging_nodes = "t8_resolved_hanging_nodes1"; t8_forest_write_vtk (forest, prefix_without_hanging_nodes); t8_global_productionf (" [subelements] Wrote adapted forest with resolved hanging nodes to vtu files: %s*\n", prefix_without_hanging_nodes); @@ -194,26 +199,26 @@ main (int argc, char **argv) forest = t8_forest_discard_subelements (forest); std::cout << "Subelements removed: " << t8_forest_has_subelements (forest) << std::endl; // Output to vtk. - const char *prefix_removed_sub = "t8_discarded_subelements1_"; + const char *prefix_removed_sub = "t8_discarded_subelements1"; t8_forest_write_vtk (forest, prefix_removed_sub); t8_global_productionf (" [subelements] Wrote adapted forest with discarded subelements to vtu files: %s*\n", prefix_removed_sub); /* --- Adapt the forest again. --- */ forest = t8_adapt_forest_2and (forest); - prefix = "t8_adapted2_"; + prefix = "t8_adapted2"; t8_forest_write_vtk (forest, prefix); t8_global_productionf (" [subelements] Adapted again and wrote to file: %s*\n", prefix); // --- Balance again. --- forest = t8_forest_balance (forest); - prefix = "t8_balanced2_"; + prefix = "t8_balanced2"; t8_forest_write_vtk (forest, prefix); t8_global_productionf (" [subelements] Balanced again and wrote to file: %s*\n", prefix); // --- Add subelements to remove hanging nodes. --- forest = t8_forest_remove_hanging_nodes (forest); - prefix = "t8_resolved_hanging_nodes2_"; + prefix = "t8_resolved_hanging_nodes2"; t8_forest_write_vtk (forest, prefix); t8_global_productionf (" [subelements] Removed hanging nodes after second adaptation and wrote to : %s*\n", prefix); From 69bb843f764f1d96dbbafc726f2bdc74d457c056 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Mon, 29 Jun 2026 16:21:05 +0200 Subject: [PATCH 20/28] inital level 0 --- example/subelements/t8_hanging_nodes_shock.cxx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/example/subelements/t8_hanging_nodes_shock.cxx b/example/subelements/t8_hanging_nodes_shock.cxx index 2937f65011..e0d24d237e 100644 --- a/example/subelements/t8_hanging_nodes_shock.cxx +++ b/example/subelements/t8_hanging_nodes_shock.cxx @@ -110,10 +110,10 @@ t8_adapt_forest (t8_forest_t forest) { struct t8_adapt_data adapt_data = { { 0, 1, 0 }, /* Midpoints of the sphere. */ - 0.45, /* Refine if inside this radius. */ - 0.1, /* Coarsen if outside this radius. */ - 2, /* minlevel*/ - 6 /*maxlevel*/ + 0.45, + 0.1, + 2, /* minlevel*/ + 6 /*maxlevel*/ }; t8_forest_t forest_adapt; @@ -127,10 +127,10 @@ t8_adapt_forest_2and (t8_forest_t forest) { struct t8_adapt_data adapt_data = { { 0, 1, 0 }, /* Midpoints of the sphere. */ - 0.6, /* Refine if inside this radius. */ - 0.1, /* Coarsen if outside this radius. */ - 2, /* minlevel*/ - 6 /*maxlevel*/ + 0.6, + 0.1, + 2, /* minlevel*/ + 6 /*maxlevel*/ }; t8_forest_t forest_adapt; @@ -154,7 +154,7 @@ int main (int argc, char **argv) { /* The uniform refinement level of the forest. */ - const int level = 3; + const int level = 0; int mpiret = sc_MPI_Init (&argc, &argv); SC_CHECK_MPI (mpiret); From b85c3cb88e5e5d00911fe60162a3300a09f9065f Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Mon, 10 Aug 2026 14:06:38 +0200 Subject: [PATCH 21/28] corrections --- example/CMakeLists.txt | 4 +- .../subelements/t8_hanging_nodes_shock.cxx | 153 ++++++++++-------- .../subelements/t8_quads_hanging_nodes.cxx | 23 +-- src/t8_cmesh/t8_cmesh_examples.cxx | 10 +- src/t8_forest/t8_forest_adapt.cxx | 10 +- src/t8_forest/t8_forest_subelement.cxx | 33 +++- src/t8_forest/t8_forest_subelement.hxx | 28 ++-- src/t8_schemes/t8_scheme.cxx | 20 +-- src/t8_schemes/t8_scheme.h | 30 ++-- 9 files changed, 162 insertions(+), 149 deletions(-) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index fa645f5406..e7b7355d26 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -95,8 +95,8 @@ add_t8_example( NAME t8_example_spheres SOURCES remove/t8_exampl add_t8_example( NAME t8_example_gauss_blob SOURCES remove/t8_example_gauss_blob.cxx ) add_t8_example( NAME t8_example_empty_trees SOURCES remove/t8_example_empty_trees.cxx ) -add_t8_example( NAME t8_hanging_nodes_shock SOURCES subelements/t8_hanging_nodes_shock.cxx ) -add_t8_example( NAME t8_example_hanging_nodes SOURCES subelements/t8_quads_hanging_nodes.cxx ) +add_t8_example( NAME t8_example_hanging_nodes_shock SOURCES subelements/t8_hanging_nodes_shock.cxx ) +add_t8_example( NAME t8_example_hanging_nodes SOURCES subelements/t8_quads_hanging_nodes.cxx ) add_t8_example( NAME t8_version SOURCES version/t8_version.cxx ) diff --git a/example/subelements/t8_hanging_nodes_shock.cxx b/example/subelements/t8_hanging_nodes_shock.cxx index e0d24d237e..46d5a18dfb 100644 --- a/example/subelements/t8_hanging_nodes_shock.cxx +++ b/example/subelements/t8_hanging_nodes_shock.cxx @@ -21,10 +21,24 @@ */ /** \file t8_hanging_nodes_shock.cxx - * This is an example to demonstrate hanging node resolution. + * Example demonstrating hanging-node resolution on a hybrid 2D mesh. + * + * The program builds a uniform forest on a hybrid (quad + triangle) 2D hypercube + * using the subelement scheme, then repeatedly grades the mesh around a circle and + * resolves the resulting hanging nodes. Each stage is written to VTK so the whole + * process can be inspected: + * 1. uniform forest, + * 2. adapt (refine near a circle) -> hanging nodes appear, + * 3. balance (enforce a 2:1 level difference between neighbors), + * 4. remove hanging nodes -> transition cells are split into subelements, + * 5. discard subelements -> back to a plain (recursively refined) forest, + * 6. a second adapt / balance / remove cycle to show the process is repeatable. + * + * Subelements are the mechanism that keeps the mesh conformal: where balancing + * leaves a coarse element adjacent to finer ones (a hanging node), that element is + * transitioned into a fan of smaller subelements so no hanging nodes remain. */ -#include "t8_eclass/t8_eclass.h" #include /* General t8code header, always include this. */ #include /* cmesh definition and basic interface. */ #include /* A collection of exemplary cmeshes */ @@ -34,86 +48,82 @@ #include /* Function for adding subelements. */ #include /* Subelement refinement scheme. */ #include /* Basic operations on 3D vectors. */ +#include /* Element class (eclass) definitions. */ -/* This is our own defined data that we will pass on to the - * adaptation callback. */ +/** User data passed to the adaptation callback \ref t8_adapt_callback. + * Defines the circle the mesh is refined around and the level bounds. */ struct t8_adapt_data { - double midpoint[3]; /* The midpoint of our sphere. */ - double radius; /* Refined close to radius with midpoint midpoint. */ - double delta; /* How close to refine. */ - int minlevel; - int maxlevel; + double midpoint[3]; /* Center of the circle the mesh is refined around. */ + double radius; /* Radius of that circle; the mesh is refined near its boundary. */ + double delta; /* Width of the transition band around the circle over which the level ranges. */ + int minlevel; /* Coarsest level, reached at distance >= delta from the circle. */ + int maxlevel; /* Finest level, reached on the circle itself. */ }; -/** The adaptation callback function. This function will be called once for each element - * and the return value decides whether this element should be refined or not. - * return > 0 -> This element should get refined. - * return = 0 -> This element should not get refined. - * If the current element is the first element of a family (= all level l elements that arise from refining - * the same level l-1 element) then this function is called with the whole family of elements - * as input and the return value additionally decides whether the whole family should get coarsened. - * return > 0 -> The first element should get refined. - * return = 0 -> The first element should not get refined. - * return < 0 -> The whole family should get coarsened. - * +/** The adaptation callback function. + * Adapts the mesh around a circle of radius \a radius centered at \a midpoint: + * Elements on the circle are refined to \a maxlevel, relaxing linearly to \a minlevel over a band of width \a delta. + * The closer an element is to the circle, the finer it gets: elements right on the circle are refined to maxlevel, + * elements delta or more away stay at minlevel, and in between the level scales linearly with the distance to the + * circle. * \param [in] forest The current forest that is in construction. - * \param [in] forest_from The forest from which we adapt the current forest (in our case, the uniform forest) + * \param [in] forest_from The forest from which we adapt (here, the uniform forest). * \param [in] which_tree The process local id of the current tree. * \param [in] tree_class The eclass of \a which_tree. - * \param [in] lelement_id The tree local index of the current element (or the first of the family). + * \param [in] lelement_id The tree local index of the current element (or first of the family). * \param [in] scheme The refinement scheme for this tree's element class. - * \param [in] is_family If 1, the first \a num_elements entries in \a elements form a family. If 0, they do not. - * \param [in] num_elements The number of entries in \a elements elements that are defined. - * \param [in] elements The element or family of elements to consider for refinement/coarsening. + * \param [in] is_family If 1, the first \a num_elements entries in \a elements form a family. + * \param [in] num_elements The number of entries in \a elements that are defined. + * \param [in] elements The element or family to consider for refinement/coarsening. + * \return 1 to refine, -1 to coarsen the family, 0 to leave unchanged. */ int t8_adapt_callback (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t which_tree, t8_eclass_t tree_class, [[maybe_unused]] t8_locidx_t lelement_id, const t8_scheme *scheme, const int is_family, [[maybe_unused]] const int num_elements, t8_element_t *elements[]) { - /* Our 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. */ - double centroid[3]; /* Will hold the element midpoint. */ const auto *adapt_data = (const struct t8_adapt_data *) t8_forest_get_user_data (forest); T8_ASSERT (adapt_data != NULL); /* Compute the element's centroid coordinates. */ + double centroid[3]; t8_forest_element_centroid (forest_from, which_tree, elements[0], centroid); - /* Compute the distance to our sphere midpoint. */ + /* Distance to the circle center, then to the circle boundary. */ double radius = t8_dist (centroid, adapt_data->midpoint); double abs_to_radius = fabs (radius - adapt_data->radius); const int level = scheme->element_get_level (tree_class, elements[0]); + /* Normalized shell distance in [0, 1]; target level is between maxlevel and minlevel. */ double alpha = std::min (abs_to_radius / adapt_data->delta, 1.0); int target_level = adapt_data->maxlevel - static_cast (std::round (alpha * (adapt_data->maxlevel - adapt_data->minlevel))); if ((level < target_level) && (level < adapt_data->maxlevel)) { - /* Refine this element. */ + /* Coarser than target: refine. */ return 1; } else if ((is_family && level > target_level) && (level > adapt_data->minlevel)) { - /* Coarsen this family. Note that we check for is_family before, since returning < 0 - * if we do not have a family as input is illegal. */ + /* Finer than target: coarsen the family. Check is_family first. */ return -1; } - /* Do not change this element. */ + /* At target level: leave unchanged. */ return 0; } -/** Adapt forest according to callback. */ +/** Adapt a forest around the circle of radius 0.45 (first adaptation cycle). + * \param[in] forest Forest to be adapted. */ t8_forest_t t8_adapt_forest (t8_forest_t forest) { struct t8_adapt_data adapt_data = { - { 0, 1, 0 }, /* Midpoints of the sphere. */ - 0.45, - 0.1, - 2, /* minlevel*/ - 6 /*maxlevel*/ + { 0, 1, 0 }, /* Center of the circle. */ + 0.45, /* Radius */ + 0.1, /* Delta (transition band width) */ + 2, /* Minlevel */ + 6 /* Maxlevel */ }; t8_forest_t forest_adapt; @@ -121,16 +131,19 @@ t8_adapt_forest (t8_forest_t forest) return forest_adapt; } -/** Adapt forest according to callback. */ +/** Adapt a forest around the circle of radius 0.6 (second adaptation cycle). + * Same criterion as \ref t8_adapt_forest but with a larger radius. + * \param[in] forest Forest to be adapted. + */ t8_forest_t t8_adapt_forest_2and (t8_forest_t forest) { struct t8_adapt_data adapt_data = { - { 0, 1, 0 }, /* Midpoints of the sphere. */ - 0.6, - 0.1, - 2, /* minlevel*/ - 6 /*maxlevel*/ + { 0, 1, 0 }, /* Center of the circle. */ + 0.6, /* Radius */ + 0.1, /* Delta (transition band width) */ + 2, /* Minlevel */ + 6 /* Maxlevel */ }; t8_forest_t forest_adapt; @@ -138,10 +151,13 @@ t8_adapt_forest_2and (t8_forest_t forest) return forest_adapt; } +/** Balance a forest, i.e. enforce that neighboring elements differ by at most one + * refinement level (a 2:1 balance). + * \param[in] forest Forest to be balanced. + */ t8_forest_t t8_forest_balance (t8_forest_t forest) { - t8_forest_t forest_new; t8_forest_init (&forest_new); t8_forest_set_balance (forest_new, forest, 0); @@ -149,82 +165,83 @@ t8_forest_balance (t8_forest_t forest) return forest_new; } -/** Entry point of the program. */ +/** Entry point of the program. + * + * Runs the full demonstration pipeline (uniform -> adapt -> balance -> + * remove hanging nodes -> discard -> adapt -> balance -> remove), writing the + * forest to VTK after each stage. + */ int main (int argc, char **argv) { - /* The uniform refinement level of the forest. */ - const int level = 0; - + /* Initialize MPI, libsc, and t8code. */ int mpiret = sc_MPI_Init (&argc, &argv); SC_CHECK_MPI (mpiret); sc_init (sc_MPI_COMM_WORLD, 1, 1, NULL, SC_LP_ESSENTIAL); t8_init (SC_LP_PRODUCTION); - /* We will use MPI_COMM_WORLD as a communicator. */ sc_MPI_Comm comm = sc_MPI_COMM_WORLD; - /* ---Setup. Build cmesh and uniform forest.--- */ - /* Build a cube cmesh with tet, hex, and prism trees. */ - // t8_cmesh_t cmesh = t8_cmesh_new_hypercube (T8_ECLASS_TRIANGLE, comm, 0, 0, 0); + /* --- Setup: build the cmesh and a uniform forest. --- */ + /* Hybrid 2D hypercube: a mesh containing both quad and triangle trees. */ t8_cmesh_t cmesh = t8_cmesh_new_2D_hypercube_hybrid (comm); + /* Uniform forest using the subelement scheme (required for hanging-node resolution). */ + const int level = 0; t8_forest_t forest = t8_forest_new_uniform (cmesh, t8_scheme_new_subelement (), level, 0, comm); const char *prefix = "t8_uniform"; t8_forest_write_vtk (forest, prefix); t8_global_productionf (" [subelements] Uniform forest wrote to file: %s*\n", prefix); - /* --- Adapt the forest. --- */ + /* --- Adapt the forest: refine near the first circle, creating hanging nodes. --- */ forest = t8_adapt_forest (forest); - std::cout << "Subelements before removing: " << t8_forest_has_subelements (forest) << std::endl; + std::cout << "Subelements before removing: " << t8_forest_has_local_subelements (forest) << std::endl; prefix = "t8_adapted1"; t8_forest_write_vtk (forest, prefix); t8_global_productionf (" [subelements] Wrote adapted forest with hanging nodes to vtu files: %s*\n", prefix); - // --- Balance the forest. --- + /* --- Balance the forest (2:1 balance between neighboring elements). --- */ forest = t8_forest_balance (forest); prefix = "t8_balanced1"; t8_forest_write_vtk (forest, prefix); t8_global_productionf (" [subelements] Balanced and wrote to file: %s*\n", prefix); - // --- Add subelements to remove hanging nodes. --- + /* --- Resolve hanging nodes by transitioning elements into subelements. --- */ forest = t8_forest_remove_hanging_nodes (forest); - std::cout << "Subelements after removing: " << t8_forest_has_subelements (forest) << std::endl; - // Now output to vtk. + std::cout << "Subelements after removing: " << t8_forest_has_local_subelements (forest) << std::endl; const char *prefix_without_hanging_nodes = "t8_resolved_hanging_nodes1"; t8_forest_write_vtk (forest, prefix_without_hanging_nodes); t8_global_productionf (" [subelements] Wrote adapted forest with resolved hanging nodes to vtu files: %s*\n", prefix_without_hanging_nodes); - // --- Discard Subelements. --- + /* --- Discard the subelements to recover a plain, recursively refined forest. --- */ + /* This is the inverse of the previous step and is required before adapting again. */ forest = t8_forest_discard_subelements (forest); - std::cout << "Subelements removed: " << t8_forest_has_subelements (forest) << std::endl; - // Output to vtk. + std::cout << "Subelements removed: " << t8_forest_has_local_subelements (forest) << std::endl; const char *prefix_removed_sub = "t8_discarded_subelements1"; t8_forest_write_vtk (forest, prefix_removed_sub); t8_global_productionf (" [subelements] Wrote adapted forest with discarded subelements to vtu files: %s*\n", prefix_removed_sub); - /* --- Adapt the forest again. --- */ + /* --- Second cycle: adapt around the larger circle. --- */ forest = t8_adapt_forest_2and (forest); prefix = "t8_adapted2"; t8_forest_write_vtk (forest, prefix); t8_global_productionf (" [subelements] Adapted again and wrote to file: %s*\n", prefix); - // --- Balance again. --- + /* --- Balance again. --- */ forest = t8_forest_balance (forest); prefix = "t8_balanced2"; t8_forest_write_vtk (forest, prefix); t8_global_productionf (" [subelements] Balanced again and wrote to file: %s*\n", prefix); - // --- Add subelements to remove hanging nodes. --- + /* --- Resolve hanging nodes again. --- */ forest = t8_forest_remove_hanging_nodes (forest); prefix = "t8_resolved_hanging_nodes2"; t8_forest_write_vtk (forest, prefix); t8_global_productionf (" [subelements] Removed hanging nodes after second adaptation and wrote to : %s*\n", prefix); - // --- Cleanup. --- + /* --- Cleanup: free the forest and finalize t8code / libsc / MPI. --- */ t8_forest_unref (&forest); - sc_finalize (); mpiret = sc_MPI_Finalize (); SC_CHECK_MPI (mpiret); diff --git a/example/subelements/t8_quads_hanging_nodes.cxx b/example/subelements/t8_quads_hanging_nodes.cxx index 92dd9abd2a..d95c15b929 100644 --- a/example/subelements/t8_quads_hanging_nodes.cxx +++ b/example/subelements/t8_quads_hanging_nodes.cxx @@ -24,7 +24,6 @@ * This is an example to demonstrate hanging node resolution for quads. */ -#include "t8_eclass/t8_eclass.h" #include /* General t8code header, always include this. */ #include /* cmesh definition and basic interface. */ #include /* A collection of exemplary cmeshes */ @@ -34,8 +33,9 @@ #include /* Function for adding subelements. */ #include /* Subelement refinement scheme. */ #include /* Basic operations on 3D vectors. */ +#include -/** The adaptation callback function. +/** The adaptation callback function. This refines every second element (with even global id). * \param [in] forest The current forest that is in construction. * \param [in] forest_from The forest from which we adapt the current forest (in our case, the uniform forest) * \param [in] which_tree The process local id of the current tree. @@ -82,31 +82,32 @@ main (int argc, char **argv) /* We will use MPI_COMM_WORLD as a communicator. */ sc_MPI_Comm comm = sc_MPI_COMM_WORLD; - /* ---Setup. Build cmesh and uniform forest.--- */ - /* Build a cube cmesh with tet, hex, and prism trees. */ - // t8_cmesh_t cmesh = t8_cmesh_new_hypercube (T8_ECLASS_TRIANGLE, comm, 0, 0, 0); - t8_cmesh_t cmesh = t8_cmesh_new_2D_hypercube_hybrid (comm); + /* --- Setup. Build cmesh and uniform forest.--- */ + t8_cmesh_t cmesh; + t8_cmesh_init (&cmesh); + t8_cmesh_new_hypercube (&cmesh, T8_ECLASS_QUAD, comm, 0, 0, 0); t8_forest_t forest = t8_forest_new_uniform (cmesh, t8_scheme_new_subelement (), level, 0, comm); /* --- Adapt the forest. --- */ forest = t8_adapt_forest (forest); - std::cout << "Subelements before removing: " << t8_forest_has_subelements (forest) << std::endl; + std::cout << "Subelements before removing: " << t8_forest_has_local_subelements (forest) << std::endl; const char *prefix_with_hanging_nodes = "t8_with_hanging_nodes"; t8_forest_write_vtk (forest, prefix_with_hanging_nodes); t8_global_productionf (" [subelements] Wrote adapted forest with hanging nodes to vtu files: %s*\n", prefix_with_hanging_nodes); - // --- Remove hanging nodes via adapting again. --- + /* --- Remove hanging nodes. --- */ forest = t8_forest_remove_hanging_nodes (forest); - std::cout << "Subelements after removing: " << t8_forest_has_subelements (forest) << std::endl; - // Now output to vtk. + std::cout << "Subelements after removing: " << t8_forest_has_local_subelements (forest) << std::endl; + // Output to vtk. const char *prefix_without_hanging_nodes = "t8_without_hanging_nodes"; t8_forest_write_vtk (forest, prefix_without_hanging_nodes); t8_global_productionf (" [subelements] Wrote adapted forest without hanging nodes to vtu files: %s*\n", prefix_without_hanging_nodes); + /* ---Discard subelements. --- */ forest = t8_forest_discard_subelements (forest); - std::cout << "Subelements removed: " << t8_forest_has_subelements (forest) << std::endl; + std::cout << "Subelements removed: " << t8_forest_has_local_subelements (forest) << std::endl; // Now output to vtk. const char *prefix_removed_sub = "t8_removed_sub"; t8_forest_write_vtk (forest, prefix_removed_sub); diff --git a/src/t8_cmesh/t8_cmesh_examples.cxx b/src/t8_cmesh/t8_cmesh_examples.cxx index 467c359589..28b878236b 100644 --- a/src/t8_cmesh/t8_cmesh_examples.cxx +++ b/src/t8_cmesh/t8_cmesh_examples.cxx @@ -686,7 +686,7 @@ t8_cmesh_new_2D_hypercube_hybrid (sc_MPI_Comm comm) { { /* clang-format off */ - double vertices[60] = { /* Just all vertices of all trees. partly duplicated */ + double vertices[60] = { /* All vertices of all trees. Partly duplicated */ 0, 0, 0, /* tree 0, triangle */ 0.5, 0, 0, 0.5, 0.5, 0, @@ -745,17 +745,9 @@ t8_cmesh_new_2D_hypercube_hybrid (sc_MPI_Comm comm) t8_cmesh_set_join (cmesh, 0, 1, 1, 2, 0); t8_cmesh_set_join (cmesh, 0, 2, 0, 0, 0); - //t8_cmesh_set_join (cmesh, 0, 3, 2, 3, 0); - t8_cmesh_set_join (cmesh, 1, 3, 0, 2, 1); - //t8_cmesh_set_join (cmesh, 1, 2, 1, 1, 0); - t8_cmesh_set_join (cmesh, 2, 4, 3, 2, 0); - //t8_cmesh_set_join (cmesh, 2, 5, 2, 0, 1); - t8_cmesh_set_join (cmesh, 3, 5, 1, 1, 0); - //t8_cmesh_set_join (cmesh, 3, 4, 0, 0, 0); - t8_cmesh_set_join (cmesh, 4, 5, 1, 2, 0); t8_cmesh_commit (cmesh, comm); diff --git a/src/t8_forest/t8_forest_adapt.cxx b/src/t8_forest/t8_forest_adapt.cxx index b7aa49bd29..5ad5dfb1d9 100644 --- a/src/t8_forest/t8_forest_adapt.cxx +++ b/src/t8_forest/t8_forest_adapt.cxx @@ -23,15 +23,14 @@ * Implements functions declared in \ref t8_forest_adapt.h. */ -#include "t8_eclass/t8_eclass.h" #include #include #include #include +#include #include #include #include -#include "t8_forest_subelement.hxx" /* We want to export the whole implementation to be callable from "C" */ T8_EXTERN_C_BEGIN (); @@ -435,7 +434,7 @@ t8_forest_adapt (t8_forest_t forest) if (forest->set_adapt_recursive) { if (t8_scheme_has_subelement_scheme (t8_forest_get_scheme (forest_from))) { - SC_CHECK_ABORT (!t8_forest_has_subelements (forest_from), + SC_CHECK_ABORT (!t8_forest_has_local_subelements (forest_from), "Recursive adaptation is currently not implemented for subelement schemes."); } refine_list = sc_list_new (nullptr); @@ -629,13 +628,14 @@ t8_forest_adapt (t8_forest_t forest) else if (refine > 1) { // Subelement case. T8_ASSERT (t8_eclass_scheme_is_subelement (t8_forest_get_scheme (forest_from), T8_ECLASS_QUAD)); /* The subelement-callback function returns refine = subelement_type + 1 to avoid subelement_type = 1. - * We undo this to use the subelement_type-values that match the binary encoding of the neighbour structure. */ + * We undo this (e.g. to use the subelement_type-values that match the binary encoding of the neighbour + * structure for hanging node resolution). + */ int subelement_type = refine - 1; int num_subelements = t8_element_get_number_of_subelements (scheme, tree->eclass, subelement_type); (void) t8_element_array_push_count (telements, num_subelements); for (int zz = 0; zz < num_subelements; zz++) { - /* TODO: In a future version elements_from[zz] should be const and we should call t8_element_array_index_locidx (the const version). */ elements[zz] = t8_element_array_index_locidx_mutable (telements, el_inserted + zz); } t8_refine_element_in_subelements (scheme, tree->eclass, elements_from[0], subelement_type, elements); diff --git a/src/t8_forest/t8_forest_subelement.cxx b/src/t8_forest/t8_forest_subelement.cxx index b25ff0d4fc..1af940e0d3 100644 --- a/src/t8_forest/t8_forest_subelement.cxx +++ b/src/t8_forest/t8_forest_subelement.cxx @@ -40,7 +40,17 @@ namespace detail { /** Adapt callback for \ref t8_forest_discard_subelements. All subelements are coarsened such that the mesh using only - * recursive refinement is restored and the subelements are discarded. This is necessary for another adaption cycle. + * recursive refinement is restored and the subelements are discarded. This is necessary for another adaption cycle. # + * \param [in] forest The forest to which the new elements belong. + * \param [in] forest_from The forest that is adapted. + * \param [in] which_tree The local tree containing \a elements. + * \param [in] tree_class The eclass of \a which_tree. + * \param [in] lelement_id The local element id in \a forest_from in the tree of the current element. + * \param [in] scheme The scheme of the forest. + * \param [in] is_family If 1, the first \a num_elements entries in \a elements form a family. If 0, they do not. + * \param [in] num_elements The number of entries in \a elements that are defined + * \param [in] elements Pointers to a family or, if \a is_family is zero, pointer to one element. + * \return -1 for subelements, 0 else. */ int discard_subelements_callback ([[maybe_unused]] t8_forest_t forest, [[maybe_unused]] t8_forest_t forest_from, @@ -49,8 +59,8 @@ discard_subelements_callback ([[maybe_unused]] t8_forest_t forest, [[maybe_unuse [[maybe_unused]] const int is_family, [[maybe_unused]] const int num_elements, t8_element_t *elements[]) { - // TODO - if (t8_element_is_subelement (scheme, tree_class, elements[0])) { + // Coarsen if the element is a subelement. + if (t8_element_is_subelement (scheme, tree_class, elements[0]) && is_family) { return -1; } return 0; @@ -62,6 +72,15 @@ discard_subelements_callback ([[maybe_unused]] t8_forest_t forest, [[maybe_unuse * and to 0, if the level of the neighbour is at most the level of the element. * If all faces are hanging, we use the normal 1:8 refinement and return 1. * Otherwise, we use subelements and add 1 to every type, to avoid refine = 1. + * \param [in] forest The forest to which the new elements belong. + * \param [in] forest_from The forest that is adapted. + * \param [in] which_tree The local tree containing \a elements. + * \param [in] tree_class The eclass of \a which_tree. + * \param [in] lelement_id The local element id in \a forest_from in the tree of the current element. + * \param [in] scheme The scheme of the forest. + * \param [in] is_family If 1, the first \a num_elements entries in \a elements form a family. If 0, they do not. + * \param [in] num_elements The number of entries in \a elements that are defined + * \param [in] elements Pointers to a family or, if \a is_family is zero, pointer to one element. * \return The subelement type + 1 to be used for the transition cell, which is a binary encoding of the hanging faces. */ int @@ -70,6 +89,7 @@ t8_remove_hanging_nodes_callback ([[maybe_unused]] t8_forest_t forest, t8_forest const t8_scheme *scheme, [[maybe_unused]] const int is_family, [[maybe_unused]] const int num_elements, t8_element_t *elements[]) { + // Determine the hanging faces of the element. This is stored in the subelement type. int subelement_type = 0; const int num_faces = scheme->element_get_num_faces (tree_class, elements[0]); for (int iface = 0; iface < num_faces; iface++) { @@ -82,6 +102,7 @@ t8_remove_hanging_nodes_callback ([[maybe_unused]] t8_forest_t forest, t8_forest t8_forest_leaf_face_neighbors (forest_from, which_tree, elements[0], &neighbors, iface, &dual_faces_internal, &num_neighbors, &neighids, &neigh_class); if (num_neighbors > 1) { + // Store in correct cell of the binary format. subelement_type += 1 << ((num_faces - 1) - iface); } @@ -94,7 +115,7 @@ t8_remove_hanging_nodes_callback ([[maybe_unused]] t8_forest_t forest, t8_forest } /* Returning the correct subelement type. */ - if (subelement_type == 0) { /* In this case, there are no hanging nodes and we do not need to do anything. */ + if (subelement_type == 0) { /* In this case, there are no hanging faces and we do nothing. */ return 0; } else if (subelement_type == 15) { /* Normal 1:8 refinement. */ @@ -119,14 +140,14 @@ t8_forest_remove_hanging_nodes (t8_forest_t forest) t8_forest_t t8_forest_discard_subelements (t8_forest_t forest) { - if (!t8_forest_has_subelements (forest)) { + if (!t8_forest_has_local_subelements (forest)) { return forest; } return t8_forest_new_adapt (forest, detail::discard_subelements_callback, 0, 0, NULL); } bool -t8_forest_has_subelements (const t8_forest_t forest) +t8_forest_has_local_subelements (const t8_forest_t forest) { auto scheme = t8_forest_get_scheme (forest); if (!t8_scheme_has_subelement_scheme (scheme)) { diff --git a/src/t8_forest/t8_forest_subelement.hxx b/src/t8_forest/t8_forest_subelement.hxx index abfa419e65..bc6b1a05a0 100644 --- a/src/t8_forest/t8_forest_subelement.hxx +++ b/src/t8_forest/t8_forest_subelement.hxx @@ -32,25 +32,25 @@ #include /** Remove hanging nodes from the forest by transitioning elements with hanging nodes into subelements. -* \param [in] forest The input forest, which may contain hanging nodes. -* \a forest must be committed before calling this function. Please note that the scheme provided with the -* forest has to be a fitting subelement scheme. -* \return A new forest with the same number of trees and the same connectivity, but conformal without hanging nodes. -*/ + * \param [in] forest The input forest, which may contain hanging nodes. + * \a forest must be committed before calling this function. Please note that the scheme provided with the + * forest has to be a fitting subelement scheme. + * \return A new forest with the same number of trees and the same connectivity, but conformal without hanging nodes. + */ t8_forest_t t8_forest_remove_hanging_nodes (t8_forest_t forest); /** Remove all subelements from a forest. This is required to restore the original mesh using only recursive refinement -* and to be able to adapt again. -* \param [in] forest The input forest which may contain subelements. -* \return A new forest with the same number of trees and the same connectivity, but without subelements. -*/ + * and to be able to adapt again. + * \param [in] forest The input forest which may contain subelements. + * \return A new forest with the same number of trees and the same connectivity, but without subelements. + */ t8_forest_t t8_forest_discard_subelements (t8_forest_t forest); -/** Check if a forest contains subelements. -* \param [in] forest The forest to be checked. -* \return true if there are subelements in the forest, false otherwise. -*/ +/** Check if a forest contains subelements locally. + * \param [in] forest The forest to be checked. + * \return true if there are subelements in the forest, false otherwise. + */ bool -t8_forest_has_subelements (const t8_forest_t forest); +t8_forest_has_local_subelements (const t8_forest_t forest); diff --git a/src/t8_schemes/t8_scheme.cxx b/src/t8_schemes/t8_scheme.cxx index d7560e5e8e..6457bc91d0 100644 --- a/src/t8_schemes/t8_scheme.cxx +++ b/src/t8_schemes/t8_scheme.cxx @@ -24,7 +24,7 @@ * Implements functions declared in \ref t8_scheme.h. */ -#include "sc.h" +#include "t8.h" #include #include #include @@ -438,11 +438,6 @@ t8_element_MPI_Unpack (const t8_scheme_c *scheme, const t8_eclass_t tree_class, return scheme->element_MPI_Unpack (tree_class, recvbuf, buffer_size, position, elements, count, comm); } -/** Check if \ref elem is a subelement. - * \param [in] scheme The scheme of the forest. - * \param [in] tree_class The eclass of the current tree. - * \param [in] elem The elem to be checked. - */ int t8_element_is_subelement (const t8_scheme_c *scheme, const t8_eclass_t tree_class, const t8_element_t *elem) { @@ -451,11 +446,6 @@ t8_element_is_subelement (const t8_scheme_c *scheme, const t8_eclass_t tree_clas return scheme->element_is_subelement (tree_class, elem); } -/** Get the number of subelements an element is refined into for a specific type. - * \param [in] scheme The scheme of the forest. - * \param [in] tree_class The eclass of the current tree. - * \param [in] subelement_type The subelement type used for refinement. - */ int t8_element_get_number_of_subelements (const t8_scheme_c *scheme, const t8_eclass_t tree_class, int subelement_type) { @@ -465,14 +455,6 @@ t8_element_get_number_of_subelements (const t8_scheme_c *scheme, const t8_eclass return scheme->element_get_number_of_subelements (tree_class, subelement_type); } -/** This defines how an element is refined in subelements using a specified subelement type. - * \param [in] scheme The scheme of the forest. - * \param [in] tree_class The eclass of the current tree. - * \param [in] elem The element to be refined. - * \param [in] type The subelement type to be used for refinement. - * \param [in, out] c An array of allocated elements that will be filled with the subelements of \a elem. - * The number of subelements is determined by \ref element_get_number_of_subelements. - */ void t8_refine_element_in_subelements (const t8_scheme_c *scheme, const t8_eclass_t tree_class, const t8_element_t *elem, int type, t8_element_t *c[]) diff --git a/src/t8_schemes/t8_scheme.h b/src/t8_schemes/t8_scheme.h index 48addd33d4..601e2c4e8c 100644 --- a/src/t8_schemes/t8_scheme.h +++ b/src/t8_schemes/t8_scheme.h @@ -861,29 +861,29 @@ t8_element_MPI_Unpack (const t8_scheme_c *scheme, const t8_eclass_t tree_class, int *position, t8_element_t **elements, const unsigned int count, sc_MPI_Comm comm); /** Check if \ref elem is a subelement. - * \param [in] scheme The scheme of the forest. - * \param [in] tree_class The eclass of the current tree. - * \param [in] elem The elem to be checked. - */ + * \param [in] scheme The scheme of the forest. + * \param [in] tree_class The eclass of the current tree. + * \param [in] elem The elem to be checked. + */ int t8_element_is_subelement (const t8_scheme_c *scheme, const t8_eclass_t tree_class, const t8_element_t *elem); /** Get the number of subelements an element is refined into for a specific type. - * \param [in] scheme The scheme of the forest. - * \param [in] tree_class The eclass of the current tree. - * \param [in] subelement_type The subelement type used for refinement. - */ + * \param [in] scheme The scheme of the forest. + * \param [in] tree_class The eclass of the current tree. + * \param [in] subelement_type The subelement type used for refinement. + */ int t8_element_get_number_of_subelements (const t8_scheme_c *scheme, const t8_eclass_t tree_class, int subelement_type); /** This defines how an element is refined in subelements using a specified subelement type. - * \param [in] scheme The scheme of the forest. - * \param [in] tree_class The eclass of the current tree. - * \param [in] elem The element to be refined. - * \param [in] type The subelement type to be used for refinement. - * \param [in, out] c An array of allocated elements that will be filled with the subelements of \a elem. - * The number of subelements is determined by \ref element_get_number_of_subelements. - */ + * \param [in] scheme The scheme of the forest. + * \param [in] tree_class The eclass of the current tree. + * \param [in] elem The element to be refined. + * \param [in] type The subelement type to be used for refinement. + * \param [in, out] c An array of allocated elements that will be filled with the subelements of \a elem. + * The number of subelements is determined by \ref element_get_number_of_subelements. + */ void t8_refine_element_in_subelements (const t8_scheme_c *scheme, const t8_eclass_t tree_class, const t8_element_t *elem, int type, t8_element_t *c[]); From 94ff6d791d4d40e330aea45e0f2d119f88b09b5c Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Tue, 11 Aug 2026 17:03:07 +0200 Subject: [PATCH 22/28] Documentation and improvements --- src/t8_forest/t8_forest_subelement.cxx | 2 +- src/t8_schemes/t8_scheme.hxx | 2 + src/t8_schemes/t8_subelement/README.md | 25 ++ .../specializations/t8_scheme_quads.hxx | 248 +++++++++--------- .../specializations/t8_scheme_tri.hxx | 1 + .../t8_subelement/t8_subelement.cxx | 6 +- .../t8_subelement/t8_subelement.hxx | 2 +- .../t8_subelement/t8_subelement_scheme.hxx | 91 +++++-- .../t8_subelement/t8_subelement_traits.hxx | 35 +++ .../t8_subelement/t8_subelement_type.hxx | 4 +- 10 files changed, 262 insertions(+), 154 deletions(-) create mode 100644 src/t8_schemes/t8_subelement/README.md diff --git a/src/t8_forest/t8_forest_subelement.cxx b/src/t8_forest/t8_forest_subelement.cxx index 1af940e0d3..a7266bb0a7 100644 --- a/src/t8_forest/t8_forest_subelement.cxx +++ b/src/t8_forest/t8_forest_subelement.cxx @@ -102,7 +102,7 @@ t8_remove_hanging_nodes_callback ([[maybe_unused]] t8_forest_t forest, t8_forest t8_forest_leaf_face_neighbors (forest_from, which_tree, elements[0], &neighbors, iface, &dual_faces_internal, &num_neighbors, &neighids, &neigh_class); if (num_neighbors > 1) { - // Store in correct cell of the binary format. + // Store in correct cell of the binary format. We encode it as f0 -> bit 0, ..., f_{n-1} -> bit (num_faces-1). subelement_type += 1 << ((num_faces - 1) - iface); } diff --git a/src/t8_schemes/t8_scheme.hxx b/src/t8_schemes/t8_scheme.hxx index 5374aac6ba..cb7c07b39d 100644 --- a/src/t8_schemes/t8_scheme.hxx +++ b/src/t8_schemes/t8_scheme.hxx @@ -43,6 +43,8 @@ #include #include #include +#include +#include #include #include #if T8_ENABLE_DEBUG diff --git a/src/t8_schemes/t8_subelement/README.md b/src/t8_schemes/t8_subelement/README.md new file mode 100644 index 0000000000..5d83b92f70 --- /dev/null +++ b/src/t8_schemes/t8_subelement/README.md @@ -0,0 +1,25 @@ +# t8_subelement + +This folder provides **subelement schemes**. Subelements are inserted *after* the standard recursive refinement and enable one additional refinement level that uses a different scheme. + +This is useful, for example, to resolve hanging nodes left behind by the recursive refinement, or to add a uniform subgrid to each mesh element after adaptation, which is beneficial on GPUs. + +Subelements should **never be refined any further**: they always sit at the very bottom of the refinement tree. This keeps the efficient forest-of-trees strategy intact, so that we only store the leaf elements and can recreate the whole forest from them. Before the next adaptation cycle, the subelements are removed again. This also ensures that the parent mesh bounds the mesh quality. + +![](https://github.com/user-attachments/assets/999bc7d9-5617-4dde-bb2a-abb22b921fc7) + +## Implementation details and file structure + +The scheme is built from a **common base** that provides the logic shared by all subelement schemes, plus one **specialization per element class** that supplies the parts that differ between element types. + +- [t8_subelement.hxx](./t8_subelement.hxx) / [t8_subelement.cxx](./t8_subelement.cxx): Main access point to the subelement schemes. Provides the constructor `t8_scheme_new_subelement()`, which assembles the full scheme for all element classes of t8code using the subelement schemes for element classes that are already implemented and for all other standalone/default implementations. + +- [t8_subelement_type.hxx](./t8_subelement_type.hxx): Defines the element class of a subelement. A subelement always consists of an underlying element plus a subelement **type** and **id** that define how the underlying element is transitioned into a subelement. + +- [t8_subelement_scheme.hxx](./t8_subelement_scheme.hxx): The common scheme (`t8_subelement_scheme_common`) implementing the functionality shared by all subelement schemes: construction and destruction, the element memory pool, element sizing, and the general element interface. It is templated on the underlying element class and on a specialization scheme; whenever logic is needed that is *not* identical for all subelements, it delegates to that specialization. + +- [t8_subelement_traits.hxx](./t8_subelement_traits.hxx): Trait definitions that map each concrete subelement scheme to its underlying scheme and subelement type. For example, quadrilateral subelements build on the standalone quad scheme, while triangular subelements build on the default triangle scheme. This is needed for the common subelement scheme implementation. + +- [specializations/](./specializations): Per–element-class specializations providing the subelement logic that is *not* shared by the common scheme: + - [t8_scheme_quads.hxx](./specializations/t8_scheme_quads.hxx): `t8_subelementquad_scheme`, the subelement scheme to resolve hanging nodes for quadrilateral elements. A quad is transitioned into triangular subelements; the subelement type is a binary code over the four faces indicating which of them are hanging. + - [t8_scheme_tri.hxx](./specializations/t8_scheme_tri.hxx): `t8_subelementtri_scheme`, the subelement scheme for triangular elements. diff --git a/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx b/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx index 12d46214fc..bc7adf75b3 100644 --- a/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx +++ b/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx @@ -20,46 +20,55 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** \file t8_subelement_type.hxx - * Definition of the element class of a subelement. A subelement always contains of a standalone element and a - * subelement type and id defining how the standalone element is transitioned into a subelement. +/** \file t8_scheme_quads.hxx + * Subelement scheme specialization for quadrilateral elements. A quad is transitioned into + * triangular subelements (e.g. to resolve hanging nodes). The subelement type is a binary + * code over the quad's four faces indicating which of them are hanging; type 0 means the + * element is not a subelement (it is just the underlying standalone quad). This file only + * implements the quad-specific logic. The functionality shared by all subelement schemes + * lives in t8_subelement_scheme.hxx. */ #pragma once #include +#include "../t8_subelement_scheme.hxx" +#include +/** Maximum subelement type. The subelement type ranges from 0 (=no subelement, normal standalone quad) to 14. +* The type 15 would mean in binary representation that all faces are hanging but in this case, the element just get +* refined by the standard recursive refinement in 4 standalone quads.*/ #define T8_SUB_QUAD_MAX_SUBELEMENT_TYPE 14 -/** Definition of the subelement class. A subelement always has an underlying standalone element. - * With the type, it is defined if the standalone element is further defined into subelements - * (e.g. for hanging node resolution) or not. Type 0 means no subelement and the subelement is just the - * underlying standalone element. For hanging node resolution, the type encodes which faces are hanging and therefore - * the number of subelements in which the element is transitioned. - * Accordingly, the subelement id is between 0 and num_subelement - 1. - - - f0 1 - * x - - x - - x x - - x - - x - * | | | \ | / | - * | | | \ | / | | f3 | f2 | f1 | f0 | - * f3 x | f2 --> 1 x - - x | 0 --> binary code (according to the face enumeration): | 1 | 0 | 0 | 1 | = 9 in base 10 - * | | | / \ | - * | elem | | / \ | - * x - - - - - x x - - - - - x - * f1 0 -TODO: for this i can separate definition and implementation. - +/** Subelement scheme for quadrilateral elements. + * A quad is transitioned into triangular subelements. The subelement type encodes which of + * the quad's four faces are hanging as a binary code (one bit per face). Type 0 means the + * element is not a subelement and is just the underlying standalone quad. For hanging-node + * resolution the type determines the number of subelements the quad is split into, and the + * subelement id runs from 0 to num_subelement - 1. Valid types are 1..14; the all-faces- + * hanging code 15 is excluded. + * + * \verbatim + f0 1 + x - - x - - x x - - x - - x + | | | \ | / | + | | | \ | / | | f3 | f2 | f1 | f0 | + f3 x | f2 --> 1 x - - x | 0 | 1 | 0 | 0 | 1 | = 9 + | | | / \ | + | elem | | / \ | binary code following the face + x - - - - - x x - - - - - x enumeration (here faces f0 and f3 + f1 0 are hanging) + * \endverbatim */ - struct t8_subelementquad_scheme: public t8_subelement_scheme_common { public: - using TUnderlyingScheme = typename t8_subelement_traits:: - UnderlyingScheme; /**< The used recursive scheme for the underlying elements. Every time we do not need the subelement logic, the scheme calls the functionality of this underlying scheme. */ + /** The recursive scheme used for the underlying (standalone) quad elements. Whenever the + * subelement logic is not needed, the scheme forwards to this underlying scheme. */ + using TUnderlyingScheme = typename t8_subelement_traits::UnderlyingScheme; + /** The subelement element type (an underlying element plus a subelement type and id). */ using TSubelementType = typename t8_subelement_traits::SubelementType; - using Base = t8_subelement_scheme_common; - TUnderlyingScheme underlying_scheme {}; + TUnderlyingScheme underlying_scheme {}; /**< Instance of the underlying standalone scheme. */ /** Compute the number of corners of an element. * \param [in] elem The subelement. @@ -112,8 +121,8 @@ struct t8_subelementquad_scheme: public t8_subelement_scheme_common x - - X 3 | or x - - x | or x - - x - - x or x - - x - - x - * | | | 0 / \ | | / | \ | | / \ | | / \ | - * | elem | | / 4 \ | | / | \ | | / \ | | / \ | - * + - - - - - - x x - - - - - x x - - x - - x x - - - - - x x - - - - - x - * - * Subelement_ids are counted clockwise, starting with the (lower) left subelement with id 0. - * Note, that we do not change the underlying quadrant. + * \verbatim + x - - - - - - x x - - - - - x x - - - - - x x - - - - - x x - - x - - x + | | | \ 2 / | | \ / | | \ / | | \ | / | + | | | 1 \ / | | \ / | | \ / | | \ | / | + | | --> x - - X 3 | or x - - x | or x - - x - - x or x - - x - - x + | | | 0 / \ | | / | \ | | / \ | | / \ | + | elem | | / 4 \ | | / | \ | | / \ | | / \ | + + - - - - - - x x - - - - - x x - - x - - x x - - - - - x x - - - - - x + * \endverbatim + * Subelement ids are counted clockwise, starting with the (lower) left subelement with id 0. + * Note that we do not change the underlying quadrant. */ void refine_element_in_subelements (const t8_element_t *elem, int type, t8_element_t *c[]) const noexcept { - const TSubelementType *element = (const TSubelementType *) elem; - TSubelementType **subelements = (TSubelementType **) c; + const TSubelementType *element = this->as_subelement (elem); + TSubelementType **subelements = reinterpret_cast (c); const int num_subelements = this->element_get_number_of_subelements (type); T8_ASSERT (type >= 1 && type <= T8_SUB_QUAD_MAX_SUBELEMENT_TYPE); @@ -188,18 +200,25 @@ struct t8_subelementquad_scheme: public t8_subelement_scheme_common n0 - * (1,0) -> n1 - * (1,1) -> n2 - */ + /* Mapping: (0,0) -> n0, (1,0) -> n1, (1,1) -> n2. */ out_coords[coord * 2 + 0] = (1.0 - u) * n0[0] + (u - v) * n1[0] + v * n2[0]; out_coords[coord * 2 + 1] = (1.0 - u) * n0[1] + (u - v) * n1[1] + v * n2[1]; } } private: + /** Compute the integer coordinates of one vertex of a triangular subelement. + * \param [in] elem The subelement. + * \param [in] vertex The vertex number (0, 1 or 2). + * \param [out] coords Filled with the x and y integer coordinates of \a vertex. + */ void vertex_coords_of_subelement (const t8_element_t *elem, int vertex, int coords[]) const noexcept { T8_ASSERT (this->element_is_valid (elem)); T8_ASSERT (this->element_is_subelement (elem)); const auto *subelement = this->as_subelement (elem); + T8_ASSERT (vertex >= 0 && vertex < subelement_get_num_corners (subelement)); - T8_ASSERT (vertex >= 0 && vertex < subelement_get_num_faces (subelement)); /* all subelements are triangles */ - - /* get the length of the current quadrant */ + /* Get the length of the current quadrant. */ int len = this->parent_element_get_len (subelement); /* Compute the x and y coordinates of subelement vertices, depending on the subelement type, id and vertex number @@ -245,33 +264,33 @@ struct t8_subelementquad_scheme: public t8_subelement_scheme_common location = element_get_location_of_subelement (elem); - /* the face number, the subelement is adjacent to */ + /* Face number, the subelement is adjacent to. */ int face_number = location[0]; - /* = 1, if the adjacent face is split and = 0, if not */ + /* = 1, if the adjacent face is split and = 0, if not. */ int split = location[1]; - /* = 0, if the subelement is the first (of two) subelements, at the adjacent face and = 1 if it is the second */ + /* = 0, if the subelement is the first (of two) subelements, at the adjacent face and = 1 if it is the second. */ int sub_face_id = location[2]; - /* Check, whether the get_location function provides meaningful location data */ + /* Check, whether the get_location function provides meaningful location data. */ T8_ASSERT (face_number == 0 || face_number == 1 || face_number == 2 || face_number == 3); T8_ASSERT ((split == 0 && sub_face_id == 0) || (split == 1 && (sub_face_id == 0 || sub_face_id == 1))); coords[0] = subelement->element.coords[0]; coords[1] = subelement->element.coords[1]; - /* using the location data to determine vertex coordinates */ - if (vertex == 0) { /* vertex 0 (the first vertex always equals the center of the element) */ + /* Use the location data to determine vertex coordinates. */ + if (vertex == 0) { /* Vertex 0 (the first vertex always equals the center of the element). */ coords[0] += len / 2; coords[1] += len / 2; - } /* end of vertex == 0 */ - else if (vertex == 1) { /* vertex 1 */ + } /* End of vertex 0. */ + else if (vertex == 1) { /* Vertex 1. */ if (face_number == 0) { if (split && sub_face_id) { coords[1] += len / 2; @@ -296,8 +315,8 @@ struct t8_subelementquad_scheme: public t8_subelement_scheme_common + element_get_location_of_subelement (const t8_element_t *elem) const { - const auto *subelement = this->as_subelement (elem); - - /* this function only works for subelements */ T8_ASSERT (this->element_is_subelement (elem)); - T8_ASSERT (this->element_is_valid (elem)); - - /* Consider the following subelement of type 13: - * - * f0 1 - * x - - x - - x x - - x - - x - * | | | \ 2 | 3 / | faces: f3 f2 f1 f0 - * | | | 1 \ | / 4 | binary code: 1 1 0 1 (=13) - * f3 x x f2 --> 1 x - - x - - x 1 --> rearrange binaries s.t. the faces are enumerated clockwise: 1 1 1 0 - * | | | 0 / \ 5 | number subelements at face: 2 2 2 1 - * | elem | | / 6 \ | consider sub_id 3: x -> second subelement on the upper face - * + - - - - - x x - - - - - x - * f1 0 - * - * We will use the binary representation to determine the location of the given subelement. - * - * We need to know: - * i) the face number of the first vertex (values: {0,1,2,3}). - * ii) whether this face is split in half (values: {0,1}). - * iii) if the subelement is the first or second subelement at the face (values: {0,1}). - * - * These information are then saved in the location array which will be used by the element_vertex function, - * to automatically determine the vertex coordinates of the given subelement. - * - * The location array for the above example would be {1,1,1} (upper face, split = true, second subelement at the upper face). */ - - /* 1) convert the subelement type from a decimal to a binary representation */ - int type = subelement->subelement_type; - int num_faces_quad = T8_ELEMENT_NUM_FACES[T8_ECLASS_QUAD]; + const auto *subelement = this->as_subelement (elem); + const int type = subelement->subelement_type; + + /* Example: Consider the following subelement of type 13: + * + * f3 1 + * x - - x - - x x - - x - - x + * | | | \ 2 | 3 / | + * | | | 1 \ | / 4 | + * f0 x x f1 --> 1 x - - x - - x 1 + * | | | 0 / \ 5 | + * | elem | | / 6 \ | + * + - - - - - x x - - - - - x + * f2 0 + * + * The binary representation of the type is (1 1 0 1) + * (f0 f1 f2 f3) converting to 13 in decimal format. + * The location array for subelement with id 3 would therefore be: {3, 1, 1} + * (upper face f3 of the parent quad, split = true, second subelement at f3). + */ + + /* 1) Convert the subelement type from a decimal to a binary representation. + * We store the binary representation bitwise in an array. + */ + constexpr int num_faces_quad = T8_ELEMENT_NUM_FACES[T8_ECLASS_QUAD]; int binary_array[num_faces_quad] = {}; - - for ( - int i = 0; i < num_faces_quad; - i++) { /* need an array with 4 elements to store all subelement types of the quad scheme from 1 to 15 ({0,0,0,1} to {1,1,1,1}) */ + for (int i = 0; i < num_faces_quad; i++) { binary_array[(num_faces_quad - 1) - i] = (type & (1 << i)) >> i; - } /* we now got a binary representation of the subelement type, bitwise stored in an array */ + } /* 2) rearrange the binary representation to be in clockwise order */ int binary_array_temp[num_faces_quad] = {}; @@ -387,16 +403,14 @@ struct t8_subelementquad_scheme: public t8_subelement_scheme_common the face_number, the subelement is adjacent to */ /* 3.2) location[1] -> if the face is split or not */ /* 3.3) location[2] -> if the subelement is the first or second subelement of the face (always the first, if the face is not split) */ - int num_subelements = element_get_number_of_subelements (subelement->subelement_type); + int num_subelements = element_get_number_of_subelements (type); T8_ASSERT (subelement->subelement_id < num_subelements); - int sub_id = subelement->subelement_id; + const int sub_id = subelement->subelement_id; int sub_face_id = 0; int face_number = 0; int split = 0; - int k; - int cum_neigh_array[num_faces_quad] = {}; /* construct a cumulative array of the number of neighbors from face 0 to face 3 */ @@ -410,7 +424,7 @@ struct t8_subelementquad_scheme: public t8_subelement_scheme_common= cum_neigh_array[k] && sub_id < cum_neigh_array[k + 1]) { face_number = k + 1; break; @@ -434,8 +448,6 @@ struct t8_subelementquad_scheme: public t8_subelement_scheme_common +#include "../t8_subelement_scheme.hxx" #include #include diff --git a/src/t8_schemes/t8_subelement/t8_subelement.cxx b/src/t8_schemes/t8_subelement/t8_subelement.cxx index 6a909a655c..f3a09c3c51 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement.cxx +++ b/src/t8_schemes/t8_subelement/t8_subelement.cxx @@ -24,12 +24,12 @@ * Implements functions declared in \ref t8_subelement.hxx. */ +#include #include +#include +#include "t8_subelement_scheme.hxx" #include "specializations/t8_scheme_quads.hxx" #include "specializations/t8_scheme_tri.hxx" -#include "t8_eclass/t8_eclass.h" -#include "t8_subelement_scheme.hxx" -#include const t8_scheme * t8_scheme_new_subelement (void) diff --git a/src/t8_schemes/t8_subelement/t8_subelement.hxx b/src/t8_schemes/t8_subelement/t8_subelement.hxx index 275972c91b..50ef75ed1f 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement.hxx +++ b/src/t8_schemes/t8_subelement/t8_subelement.hxx @@ -35,7 +35,7 @@ t8_scheme_new_subelement (void); /** Check whether a given eclass_scheme is one of the subelement schemes. * \param [in] scheme A (pointer to a) scheme. * \param [in] eclass The eclass to check. - * \return True if \a scheme is one of the subelement schemes, false otherwise. + * \return True if \a scheme is one of the subelement schemes for the element class, false otherwise. */ bool t8_eclass_scheme_is_subelement (const t8_scheme *scheme, const t8_eclass_t eclass); diff --git a/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx b/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx index 97e9e2ee18..e5d24e8703 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx +++ b/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx @@ -20,31 +20,36 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** \file TODO +/** \file t8_subelement_scheme.hxx + * Common functionality for schemes that support subelements. + * Subelements are temporary elements used after common recursive adaptation and are discarded before the next + * adaptation cycle. + * Scheme-specific subelement functionality is provided by the corresponding specialization in the specializations + * folder and this file only implements functionality that is equal for all subelements. */ #pragma once #include -#include #include +#include #include -#include #include "t8_subelement_traits.hxx" +#include #include /** Scheme for the common functionality of all subelements. * Subelements are discarded before the next adaptation cycle and do not have children. * \tparam TEclass The element class of the underlying elements which we want to define subelements for. - * The subelements themselves could have another eclass. - * \tparam TSubelementSchemeSpecialization Specialization scheme for the subelements. Every time we need the subelement logic which - * is not equal for all subelements, the scheme calls the functionality of this subelement scheme. - + * The subelements themselves could have another eclass. + * \tparam TSubelementSchemeSpecialization Specialization scheme for the subelements. Every time we need the subelement + * logic which is not equal for all subelements, the scheme calls the functionality of this subelement scheme. */ template struct t8_subelement_scheme_common: public t8_scheme_helpers> { public: + /** The subelement type used by this subelement scheme defined by a trait. */ using TSubelementType = typename t8_subelement_traits::SubelementType; /** Constructor. */ @@ -223,7 +228,7 @@ struct t8_subelement_scheme_common: /** Compute the shape of the face of an element. * \param [in] elem The element. * \param [in] face A face of \a elem. - * \return The element shape of the face. As we are in 2D, here always LINE. + * \return The element shape of the face. */ t8_element_shape_t element_get_face_shape (const t8_element_t *elem, const int face) const noexcept @@ -462,8 +467,8 @@ struct t8_subelement_scheme_common: int elements_are_family (t8_element_t *const *fam) const noexcept { -#if T8_ENABLE_DEBUG const int num_siblings = element_get_num_siblings (fam[0]); +#if T8_ENABLE_DEBUG for (int isib = 0; isib < num_siblings; isib++) { T8_ASSERT (element_is_valid (fam[isib])); } @@ -472,7 +477,7 @@ struct t8_subelement_scheme_common: * elements must be equal. */ if (element_is_subelement (fam[0])) { auto element_0 = element_to_standalone (fam[0]); - for (int isib = 1; isib < element_get_num_siblings (fam[0]); ++isib) { + for (int isib = 1; isib < num_siblings; ++isib) { if (!element_is_subelement (fam[isib]) || !derived ().underlying_scheme.element_is_equal (element_0, element_to_standalone (fam[isib]))) { return 0; @@ -482,9 +487,10 @@ struct t8_subelement_scheme_common: } /* If the first element is no subelement, the remaining elements should not be subelements and * they must form a family. */ - t8_element_t **standalone_children_ptrs = T8_ALLOC (t8_element_t *, element_get_num_siblings (fam[0])); - for (int isib = 0; isib < element_get_num_siblings (fam[0]); ++isib) { + t8_element_t **standalone_children_ptrs = T8_ALLOC (t8_element_t *, num_siblings); + for (int isib = 0; isib < num_siblings; ++isib) { if (element_is_subelement (fam[isib])) { + T8_FREE (standalone_children_ptrs); return 0; } standalone_children_ptrs[isib] = element_to_standalone (fam[isib]); @@ -548,7 +554,7 @@ struct t8_subelement_scheme_common: { derived ().underlying_scheme.element_get_first_descendant (element_to_standalone (elem), element_to_standalone (desc), level); - reset_subelement_values ((TSubelementType *) desc); + reset_subelement_values (as_subelement (desc)); } /** Compute the last descendant of a given element. @@ -1118,7 +1124,7 @@ struct t8_subelement_scheme_common: } // ########################################____SUBELEMENTS____######################################################## - /** Check if \ref elem is a subelement. + /** Check if \a elem is a subelement. * \param [in] elem The elem to be checked. */ static bool @@ -1151,44 +1157,67 @@ struct t8_subelement_scheme_common: protected: // PRIVATE HELPER + /** Return the standalone element stored inside a subelement. + * Const version. + * \param[in] subelement The subelement for which the standalone element should be extracted. + */ static const t8_element_t * subelement_to_standalone (const TSubelementType *subelement) noexcept { return (const t8_element_t *) &subelement->element; } + /** Return the standalone element stored inside a subelement. + * Non-const version. + * \param[in] subelement The subelement for which the standalone element should be extracted. + */ static t8_element_t * subelement_to_standalone (TSubelementType *subelement) noexcept { return (t8_element_t *) &subelement->element; } + /** Same as \ref subelement_to_standalone but takes a general element type. + * Const version. + * \param[in] element Element of class t8_element_t that can be interpreted as subelement. + */ static const t8_element_t * element_to_standalone (const t8_element_t *element) noexcept { return subelement_to_standalone (as_subelement (element)); } + /** Same as \ref subelement_to_standalone but takes a general element type. + * Non-const version. + * \param[in] element Element of class t8_element_t that can be interpreted as subelement. + */ static t8_element_t * element_to_standalone (t8_element_t *element) noexcept { return subelement_to_standalone (as_subelement (element)); } + /** Interpret an element as a subelement. + * Const version. + * \param[in] element Element of class t8_element_t that can be interpreted as subelement. + */ static const TSubelementType * as_subelement (const t8_element_t *element) noexcept { return reinterpret_cast (element); } - + /** Interpret an element as a subelement. + * Non-const version. + * \param[in] element Element of class t8_element_t that can be interpreted as subelement. + */ static TSubelementType * as_subelement (t8_element_t *element) noexcept { return reinterpret_cast (element); } - /** create the root element - * \param [in,out] elem The element that is filled with the root + /** Reset the subelement-specific data. + * \param [in,out] subelement The element that is filled with the root of the subelement. */ static void reset_subelement_values (TSubelementType *subelement) noexcept @@ -1197,6 +1226,10 @@ struct t8_subelement_scheme_common: subelement->subelement_id = 0; } + /** Return the edge length of the parent standalone element. + * \param [in] subelement A subelement of the parent element. + * \return The length of the parent element in integer coordinates. + */ t8_element_coord parent_element_get_len (const TSubelementType *subelement) const noexcept { @@ -1204,21 +1237,21 @@ struct t8_subelement_scheme_common: - (derived ().underlying_scheme.element_get_level (subelement_to_standalone (subelement)))); } - TSubelementSchemeSpecialization & - derived () noexcept - { - return static_cast (*this); - } + /** Return the derived subelement scheme. + * Const Version. + */ const TSubelementSchemeSpecialization & derived () const noexcept { return static_cast (*this); } -}; -// At the very bottom of t8_subelement_scheme.hxx, AFTER the class definition: -// These must come after the base class definition to break the circular dependency. -// The specializations need the base class complete; the base needs the specializations -// complete only when its methods are instantiated (not when the class is defined). -#include "specializations/t8_scheme_quads.hxx" -#include "specializations/t8_scheme_tri.hxx" + /** Return the derived subelement scheme. + * Non-const Version. + */ + TSubelementSchemeSpecialization & + derived () noexcept + { + return static_cast (*this); + } +}; diff --git a/src/t8_schemes/t8_subelement/t8_subelement_traits.hxx b/src/t8_schemes/t8_subelement/t8_subelement_traits.hxx index 2efbc46ba3..9c5a15b0a7 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement_traits.hxx +++ b/src/t8_schemes/t8_subelement/t8_subelement_traits.hxx @@ -1,3 +1,31 @@ +/* + 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_subelement_traits.hxx + * Traits for subelement schemes. + * Each trait defines the underlying scheme and subelement type associated with the subelement scheme. + * This allows the common subelement implementation in \ref t8_subelement_scheme.hxx to remain generic + * while using the types required by each specialization. + */ #pragma once @@ -7,13 +35,19 @@ #include #include +/** Forward declaration of the quadrilateral subelement scheme. */ struct t8_subelementquad_scheme; +/** Forward declaration of the triangular subelement scheme. */ struct t8_subelementtri_scheme; +/** Traits associating a subelement scheme with its underlying scheme and subelement type. + * \tparam TScheme The subelement scheme for which the traits are defined. + */ template struct t8_subelement_traits; +/** Traits specialization for quadrilateral subelements. */ template <> struct t8_subelement_traits { @@ -21,6 +55,7 @@ struct t8_subelement_traits using SubelementType = t8_subelement_element>; }; +/** Traits specialization for triangular subelements. */ template <> struct t8_subelement_traits { diff --git a/src/t8_schemes/t8_subelement/t8_subelement_type.hxx b/src/t8_schemes/t8_subelement/t8_subelement_type.hxx index 437db706b2..0b6b951f35 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement_type.hxx +++ b/src/t8_schemes/t8_subelement/t8_subelement_type.hxx @@ -21,7 +21,7 @@ */ /** \file t8_subelement_type.hxx - * Definition of the element class of a subelement. A subelement always contains of an underlying element and + * Definition of the element class of a subelement. A subelement always consists of an underlying element and * subelement type and id defining how the underlying element is transitioned into a subelement. */ @@ -29,7 +29,7 @@ /** Definition of the subelement class. A subelement always has an underlying element. * With the type, it is defined if the element is further defined into subelements (e.g. for hanging node resolution). - * Type 0 means no subelement and the subelement is just the underlying element. + * Type 0 means no subelement and the subelement is just the underlying element. * For hanging node resolution, the type encodes which faces are hanging and therefore the number of subelements in * which the element is transitioned. * Accordingly, the subelement id is between 0 and num_subelement - 1. From 5171f821a0b4ce6c979c45376a237f8227060099 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Thu, 13 Aug 2026 11:51:27 +0200 Subject: [PATCH 23/28] clean up remaining files --- src/t8_forest/t8_forest_subelement.cxx | 3 +- .../specializations/t8_scheme_quads.hxx | 310 +++++++----------- .../specializations/t8_scheme_tri.hxx | 292 +++++++++-------- 3 files changed, 269 insertions(+), 336 deletions(-) diff --git a/src/t8_forest/t8_forest_subelement.cxx b/src/t8_forest/t8_forest_subelement.cxx index a7266bb0a7..e217032a96 100644 --- a/src/t8_forest/t8_forest_subelement.cxx +++ b/src/t8_forest/t8_forest_subelement.cxx @@ -102,7 +102,8 @@ t8_remove_hanging_nodes_callback ([[maybe_unused]] t8_forest_t forest, t8_forest t8_forest_leaf_face_neighbors (forest_from, which_tree, elements[0], &neighbors, iface, &dual_faces_internal, &num_neighbors, &neighids, &neigh_class); if (num_neighbors > 1) { - // Store in correct cell of the binary format. We encode it as f0 -> bit 0, ..., f_{n-1} -> bit (num_faces-1). + /* Store in correct cell of the binary format. We encode it as f0 -> bit (num_faces-1), ..., f_{n-1} -> bit 0. + * This means (f0 f1 ... f_{n-1}). */ subelement_type += 1 << ((num_faces - 1) - iface); } diff --git a/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx b/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx index bc7adf75b3..2e825f7628 100644 --- a/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx +++ b/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx @@ -26,7 +26,7 @@ * code over the quad's four faces indicating which of them are hanging; type 0 means the * element is not a subelement (it is just the underlying standalone quad). This file only * implements the quad-specific logic. The functionality shared by all subelement schemes - * lives in t8_subelement_scheme.hxx. + * lives in \ref t8_subelement_scheme.hxx. */ #pragma once @@ -48,16 +48,18 @@ * hanging code 15 is excluded. * * \verbatim - f0 1 + f3 1 x - - x - - x x - - x - - x | | | \ | / | - | | | \ | / | | f3 | f2 | f1 | f0 | - f3 x | f2 --> 1 x - - x | 0 | 1 | 0 | 0 | 1 | = 9 + | | | \ | / | | f0 | f1 | f2 | f3 | + f0 x | f1 --> 1 x - - x | 0 | 1 | 0 | 0 | 1 | = 9 | | | / \ | | elem | | / \ | binary code following the face x - - - - - x x - - - - - x enumeration (here faces f0 and f3 - f1 0 are hanging) + f2 0 are hanging) * \endverbatim + * Also have a look at \ref vertex_coords_of_subelement for the definition of the subelement ids for quads and the + * order of vertices. */ struct t8_subelementquad_scheme: public t8_subelement_scheme_common { @@ -121,13 +123,13 @@ struct t8_subelementquad_scheme: public t8_subelement_scheme_common, 3> vertex_coords; + vertex_coords_of_subelement (elem, vertex_coords); /* Normalize to [0,1] by dividing by root length. */ const double root_len = (1 << T8_ELEMENT_MAXLEVEL[T8_ECLASS_QUAD]); - double n0[2] = { v0[0] / root_len, v0[1] / root_len }; - double n1[2] = { v1[0] / root_len, v1[1] / root_len }; - double n2[2] = { v2[0] / root_len, v2[1] / root_len }; + const double n0[2] = { vertex_coords[0][0] / root_len, vertex_coords[0][1] / root_len }; + const double n1[2] = { vertex_coords[1][0] / root_len, vertex_coords[1][1] / root_len }; + const double n2[2] = { vertex_coords[2][0] / root_len, vertex_coords[2][1] / root_len }; for (size_t coord = 0; coord < num_coords; ++coord) { const double u = ref_coords[coord * 2 + 0]; @@ -235,121 +235,109 @@ struct t8_subelementquad_scheme: public t8_subelement_scheme_commonelement_is_valid (elem)); - T8_ASSERT (this->element_is_subelement (elem)); - const auto *subelement = this->as_subelement (elem); - T8_ASSERT (vertex >= 0 && vertex < subelement_get_num_corners (subelement)); + return ((type >> ((T8_ELEMENT_NUM_FACES[T8_ECLASS_QUAD] - 1) - iface)) & 1u) != 0u; + } - /* Get the length of the current quadrant. */ - int len = this->parent_element_get_len (subelement); + /** For each parent face, its two vertexs in clockwise order. */ + static constexpr int face_to_clockwise_vertex[4][2] = { { 0, 2 }, { 3, 1 }, { 1, 0 }, { 2, 3 } }; - /* Compute the x and y coordinates of subelement vertices, depending on the subelement type, id and vertex number - * (faces enumerated clockwise, starting at the center of the transition cell): - * - * f1 V1 + /** Compute the integer coordinates of all three vertices of a triangular subelement. + * We use the following order of subelements in a quad: + * Subelement ids are counted clockwise, starting with the (lower) left subelement with id 0. + * The vertices are enumerated clockwise, starting at the center of the transition cell. + * Therefore vertex 0 is always the center of the transition cell. + * For example: + * \verbatim + * f3 V1 * x - - - - - x x * | \ 2 / | / | * | 1 \ / 3 | / 3 | - * f0 x - - + - - x f2 --> + - - x + * f0 x - - + - - x f1 --> + - - x * | 0 / | \ 4 | V0 V2 * | / 6 | 5 \ | * x - - x - - x - * f3 - * - * In this example, the "location array" would contain the values [2, 1, 1] - * (second face, split, first subelement at this face). + * f2 + * \endverbatim + * \param [in] elem The subelement. + * \param [out] vertex_coords The three (x, y) integer vertex coordinates of the subelement. */ + void + vertex_coords_of_subelement (const t8_element_t *elem, + std::array, 3> &vertex_coords) const noexcept + { + T8_ASSERT (this->element_is_valid (elem)); + T8_ASSERT (this->element_is_subelement (elem)); + const auto *subelement = this->as_subelement (elem); - /* Get location information of the given subelement. */ - const std::array location = element_get_location_of_subelement (elem); - - /* Face number, the subelement is adjacent to. */ - int face_number = location[0]; - /* = 1, if the adjacent face is split and = 0, if not. */ - int split = location[1]; - /* = 0, if the subelement is the first (of two) subelements, at the adjacent face and = 1 if it is the second. */ - int sub_face_id = location[2]; + /* The length of the parent quadrant and its lower left corner. */ + const int len = this->parent_element_get_len (subelement); + const int origin[2] = { subelement->element.coords[0], subelement->element.coords[1] }; + // Fill location information. + const std::array location = element_get_location_of_subelement (elem); + const int face_number = location[0]; + const int split = location[1]; + const int sub_face_id = location[2]; /* Check, whether the get_location function provides meaningful location data. */ T8_ASSERT (face_number == 0 || face_number == 1 || face_number == 2 || face_number == 3); T8_ASSERT ((split == 0 && sub_face_id == 0) || (split == 1 && (sub_face_id == 0 || sub_face_id == 1))); - coords[0] = subelement->element.coords[0]; - coords[1] = subelement->element.coords[1]; - - /* Use the location data to determine vertex coordinates. */ - if (vertex == 0) { /* Vertex 0 (the first vertex always equals the center of the element). */ - coords[0] += len / 2; - coords[1] += len / 2; - } /* End of vertex 0. */ - else if (vertex == 1) { /* Vertex 1. */ - if (face_number == 0) { - if (split && sub_face_id) { - coords[1] += len / 2; - } - } - else if (face_number == 1) { - coords[1] += len; - if (split && sub_face_id) { - coords[0] += len / 2; - } - } - else if (face_number == 2) { - coords[0] += len; - coords[1] += len; - if (split && sub_face_id) { - coords[1] -= len / 2; - } - } - else { - coords[0] += len; - if (split && sub_face_id) { - coords[0] -= len / 2; - } - } - } /* End of vertex 1. */ - else if (vertex == 2) { /* Vertex 2. */ - if (face_number == 0) { - coords[1] += len; - if (split && (sub_face_id == 0)) { - coords[1] -= len / 2; - } - } - else if (face_number == 1) { - coords[0] += len; - coords[1] += len; - if (split && (sub_face_id == 0)) { - coords[0] -= len / 2; - } - } - else if (face_number == 2) { - coords[0] += len; - if (split && (sub_face_id == 0)) { - coords[1] += len / 2; - } - } - else { - if (split && (sub_face_id == 0)) { - coords[0] += len / 2; - } - } - } /* End of vertex 2. */ + /** The vertex offsets of a quad (as multiples of its edge length). */ + static constexpr int vertex_offset[4][2] = { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 } }; + /** Function lambda to get the vertex coordinates of the parent element. */ + const auto vertex_coords_parent = [&] (const int vertex) { + return std::array { origin[0] + len * vertex_offset[vertex][0], + origin[1] + len * vertex_offset[vertex][1] }; + }; + /** Function lambda to get the midpoint of a face of the parent element. */ + const auto vertex_midpoint_coords_parent = [&] (const int face) { + const int face_vertex1 = face_to_clockwise_vertex[face][0]; + const int face_vertex2 = face_to_clockwise_vertex[face][1]; + return std::array { + origin[0] + (len * (vertex_offset[face_vertex1][0] + vertex_offset[face_vertex2][0])) / 2, + origin[1] + (len * (vertex_offset[face_vertex1][1] + vertex_offset[face_vertex2][1])) / 2 + }; + }; + + /* Vertex 0 is always the centre of the transition cell. */ + vertex_coords[0] = { origin[0] + len / 2, origin[1] + len / 2 }; + /* Vertices 1 and 2 are the face's two clockwise vertices, unless the face is split: then one of + * them is replaced by the face midpoint. */ + const std::array vertex_start = vertex_coords_parent (face_to_clockwise_vertex[face_number][0]); + const std::array vertex_end = vertex_coords_parent (face_to_clockwise_vertex[face_number][1]); + const std::array face_midpoint = vertex_midpoint_coords_parent (face_number); + + vertex_coords[1] = (split && sub_face_id) ? face_midpoint : vertex_start; + vertex_coords[2] = (split && !sub_face_id) ? face_midpoint : vertex_end; } - /** Determine the location of a subelement within its transition cell. + /** Determine the location of a subelement within its transition cell. * \param [in] elem The subelement. * \return Three values: * - the face of the parent quad the subelement is adjacent to ({0,1,2,3}) * - whether that face is split in half ({0,1}) * - and whether it is the first or second subelement at the face ({0,1}). + * + * For a subelement of type 14 the location array is {1,1,0} for id 3 and {2,1,1} for id 6. + * \verbatim + * f3 V1 + * x - - - - - x x + * | \ 2 / | / | + * | 1 \ / 3 | / 3 | + * f0 x - - + - - x f1 --> + - - x + * | 0 / | \ 4 | V0 V2 + * | / 6 | 5 \ | + * x - - x - - x + * f2 + * \endverbatim */ std::array element_get_location_of_subelement (const t8_element_t *elem) const @@ -357,97 +345,31 @@ struct t8_subelementquad_scheme: public t8_subelement_scheme_commonelement_is_subelement (elem)); T8_ASSERT (this->element_is_valid (elem)); const auto *subelement = this->as_subelement (elem); - const int type = subelement->subelement_type; - - /* Example: Consider the following subelement of type 13: - * - * f3 1 - * x - - x - - x x - - x - - x - * | | | \ 2 | 3 / | - * | | | 1 \ | / 4 | - * f0 x x f1 --> 1 x - - x - - x 1 - * | | | 0 / \ 5 | - * | elem | | / 6 \ | - * + - - - - - x x - - - - - x - * f2 0 - * - * The binary representation of the type is (1 1 0 1) - * (f0 f1 f2 f3) converting to 13 in decimal format. - * The location array for subelement with id 3 would therefore be: {3, 1, 1} - * (upper face f3 of the parent quad, split = true, second subelement at f3). - */ - - /* 1) Convert the subelement type from a decimal to a binary representation. - * We store the binary representation bitwise in an array. - */ - constexpr int num_faces_quad = T8_ELEMENT_NUM_FACES[T8_ECLASS_QUAD]; - int binary_array[num_faces_quad] = {}; - for (int i = 0; i < num_faces_quad; i++) { - binary_array[(num_faces_quad - 1) - i] = (type & (1 << i)) >> i; - } - - /* 2) rearrange the binary representation to be in clockwise order */ - int binary_array_temp[num_faces_quad] = {}; - - int j; - - for (j = 0; j < num_faces_quad; j++) { /* copying the binary array */ - binary_array_temp[j] = binary_array[j]; - } - const int subelement_location_to_parent_face[4] = { 0, 3, 1, 2 }; - for (j = 0; j < num_faces_quad; j++) { /* bringing the entries of binary array into clockwise order */ - binary_array[j] = binary_array_temp[subelement_location_to_parent_face[j]]; - } - - /* 3) use the rearranged binary representation, and the sub_id to determine the location of the subelement and store these information in an array */ - /* 3.1) location[0] -> the face_number, the subelement is adjacent to */ - /* 3.2) location[1] -> if the face is split or not */ - /* 3.3) location[2] -> if the subelement is the first or second subelement of the face (always the first, if the face is not split) */ - int num_subelements = element_get_number_of_subelements (type); - T8_ASSERT (subelement->subelement_id < num_subelements); - + const unsigned type = static_cast (subelement->subelement_type); const int sub_id = subelement->subelement_id; - int sub_face_id = 0; - int face_number = 0; + T8_ASSERT (sub_id < element_get_number_of_subelements (static_cast (type))); + /** The parent face at each clockwise position, starting at the left face: left (f0), top (f3), + * right (f1), bottom (f2). Subelement ids are assigned in this order. */ + const int clockwise_ordering_to_parent_face[4] = { 0, 3, 1, 2 }; + + /* Walk the faces in clockwise order (the order in which subelement ids are assigned). Each face + * contributes one subelement, or two if it is split. The subelement lies at the first face whose + * running count exceeds sub_id. */ + int clockwise_face = 0; int split = 0; - - int cum_neigh_array[num_faces_quad] = {}; - - /* construct a cumulative array of the number of neighbors from face 0 to face 3 */ - cum_neigh_array[0] = binary_array[0] + 1; - cum_neigh_array[1] = cum_neigh_array[0] + binary_array[1] + 1; - cum_neigh_array[2] = cum_neigh_array[1] + binary_array[2] + 1; - cum_neigh_array[3] = cum_neigh_array[2] + binary_array[3] + 1; - - /* 3.1) we can use the cumulative array to determine the face number of the given subelement */ - if (sub_id < cum_neigh_array[0]) { - face_number = 0; - } - else { - for (int k = 0; k < num_faces_quad - 1; ++k) { - if (sub_id >= cum_neigh_array[k] && sub_id < cum_neigh_array[k + 1]) { - face_number = k + 1; - break; - } + int subelements_up_to = 0; // The current clockwise face iface contains subelements with ids < this number. + for (clockwise_face = 0; clockwise_face < T8_ELEMENT_NUM_FACES[T8_ECLASS_QUAD]; ++clockwise_face) { + split = face_is_split (type, clockwise_ordering_to_parent_face[clockwise_face]); + subelements_up_to += split + 1; + if (sub_id < subelements_up_to) { + break; } - } - - /* 3.2) determine, whether the face is split or not */ - if (binary_array[face_number] == 0) { - split = 0; /* the face is not split */ - } - else { - split = 1; /* the face is split */ - } - - /* 3.3) determine, whether the subelement is the first or the second subelement at the face */ - if (sub_id + 1 == cum_neigh_array[face_number] && split == 1) { - sub_face_id = 1; /* second subelement */ - } - else { - sub_face_id = 0; /* first subelement */ - } + } // Now split and the clockwise face are set correctly. + /* On a split face the two subelements take the last two ids of its range. Determine which one. + * It is the second subelement if sub_id + 1 == subelements_up_to (as this is the last sub id that is contained in + * this face), otherwise it is the first. (and 0=false if not split). */ + const int sub_face_id = split && (sub_id + 1 == subelements_up_to); - return { face_number, split, sub_face_id }; + return { clockwise_ordering_to_parent_face[clockwise_face], split, sub_face_id }; } }; diff --git a/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx b/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx index c6e50f9a1c..eca36c9b91 100644 --- a/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx +++ b/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx @@ -20,30 +20,46 @@ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** \file TODO */ +/** \file t8_scheme_tri.hxx + * Subelement scheme specialization for triangular elements. A triangle is transitioned into + * triangular subelements (e.g. to resolve hanging nodes). The subelement type is a binary code + * over the triangle's three faces indicating which of them are hanging; type 0 means the element + * is not a subelement (it is just the underlying standalone triangle). This file implements only + * the triangle-specific logic. The functionality shared by all subelement schemes lives in + * t8_subelement_scheme.hxx. + */ #pragma once +#include #include #include "../t8_subelement_scheme.hxx" #include #include +/** Maximum subelement type. The subelement type ranges from 0 (= no subelement, normal standalone + * triangle) to 6. Type 7 would mean in binary representation that all faces are hanging, but in + * that case the element is refined by the standard recursive refinement instead. */ #define T8_TRI_MAX_SUBELEMENT_TYPE 6 -/** ID is as follow: always start with f0, the 2 with faces sharing f0, first also sharing v1, then v2. - * Then f1, first sharing v0 then v2 ,... +/** Subelement scheme for triangular elements. + * A triangle is transitioned into triangular subelements. The subelement type encodes which of the + * triangle's three faces are hanging as a binary code (one bit per face). Type 0 means the element + * is not a subelement and is just the underlying standalone triangle. Valid types are 1..6; the + * all-faces-hanging code 7 is excluded, since that is a normal recursive refinement. + * + * Please have a look at \ref vertex_coords_of_subelement for the definition of the subelement ids for triangles. */ - struct t8_subelementtri_scheme: public t8_subelement_scheme_common { public: - using TUnderlyingScheme = typename t8_subelement_traits:: - UnderlyingScheme; /**< The used recursive scheme for the underlying elements. Every time we do not need the subelement logic, the scheme calls the functionality of this underlying scheme. */ + /** The recursive scheme used for the underlying (standalone) triangle elements. Whenever the + * subelement logic is not needed, the scheme forwards to this underlying scheme. */ + using TUnderlyingScheme = typename t8_subelement_traits::UnderlyingScheme; + /** The subelement element type (an underlying element plus a subelement type and id). */ using TSubelementType = typename t8_subelement_traits::SubelementType; - using Base = t8_subelement_scheme_common; - TUnderlyingScheme underlying_scheme {}; + TUnderlyingScheme underlying_scheme {}; /**< Instance of the underlying standalone scheme. */ /** Compute the number of corners of an element. * \param [in] elem The subelement. @@ -96,8 +112,8 @@ struct t8_subelementtri_scheme: public t8_subelement_scheme_commonas_subelement (elem); + TSubelementType **subelements = reinterpret_cast (c); const int num_subelements = this->element_get_number_of_subelements (type); T8_ASSERT (type >= 1 && type <= T8_TRI_MAX_SUBELEMENT_TYPE); @@ -160,17 +179,23 @@ struct t8_subelementtri_scheme: public t8_subelement_scheme_common, 3> vertex_coords; vertex_coords_of_subelement (elem, vertex_coords); - /* Normalize to [0,1] by dividing by root length */ + /* Normalize to [0,1] by dividing by root length. */ const double root_len = (1 << T8_ELEMENT_MAXLEVEL[T8_ECLASS_TRIANGLE]); double n0[2] = { vertex_coords[0][0] / root_len, vertex_coords[0][1] / root_len }; double n1[2] = { vertex_coords[1][0] / root_len, vertex_coords[1][1] / root_len }; @@ -180,17 +205,68 @@ struct t8_subelementtri_scheme: public t8_subelement_scheme_common n0 - * (1,0) -> n1 - * (1,1) -> n2 - */ + /* Mapping: (0,0) -> n0, (1,0) -> n1, (1,1) -> n2. */ out_coords[coord * 2 + 0] = (1.0 - u) * n0[0] + (u - v) * n1[0] + v * n2[0]; out_coords[coord * 2 + 1] = (1.0 - u) * n0[1] + (u - v) * n1[1] + v * n2[1]; } } private: + /** Check whether a given face of the parent triangle is hanging. + * \param [in] type The subelement type (binary code over the faces, f0 is the most significant bit). + * \param [in] iface The face to check. + * \return True if \a iface is hanging for \a type. + */ + static bool + face_is_hanging (const unsigned type, const int iface) noexcept + { + // Get the bit corresponding to iface. + // If that bit is 1, the face is hanging. + // 1u is for lowest bit extraction. + return ((type >> ((T8_ELEMENT_NUM_FACES[T8_ECLASS_TRIANGLE] - 1) - iface)) & 1u) != 0u; + } + + /** Compute the integer coordinates of the three vertices of a triangular subelement. + * + * For this, we first define the order of the subelements and the subelement vertices: + * All subelements of a transition cell share one common point, which we define as \a m_c, and each subelement is + * spanned by \a m_c together with two consecutive points of a \b path along the boundary of the + * parent triangle. This defines the numbering completely: + * - The \a main \a face is the lowest-indexed hanging face \a fA. + * - The common point \a m_c is the midpoint of the main face. Every subelement contains it. + * - Let \a v_a < \a v_b be the two end vertices of the main face fA. The \b path walks the parent + * boundary from \a v_a to \a v_b the way that does not traverse the main face fA (so the other way around + * such that we go over all other faces). That walk passes through exactly one other vertex, \a v_c, and + * traverses exactly two faces: the edge (\a v_a, \a v_c) and the edge (\a v_c, \a v_b). The midpoint of + * each traversed face that is hanging is inserted at its position on the walk. + * - The subelement with id \a i is then defined through the vertices: + * vertex 0 = \a m_c, vertex 1 = path[ \a i ], vertex 2 = path[ \a i+1 ]. + * + * Since the path has (number of hanging faces + 2) points, there are (number of hanging faces + 1) + * subelements, which matches \ref element_get_number_of_subelements. No case distinction is needed: + * one hanging face yields a path of three points, two hanging faces a path of four. + * + * \verbatim + f2 hanging f2 hanging + one hanging face (here f2) two hanging faces (here f1 and f2) (not nicely displayed) + + v2 v2 + /| \ / \ + / | \ / 2 \ + f1 / | \ f0 M1 x – \ f0 + / | \ / | \ \ + / 0 | 1 \ / 0 \ –– \ + / | \ / | 1 \\ + v0 ---- M2----- v1 v0 ---- M2------ v1 + f2 f2 + + main face = f2, \a m_c = M2 main face = f1 (lowest), \a m_c = M1 + path: v0 -> v2 -> v1 path: v0 -> M2 -> v1 -> v2 + * \endverbatim + * + * \param [in] elem The subelement. + * \param [out] vertex_coords The three (x, y) integer vertex coordinates of the subelement. + */ void vertex_coords_of_subelement (const t8_element_t *elem, std::array, 3> &vertex_coords) const noexcept @@ -200,126 +276,60 @@ struct t8_subelementtri_scheme: public t8_subelement_scheme_commonas_subelement (elem); const unsigned type = static_cast (subelement->subelement_type); const unsigned id = static_cast (subelement->subelement_id); - - const auto num_ones = std::popcount (type); - - T8_ASSERT (num_ones == 1 || num_ones == 2); - - std::array x0_coords_parent; - underlying_scheme.element_get_vertex_integer_coords (this->subelement_to_standalone (subelement), 0, - x0_coords_parent.data ()); - std::array x1_coords_parent; - underlying_scheme.element_get_vertex_integer_coords (this->subelement_to_standalone (subelement), 1, - x1_coords_parent.data ()); - std::array x2_coords_parent; - underlying_scheme.element_get_vertex_integer_coords (this->subelement_to_standalone (subelement), 2, - x2_coords_parent.data ()); - - // Just to initialize - std::fill (vertex_coords.begin (), vertex_coords.end (), x0_coords_parent); - /** If we have only one hanging face, we rotate the triangle such that the hanging face is at the bottom and count - * as follows: - * A With order of vertices for T1: B,M,A - * /|\ T2. M,C,A - * / | \ - * / | \ - * /T1 | T2\ - * /____|____\ - * B M C - */ - if (num_ones == 1) { - // Binary representation of type is: ( hanging_face(0), hanging_face(1), hanging_face(2) ), - // where hanging_face i is 1 if face i is hanging and 0 otherwise. - const int hanging_face = (T8_ELEMENT_NUM_FACES[T8_ECLASS_TRIANGLE] - 1) - std::countr_zero (type); - switch (hanging_face) { - case 0: - vertex_coords[2][0] = 0.5 * (x1_coords_parent[0] + x2_coords_parent[0]); - vertex_coords[2][1] = 0.5 * (x1_coords_parent[1] + x2_coords_parent[1]); - if (id == 0) { - vertex_coords[1] = x1_coords_parent; - } - if (id == 1) { - vertex_coords[1] = x2_coords_parent; - } - break; - case 1: - vertex_coords[0] = x1_coords_parent; - vertex_coords[2][0] = 0.5 * (x0_coords_parent[0] + x2_coords_parent[0]); - vertex_coords[2][1] = 0.5 * (x0_coords_parent[1] + x2_coords_parent[1]); - // if (id == 0) { - // vertex_coords[1] = x0_coords_parent; - // } - if (id == 1) { - vertex_coords[1] = x2_coords_parent; - } - break; - case 2: - vertex_coords[0] = x2_coords_parent; - vertex_coords[2][0] = 0.5 * (x0_coords_parent[0] + x1_coords_parent[0]); - vertex_coords[2][1] = 0.5 * (x0_coords_parent[1] + x1_coords_parent[1]); - // if (id == 0) { - // vertex_coords[1] = x0_coords_parent; - // } - if (id == 1) { - vertex_coords[1] = x1_coords_parent; - } - break; - } + const int num_hanging_faces = std::popcount (type); + T8_ASSERT (num_hanging_faces == 1 || num_hanging_faces == 2); + + /* The corners of the parent triangle. */ + std::array, 3> parent_coords; + for (int icorner = 0; icorner < T8_ELEMENT_NUM_CORNERS[T8_ECLASS_TRIANGLE]; ++icorner) { + underlying_scheme.element_get_vertex_integer_coords (this->subelement_to_standalone (subelement), icorner, + parent_coords[icorner].data ()); } - if (num_ones == 2) { - unsigned copy_type = type; - copy_type &= (copy_type - 1); - const int hanging_face1 = (T8_ELEMENT_NUM_FACES[T8_ECLASS_TRIANGLE] - 1) - std::countr_zero (copy_type); - const int hanging_face2 = (T8_ELEMENT_NUM_FACES[T8_ECLASS_TRIANGLE] - 1) - std::countr_zero (type); - - switch (hanging_face1) { - case 0: - if (hanging_face2 == 1) { - vertex_coords[2][0] = 0.5 * (x1_coords_parent[0] + x2_coords_parent[0]); - vertex_coords[2][1] = 0.5 * (x1_coords_parent[1] + x2_coords_parent[1]); - if (id == 0) { - vertex_coords[1] = x1_coords_parent; - } - if ((id == 1) || (id == 2)) { - vertex_coords[1][0] = 0.5 * (x0_coords_parent[0] + x2_coords_parent[0]); - vertex_coords[1][1] = 0.5 * (x0_coords_parent[1] + x2_coords_parent[1]); - } - if (id == 2) { - vertex_coords[0] = x2_coords_parent; - } - } - if (hanging_face2 == 2) { - vertex_coords[2][0] = 0.5 * (x1_coords_parent[0] + x2_coords_parent[0]); - vertex_coords[2][1] = 0.5 * (x1_coords_parent[1] + x2_coords_parent[1]); - if (id == 0) { - vertex_coords[0] = x1_coords_parent; - } - if ((id == 0) || (id == 1)) { - vertex_coords[1][0] = 0.5 * (x0_coords_parent[0] + x1_coords_parent[0]); - vertex_coords[1][1] = 0.5 * (x0_coords_parent[1] + x1_coords_parent[1]); - } - if (id == 2) { - vertex_coords[0] = x2_coords_parent; - } - } - break; - case 1: - T8_ASSERT (hanging_face2 == 2); - if (id == 0) { - vertex_coords[2] = x2_coords_parent; - } - if ((id == 1) || (id == 0)) { - vertex_coords[0] = x1_coords_parent; - } - vertex_coords[1][0] = 0.5 * (x0_coords_parent[0] + x2_coords_parent[0]); - vertex_coords[1][1] = 0.5 * (x0_coords_parent[1] + x2_coords_parent[1]); - if ((id == 1) || (id == 2)) { - vertex_coords[2][0] = 0.5 * (x0_coords_parent[0] + x1_coords_parent[0]); - vertex_coords[2][1] = 0.5 * (x0_coords_parent[1] + x1_coords_parent[1]); - } - break; - } + /* Lambda for the midpoints of a face of the parent triangle. */ + const auto face_midpoint = [&parent_coords] (const int iface) { + const std::array &first = parent_coords[t8_face_vertex_to_tree_vertex[T8_ECLASS_TRIANGLE][iface][0]]; + const std::array &second = parent_coords[t8_face_vertex_to_tree_vertex[T8_ECLASS_TRIANGLE][iface][1]]; + return std::array { (first[0] + second[0]) / 2, (first[1] + second[1]) / 2 }; + }; + + /* The main face is the lowest-indexed hanging face; m_c is its midpoint. */ + int main_face = 0; + while (!face_is_hanging (type, main_face)) { + ++main_face; } + T8_ASSERT (main_face < T8_ELEMENT_NUM_FACES[T8_ECLASS_TRIANGLE]); + const std::array m_c = face_midpoint (main_face); + + /* Build the path: Walk the parent edges from the first to the second end vertex of the main face, the way + * that does not traverse the main face itself. + */ + const int start_vertex = t8_face_vertex_to_tree_vertex[T8_ECLASS_TRIANGLE][main_face][0]; + const int end_vertex = t8_face_vertex_to_tree_vertex[T8_ECLASS_TRIANGLE][main_face][1]; + + // The path has maximal length 4 for 2 hanging faces. + // For the path we use the property of the triangle enumeration that the face has always the id of the opposite + // vertex (so the only vertex it is not adjacent to). Therefore we can use the face ids to get the midpoints of the hanging faces. + std::array, 4> path; + int path_length = 0; + path[path_length++] = parent_coords[start_vertex]; + // The next face to traverse is the face opposite to the end vertex. Therefore it has the id "end_vertex". + if (face_is_hanging (type, end_vertex)) { + path[path_length++] = face_midpoint (end_vertex); + } + // Next vertex has the id of the main face. + path[path_length++] = parent_coords[main_face]; + if (face_is_hanging (type, start_vertex)) { + path[path_length++] = face_midpoint (start_vertex); + } + path[path_length++] = parent_coords[end_vertex]; + + /* Path length should be 4 for 2 hanging faces and 3 for 1. */ + T8_ASSERT (path_length == num_hanging_faces + 2); + T8_ASSERT (static_cast (id) + 1 < path_length); + + vertex_coords[0] = m_c; + vertex_coords[1] = path[id]; + vertex_coords[2] = path[id + 1]; } }; From 7b6d1ebdbc8838d1c5b153b6206333439ceb613c Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Thu, 13 Aug 2026 12:01:04 +0200 Subject: [PATCH 24/28] add global has subelements function --- example/subelements/t8_hanging_nodes_shock.cxx | 6 +++--- src/t8_forest/t8_forest_subelement.cxx | 16 ++++++++++++++++ src/t8_forest/t8_forest_subelement.hxx | 7 +++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/example/subelements/t8_hanging_nodes_shock.cxx b/example/subelements/t8_hanging_nodes_shock.cxx index 46d5a18dfb..9745b91437 100644 --- a/example/subelements/t8_hanging_nodes_shock.cxx +++ b/example/subelements/t8_hanging_nodes_shock.cxx @@ -194,7 +194,7 @@ main (int argc, char **argv) /* --- Adapt the forest: refine near the first circle, creating hanging nodes. --- */ forest = t8_adapt_forest (forest); - std::cout << "Subelements before removing: " << t8_forest_has_local_subelements (forest) << std::endl; + std::cout << "Subelements before removing: " << t8_forest_has_global_subelements (forest) << std::endl; prefix = "t8_adapted1"; t8_forest_write_vtk (forest, prefix); t8_global_productionf (" [subelements] Wrote adapted forest with hanging nodes to vtu files: %s*\n", prefix); @@ -207,7 +207,7 @@ main (int argc, char **argv) /* --- Resolve hanging nodes by transitioning elements into subelements. --- */ forest = t8_forest_remove_hanging_nodes (forest); - std::cout << "Subelements after removing: " << t8_forest_has_local_subelements (forest) << std::endl; + std::cout << "Subelements after removing: " << t8_forest_has_global_subelements (forest) << std::endl; const char *prefix_without_hanging_nodes = "t8_resolved_hanging_nodes1"; t8_forest_write_vtk (forest, prefix_without_hanging_nodes); t8_global_productionf (" [subelements] Wrote adapted forest with resolved hanging nodes to vtu files: %s*\n", @@ -216,7 +216,7 @@ main (int argc, char **argv) /* --- Discard the subelements to recover a plain, recursively refined forest. --- */ /* This is the inverse of the previous step and is required before adapting again. */ forest = t8_forest_discard_subelements (forest); - std::cout << "Subelements removed: " << t8_forest_has_local_subelements (forest) << std::endl; + std::cout << "Subelements removed: " << t8_forest_has_global_subelements (forest) << std::endl; const char *prefix_removed_sub = "t8_discarded_subelements1"; t8_forest_write_vtk (forest, prefix_removed_sub); t8_global_productionf (" [subelements] Wrote adapted forest with discarded subelements to vtu files: %s*\n", diff --git a/src/t8_forest/t8_forest_subelement.cxx b/src/t8_forest/t8_forest_subelement.cxx index e217032a96..f1f1a87f09 100644 --- a/src/t8_forest/t8_forest_subelement.cxx +++ b/src/t8_forest/t8_forest_subelement.cxx @@ -168,3 +168,19 @@ t8_forest_has_local_subelements (const t8_forest_t forest) } return false; } + +bool +t8_forest_has_global_subelements (const t8_forest_t forest) +{ + /* Extract the MPI communicator from the forest */ + sc_MPI_Comm comm = t8_forest_get_mpicomm (forest); + + /* Convert boolean condition to MPI-compatible integer */ + const int local = t8_forest_has_local_subelements (forest) ? 1 : 0; + int global = 0; + + const int mpiret = sc_MPI_Allreduce (&local, &global, 1, sc_MPI_INT, sc_MPI_LOR, comm); + SC_CHECK_MPI (mpiret); + + return global != 0; +} diff --git a/src/t8_forest/t8_forest_subelement.hxx b/src/t8_forest/t8_forest_subelement.hxx index bc6b1a05a0..1222a290c1 100644 --- a/src/t8_forest/t8_forest_subelement.hxx +++ b/src/t8_forest/t8_forest_subelement.hxx @@ -54,3 +54,10 @@ t8_forest_discard_subelements (t8_forest_t forest); */ bool t8_forest_has_local_subelements (const t8_forest_t forest); + +/** Check if a forest contains subelements globally. + * \param [in] forest The forest to be checked. + * \return true if there are subelements in the forest, false otherwise. + */ +bool +t8_forest_has_global_subelements (const t8_forest_t forest); From 3354b223b6b8db18e1a51beb30645c1db30c9bad Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Thu, 13 Aug 2026 15:54:35 +0200 Subject: [PATCH 25/28] Added test. --- .../subelements/t8_hanging_nodes_shock.cxx | 4 +- src/t8_cmesh/t8_cmesh_examples.cxx | 14 +-- src/t8_cmesh/t8_cmesh_examples.h | 7 +- test/CMakeLists.txt | 1 + test/t8_forest/t8_gtest_subelement.cxx | 91 +++++++++++++++++++ test/t8_gtest_adapt_callbacks.cxx | 12 +++ test/t8_gtest_adapt_callbacks.hxx | 18 ++++ 7 files changed, 136 insertions(+), 11 deletions(-) create mode 100644 test/t8_forest/t8_gtest_subelement.cxx diff --git a/example/subelements/t8_hanging_nodes_shock.cxx b/example/subelements/t8_hanging_nodes_shock.cxx index 9745b91437..8574994f30 100644 --- a/example/subelements/t8_hanging_nodes_shock.cxx +++ b/example/subelements/t8_hanging_nodes_shock.cxx @@ -184,7 +184,9 @@ main (int argc, char **argv) /* --- Setup: build the cmesh and a uniform forest. --- */ /* Hybrid 2D hypercube: a mesh containing both quad and triangle trees. */ - t8_cmesh_t cmesh = t8_cmesh_new_2D_hypercube_hybrid (comm); + t8_cmesh_t cmesh; + t8_cmesh_init (&cmesh); + t8_cmesh_new_2D_hypercube_hybrid (cmesh, comm); /* Uniform forest using the subelement scheme (required for hanging-node resolution). */ const int level = 0; t8_forest_t forest = t8_forest_new_uniform (cmesh, t8_scheme_new_subelement (), level, 0, comm); diff --git a/src/t8_cmesh/t8_cmesh_examples.cxx b/src/t8_cmesh/t8_cmesh_examples.cxx index 28b878236b..ef4d49c92e 100644 --- a/src/t8_cmesh/t8_cmesh_examples.cxx +++ b/src/t8_cmesh/t8_cmesh_examples.cxx @@ -681,9 +681,14 @@ t8_cmesh_new_hypercube_hybrid (t8_cmesh_t cmesh, sc_MPI_Comm comm, int periodic) t8_cmesh_commit (cmesh, comm); } -t8_cmesh_t -t8_cmesh_new_2D_hypercube_hybrid (sc_MPI_Comm comm) +void +t8_cmesh_new_2D_hypercube_hybrid (t8_cmesh_t cmesh, sc_MPI_Comm comm) { + + T8_ASSERT (cmesh != NULL); + T8_ASSERT (t8_cmesh_is_initialized (cmesh)); + T8_ASSERT (!t8_cmesh_is_committed (cmesh, 0)); + T8_ASSERT (t8_cmesh_stash_is_empty (cmesh)); { /* clang-format off */ double vertices[60] = { /* All vertices of all trees. Partly duplicated */ @@ -709,8 +714,6 @@ t8_cmesh_new_2D_hypercube_hybrid (sc_MPI_Comm comm) }; /* clang-format on */ - t8_cmesh_t cmesh; - /* * This is how the cmesh looks like. The numbers are the tree numbers: * @@ -725,7 +728,6 @@ t8_cmesh_new_2D_hypercube_hybrid (sc_MPI_Comm comm) * +---+---+ */ - t8_cmesh_init (&cmesh); /* Use linear geometry */ t8_cmesh_register_geometry (cmesh); @@ -751,8 +753,6 @@ t8_cmesh_new_2D_hypercube_hybrid (sc_MPI_Comm comm) t8_cmesh_set_join (cmesh, 4, 5, 1, 2, 0); t8_cmesh_commit (cmesh, comm); - - return cmesh; } } diff --git a/src/t8_cmesh/t8_cmesh_examples.h b/src/t8_cmesh/t8_cmesh_examples.h index 3903456e35..a50362732d 100644 --- a/src/t8_cmesh/t8_cmesh_examples.h +++ b/src/t8_cmesh/t8_cmesh_examples.h @@ -215,11 +215,12 @@ void t8_cmesh_new_hypercube_hybrid (t8_cmesh_t cmesh, sc_MPI_Comm comm, int periodic); /** Construct a unit square of two quads and four triangles. + * \param [in,out] cmesh An initialized, but not committed cmesh, as created by \ref t8_cmesh_init. + * Filled and committed in place. * \param [in] comm The mpi communicator to use. - * \return A valid cmesh, as if _init and _commit had been called. */ -t8_cmesh_t -t8_cmesh_new_2D_hypercube_hybrid (sc_MPI_Comm comm); +void +t8_cmesh_new_2D_hypercube_hybrid (t8_cmesh_t cmesh, sc_MPI_Comm comm); /** Construct a unit interval/square/cube coarse mesh that is periodic in each direction. * Element class? diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f1e2f85152..0c41dd2bc1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -160,6 +160,7 @@ add_t8_cpp_test( NAME t8_gtest_partition_data_parallel SOURCES t8_for add_t8_cpp_test( NAME t8_gtest_set_partition_offset_parallel SOURCES t8_forest/t8_gtest_set_partition_offset.cxx ) add_t8_cpp_test( NAME t8_gtest_partition_for_coarsening_parallel SOURCES t8_forest/t8_gtest_partition_for_coarsening.cxx ) add_t8_cpp_test( NAME t8_gtest_weighted_partitioning_parallel SOURCES t8_forest/t8_gtest_weighted_partitioning.cxx ) +add_t8_cpp_test( NAME t8_gtest_subelement_parallel SOURCES t8_forest/t8_gtest_subelement.cxx t8_gtest_adapt_callbacks.cxx ) add_t8_cpp_test( NAME t8_gtest_element_is_leaf_parallel SOURCES t8_forest/t8_gtest_element_is_leaf.cxx t8_gtest_adapt_callbacks.cxx ) add_t8_cpp_test( NAME t8_gtest_permute_hole_serial SOURCES t8_forest_incomplete/t8_gtest_permute_hole.cxx ) diff --git a/test/t8_forest/t8_gtest_subelement.cxx b/test/t8_forest/t8_gtest_subelement.cxx new file mode 100644 index 0000000000..88d4056a65 --- /dev/null +++ b/test/t8_forest/t8_gtest_subelement.cxx @@ -0,0 +1,91 @@ +/* + 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_subelement.cxx + * Unit test for the subelement scheme. This test checks the complete pipeline of hanging node resolution for + *´hybrid 2D meshes. Currently, we only test that the functions required for visualization work correctly. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +/** Check that the hanging node resolution for 2D hybrid meshes works. At the moment we only check the functionality +* needed for visualization (so e.g. no connectivity). +*/ +TEST (t8_gtest_subelement, hybrid_hanging_nodes_visualization) +{ + /* Setup: Build hypercube cmesh and uniform forest with the subelement scheme. */ + const int level = 3; + t8_cmesh_t cmesh; + t8_cmesh_init (&cmesh); + t8_cmesh_new_2D_hypercube_hybrid (cmesh, sc_MPI_COMM_WORLD); + + t8_forest_t forest = t8_forest_new_uniform (cmesh, t8_scheme_new_subelement (), level, 0, sc_MPI_COMM_WORLD); + + /* Initial uniform forest should not have any subelements. */ + EXPECT_FALSE (t8_forest_has_global_subelements (forest)); + + /* Adapt the forest (refining every second element). */ + forest = t8_forest_new_adapt (forest, t8_test_adapt_even_global_id, 0, 0, NULL); + + /* Before resolving hanging nodes, subelements should not yet be introduced. */ + EXPECT_FALSE (t8_forest_has_global_subelements (forest)); + const t8_gloidx_t num_leaves_adapted = t8_forest_get_global_num_leaf_elements (forest); + + /* Remove hanging nodes by inserting subelements. The forest is already balanced as we only adapted once. */ + forest = t8_forest_remove_hanging_nodes (forest); + EXPECT_TRUE(t8_forest_is_committed (forest)); + + /* Hanging node resolution must introduce subelements into the forest. */ + EXPECT_TRUE (t8_forest_has_global_subelements (forest)); + + /* Adding transition subelements must increase (or equal) the total leaf count. */ + const t8_gloidx_t num_leaves_sub = t8_forest_get_global_num_leaf_elements (forest); + EXPECT_GT (num_leaves_sub, num_leaves_adapted); + + /* Repartition the forest containing subelements (exercises MPI_Pack / MPI_Unpack). */ + t8_forest_t forest_partitioned; + t8_forest_init (&forest_partitioned); + t8_forest_set_partition (forest_partitioned, forest, 0); + t8_forest_commit (forest_partitioned); + + /* Subelements and leaf count must remain consistent after repartitioning. */ + EXPECT_TRUE (t8_forest_has_global_subelements (forest_partitioned)); + EXPECT_EQ (t8_forest_get_global_num_leaf_elements (forest_partitioned), num_leaves_sub); + + /* Discard subelements from the partitioned forest. */ + forest = t8_forest_discard_subelements (forest_partitioned); + /* Subelements should now be completely removed. */ + EXPECT_FALSE (t8_forest_has_global_subelements (forest)); + /* Discarding subelements should restore the pre-resolution leaf count. */ + EXPECT_EQ (t8_forest_get_global_num_leaf_elements (forest), num_leaves_adapted); + + /* Clean up forest */ + t8_forest_unref (&forest); +} \ No newline at end of file diff --git a/test/t8_gtest_adapt_callbacks.cxx b/test/t8_gtest_adapt_callbacks.cxx index bab9f26a08..85cd42f1ee 100644 --- a/test/t8_gtest_adapt_callbacks.cxx +++ b/test/t8_gtest_adapt_callbacks.cxx @@ -56,3 +56,15 @@ t8_test_adapt_first_child (t8_forest_t forest, [[maybe_unused]] t8_forest_t fore } return 0; } + +int +t8_test_adapt_even_global_id ([[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, [[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; +} diff --git a/test/t8_gtest_adapt_callbacks.hxx b/test/t8_gtest_adapt_callbacks.hxx index 6198d0ab5e..2e257c36e4 100644 --- a/test/t8_gtest_adapt_callbacks.hxx +++ b/test/t8_gtest_adapt_callbacks.hxx @@ -53,4 +53,22 @@ t8_test_adapt_first_child (t8_forest_t forest, t8_forest_t forest_from, t8_locid const t8_eclass_t eclass, t8_locidx_t lelement_id, const t8_scheme *scheme, const int is_family, const int num_elements, t8_element_t *elements[]); +/** Adapt callback for a forest to refine every second element, so every element with an even global id. + * It is not intended to be used as a recursive adaption callback and does not check the level of an element. + * + * \param [in] forest The forest to which the new elements belong. + * \param [in] forest_from The forest that is adapted. + * \param [in] which_tree The local tree containing \a elements. + * \param [in] eclass The eclass of \a which_tree. + * \param [in] lelement_id The local element id in \a forest_from in the tree of the current element. + * \param [in] scheme The scheme of the forest. + * \param [in] is_family If 1, the first \a num_elements entries in \a elements form a family. If 0, they do not. + * \param [in] num_elements The number of entries in \a elements that are defined + * \param [in] elements Pointers to a family or, if \a is_family is zero, pointer to one element. + */ +int +t8_test_adapt_even_global_id (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t which_tree, + t8_eclass_t tree_class, t8_locidx_t lelement_id, const t8_scheme *scheme, + const int is_family, const int num_elements, t8_element_t *elements[]); + #endif /* T8_GTEST_ADAPT_CALLBACKS */ From 8cad411971814dbcffc60d7669a18a377e635f48 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Thu, 13 Aug 2026 15:54:57 +0200 Subject: [PATCH 26/28] indent --- test/t8_forest/t8_gtest_subelement.cxx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/t8_forest/t8_gtest_subelement.cxx b/test/t8_forest/t8_gtest_subelement.cxx index 88d4056a65..275b563b12 100644 --- a/test/t8_forest/t8_gtest_subelement.cxx +++ b/test/t8_forest/t8_gtest_subelement.cxx @@ -39,7 +39,7 @@ * needed for visualization (so e.g. no connectivity). */ TEST (t8_gtest_subelement, hybrid_hanging_nodes_visualization) -{ +{ /* Setup: Build hypercube cmesh and uniform forest with the subelement scheme. */ const int level = 3; t8_cmesh_t cmesh; @@ -60,7 +60,7 @@ TEST (t8_gtest_subelement, hybrid_hanging_nodes_visualization) /* Remove hanging nodes by inserting subelements. The forest is already balanced as we only adapted once. */ forest = t8_forest_remove_hanging_nodes (forest); - EXPECT_TRUE(t8_forest_is_committed (forest)); + EXPECT_TRUE (t8_forest_is_committed (forest)); /* Hanging node resolution must introduce subelements into the forest. */ EXPECT_TRUE (t8_forest_has_global_subelements (forest)); @@ -76,16 +76,16 @@ TEST (t8_gtest_subelement, hybrid_hanging_nodes_visualization) t8_forest_commit (forest_partitioned); /* Subelements and leaf count must remain consistent after repartitioning. */ - EXPECT_TRUE (t8_forest_has_global_subelements (forest_partitioned)); + EXPECT_TRUE (t8_forest_has_global_subelements (forest_partitioned)); EXPECT_EQ (t8_forest_get_global_num_leaf_elements (forest_partitioned), num_leaves_sub); /* Discard subelements from the partitioned forest. */ forest = t8_forest_discard_subelements (forest_partitioned); /* Subelements should now be completely removed. */ - EXPECT_FALSE (t8_forest_has_global_subelements (forest)); + EXPECT_FALSE (t8_forest_has_global_subelements (forest)); /* Discarding subelements should restore the pre-resolution leaf count. */ EXPECT_EQ (t8_forest_get_global_num_leaf_elements (forest), num_leaves_adapted); /* Clean up forest */ t8_forest_unref (&forest); -} \ No newline at end of file +} From 101ab57cd818b22b2727d42f9852e90fe49eb7e2 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Thu, 13 Aug 2026 17:19:17 +0200 Subject: [PATCH 27/28] fix some errors --- src/t8_forest/t8_forest_subelement.cxx | 2 +- src/t8_schemes/t8_scheme.h | 4 ++-- src/t8_schemes/t8_scheme.hxx | 2 +- .../t8_subelement/specializations/t8_scheme_quads.hxx | 4 ++-- .../t8_subelement/specializations/t8_scheme_tri.hxx | 4 ++-- src/t8_schemes/t8_subelement/t8_subelement_traits.hxx | 8 ++++++-- test/t8_gtest_adapt_callbacks.cxx | 2 +- test/t8_gtest_adapt_callbacks.hxx | 10 +++++----- 8 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/t8_forest/t8_forest_subelement.cxx b/src/t8_forest/t8_forest_subelement.cxx index f1f1a87f09..5f50d3e785 100644 --- a/src/t8_forest/t8_forest_subelement.cxx +++ b/src/t8_forest/t8_forest_subelement.cxx @@ -176,7 +176,7 @@ t8_forest_has_global_subelements (const t8_forest_t forest) sc_MPI_Comm comm = t8_forest_get_mpicomm (forest); /* Convert boolean condition to MPI-compatible integer */ - const int local = t8_forest_has_local_subelements (forest) ? 1 : 0; + int local = t8_forest_has_local_subelements (forest) ? 1 : 0; int global = 0; const int mpiret = sc_MPI_Allreduce (&local, &global, 1, sc_MPI_INT, sc_MPI_LOR, comm); diff --git a/src/t8_schemes/t8_scheme.h b/src/t8_schemes/t8_scheme.h index 601e2c4e8c..1c2a4feb6c 100644 --- a/src/t8_schemes/t8_scheme.h +++ b/src/t8_schemes/t8_scheme.h @@ -860,7 +860,7 @@ void t8_element_MPI_Unpack (const t8_scheme_c *scheme, const t8_eclass_t tree_class, void *recvbuf, const int buffer_size, int *position, t8_element_t **elements, const unsigned int count, sc_MPI_Comm comm); -/** Check if \ref elem is a subelement. +/** Check if \a elem is a subelement. * \param [in] scheme The scheme of the forest. * \param [in] tree_class The eclass of the current tree. * \param [in] elem The elem to be checked. @@ -882,7 +882,7 @@ t8_element_get_number_of_subelements (const t8_scheme_c *scheme, const t8_eclass * \param [in] elem The element to be refined. * \param [in] type The subelement type to be used for refinement. * \param [in, out] c An array of allocated elements that will be filled with the subelements of \a elem. - * The number of subelements is determined by \ref element_get_number_of_subelements. + * The number of subelements is determined by \ref t8_element_get_number_of_subelements. */ void t8_refine_element_in_subelements (const t8_scheme_c *scheme, const t8_eclass_t tree_class, const t8_element_t *elem, diff --git a/src/t8_schemes/t8_scheme.hxx b/src/t8_schemes/t8_scheme.hxx index cb7c07b39d..593d5c5960 100644 --- a/src/t8_schemes/t8_scheme.hxx +++ b/src/t8_schemes/t8_scheme.hxx @@ -1196,7 +1196,7 @@ struct t8_scheme eclass_schemes[tree_class]); }; - /** Check if \ref elem is a subelement. + /** Check if \a elem is a subelement. * \param [in] tree_class The eclass of the current tree. * \param [in] elem The elem to be checked. */ diff --git a/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx b/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx index 2e825f7628..8cb3872fd7 100644 --- a/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx +++ b/src/t8_schemes/t8_subelement/specializations/t8_scheme_quads.hxx @@ -58,7 +58,7 @@ x - - - - - x x - - - - - x enumeration (here faces f0 and f3 f2 0 are hanging) * \endverbatim - * Also have a look at \ref vertex_coords_of_subelement for the definition of the subelement ids for quads and the + * Also have a look at \a vertex_coords_of_subelement for the definition of the subelement ids for quads and the * order of vertices. */ struct t8_subelementquad_scheme: public t8_subelement_scheme_common @@ -246,7 +246,7 @@ struct t8_subelementquad_scheme: public t8_subelement_scheme_common> ((T8_ELEMENT_NUM_FACES[T8_ECLASS_QUAD] - 1) - iface)) & 1u) != 0u; } - /** For each parent face, its two vertexs in clockwise order. */ + /** For each parent face, its two vertices in clockwise order. */ static constexpr int face_to_clockwise_vertex[4][2] = { { 0, 2 }, { 3, 1 }, { 1, 0 }, { 2, 3 } }; /** Compute the integer coordinates of all three vertices of a triangular subelement. diff --git a/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx b/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx index eca36c9b91..717256de51 100644 --- a/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx +++ b/src/t8_schemes/t8_subelement/specializations/t8_scheme_tri.hxx @@ -48,7 +48,7 @@ * is not a subelement and is just the underlying standalone triangle. Valid types are 1..6; the * all-faces-hanging code 7 is excluded, since that is a normal recursive refinement. * - * Please have a look at \ref vertex_coords_of_subelement for the definition of the subelement ids for triangles. + * Please have a look at \a vertex_coords_of_subelement for the definition of the subelement ids for triangles. */ struct t8_subelementtri_scheme: public t8_subelement_scheme_common { @@ -276,7 +276,7 @@ struct t8_subelementtri_scheme: public t8_subelement_scheme_commonas_subelement (elem); const unsigned type = static_cast (subelement->subelement_type); const unsigned id = static_cast (subelement->subelement_id); - const int num_hanging_faces = std::popcount (type); + [[maybe_unused]] const int num_hanging_faces = std::popcount (type); T8_ASSERT (num_hanging_faces == 1 || num_hanging_faces == 2); /* The corners of the parent triangle. */ diff --git a/src/t8_schemes/t8_subelement/t8_subelement_traits.hxx b/src/t8_schemes/t8_subelement/t8_subelement_traits.hxx index 9c5a15b0a7..4c80ea2f22 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement_traits.hxx +++ b/src/t8_schemes/t8_subelement/t8_subelement_traits.hxx @@ -51,14 +51,18 @@ struct t8_subelement_traits; template <> struct t8_subelement_traits { - using UnderlyingScheme = t8_standalone_scheme; + /** Subelement class used for the quad scheme. See also \ref t8_subelement_type.hxx. */ using SubelementType = t8_subelement_element>; + /** The standalone scheme for recursive refinement that gets extended by the hanging node resolution for quads. */ + using UnderlyingScheme = t8_standalone_scheme; }; /** Traits specialization for triangular subelements. */ template <> struct t8_subelement_traits { - using UnderlyingScheme = t8_default_scheme_tri; + /** Subelement class used for the triangle scheme. See also \ref t8_subelement_type.hxx. */ using SubelementType = t8_subelement_element; + /** The scheme for recursive refinement that gets extended by the hanging node resolution for triangles. */ + using UnderlyingScheme = t8_default_scheme_tri; }; diff --git a/test/t8_gtest_adapt_callbacks.cxx b/test/t8_gtest_adapt_callbacks.cxx index 85cd42f1ee..b574f52418 100644 --- a/test/t8_gtest_adapt_callbacks.cxx +++ b/test/t8_gtest_adapt_callbacks.cxx @@ -59,7 +59,7 @@ t8_test_adapt_first_child (t8_forest_t forest, [[maybe_unused]] t8_forest_t fore int t8_test_adapt_even_global_id ([[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]] t8_eclass_t eclass, 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[]) { diff --git a/test/t8_gtest_adapt_callbacks.hxx b/test/t8_gtest_adapt_callbacks.hxx index 2e257c36e4..9409c0bc34 100644 --- a/test/t8_gtest_adapt_callbacks.hxx +++ b/test/t8_gtest_adapt_callbacks.hxx @@ -40,7 +40,7 @@ * \param [in] forest The forest to which the new elements belong. * \param [in] forest_from The forest that is adapted. * \param [in] which_tree The local tree containing \a elements. - * \param [in] eclass The eclass of \a which_tree. + * \param [in] eclass The eclass of \a which_tree. * \param [in] lelement_id The local element id in \a forest_from in the tree of the current element. * \param [in] scheme The scheme of the forest. * \param [in] is_family If 1, the first \a num_elements entries in \a elements form a family. If 0, they do not. @@ -59,7 +59,7 @@ t8_test_adapt_first_child (t8_forest_t forest, t8_forest_t forest_from, t8_locid * \param [in] forest The forest to which the new elements belong. * \param [in] forest_from The forest that is adapted. * \param [in] which_tree The local tree containing \a elements. - * \param [in] eclass The eclass of \a which_tree. + * \param [in] eclass The eclass of \a which_tree. * \param [in] lelement_id The local element id in \a forest_from in the tree of the current element. * \param [in] scheme The scheme of the forest. * \param [in] is_family If 1, the first \a num_elements entries in \a elements form a family. If 0, they do not. @@ -67,8 +67,8 @@ t8_test_adapt_first_child (t8_forest_t forest, t8_forest_t forest_from, t8_locid * \param [in] elements Pointers to a family or, if \a is_family is zero, pointer to one element. */ int -t8_test_adapt_even_global_id (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t which_tree, - t8_eclass_t tree_class, t8_locidx_t lelement_id, const t8_scheme *scheme, - const int is_family, const int num_elements, t8_element_t *elements[]); +t8_test_adapt_even_global_id (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t which_tree, t8_eclass_t eclass, + t8_locidx_t lelement_id, const t8_scheme *scheme, const int is_family, + const int num_elements, t8_element_t *elements[]); #endif /* T8_GTEST_ADAPT_CALLBACKS */ From 1713648a0ffb653d65d7c1867aa4ca7c5cf51193 Mon Sep 17 00:00:00 2001 From: Lena Ploetzke Date: Fri, 14 Aug 2026 10:32:40 +0200 Subject: [PATCH 28/28] fix release mode --- .../t8_subelement/t8_subelement_scheme.hxx | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx b/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx index e5d24e8703..9aaa72af3d 100644 --- a/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx +++ b/src/t8_schemes/t8_subelement/t8_subelement_scheme.hxx @@ -935,14 +935,11 @@ struct t8_subelement_scheme_common: for (int i = 0; i < length; ++i) { elems[i] = (t8_element_t *) sc_mempool_alloc ((sc_mempool_t *) this->scheme_context); } -/* In debug mode, set sensible default values. */ -#if T8_ENABLE_DEBUG - { - for (int i = 0; i < length; i++) { - element_init (1, elems[i]); - } + /* For other schemes, we only set sensible data in debug mode. For subelements, it is important that we always set + * the subelement id and type to zero for new elements. */ + for (int i = 0; i < length; i++) { + element_init (1, elems[i]); } -#endif } /** Initialize an array of allocated elements. @@ -957,14 +954,12 @@ struct t8_subelement_scheme_common: void element_init ([[maybe_unused]] const int length, [[maybe_unused]] t8_element_t *elems) const noexcept { -#if T8_ENABLE_DEBUG TSubelementType *subelement = (TSubelementType *) elems; for (int ielem = 0; ielem < length; ielem++) { reset_subelement_values (subelement + ielem); derived ().underlying_scheme.element_init (1, subelement_to_standalone (subelement + ielem)); T8_ASSERT (element_is_valid ((t8_element_t *) (subelement + ielem))); } -#endif } /** Deinitialize an array of allocated elements.