From 3223c6301a6f479fc2bcc45c637e3d3eccb891f5 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Fri, 6 Jun 2025 15:13:43 +0200 Subject: [PATCH 01/41] Add a comment to bin_search --- src/t8_forest/t8_forest_private.cxx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/t8_forest/t8_forest_private.cxx b/src/t8_forest/t8_forest_private.cxx index ce3e91879d..66fe4ebcd6 100644 --- a/src/t8_forest/t8_forest_private.cxx +++ b/src/t8_forest/t8_forest_private.cxx @@ -57,6 +57,14 @@ t8_forest_get_tree_leaf_element_array_mutable (const t8_forest_t forest, t8_loci return (t8_element_array_t *) t8_forest_get_tree_leaf_element_array (forest, ltreeid); } +/* TODO: does the search fail when element_level is smaller then levels in the array? + For example entering the search with the root element or a level 1 element + and the array contains much finer elements. + Will it still return the largest index, or just any index? + */ +/* TODO: This may be implementable with std::partition_point, which would yield an easier implementation. + Need to check. + */ /** \brief Search for a linear element id (at level element_level) in a sorted array of * elements. If the element does not exist, return the largest index i * such that the element at position i has a smaller id than the given one. From a3422fcf58ed2b65db600c8ee35fde554bb19966 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Fri, 6 Jun 2025 15:14:32 +0200 Subject: [PATCH 02/41] Implement t8_forest_bin_search_upper --- src/t8_forest/t8_forest_private.cxx | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/t8_forest/t8_forest_private.cxx b/src/t8_forest/t8_forest_private.cxx index 66fe4ebcd6..682f01b4de 100644 --- a/src/t8_forest/t8_forest_private.cxx +++ b/src/t8_forest/t8_forest_private.cxx @@ -101,4 +101,49 @@ t8_forest_bin_search_lower (const t8_element_array_t *elements, const t8_lineari return elem_iter.get_current_index () - 1; } +/** \brief Search for a linear element id (at level element_level) in a sorted array of + * elements. If the element does not exist, return the smallest index i + * such that the element at position i has a larger id than the given one. + * If no such i exists, return -1. + */ +t8_locidx_t +t8_forest_bin_search_upper (const t8_element_array_t *elements, const t8_linearidx_t element_id, + const int element_level) +{ + const t8_scheme *scheme = t8_element_array_get_scheme (elements); + const t8_eclass_t tree_class = t8_element_array_get_tree_class (elements); + /* At first, we check whether any element has smaller id than the + * given one. */ + const t8_locidx_t num_elements = t8_element_array_get_count (elements); + if (num_elements == 0) { + /* This array is empty. */ + return -1; + } + const t8_element_t *query = t8_element_array_index_int (elements, num_elements - 1); + const t8_linearidx_t query_id = scheme->element_get_linear_id (tree_class, query, element_level); + if (query_id < element_id) { + /* No element has id larger than the given one. */ + return -1; + } + + /* We search for the first element E in the array, where element_id > ID(E) is false. + Thus, E is the first element with ID(E) >= element_id . */ + auto elem_iter + = std::lower_bound (t8_element_array_begin (elements), t8_element_array_end (elements), element_id, + [&element_level, &scheme, &tree_class] (const t8_linearidx_t element_id_, + const t8_element_array_iterator::value_type &elem_ptr) { + return (element_id_ > scheme->element_get_linear_id (tree_class, elem_ptr, element_level)); + }); + + /* In case we do not find an element that is greater than the given element_id, the binary search returns + * the end-iterator of the element array. */ + if (elem_iter == t8_element_array_end (elements)) { + // No element was found. + return -1; + } + else { + return elem_iter.get_current_index () + } +} + T8_EXTERN_C_END (); From 5584c0f00bc4f455f8c70aa537841d3a99b1535a Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Fri, 6 Jun 2025 15:15:05 +0200 Subject: [PATCH 03/41] Implement t8_forest_element_is_ancestor --- src/t8_forest/t8_forest_private.cxx | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/t8_forest/t8_forest_private.cxx b/src/t8_forest/t8_forest_private.cxx index 682f01b4de..8f52cd6c0f 100644 --- a/src/t8_forest/t8_forest_private.cxx +++ b/src/t8_forest/t8_forest_private.cxx @@ -146,4 +146,42 @@ t8_forest_bin_search_upper (const t8_element_array_t *elements, const t8_lineari } } +/** Query whether one element is an ancestor of the other. + * 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] scheme A scheme. + * \param [in] eclass An eclass. + * \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. +*/ +// TODO: Move this function to the scheme class. +static bool +t8_forest_element_is_ancestor (const t8_scheme *scheme, t8_eclass_t eclass, const t8_element_t *element_A, + const t8_element_t *element_B) +{ + /* A is ancestor of B if and only if it has smaller or equal level and + restricted to A's level, B has the same id as A. + + level(A) <= level(B) and ID(A,level(A)) == ID(B,level(B)) + */ + T8_ASSERT (scheme->element_is_valid (eclass, element_A)); + T8_ASSERT (scheme->element_is_valid (eclass, element_B)); + + const int level_A = scheme->element_get_level (element_A); + const int level_B = scheme->element_get_level (element_B); + + if (level_A > level_B) { + /* element A is finer than element B and thus cannot be + * an ancestor of B. */ + return false; + } + + const t8_locidx_t id_A = scheme->element_get_linear_id (element_A, level_A); + const t8_locidx_t id_B = scheme->element_get_linear_id (element_B, level_A); + + // If both elements have the same linear ID and level_A then A is an ancestor of B. + return id_A == id_B; +} + T8_EXTERN_C_END (); From 5d1564a0f7e278342c71035490ae796cd63b8682 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Fri, 6 Jun 2025 15:15:22 +0200 Subject: [PATCH 04/41] Add t8_forest_bin_search_first_descendant_ancenstor --- src/t8_forest/t8_forest_private.cxx | 74 +++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/t8_forest/t8_forest_private.cxx b/src/t8_forest/t8_forest_private.cxx index 8f52cd6c0f..4849350fb9 100644 --- a/src/t8_forest/t8_forest_private.cxx +++ b/src/t8_forest/t8_forest_private.cxx @@ -184,4 +184,78 @@ t8_forest_element_is_ancestor (const t8_scheme *scheme, t8_eclass_t eclass, cons return id_A == id_B; } +/** \brief Search for a linear element id (at level element_level) in a sorted array of + * elements. If the element does not exist, return the first index i such that + * the element at position i is an ancestor or descendant of the element corresponding to the element id. + * If no such i exists, return -1. + */ +t8_locidx_t +t8_forest_bin_search_first_descendant_ancenstor (const t8_element_array_t *elements, const t8_element_t *element, + const t8_element_t *element_found) +{ + /* This search works as follows: + + Let E denote the element with element_id at level L. + If an ancestor or descendant of E exists in the array then they are either: + A: The search result of t8_forest_bin_search_lower + B: The search result of t8_forest_bin_search_upper + + Let ID(element,level) denote the linear id of an element at a given level. + + Case A: There is an element F in the array that is E itself or an ancestor of E (i.e. level(F) <= level(E)). + In that case + ID(E,L) >= ID(F,L) and there can be no element with id in between (since it would also be an ancestor of E). + Then F will be the search result of t8_forest_bin_search_lower + Case B: There is an element F in the array that is a descendant of E and it has the smallest index in the array of all descendants. + Then + ID(E,L) = ID(F,L) + and also + ID(E,L) = ID(D,L) for all other descendants of E. + But since F is the first it will be the search result of t8_forest_bin_search_upper. + Case C: There is no descendant or ancestor of E in the array. In both cases t8_forest_bin_search_lower and + t8_forest_bin_search_upper may find elements but the results will not be ancestors/descendants of E. + + From this, we determine the following algorithm: + + 1. Query t8_forest_bin_search_lower with N. + 2. If no element was found, or the resulting element is not an ancestor of N. + 3. Query t8_forest_bin_search_upper with N. + 3. If an element was found and it is a descendant of N, we found our element. + 4. If not, no element was found. + */ + + /* Compute the element's level and linear id. In order to do so, + * we first need the scheme and eclass. */ + const t8_scheme *scheme = t8_element_array_get_scheme (elements); + const t8_eclass eclass = t8_element_array_get_tree_class (elements); + const int element_level = scheme->element_get_level (eclass, element); + const t8_linearidx_t element_id = scheme->element_get_linear_id (eclass, element, element_level); + + const t8_locidx_t search_pos_lower = t8_forest_bin_search_lower (elements, element_id, element_level); + + /* Get the element at the current position. */ + if (search_pos_lower >= 0) { + element_found = t8_element_array_index_locidx (elements, search_pos_lower); + const bool is_ancestor = t8_forest_element_is_ancestor (scheme, eclass, element_found, element); + if (is_ancestor) { + /* The element at this position is an ancestor or descendant. */ + return search_pos_lower; + } + } + /* t8_forest_bin_search_lower did not return a result or an ancestor. */ + + const t8_locidx_t search_pos_upper = t8_forest_bin_search_upper (elements, element_id, element_level); + if (search_pos_upper >= 0) { + element_found = t8_element_array_index_locidx (elements, search_pos_upper); + const bool is_descendant = t8_forest_element_is_ancestor (scheme, eclass, element, element_found); + if (is_descendant) { + /* The element at this position is an ancestor or descendant. */ + return search_pos_upper; + } + } + // No ancestor or descendant was found + element_found = nullptr; + return -1; +} + T8_EXTERN_C_END (); From ca8989ed815f8bc053159acdba0897fa66713bd2 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Fri, 6 Jun 2025 15:44:13 +0200 Subject: [PATCH 05/41] fix bin_search_upper --- src/t8_forest/t8_forest_private.cxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/t8_forest/t8_forest_private.cxx b/src/t8_forest/t8_forest_private.cxx index 4849350fb9..83b1ae4fff 100644 --- a/src/t8_forest/t8_forest_private.cxx +++ b/src/t8_forest/t8_forest_private.cxx @@ -130,8 +130,8 @@ t8_forest_bin_search_upper (const t8_element_array_t *elements, const t8_lineari Thus, E is the first element with ID(E) >= element_id . */ auto elem_iter = std::lower_bound (t8_element_array_begin (elements), t8_element_array_end (elements), element_id, - [&element_level, &scheme, &tree_class] (const t8_linearidx_t element_id_, - const t8_element_array_iterator::value_type &elem_ptr) { + [&element_level, &scheme, &tree_class] (const t8_element_array_iterator::value_type &elem_ptr, + const t8_linearidx_t element_id_) { return (element_id_ > scheme->element_get_linear_id (tree_class, elem_ptr, element_level)); }); @@ -142,7 +142,7 @@ t8_forest_bin_search_upper (const t8_element_array_t *elements, const t8_lineari return -1; } else { - return elem_iter.get_current_index () + return elem_iter.get_current_index (); } } From d50a3d818f5f46cc6437d6e9a057501846c73248 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Fri, 6 Jun 2025 15:44:29 +0200 Subject: [PATCH 06/41] fix element_is_ancestor --- src/t8_forest/t8_forest_private.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/t8_forest/t8_forest_private.cxx b/src/t8_forest/t8_forest_private.cxx index 83b1ae4fff..0d0d2b63e8 100644 --- a/src/t8_forest/t8_forest_private.cxx +++ b/src/t8_forest/t8_forest_private.cxx @@ -168,8 +168,8 @@ t8_forest_element_is_ancestor (const t8_scheme *scheme, t8_eclass_t eclass, cons T8_ASSERT (scheme->element_is_valid (eclass, element_A)); T8_ASSERT (scheme->element_is_valid (eclass, element_B)); - const int level_A = scheme->element_get_level (element_A); - const int level_B = scheme->element_get_level (element_B); + const int level_A = scheme->element_get_level (eclass, element_A); + const int level_B = scheme->element_get_level (eclass, element_B); if (level_A > level_B) { /* element A is finer than element B and thus cannot be @@ -177,8 +177,8 @@ t8_forest_element_is_ancestor (const t8_scheme *scheme, t8_eclass_t eclass, cons return false; } - const t8_locidx_t id_A = scheme->element_get_linear_id (element_A, level_A); - const t8_locidx_t id_B = scheme->element_get_linear_id (element_B, level_A); + const t8_linearidx_t id_A = scheme->element_get_linear_id (eclass, element_A, level_A); + const t8_linearidx_t id_B = scheme->element_get_linear_id (eclass, element_B, level_A); // If both elements have the same linear ID and level_A then A is an ancestor of B. return id_A == id_B; From b700bd773d077d1228182c18d99524e62e64ca7c Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Fri, 6 Jun 2025 16:15:19 +0200 Subject: [PATCH 07/41] document bin_search_lower --- src/t8_forest/t8_forest_private.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/t8_forest/t8_forest_private.h b/src/t8_forest/t8_forest_private.h index 170aa2f13a..40e0fdb750 100644 --- a/src/t8_forest/t8_forest_private.h +++ b/src/t8_forest/t8_forest_private.h @@ -196,10 +196,11 @@ t8_forest_get_tree_leaf_element_array_mutable (const t8_forest_t forest, t8_loci * elements. If the element does not exist, return the largest index i * such that the element at position i has a smaller id than the given one. * If no such i exists, return -1. - * \param [in] elements The array of elements. + * \param [in] elements An array of elements. Must be sorted according to linear id at maximum level. + * Must correspond to a valid refinement (i.e. contain no duplicate elements or elements and their descendants). * \param [in] element_id The linear id of the element to search for. * \param [in] element_level The level of the element to search for. Thus, the level at which \a element_id was computed. - * \return The index \a i of an element with the linear_id \a element_id in \a elements if it exists. + * \return The largest index \a i of an element with linear_id smaller than or equal to \a element_id in \a elements if it exists. * -1 if no such element was found in \a elements. */ t8_locidx_t From b28d548ef3a88ca4f7f60820716e9f77c1e1e645 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Fri, 6 Jun 2025 16:15:41 +0200 Subject: [PATCH 08/41] document bin_search_upper --- src/t8_forest/t8_forest_private.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/t8_forest/t8_forest_private.h b/src/t8_forest/t8_forest_private.h index 40e0fdb750..8db9d41b1a 100644 --- a/src/t8_forest/t8_forest_private.h +++ b/src/t8_forest/t8_forest_private.h @@ -207,6 +207,21 @@ t8_locidx_t t8_forest_bin_search_lower (const t8_element_array_t *elements, const t8_linearidx_t element_id, const int element_level); +/** \brief Search for a linear element id (at level element_level) in a sorted array of + * elements. If the element does not exist, return the smallest index i + * such that the element at position i has a larger id than the given one. + * If no such i exists, return -1. + * \param [in] elements An array of elements. Must be sorted according to linear id at maximum level. + * Must correspond to a valid refinement (i.e. contain no duplicate elements or elements and their descendants). + * \param [in] element_id The linear id of the element to search for. + * \param [in] element_level The level of the element to search for. Thus, the level at which \a element_id was computed. + * \return The smallest index \a i of an element with linear_id larger than or equal to \a element_id in \a elements if it exists. + * -1 if no such element was found in \a elements. + */ +t8_locidx_t +t8_forest_bin_search_upper (const t8_element_array_t *elements, const t8_linearidx_t element_id, + const int element_level); + /** Find the owner process of a given element, deprecated version. * Use t8_forest_element_find_owner instead. * \param [in] forest The forest. From 67d62654e97ac5ae0eb855837fdf4aab21e1c03b Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Fri, 6 Jun 2025 16:16:05 +0200 Subject: [PATCH 09/41] Add declaration of +t8_forest_bin_search_first_descendant_ancenstor. docstring still missing --- src/t8_forest/t8_forest_private.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/t8_forest/t8_forest_private.h b/src/t8_forest/t8_forest_private.h index 8db9d41b1a..4de2da3d6b 100644 --- a/src/t8_forest/t8_forest_private.h +++ b/src/t8_forest/t8_forest_private.h @@ -222,6 +222,12 @@ t8_locidx_t t8_forest_bin_search_upper (const t8_element_array_t *elements, const t8_linearidx_t element_id, const int element_level); +/** \brief TODO: document + */ +t8_locidx_t +t8_forest_bin_search_first_descendant_ancenstor (const t8_element_array_t *elements, const t8_element_t *element, + const t8_element_t *element_found); + /** Find the owner process of a given element, deprecated version. * Use t8_forest_element_find_owner instead. * \param [in] forest The forest. From fb0cc9e7273b865a4cc219c944ed0af519695dc4 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Fri, 6 Jun 2025 16:44:09 +0200 Subject: [PATCH 10/41] t8_forest_bin_search_first_descendant_ancenstor fix parameter --- src/t8_forest/t8_forest_private.cxx | 12 ++++++------ src/t8_forest/t8_forest_private.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/t8_forest/t8_forest_private.cxx b/src/t8_forest/t8_forest_private.cxx index 0d0d2b63e8..42334955f8 100644 --- a/src/t8_forest/t8_forest_private.cxx +++ b/src/t8_forest/t8_forest_private.cxx @@ -191,7 +191,7 @@ t8_forest_element_is_ancestor (const t8_scheme *scheme, t8_eclass_t eclass, cons */ t8_locidx_t t8_forest_bin_search_first_descendant_ancenstor (const t8_element_array_t *elements, const t8_element_t *element, - const t8_element_t *element_found) + const t8_element_t **element_found) { /* This search works as follows: @@ -235,8 +235,8 @@ t8_forest_bin_search_first_descendant_ancenstor (const t8_element_array_t *eleme /* Get the element at the current position. */ if (search_pos_lower >= 0) { - element_found = t8_element_array_index_locidx (elements, search_pos_lower); - const bool is_ancestor = t8_forest_element_is_ancestor (scheme, eclass, element_found, element); + *element_found = t8_element_array_index_locidx (elements, search_pos_lower); + const bool is_ancestor = t8_forest_element_is_ancestor (scheme, eclass, *element_found, element); if (is_ancestor) { /* The element at this position is an ancestor or descendant. */ return search_pos_lower; @@ -246,15 +246,15 @@ t8_forest_bin_search_first_descendant_ancenstor (const t8_element_array_t *eleme const t8_locidx_t search_pos_upper = t8_forest_bin_search_upper (elements, element_id, element_level); if (search_pos_upper >= 0) { - element_found = t8_element_array_index_locidx (elements, search_pos_upper); - const bool is_descendant = t8_forest_element_is_ancestor (scheme, eclass, element, element_found); + *element_found = t8_element_array_index_locidx (elements, search_pos_upper); + const bool is_descendant = t8_forest_element_is_ancestor (scheme, eclass, element, *element_found); if (is_descendant) { /* The element at this position is an ancestor or descendant. */ return search_pos_upper; } } // No ancestor or descendant was found - element_found = nullptr; + *element_found = nullptr; return -1; } diff --git a/src/t8_forest/t8_forest_private.h b/src/t8_forest/t8_forest_private.h index 4de2da3d6b..50321113dc 100644 --- a/src/t8_forest/t8_forest_private.h +++ b/src/t8_forest/t8_forest_private.h @@ -226,7 +226,7 @@ t8_forest_bin_search_upper (const t8_element_array_t *elements, const t8_lineari */ t8_locidx_t t8_forest_bin_search_first_descendant_ancenstor (const t8_element_array_t *elements, const t8_element_t *element, - const t8_element_t *element_found); + const t8_element_t **element_found); /** Find the owner process of a given element, deprecated version. * Use t8_forest_element_find_owner instead. From f168141dd0ffff01151df3efbc4ed8496f111177 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Mon, 23 Jun 2025 11:20:35 +0200 Subject: [PATCH 11/41] Documentation --- src/t8_forest/t8_forest_private.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/t8_forest/t8_forest_private.h b/src/t8_forest/t8_forest_private.h index 50321113dc..85993541f1 100644 --- a/src/t8_forest/t8_forest_private.h +++ b/src/t8_forest/t8_forest_private.h @@ -192,7 +192,7 @@ t8_forest_get_tree_leaf_element_array (t8_forest_t forest, t8_locidx_t ltreeid); t8_element_array_t * t8_forest_get_tree_leaf_element_array_mutable (const t8_forest_t forest, t8_locidx_t ltreeid); -/** Search for a linear element id (at forest->maxlevel) in a sorted array of +/** Search for a linear element id in a sorted array of * elements. If the element does not exist, return the largest index i * such that the element at position i has a smaller id than the given one. * If no such i exists, return -1. @@ -222,7 +222,15 @@ t8_locidx_t t8_forest_bin_search_upper (const t8_element_array_t *elements, const t8_linearidx_t element_id, const int element_level); -/** \brief TODO: document +/** \brief Search for the first descendant or ancestor of an element in a sorted array of elements. + * \param [in] elements An array of elements. Must be sorted according to linear id at maximum level. + * Must correspond to a valid refinement (i.e. contain no duplicate elements or elements and their descendants). + * \param [in] element The element to search for. + * \param [in] element_found On return either a descendant or ancestor of \a element in \a elements if it exists. NULL if no + * such element exists in \a elements. + * \return The smallest index \a i such that elements[i] (= \a element_found) is an ancestor or a descendant of \a element. + * -1 if no such element was found in \a elements. + * \note \a element is ancestor and descendant of itself, so if \a element is contained in \a elements then it will be found by this function. */ t8_locidx_t t8_forest_bin_search_first_descendant_ancenstor (const t8_element_array_t *elements, const t8_element_t *element, From 511cbefdcbe316a198b16884e9d138a4d271f78c Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Mon, 23 Jun 2025 11:27:34 +0200 Subject: [PATCH 12/41] typo --- src/t8_forest/t8_forest_private.cxx | 4 ++-- src/t8_forest/t8_forest_private.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/t8_forest/t8_forest_private.cxx b/src/t8_forest/t8_forest_private.cxx index 42334955f8..1dde5aa78d 100644 --- a/src/t8_forest/t8_forest_private.cxx +++ b/src/t8_forest/t8_forest_private.cxx @@ -190,8 +190,8 @@ t8_forest_element_is_ancestor (const t8_scheme *scheme, t8_eclass_t eclass, cons * If no such i exists, return -1. */ t8_locidx_t -t8_forest_bin_search_first_descendant_ancenstor (const t8_element_array_t *elements, const t8_element_t *element, - const t8_element_t **element_found) +t8_forest_bin_search_first_descendant_ancestor (const t8_element_array_t *elements, const t8_element_t *element, + const t8_element_t **element_found) { /* This search works as follows: diff --git a/src/t8_forest/t8_forest_private.h b/src/t8_forest/t8_forest_private.h index 85993541f1..46e01fc55d 100644 --- a/src/t8_forest/t8_forest_private.h +++ b/src/t8_forest/t8_forest_private.h @@ -233,8 +233,8 @@ t8_forest_bin_search_upper (const t8_element_array_t *elements, const t8_lineari * \note \a element is ancestor and descendant of itself, so if \a element is contained in \a elements then it will be found by this function. */ t8_locidx_t -t8_forest_bin_search_first_descendant_ancenstor (const t8_element_array_t *elements, const t8_element_t *element, - const t8_element_t **element_found); +t8_forest_bin_search_first_descendant_ancestor (const t8_element_array_t *elements, const t8_element_t *element, + const t8_element_t **element_found); /** Find the owner process of a given element, deprecated version. * Use t8_forest_element_find_owner instead. From 488a6bbe9759df79a5d71cf00cdbc328f6f88355 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Wed, 2 Jul 2025 10:10:36 +0200 Subject: [PATCH 13/41] Change docstring Co-authored-by: David Knapp --- src/t8_forest/t8_forest_private.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/t8_forest/t8_forest_private.cxx b/src/t8_forest/t8_forest_private.cxx index 1dde5aa78d..a0a3d73bb3 100644 --- a/src/t8_forest/t8_forest_private.cxx +++ b/src/t8_forest/t8_forest_private.cxx @@ -146,7 +146,7 @@ t8_forest_bin_search_upper (const t8_element_array_t *elements, const t8_lineari } } -/** Query whether one element is an ancestor of the other. +/** 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] scheme A scheme. From 30bbb699029cab8ff2d92c97a396c6b60462e90e Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Wed, 2 Jul 2025 10:11:25 +0200 Subject: [PATCH 14/41] Add const to parameter Co-authored-by: David Knapp --- src/t8_forest/t8_forest_private.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/t8_forest/t8_forest_private.cxx b/src/t8_forest/t8_forest_private.cxx index a0a3d73bb3..436cc4ae45 100644 --- a/src/t8_forest/t8_forest_private.cxx +++ b/src/t8_forest/t8_forest_private.cxx @@ -157,7 +157,7 @@ t8_forest_bin_search_upper (const t8_element_array_t *elements, const t8_lineari */ // TODO: Move this function to the scheme class. static bool -t8_forest_element_is_ancestor (const t8_scheme *scheme, t8_eclass_t eclass, const t8_element_t *element_A, +t8_forest_element_is_ancestor (const t8_scheme *scheme, const t8_eclass_t eclass, const t8_element_t *element_A, const t8_element_t *element_B) { /* A is ancestor of B if and only if it has smaller or equal level and From 70f4a37344ac1c3e351d2f211fd2d1d23e4372cd Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Wed, 2 Jul 2025 15:14:56 +0200 Subject: [PATCH 15/41] Add element_is_ancestor function to the scheme interface and the default scheme --- src/t8_forest/t8_forest_private.cxx | 42 +------------------ .../t8_default_common/t8_default_common.hxx | 36 ++++++++++++++++ src/t8_schemes/t8_scheme.hxx | 16 +++++++ 3 files changed, 54 insertions(+), 40 deletions(-) diff --git a/src/t8_forest/t8_forest_private.cxx b/src/t8_forest/t8_forest_private.cxx index 436cc4ae45..49d3367eda 100644 --- a/src/t8_forest/t8_forest_private.cxx +++ b/src/t8_forest/t8_forest_private.cxx @@ -146,44 +146,6 @@ t8_forest_bin_search_upper (const t8_element_array_t *elements, const t8_lineari } } -/** 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] scheme A scheme. - * \param [in] eclass An eclass. - * \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. -*/ -// TODO: Move this function to the scheme class. -static bool -t8_forest_element_is_ancestor (const t8_scheme *scheme, const t8_eclass_t eclass, const t8_element_t *element_A, - const t8_element_t *element_B) -{ - /* A is ancestor of B if and only if it has smaller or equal level and - restricted to A's level, B has the same id as A. - - level(A) <= level(B) and ID(A,level(A)) == ID(B,level(B)) - */ - T8_ASSERT (scheme->element_is_valid (eclass, element_A)); - T8_ASSERT (scheme->element_is_valid (eclass, element_B)); - - const int level_A = scheme->element_get_level (eclass, element_A); - const int level_B = scheme->element_get_level (eclass, element_B); - - if (level_A > level_B) { - /* element A is finer than element B and thus cannot be - * an ancestor of B. */ - return false; - } - - const t8_linearidx_t id_A = scheme->element_get_linear_id (eclass, element_A, level_A); - const t8_linearidx_t id_B = scheme->element_get_linear_id (eclass, element_B, level_A); - - // If both elements have the same linear ID and level_A then A is an ancestor of B. - return id_A == id_B; -} - /** \brief Search for a linear element id (at level element_level) in a sorted array of * elements. If the element does not exist, return the first index i such that * the element at position i is an ancestor or descendant of the element corresponding to the element id. @@ -236,7 +198,7 @@ t8_forest_bin_search_first_descendant_ancestor (const t8_element_array_t *elemen /* Get the element at the current position. */ if (search_pos_lower >= 0) { *element_found = t8_element_array_index_locidx (elements, search_pos_lower); - const bool is_ancestor = t8_forest_element_is_ancestor (scheme, eclass, *element_found, element); + const bool is_ancestor = scheme->element_is_ancestor (eclass, *element_found, element); if (is_ancestor) { /* The element at this position is an ancestor or descendant. */ return search_pos_lower; @@ -247,7 +209,7 @@ t8_forest_bin_search_first_descendant_ancestor (const t8_element_array_t *elemen const t8_locidx_t search_pos_upper = t8_forest_bin_search_upper (elements, element_id, element_level); if (search_pos_upper >= 0) { *element_found = t8_element_array_index_locidx (elements, search_pos_upper); - const bool is_descendant = t8_forest_element_is_ancestor (scheme, eclass, element, *element_found); + const bool is_descendant = scheme->element_is_ancestor (eclass, element, *element_found); if (is_descendant) { /* The element at this position is an ancestor or descendant. */ return search_pos_upper; diff --git a/src/t8_schemes/t8_default/t8_default_common/t8_default_common.hxx b/src/t8_schemes/t8_default/t8_default_common/t8_default_common.hxx index 7e1b87cc3c..1e54ec3826 100644 --- a/src/t8_schemes/t8_default/t8_default_common/t8_default_common.hxx +++ b/src/t8_schemes/t8_default/t8_default_common/t8_default_common.hxx @@ -200,6 +200,42 @@ class t8_default_scheme_common: public t8_crtp_operatorunderlying ().element_is_valid (element_A)); + T8_ASSERT (this->underlying ().element_is_valid (element_B)); + + const int level_A = this->underlying ().element_get_level (element_A); + const int level_B = this->underlying ().element_get_level (element_B); + + if (level_A > level_B) { + /* element A is finer than element B and thus cannot be + * an ancestor of B. */ + return false; + } + + const t8_linearidx_t id_A = this->underlying ().element_get_linear_id (element_A, level_A); + const t8_linearidx_t id_B = this->underlying ().element_get_linear_id (element_B, level_A); + + // If both elements have the same linear ID at level_A then A is an ancestor of B. + return id_A == id_B; + } + /** Allocate space for a bunch of elements. * \param [in] length The number of elements to allocate. * \param [out] elem The elements to allocate. diff --git a/src/t8_schemes/t8_scheme.hxx b/src/t8_schemes/t8_scheme.hxx index f97d835729..a41f64b2ca 100644 --- a/src/t8_schemes/t8_scheme.hxx +++ b/src/t8_schemes/t8_scheme.hxx @@ -493,6 +493,22 @@ class t8_scheme { eclass_schemes[tree_class]); }; + /** 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] scheme A scheme. + * \param [in] eclass An eclass. + * \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. + */ + inline bool + element_is_ancestor (const t8_eclass_t tree_class, const t8_element_t *element_A, const t8_element_t *element_B) const + { + return std::visit ([&] (auto &&scheme) { return scheme.element_is_ancestor (element_A, element_B); }, + eclass_schemes[tree_class]); + } + /** Query whether a given set of elements is a family or not. * \param [in] tree_class The eclass of the current tree. * \param [in] fam An array of as many elements as an element of class From f758a8bb5bb44cc566250f69ef43508d999078de Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Wed, 2 Jul 2025 15:15:37 +0200 Subject: [PATCH 16/41] Add a non-implementation i.e. abort of element_is_ancestor to the standalone scheme --- .../t8_standalone_implementation.hxx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx b/src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx index 0a36d2c1ff..8e1c78c108 100644 --- a/src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx +++ b/src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx @@ -610,6 +610,21 @@ struct t8_standalone_scheme return 1; } + /** 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] scheme A scheme. + * \param [in] eclass An eclass. + * \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. + */ + static constexpr bool + element_is_ancestor (const t8_element_t *element_A, const t8_element_t *element_B) noexcept + { + SC_ABORT ("element_is_ancestor is currently not implemented for the standalone scheme."); + } + /** Compute the nearest common ancestor of two elements. That is, * the element with highest level that still has both given elements as * descendants. From 18fe45a6bc8cd131c888e2e3227e24e75b5bf950 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Thu, 3 Jul 2025 15:03:44 +0200 Subject: [PATCH 17/41] Implement standalone version of is_ancestor --- .../t8_standalone_implementation.hxx | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx b/src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx index 8e1c78c108..1eb314dd9f 100644 --- a/src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx +++ b/src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx @@ -610,6 +610,8 @@ struct t8_standalone_scheme return 1; } + // 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. @@ -619,10 +621,43 @@ struct t8_standalone_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. */ - static constexpr bool - element_is_ancestor (const t8_element_t *element_A, const t8_element_t *element_B) noexcept - { - SC_ABORT ("element_is_ancestor is currently not implemented for the standalone scheme."); + constexpr bool + element_is_ancestor (const t8_element_t *element_A, const t8_element_t *element_B) const noexcept + { + /* We compute whether A is an ancestor of B by + + - If level(A) > 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, From 0d8b09a2d4d46a2c21a661b0f1ec18eb226f2823 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Thu, 3 Jul 2025 15:22:23 +0200 Subject: [PATCH 18/41] Add an is_ancestor test --- test/CMakeLists.txt | 1 + test/t8_schemes/t8_gtest_is_ancestor.cxx | 94 ++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 test/t8_schemes/t8_gtest_is_ancestor.cxx diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2a2381bfe9..89dd6d9b72 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -173,6 +173,7 @@ add_t8_cpp_test( NAME t8_gtest_pyra_connectivity_serial SOURCES t8_schemes/t add_t8_cpp_test( NAME t8_gtest_face_neigh_serial SOURCES t8_schemes/t8_gtest_face_neigh.cxx ) add_t8_cpp_test( NAME t8_gtest_get_linear_id_serial SOURCES t8_schemes/t8_gtest_get_linear_id.cxx ) add_t8_cpp_test( NAME t8_gtest_ancestor_serial SOURCES t8_schemes/t8_gtest_ancestor.cxx ) +add_t8_cpp_test( NAME t8_gtest_is_ancestor_serial SOURCES t8_schemes/t8_gtest_is_ancestor.cxx ) add_t8_cpp_test( NAME t8_gtest_ancestor_id_serial SOURCES t8_schemes/t8_gtest_ancestor_id.cxx ) add_t8_cpp_test( NAME t8_gtest_element_count_leaves_serial SOURCES t8_schemes/t8_gtest_element_count_leaves.cxx ) add_t8_cpp_test( NAME t8_gtest_element_ref_coords_serial SOURCES t8_schemes/t8_gtest_element_ref_coords.cxx ) diff --git a/test/t8_schemes/t8_gtest_is_ancestor.cxx b/test/t8_schemes/t8_gtest_is_ancestor.cxx new file mode 100644 index 0000000000..3f12cd7bf8 --- /dev/null +++ b/test/t8_schemes/t8_gtest_is_ancestor.cxx @@ -0,0 +1,94 @@ +/* + 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_gtest_is_ancestor.cxx + * This test checks the element_is_ancestor function of the scheme interface. + * Starting from an element we build up all its ancestor up to root level and + * test whether the element_is_ancestor function returns true. + * We also test sone cases where the function returns false, namely when putting + * in an element and an ancestor in reverse order. + * + * This test is modified from the ancestor_id test. + */ + +#include +#include +#include +#include +#include "t8_gtest_dfs_base.hxx" +#include + +class class_is_ancestor: public TestDFS { + void + check_element () override + { + t8_element_t *ancestor; + scheme->element_new (eclass, 1, &ancestor); + /* Get level of current element */ + const int level = scheme->element_get_level (eclass, element); + + /* Iterate over all levels above the current element and check + if ancestor id corresponds with the child id of elem, parent, grandparent, ... */ + for (int levels_above_elem = 0; levels_above_elem < level; levels_above_elem++) { + const int ancestor_level = level - levels_above_elem; + /* Compute the ancestor by iteratively computing the parent */ + scheme->element_copy (eclass, element, ancestor); + for (int level_diff = 0; level_diff < levels_above_elem; level_diff++) { + scheme->element_get_parent (eclass, ancestor, ancestor); + } + // Check whether element_is_ancestor correctly identifies our candidate as an ancestor + EXPECT_TRUE (scheme->element_is_ancestor (eclass, ancestor, element)); + // We check that element_is_ancestor properly returns false by + // checking that element is not an ancestor of our candidate if their levels do not agree. + if (ancestor_level != level) { + EXPECT_FALSE (scheme->element_is_ancestor (eclass, element, ancestor)); + } + } + scheme->element_destroy (eclass, 1, &ancestor); + } + + protected: + void + SetUp () override + { + /* Setup DFS test */ + dfs_test_setup (); + } + void + TearDown () override + { + /* Destroy DFS test */ + dfs_test_teardown (); + } +}; + +TEST_P (class_is_ancestor, t8_recursive_dfs_is_ancestor) +{ +#if T8CODE_TEST_LEVEL >= 1 + const int maxlvl = 4; +#else + const int maxlvl = 6; +#endif + check_recursive_dfs_to_max_lvl (maxlvl); +} + +INSTANTIATE_TEST_SUITE_P (t8_gtest_is_ancestor, class_is_ancestor, AllSchemes, print_all_schemes); From 9acef23f00f84d420fec3d45e08fd78e7411b0a7 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Thu, 3 Jul 2025 15:34:44 +0200 Subject: [PATCH 19/41] Add an is_ancestor assertion to nca computation --- src/t8_schemes/t8_scheme.hxx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/t8_schemes/t8_scheme.hxx b/src/t8_schemes/t8_scheme.hxx index a41f64b2ca..ec09fd8dc2 100644 --- a/src/t8_schemes/t8_scheme.hxx +++ b/src/t8_schemes/t8_scheme.hxx @@ -537,8 +537,9 @@ class t8_scheme { element_get_nca (const t8_eclass_t tree_class, const t8_element_t *elem1, const t8_element_t *elem2, t8_element_t *const nca) const { - return std::visit ([&] (auto &&scheme) { return scheme.element_get_nca (elem1, elem2, nca); }, - eclass_schemes[tree_class]); + std::visit ([&] (auto &&scheme) { return scheme.element_get_nca (elem1, elem2, nca); }, eclass_schemes[tree_class]); + T8_ASSERT (element_is_ancestor (tree_class, nca, elem1)); + T8_ASSERT (element_is_ancestor (tree_class, nca, elem2)); }; /** Compute the shape of the face of an element. From f6c751bffbfe38084b988d8db38ab48375e4f2e6 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Thu, 3 Jul 2025 15:37:10 +0200 Subject: [PATCH 20/41] Add is_ancestor check to nca unit test --- test/t8_schemes/t8_gtest_nca.cxx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/t8_schemes/t8_gtest_nca.cxx b/test/t8_schemes/t8_gtest_nca.cxx index 5e19cc5816..a0263ac90c 100644 --- a/test/t8_schemes/t8_gtest_nca.cxx +++ b/test/t8_schemes/t8_gtest_nca.cxx @@ -80,6 +80,10 @@ TEST_P (nca, nca_check_shallow) scheme->element_get_nca (tree_class, desc_a, desc_b, check); /*expect equality */ EXPECT_ELEM_EQ (scheme, tree_class, check, correct_nca); + // Check agains element_is_ancestor. This adds another test layer + // to both element_get_nca and element_is_ancestor. + EXPECT_TRUE (scheme->element_is_ancestor (tree_class, check, desc_a)); + EXPECT_TRUE (scheme->element_is_ancestor (tree_class, check, desc_b)); } } } @@ -124,6 +128,10 @@ TEST_P (nca, nca_check_deep) /* Expect equality of correct_nca and check for every other class */ EXPECT_ELEM_EQ (scheme, tree_class, correct_nca, check); } + // Check agains element_is_ancestor. This adds another test layer + // to both element_get_nca and element_is_ancestor. + EXPECT_TRUE (scheme->element_is_ancestor (tree_class, check, desc_a)); + EXPECT_TRUE (scheme->element_is_ancestor (tree_class, check, desc_b)); } } } @@ -178,6 +186,10 @@ t8_recursive_nca_check (t8_element_t *check_nca, t8_element_t *desc_a, t8_elemen for (j = 0; j < num_children_b; j++) { scheme->element_get_child (tree_class, parent_b, j, desc_b); scheme->element_get_nca (tree_class, desc_a, desc_b, check); + // Check agains element_is_ancestor. This adds another test layer + // to both element_get_nca and element_is_ancestor. + EXPECT_TRUE (scheme->element_is_ancestor (tree_class, check, desc_a)); + EXPECT_TRUE (scheme->element_is_ancestor (tree_class, check, desc_b)); if (!scheme->element_is_equal (tree_class, check_nca, check)) { level_a = scheme->element_get_level (tree_class, desc_a); @@ -306,6 +318,10 @@ TEST_P (nca, recursive_check_higher_level) scheme->element_get_nca (tree_class, parent_a, parent_b, check); EXPECT_ELEM_EQ (scheme, tree_class, parent_a, check); EXPECT_ELEM_EQ (scheme, tree_class, parent_b, check); + // Check agains element_is_ancestor. This adds another test layer + // to both element_get_nca and element_is_ancestor. + EXPECT_TRUE (scheme->element_is_ancestor (tree_class, check, parent_a)); + EXPECT_TRUE (scheme->element_is_ancestor (tree_class, check, parent_b)); } } } From 35402c73028788cf4ecd2eba1651e107be124516 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Thu, 3 Jul 2025 15:42:23 +0200 Subject: [PATCH 21/41] typos --- test/t8_schemes/t8_gtest_nca.cxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/t8_schemes/t8_gtest_nca.cxx b/test/t8_schemes/t8_gtest_nca.cxx index a0263ac90c..32b0876623 100644 --- a/test/t8_schemes/t8_gtest_nca.cxx +++ b/test/t8_schemes/t8_gtest_nca.cxx @@ -80,7 +80,7 @@ TEST_P (nca, nca_check_shallow) scheme->element_get_nca (tree_class, desc_a, desc_b, check); /*expect equality */ EXPECT_ELEM_EQ (scheme, tree_class, check, correct_nca); - // Check agains element_is_ancestor. This adds another test layer + // Check against element_is_ancestor. This adds another test layer // to both element_get_nca and element_is_ancestor. EXPECT_TRUE (scheme->element_is_ancestor (tree_class, check, desc_a)); EXPECT_TRUE (scheme->element_is_ancestor (tree_class, check, desc_b)); @@ -128,7 +128,7 @@ TEST_P (nca, nca_check_deep) /* Expect equality of correct_nca and check for every other class */ EXPECT_ELEM_EQ (scheme, tree_class, correct_nca, check); } - // Check agains element_is_ancestor. This adds another test layer + // Check against element_is_ancestor. This adds another test layer // to both element_get_nca and element_is_ancestor. EXPECT_TRUE (scheme->element_is_ancestor (tree_class, check, desc_a)); EXPECT_TRUE (scheme->element_is_ancestor (tree_class, check, desc_b)); @@ -186,7 +186,7 @@ t8_recursive_nca_check (t8_element_t *check_nca, t8_element_t *desc_a, t8_elemen for (j = 0; j < num_children_b; j++) { scheme->element_get_child (tree_class, parent_b, j, desc_b); scheme->element_get_nca (tree_class, desc_a, desc_b, check); - // Check agains element_is_ancestor. This adds another test layer + // Check against element_is_ancestor. This adds another test layer // to both element_get_nca and element_is_ancestor. EXPECT_TRUE (scheme->element_is_ancestor (tree_class, check, desc_a)); EXPECT_TRUE (scheme->element_is_ancestor (tree_class, check, desc_b)); @@ -318,7 +318,7 @@ TEST_P (nca, recursive_check_higher_level) scheme->element_get_nca (tree_class, parent_a, parent_b, check); EXPECT_ELEM_EQ (scheme, tree_class, parent_a, check); EXPECT_ELEM_EQ (scheme, tree_class, parent_b, check); - // Check agains element_is_ancestor. This adds another test layer + // Check against element_is_ancestor. This adds another test layer // to both element_get_nca and element_is_ancestor. EXPECT_TRUE (scheme->element_is_ancestor (tree_class, check, parent_a)); EXPECT_TRUE (scheme->element_is_ancestor (tree_class, check, parent_b)); From 4aa336305510f29179f6ad129ee226c8314ce7f1 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Mon, 1 Dec 2025 12:52:31 +0100 Subject: [PATCH 22/41] Update src/t8_schemes/t8_scheme.hxx Co-authored-by: David Knapp --- src/t8_schemes/t8_scheme.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/t8_schemes/t8_scheme.hxx b/src/t8_schemes/t8_scheme.hxx index ec09fd8dc2..981f861128 100644 --- a/src/t8_schemes/t8_scheme.hxx +++ b/src/t8_schemes/t8_scheme.hxx @@ -502,7 +502,7 @@ class t8_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. */ - inline bool + constexpr bool element_is_ancestor (const t8_eclass_t tree_class, const t8_element_t *element_A, const t8_element_t *element_B) const { return std::visit ([&] (auto &&scheme) { return scheme.element_is_ancestor (element_A, element_B); }, From 1fe100b3c1b78d24887554fa52168e44943b3e4e Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Thu, 15 Jan 2026 09:51:04 +0100 Subject: [PATCH 23/41] remove constexpr from is_ancestor function since it does not work with [] from std::vector anyways --- src/t8_schemes/t8_scheme.hxx | 2 +- src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/t8_schemes/t8_scheme.hxx b/src/t8_schemes/t8_scheme.hxx index 0fadab5779..57c2bfbaa0 100644 --- a/src/t8_schemes/t8_scheme.hxx +++ b/src/t8_schemes/t8_scheme.hxx @@ -517,7 +517,7 @@ class t8_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. */ - constexpr bool + bool element_is_ancestor (const t8_eclass_t tree_class, const t8_element_t *element_A, const t8_element_t *element_B) const { return std::visit ([&] (auto &&scheme) { return scheme.element_is_ancestor (element_A, element_B); }, diff --git a/src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx b/src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx index b5fbb95d5f..2861bb2de6 100644 --- a/src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx +++ b/src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx @@ -619,7 +619,7 @@ struct t8_standalone_scheme: public t8_scheme_helpers Date: Thu, 15 Jan 2026 09:53:19 +0100 Subject: [PATCH 24/41] fix documentation --- .../t8_default/t8_default_common/t8_default_common.hxx | 2 -- src/t8_schemes/t8_scheme.hxx | 3 +-- src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx | 2 -- 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/t8_schemes/t8_default/t8_default_common/t8_default_common.hxx b/src/t8_schemes/t8_default/t8_default_common/t8_default_common.hxx index 0205f62190..94b801dd66 100644 --- a/src/t8_schemes/t8_default/t8_default_common/t8_default_common.hxx +++ b/src/t8_schemes/t8_default/t8_default_common/t8_default_common.hxx @@ -193,8 +193,6 @@ class t8_default_scheme_common: public t8_scheme_helpers Date: Wed, 28 Jan 2026 15:38:17 +0100 Subject: [PATCH 25/41] Add own header for reusable adapt functions to test environment --- test/t8_gtest_adapt_callbacks.cxx | 58 +++++++++++++++++++++++++++++++ test/t8_gtest_adapt_callbacks.hxx | 45 ++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 test/t8_gtest_adapt_callbacks.cxx create mode 100644 test/t8_gtest_adapt_callbacks.hxx diff --git a/test/t8_gtest_adapt_callbacks.cxx b/test/t8_gtest_adapt_callbacks.cxx new file mode 100644 index 0000000000..bab9f26a08 --- /dev/null +++ b/test/t8_gtest_adapt_callbacks.cxx @@ -0,0 +1,58 @@ +/* + 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_gtest_adapt_callbacks.cxx +* Provide forest adapt callback functions that we use in our tests. +*/ + +#include + +/* Adapt a forest such that always the first child of a + * family is refined and no other elements. This results in a highly + * imbalanced forest. + * + * This adapt callbacks requires an integer as forest user data. + * This integer is the maximum refinement level. + */ +int +t8_test_adapt_first_child (t8_forest_t forest, [[maybe_unused]] t8_forest_t forest_from, + [[maybe_unused]] t8_locidx_t which_tree, const t8_eclass_t eclass, + [[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[]) +{ + T8_ASSERT (!is_family || (is_family && num_elements == scheme->element_get_num_children (eclass, elements[0]))); + + int level = scheme->element_get_level (eclass, elements[0]); + + /* we set a maximum refinement level as forest user data */ + int maxlevel = *(int *) t8_forest_get_user_data (forest); + if (level >= maxlevel) { + /* Do not refine after the maxlevel */ + return 0; + } + int child_id = scheme->element_get_child_id (eclass, elements[0]); + if (child_id == 1) { + return 1; + } + return 0; +} diff --git a/test/t8_gtest_adapt_callbacks.hxx b/test/t8_gtest_adapt_callbacks.hxx new file mode 100644 index 0000000000..584015a3f1 --- /dev/null +++ b/test/t8_gtest_adapt_callbacks.hxx @@ -0,0 +1,45 @@ +/* + 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_gtest_adapt_callbacks.hxx +* Provide forest adapt callback functions that we use in our tests. +*/ + +#ifndef T8_GTEST_ADAPT_CALLBACKS +#define T8_GTEST_ADAPT_CALLBACKS + +#include +#include + +/* Adapt a forest such that always the first child of a + * family is refined and no other elements. This results in a highly + * imbalanced forest. + * + * This adapt callbacks requires an integer as forest user data. + * This integer is the maximum refinement level. + */ +int +t8_test_adapt_first_child (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t which_tree, + 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[]); + +#endif /* T8_GTEST_ADAPT_CALLBACKS */ From 746a4cc8c81c92bfed23f4b064c5d4ca8ab53807 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Wed, 28 Jan 2026 15:44:56 +0100 Subject: [PATCH 26/41] Use new adapt callback in existing test --- test/CMakeLists.txt | 2 +- test/t8_forest/t8_gtest_element_is_leaf.cxx | 27 +-------------------- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a6713eacab..e721229af8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -151,7 +151,7 @@ add_t8_cpp_test( NAME t8_gtest_ghost_and_owner_parallel SOURCES t8_for add_t8_cpp_test( NAME t8_gtest_balance_parallel SOURCES t8_forest/t8_gtest_balance.cxx ) add_t8_cpp_test( NAME t8_gtest_forest_commit_parallel SOURCES t8_forest/t8_gtest_forest_commit.cxx ) add_t8_cpp_test( NAME t8_gtest_forest_face_normal_serial SOURCES t8_forest/t8_gtest_forest_face_normal.cxx ) -add_t8_cpp_test( NAME t8_gtest_element_is_leaf_serial SOURCES t8_forest/t8_gtest_element_is_leaf.cxx ) +add_t8_cpp_test( NAME t8_gtest_element_is_leaf_serial SOURCES t8_forest/t8_gtest_element_is_leaf.cxx t8_gtest_adapt_callbacks.cxx ) add_t8_cpp_test( NAME t8_gtest_partition_data_parallel SOURCES t8_forest/t8_gtest_partition_data.cxx ) 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 ) diff --git a/test/t8_forest/t8_gtest_element_is_leaf.cxx b/test/t8_forest/t8_gtest_element_is_leaf.cxx index 835b899421..f10eb951a5 100644 --- a/test/t8_forest/t8_gtest_element_is_leaf.cxx +++ b/test/t8_forest/t8_gtest_element_is_leaf.cxx @@ -27,6 +27,7 @@ #include #include #include "test/t8_cmesh_generator/t8_cmesh_example_sets.hxx" +#include #include /* In this test we check the t8_forest_element_is_leaf function. @@ -42,32 +43,6 @@ #else #define T8_IS_LEAF_MAX_LVL 4 #endif -/* Adapt a forest such that always the first child of a - * family is refined and no other elements. This results in a highly - * imbalanced forest. */ -static int -t8_test_adapt_first_child (t8_forest_t forest, [[maybe_unused]] t8_forest_t forest_from, - [[maybe_unused]] t8_locidx_t which_tree, const 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[]) -{ - T8_ASSERT (!is_family || (is_family && num_elements == scheme->element_get_num_children (tree_class, elements[0]))); - - const int level = scheme->element_get_level (tree_class, elements[0]); - - /* we set a maximum refinement level as forest user data */ - int maxlevel = *(int *) t8_forest_get_user_data (forest); - if (level >= maxlevel) { - /* Do not refine after the maxlevel */ - return 0; - } - const int child_id = scheme->element_get_child_id (tree_class, elements[0]); - if (child_id == 1) { - return 1; - } - return 0; -} class element_is_leaf: public testing::TestWithParam, int>> { protected: From f4365b4b0113e8a167f79f329eddcd49823313f9 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Wed, 28 Jan 2026 15:51:40 +0100 Subject: [PATCH 27/41] Start test for bin search --- test/CMakeLists.txt | 1 + test/t8_forest/t8_gtest_bin_search.cxx | 210 +++++++++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 test/t8_forest/t8_gtest_bin_search.cxx diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e721229af8..30f4380348 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -139,6 +139,7 @@ add_t8_cpp_test( NAME t8_gtest_cmesh_bounding_box_serial SOUR add_t8_cpp_test( NAME t8_gtest_shmem_parallel SOURCES t8_data/t8_gtest_shmem.cxx ) add_t8_cpp_test( NAME t8_gtest_data_pack_parallel SOURCES t8_data/t8_gtest_data_handler.cxx t8_data/t8_data_handler_specs.cxx) +add_t8_cpp_test( NAME t8_gtest_bin_search SOURCES t8_forest/t8_gtest_bin_search.cxx t8_gtest_adapt_callbacks.cxx ) add_t8_cpp_test( NAME t8_gtest_element_volume_serial SOURCES t8_forest/t8_gtest_element_volume.cxx ) add_t8_cpp_test( NAME t8_gtest_search_parallel SOURCES t8_forest/t8_gtest_search.cxx ) add_t8_cpp_test( NAME t8_gtest_half_neighbors_parallel SOURCES t8_forest/t8_gtest_half_neighbors.cxx ) diff --git a/test/t8_forest/t8_gtest_bin_search.cxx b/test/t8_forest/t8_gtest_bin_search.cxx new file mode 100644 index 0000000000..9507820498 --- /dev/null +++ b/test/t8_forest/t8_gtest_bin_search.cxx @@ -0,0 +1,210 @@ +/* + 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) 2015 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. +*/ + +#include +#include +#include +#include +#include +#include +#include "test/t8_cmesh_generator/t8_cmesh_example_sets.hxx" +#include + +/* In these tests we check the t8_forest_bin_search_lower, t8_forest_bin_search_upper + * and t8_forest_bin_search_first_descendant_ancestor functions. + * Iterating over all cmesh test cases, we create a uniform and an adaptive forest. + * For each forest, we + * TODO: Fill out comment + */ + +/* Maximum uniform level for forest. */ + +#if T8_TEST_LEVEL_INT >= 1 +#define T8_IS_LEAF_MAX_LVL 3 +#else +#define T8_IS_LEAF_MAX_LVL 4 +#endif +/* Adapt a forest such that always the first child of a + * family is refined and no other elements. This results in a highly + * imbalanced forest. */ +static int +t8_test_adapt_first_child (t8_forest_t forest, [[maybe_unused]] t8_forest_t forest_from, + [[maybe_unused]] t8_locidx_t which_tree, const 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[]) +{ + T8_ASSERT (!is_family || (is_family && num_elements == scheme->element_get_num_children (tree_class, elements[0]))); + + const int level = scheme->element_get_level (tree_class, elements[0]); + + /* we set a maximum refinement level as forest user data */ + int maxlevel = *(int *) t8_forest_get_user_data (forest); + if (level >= maxlevel) { + /* Do not refine after the maxlevel */ + return 0; + } + const int child_id = scheme->element_get_child_id (tree_class, elements[0]); + if (child_id == 1) { + return 1; + } + return 0; +} + +class element_is_leaf: public testing::TestWithParam, int>> { + protected: + void + SetUp () override + { + /* Construct a cmesh */ + const int scheme_id = std::get<0> (std::get<0> (GetParam ())); + scheme = create_from_scheme_id (scheme_id); + const t8_eclass_t tree_class = std::get<1> (std::get<0> (GetParam ())); + const int level = std::get<1> (GetParam ()); + t8_cmesh_t cmesh = t8_cmesh_new_from_class (tree_class, sc_MPI_COMM_WORLD); + + forest = t8_forest_new_uniform (cmesh, scheme, level, 0, sc_MPI_COMM_WORLD); + t8_forest_ref (forest); + //const int maxlevel = t8_forest_get_maxlevel (forest); + int maxlevel = 7; + const int recursive_adapt = 1; + forest_adapt = t8_forest_new_adapt (forest, t8_test_adapt_first_child, recursive_adapt, 0, &maxlevel); + } + + void + TearDown () override + { + if (forest != NULL) { + t8_forest_unref (&forest); + } + if (forest_adapt != NULL) { + t8_forest_unref (&forest_adapt); + } + } + + t8_forest_t forest { NULL }; + t8_forest_t forest_adapt { NULL }; + const t8_scheme *scheme; +}; + +class element_is_leaf_hybrid: public testing::TestWithParam { + protected: + void + SetUp () override + { + /* Construct a cmesh */ + const int scheme_id = GetParam (); + scheme = create_from_scheme_id (scheme_id); + t8_cmesh_t cmesh = t8_cmesh_new_full_hybrid (sc_MPI_COMM_WORLD); + const int level = 0; + forest = t8_forest_new_uniform (cmesh, scheme, level, 0, sc_MPI_COMM_WORLD); + t8_forest_ref (forest); + int maxlevel = 7; + const int recursive_adapt = 1; + forest_adapt = t8_forest_new_adapt (forest, t8_test_adapt_first_child, recursive_adapt, 0, &maxlevel); + } + + void + TearDown () override + { + if (forest != NULL) { + t8_forest_unref (&forest); + } + if (forest_adapt != NULL) { + t8_forest_unref (&forest_adapt); + } + } + + t8_forest_t forest { NULL }; + t8_forest_t forest_adapt { NULL }; + const t8_scheme *scheme; +}; + +static void +t8_test_element_is_leaf_for_forest (t8_forest_t forest) +{ + const t8_locidx_t num_local_trees = t8_forest_get_num_local_trees (forest); + + const t8_scheme *scheme = t8_forest_get_scheme (forest); + for (t8_locidx_t itree = 0; itree < num_local_trees; ++itree) { + const t8_locidx_t num_elements_in_tree = t8_forest_get_tree_num_leaf_elements (forest, itree); + const t8_eclass_t tree_class = t8_forest_get_tree_class (forest, itree); + /* Allocate memory to build a non-leaf element. */ + t8_element_t *not_leaf; + scheme->element_new (tree_class, 1, ¬_leaf); + /* Iterate over all the tree's leaf elements, check whether the leaf + * is correctly identified by t8_forest_element_is_leaf, + * build its parent and its first child (if they exist), and verify + * that t8_forest_element_is_leaf returns false. */ + for (t8_locidx_t ielement = 0; ielement < num_elements_in_tree; ++ielement) { + const t8_element_t *leaf_element = t8_forest_get_leaf_element_in_tree (forest, itree, ielement); + EXPECT_TRUE (t8_forest_element_is_leaf (forest, leaf_element, itree)); + /* Compute parent and first child of element and check that they are not in the tree */ + const int element_level = scheme->element_get_level (tree_class, leaf_element); + if (element_level > 0) { + scheme->element_get_parent (tree_class, leaf_element, not_leaf); + EXPECT_FALSE (t8_forest_element_is_leaf (forest, not_leaf, itree)); + } + if (element_level < scheme->get_maxlevel (tree_class)) { + scheme->element_get_child (tree_class, leaf_element, 0, not_leaf); + EXPECT_FALSE (t8_forest_element_is_leaf (forest, not_leaf, itree)); + } + } + scheme->element_destroy (tree_class, 1, ¬_leaf); + } +} + +TEST_P (element_is_leaf, element_is_leaf) +{ + t8_test_element_is_leaf_for_forest (forest); +} + +TEST_P (element_is_leaf, element_is_leaf_adapt) +{ + t8_test_element_is_leaf_for_forest (forest_adapt); +} + +TEST_P (element_is_leaf_hybrid, element_is_leaf) +{ + t8_test_element_is_leaf_for_forest (forest); +} + +TEST_P (element_is_leaf_hybrid, element_is_leaf_adapt) +{ + t8_test_element_is_leaf_for_forest (forest_adapt); +} + +/* Define a lambda to beatify gtest output for tuples . + * This will set the correct level and cmesh name as part of the test case name. */ +auto pretty_print_eclass_scheme_and_level + = [] (const testing::TestParamInfo, int>> &info) { + std::string scheme = t8_scheme_to_string[std::get<0> (std::get<0> (info.param))]; + std::string eclass = t8_eclass_to_string[std::get<1> (std::get<0> (info.param))]; + std::string level = std::string ("_level_") + std::to_string (std::get<1> (info.param)); + return scheme + "_" + eclass + level; + }; + +INSTANTIATE_TEST_SUITE_P (t8_gtest_element_is_leaf, element_is_leaf, + testing::Combine (AllSchemes, testing::Range (0, T8_IS_LEAF_MAX_LVL)), + pretty_print_eclass_scheme_and_level); + +INSTANTIATE_TEST_SUITE_P (t8_gtest_element_is_leaf_hybrid, element_is_leaf_hybrid, AllSchemeCollections, print_scheme); From 9559150ec3b704e57b495872e261a6ec8da420d8 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Wed, 28 Jan 2026 16:44:19 +0100 Subject: [PATCH 28/41] Add first version of bin search lower test --- test/t8_forest/t8_gtest_bin_search.cxx | 132 ++++++++----------------- 1 file changed, 42 insertions(+), 90 deletions(-) diff --git a/test/t8_forest/t8_gtest_bin_search.cxx b/test/t8_forest/t8_gtest_bin_search.cxx index 9507820498..e4aa7cbdd4 100644 --- a/test/t8_forest/t8_gtest_bin_search.cxx +++ b/test/t8_forest/t8_gtest_bin_search.cxx @@ -25,8 +25,10 @@ #include #include #include +#include #include #include "test/t8_cmesh_generator/t8_cmesh_example_sets.hxx" +#include #include /* In these tests we check the t8_forest_bin_search_lower, t8_forest_bin_search_upper @@ -43,34 +45,9 @@ #else #define T8_IS_LEAF_MAX_LVL 4 #endif -/* Adapt a forest such that always the first child of a - * family is refined and no other elements. This results in a highly - * imbalanced forest. */ -static int -t8_test_adapt_first_child (t8_forest_t forest, [[maybe_unused]] t8_forest_t forest_from, - [[maybe_unused]] t8_locidx_t which_tree, const 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[]) -{ - T8_ASSERT (!is_family || (is_family && num_elements == scheme->element_get_num_children (tree_class, elements[0]))); - - const int level = scheme->element_get_level (tree_class, elements[0]); - - /* we set a maximum refinement level as forest user data */ - int maxlevel = *(int *) t8_forest_get_user_data (forest); - if (level >= maxlevel) { - /* Do not refine after the maxlevel */ - return 0; - } - const int child_id = scheme->element_get_child_id (tree_class, elements[0]); - if (child_id == 1) { - return 1; - } - return 0; -} -class element_is_leaf: public testing::TestWithParam, int>> { +// TODO: ADd this class to common headers since it is reused +class t8_bin_search_tester: public testing::TestWithParam, int>> { protected: void SetUp () override @@ -82,44 +59,12 @@ class element_is_leaf: public testing::TestWithParam (GetParam ()); t8_cmesh_t cmesh = t8_cmesh_new_from_class (tree_class, sc_MPI_COMM_WORLD); - forest = t8_forest_new_uniform (cmesh, scheme, level, 0, sc_MPI_COMM_WORLD); - t8_forest_ref (forest); - //const int maxlevel = t8_forest_get_maxlevel (forest); - int maxlevel = 7; - const int recursive_adapt = 1; - forest_adapt = t8_forest_new_adapt (forest, t8_test_adapt_first_child, recursive_adapt, 0, &maxlevel); - } - - void - TearDown () override - { - if (forest != NULL) { - t8_forest_unref (&forest); - } - if (forest_adapt != NULL) { - t8_forest_unref (&forest_adapt); - } - } - - t8_forest_t forest { NULL }; - t8_forest_t forest_adapt { NULL }; - const t8_scheme *scheme; -}; - -class element_is_leaf_hybrid: public testing::TestWithParam { - protected: - void - SetUp () override - { - /* Construct a cmesh */ - const int scheme_id = GetParam (); - scheme = create_from_scheme_id (scheme_id); - t8_cmesh_t cmesh = t8_cmesh_new_full_hybrid (sc_MPI_COMM_WORLD); - const int level = 0; + // Construct a uniform forest forest = t8_forest_new_uniform (cmesh, scheme, level, 0, sc_MPI_COMM_WORLD); t8_forest_ref (forest); int maxlevel = 7; const int recursive_adapt = 1; + // Construct an adaptive forest forest_adapt = t8_forest_new_adapt (forest, t8_test_adapt_first_child, recursive_adapt, 0, &maxlevel); } @@ -140,7 +85,7 @@ class element_is_leaf_hybrid: public testing::TestWithParam { }; static void -t8_test_element_is_leaf_for_forest (t8_forest_t forest) +t8_test_forest_bin_search_lower (t8_forest_t forest) { const t8_locidx_t num_local_trees = t8_forest_get_num_local_trees (forest); @@ -148,50 +93,59 @@ t8_test_element_is_leaf_for_forest (t8_forest_t forest) for (t8_locidx_t itree = 0; itree < num_local_trees; ++itree) { const t8_locidx_t num_elements_in_tree = t8_forest_get_tree_num_leaf_elements (forest, itree); const t8_eclass_t tree_class = t8_forest_get_tree_class (forest, itree); - /* Allocate memory to build a non-leaf element. */ - t8_element_t *not_leaf; - scheme->element_new (tree_class, 1, ¬_leaf); + const t8_element_array_t *leafs = t8_forest_tree_get_leaf_elements (forest, itree); + /* Iterate over all the tree's leaf elements, check whether the leaf * is correctly identified by t8_forest_element_is_leaf, * build its parent and its first child (if they exist), and verify * that t8_forest_element_is_leaf returns false. */ for (t8_locidx_t ielement = 0; ielement < num_elements_in_tree; ++ielement) { const t8_element_t *leaf_element = t8_forest_get_leaf_element_in_tree (forest, itree, ielement); - EXPECT_TRUE (t8_forest_element_is_leaf (forest, leaf_element, itree)); - /* Compute parent and first child of element and check that they are not in the tree */ const int element_level = scheme->element_get_level (tree_class, leaf_element); - if (element_level > 0) { - scheme->element_get_parent (tree_class, leaf_element, not_leaf); - EXPECT_FALSE (t8_forest_element_is_leaf (forest, not_leaf, itree)); + const t8_linearidx_t element_id = scheme->element_get_linear_id (tree_class, leaf_element, element_level); + + /* Search for a linear element id in a sorted array of + * elements. If the element does not exist, return the largest index i + * such that the element at position i has a smaller id than the given one. + * If no such i exists, return -1. */ + const t8_locidx_t search_index = t8_forest_bin_search_lower (leafs, element_id, element_level); + // We expect the leaf element to be found at position ielement + EXPECT_EQ (search_index, ielement) << "Found wrong position of leaf element. Expected: " << ielement + << " got: " << search_index; + // If we increase the level, we expect the element to not be found, but the search + // should return the index of the original element. + if ( + element_level < scheme->get_maxlevel ( + tree_class)) { // TODO: This eventually should be element dependent. I know that an element dependent maxlevel function was developed in a different branch (by Sandro?) + const t8_locidx_t search_index = t8_forest_bin_search_lower (leafs, element_id, element_level + 1); + // We expect the leaf element to be found at position ielement + EXPECT_EQ (search_index, ielement) + << "Found wrong position of leaf element. Expected: " << ielement << " got: " << search_index; } - if (element_level < scheme->get_maxlevel (tree_class)) { - scheme->element_get_child (tree_class, leaf_element, 0, not_leaf); - EXPECT_FALSE (t8_forest_element_is_leaf (forest, not_leaf, itree)); + + // Construct an element that is definitely not in the array and + // does not have an element of smaller id in the array. We expect -1 as return. + // We take the first element of the forest and subtract 1 from its id. + if (ielement == 0 && element_id > 0) { + const t8_linearidx_t element_not_found_id = element_id - 1; + const t8_locidx_t search_index = t8_forest_bin_search_lower (leafs, element_not_found_id, element_level); + EXPECT_EQ (search_index, -1) << "Wrong return value for element that should not be in array. Expectec -1."; } } - scheme->element_destroy (tree_class, 1, ¬_leaf); } } -TEST_P (element_is_leaf, element_is_leaf) -{ - t8_test_element_is_leaf_for_forest (forest); -} - -TEST_P (element_is_leaf, element_is_leaf_adapt) +TEST_P (t8_bin_search_tester, bin_search_lower) { - t8_test_element_is_leaf_for_forest (forest_adapt); + t8_test_forest_bin_search_lower (forest); } -TEST_P (element_is_leaf_hybrid, element_is_leaf) +TEST_P (t8_bin_search_tester, bin_search_lower_adapt) { - t8_test_element_is_leaf_for_forest (forest); + t8_test_forest_bin_search_lower (forest_adapt); } -TEST_P (element_is_leaf_hybrid, element_is_leaf_adapt) -{ - t8_test_element_is_leaf_for_forest (forest_adapt); -} +// TODO: Add these lambda to common headers since it is reused /* Define a lambda to beatify gtest output for tuples . * This will set the correct level and cmesh name as part of the test case name. */ @@ -203,8 +157,6 @@ auto pretty_print_eclass_scheme_and_level return scheme + "_" + eclass + level; }; -INSTANTIATE_TEST_SUITE_P (t8_gtest_element_is_leaf, element_is_leaf, +INSTANTIATE_TEST_SUITE_P (t8_gtest_bin_search, t8_bin_search_tester, testing::Combine (AllSchemes, testing::Range (0, T8_IS_LEAF_MAX_LVL)), pretty_print_eclass_scheme_and_level); - -INSTANTIATE_TEST_SUITE_P (t8_gtest_element_is_leaf_hybrid, element_is_leaf_hybrid, AllSchemeCollections, print_scheme); From 1579ae46a911e9f247d65ab909c1884050535df0 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Thu, 29 Jan 2026 15:46:57 +0100 Subject: [PATCH 29/41] Continue test, crashes during search --- test/CMakeLists.txt | 2 +- test/t8_forest/t8_gtest_bin_search.cxx | 29 ++++++++++++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8c3045d689..88f850d93d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -137,7 +137,7 @@ add_t8_cpp_test( NAME t8_gtest_cmesh_bounding_box_serial SOUR add_t8_cpp_test( NAME t8_gtest_shmem_parallel SOURCES t8_data/t8_gtest_shmem.cxx ) add_t8_cpp_test( NAME t8_gtest_data_pack_parallel SOURCES t8_data/t8_gtest_data_handler.cxx t8_data/t8_data_handler_specs.cxx) -add_t8_cpp_test( NAME t8_gtest_bin_search SOURCES t8_forest/t8_gtest_bin_search.cxx t8_gtest_adapt_callbacks.cxx ) +add_t8_cpp_test( NAME t8_gtest_bin_search_parallel SOURCES t8_forest/t8_gtest_bin_search.cxx t8_gtest_adapt_callbacks.cxx ) add_t8_cpp_test( NAME t8_gtest_element_volume_serial SOURCES t8_forest/t8_gtest_element_volume.cxx ) add_t8_cpp_test( NAME t8_gtest_search_parallel SOURCES t8_forest/t8_gtest_search.cxx ) add_t8_cpp_test( NAME t8_gtest_half_neighbors_parallel SOURCES t8_forest/t8_gtest_half_neighbors.cxx ) diff --git a/test/t8_forest/t8_gtest_bin_search.cxx b/test/t8_forest/t8_gtest_bin_search.cxx index e4aa7cbdd4..5706afe50a 100644 --- a/test/t8_forest/t8_gtest_bin_search.cxx +++ b/test/t8_forest/t8_gtest_bin_search.cxx @@ -28,7 +28,7 @@ #include #include #include "test/t8_cmesh_generator/t8_cmesh_example_sets.hxx" -#include +#include #include /* In these tests we check the t8_forest_bin_search_lower, t8_forest_bin_search_upper @@ -114,13 +114,30 @@ t8_test_forest_bin_search_lower (t8_forest_t forest) << " got: " << search_index; // If we increase the level, we expect the element to not be found, but the search // should return the index of the original element. - if ( - element_level < scheme->get_maxlevel ( - tree_class)) { // TODO: This eventually should be element dependent. I know that an element dependent maxlevel function was developed in a different branch (by Sandro?) - const t8_locidx_t search_index = t8_forest_bin_search_lower (leafs, element_id, element_level + 1); + if (element_level < scheme->get_maxlevel (tree_class)) { + t8_debugf ("Computing element for level %i, Max is %i\n", element_level, T8_DLINE_MAXLEVEL); + // TODO: The maxlevel eventually should be element dependent. I know that an element dependent maxlevel function was developed in a different branch (by Sandro?) + const t8_linearidx_t element_id_at_next_level + = scheme->element_get_linear_id (tree_class, leaf_element, element_level + 1); + + // TODO: This search call is running into an + /* + [libsc 0] Abort: Assertion '0 <= level && level <= T8_DLINE_MAXLEVEL' +[libsc 0] Abort: /localdata1/holk_jo/coding/source/t8code/feature-ancestor_search/src/t8_schemes/t8_default/t8_default_line/t8_default_line.cxx:323 +[libsc 0] Abort: Obtained 28 stack frames +[libsc 0] Stack 0: libsc.so.3.0.0(+0x20850) [0x7ffff7f25850] +[libsc 0] Stack 1: libsc.so.3.0.0(sc_abort+0x1f) [0x7ffff7f257e8] +[libsc 0] Stack 2: libsc.so.3.0.0(sc_abort_verbosef+0) [0x7ffff7f25b94] +[libsc 0] Stack 3: libt8.so.4.0.0-26.01-178-g9559150ec-dirty(_ZNK22t8_default_scheme_line21element_get_linear_idEPK10t8_elementi+0xce) [0x7ffff7a9e362] + +Due to search calling get_linear_id with a too large level. +*/ + + const t8_locidx_t search_index = t8_forest_bin_search_lower (leafs, element_id + 1, element_id_at_next_level); // We expect the leaf element to be found at position ielement EXPECT_EQ (search_index, ielement) - << "Found wrong position of leaf element. Expected: " << ielement << " got: " << search_index; + << "Found wrong position of level " << element_level << " leaf element with id " << element_id + << ". Expected: " << ielement << " got: " << search_index; } // Construct an element that is definitely not in the array and From b2ec0dfa3cf79a23352f876ca8b12c7a3fec15f5 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Tue, 3 Feb 2026 10:30:10 +0100 Subject: [PATCH 30/41] Fix parameter usage --- src/t8_forest/t8_forest_private.h | 2 +- test/t8_forest/t8_gtest_bin_search.cxx | 20 ++++---------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/src/t8_forest/t8_forest_private.h b/src/t8_forest/t8_forest_private.h index 0949de3f2d..e8c9d5af4f 100644 --- a/src/t8_forest/t8_forest_private.h +++ b/src/t8_forest/t8_forest_private.h @@ -204,7 +204,7 @@ t8_forest_get_tree_leaf_element_array_mutable (const t8_forest_t forest, t8_loci /** Search for a linear element id in a sorted array of * elements. If the element does not exist, return the largest index i - * such that the element at position i has a smaller id than the given one. + * such that the element at position i has a smaller or equal id than the given one. * If no such i exists, return -1. * \param [in] elements An array of elements. Must be sorted according to linear id at maximum level. * Must correspond to a valid refinement (i.e. contain no duplicate elements or elements and their descendants). diff --git a/test/t8_forest/t8_gtest_bin_search.cxx b/test/t8_forest/t8_gtest_bin_search.cxx index 5706afe50a..92983adb3d 100644 --- a/test/t8_forest/t8_gtest_bin_search.cxx +++ b/test/t8_forest/t8_gtest_bin_search.cxx @@ -120,24 +120,12 @@ t8_test_forest_bin_search_lower (t8_forest_t forest) const t8_linearidx_t element_id_at_next_level = scheme->element_get_linear_id (tree_class, leaf_element, element_level + 1); - // TODO: This search call is running into an - /* - [libsc 0] Abort: Assertion '0 <= level && level <= T8_DLINE_MAXLEVEL' -[libsc 0] Abort: /localdata1/holk_jo/coding/source/t8code/feature-ancestor_search/src/t8_schemes/t8_default/t8_default_line/t8_default_line.cxx:323 -[libsc 0] Abort: Obtained 28 stack frames -[libsc 0] Stack 0: libsc.so.3.0.0(+0x20850) [0x7ffff7f25850] -[libsc 0] Stack 1: libsc.so.3.0.0(sc_abort+0x1f) [0x7ffff7f257e8] -[libsc 0] Stack 2: libsc.so.3.0.0(sc_abort_verbosef+0) [0x7ffff7f25b94] -[libsc 0] Stack 3: libt8.so.4.0.0-26.01-178-g9559150ec-dirty(_ZNK22t8_default_scheme_line21element_get_linear_idEPK10t8_elementi+0xce) [0x7ffff7a9e362] - -Due to search calling get_linear_id with a too large level. -*/ - - const t8_locidx_t search_index = t8_forest_bin_search_lower (leafs, element_id + 1, element_id_at_next_level); + const t8_locidx_t search_index + = t8_forest_bin_search_lower (leafs, element_id_at_next_level, element_level + 1); // We expect the leaf element to be found at position ielement EXPECT_EQ (search_index, ielement) - << "Found wrong position of level " << element_level << " leaf element with id " << element_id - << ". Expected: " << ielement << " got: " << search_index; + << "Found wrong position of level " << element_level + 1 << " leaf element with id " + << element_id_at_next_level << ". Expected: " << ielement << " got: " << search_index; } // Construct an element that is definitely not in the array and From df66454bf22e9fdde948e2f32a7c0040b67995bd Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Tue, 3 Feb 2026 10:56:18 +0100 Subject: [PATCH 31/41] correct function documentation --- src/t8_forest/t8_forest_private.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/t8_forest/t8_forest_private.h b/src/t8_forest/t8_forest_private.h index e8c9d5af4f..dff9545684 100644 --- a/src/t8_forest/t8_forest_private.h +++ b/src/t8_forest/t8_forest_private.h @@ -219,7 +219,7 @@ t8_forest_bin_search_lower (const t8_element_array_t *elements, const t8_lineari /** \brief Search for a linear element id (at level element_level) in a sorted array of * elements. If the element does not exist, return the smallest index i - * such that the element at position i has a larger id than the given one. + * such that the element at position i has a larger or equal id than the given one. * If no such i exists, return -1. * \param [in] elements An array of elements. Must be sorted according to linear id at maximum level. * Must correspond to a valid refinement (i.e. contain no duplicate elements or elements and their descendants). From 17cf8e75984a4b1a76c5be5e0a25eedaedc7cc85 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Tue, 3 Feb 2026 10:57:12 +0100 Subject: [PATCH 32/41] Add test for bin_search_upper --- test/t8_forest/t8_gtest_bin_search.cxx | 97 +++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/test/t8_forest/t8_gtest_bin_search.cxx b/test/t8_forest/t8_gtest_bin_search.cxx index 92983adb3d..9dde7d2d3e 100644 --- a/test/t8_forest/t8_gtest_bin_search.cxx +++ b/test/t8_forest/t8_gtest_bin_search.cxx @@ -84,6 +84,90 @@ class t8_bin_search_tester: public testing::TestWithParam=0 ) and search for the L-1 element with this id. + * We expect E to be found since it fulfills that its id is >= than the id we search for. + * We then build one case per tree where we search for an element that is not contained and will not match + * any other element: + * We compute the linear Id of the last element in the tree and add 1 to it (if it is not zero). + * We expect the search to not find anything. +*/ +static void +t8_test_forest_bin_search_upper (t8_forest_t forest) +{ + const t8_locidx_t num_local_trees = t8_forest_get_num_local_trees (forest); + + const t8_scheme *scheme = t8_forest_get_scheme (forest); + for (t8_locidx_t itree = 0; itree < num_local_trees; ++itree) { + const t8_locidx_t num_elements_in_tree = t8_forest_get_tree_num_leaf_elements (forest, itree); + const t8_eclass_t tree_class = t8_forest_get_tree_class (forest, itree); + const t8_element_array_t *leafs = t8_forest_tree_get_leaf_elements (forest, itree); + + /* Iterate over all the tree's leaf elements, check whether the leaf + * is correctly identified by t8_forest_element_is_leaf, + * build its parent and its first child (if they exist), and verify + * that t8_forest_element_is_leaf returns false. */ + for (t8_locidx_t ielement = 0; ielement < num_elements_in_tree; ++ielement) { + const t8_element_t *leaf_element = t8_forest_get_leaf_element_in_tree (forest, itree, ielement); + const int element_level = scheme->element_get_level (tree_class, leaf_element); + const t8_linearidx_t element_id = scheme->element_get_linear_id (tree_class, leaf_element, element_level); + + /* Search for a linear element id in a sorted array of + * elements. If the element does not exist, return the largest index i + * such that the element at position i has a smaller id than the given one. + * If no such i exists, return -1. */ + const t8_locidx_t search_index = t8_forest_bin_search_upper (leafs, element_id, element_level); + // We expect the leaf element to be found at position ielement + EXPECT_EQ (search_index, ielement) << "Found wrong position of leaf element. Expected: " << ielement + << " got: " << search_index; + + // If we increase the level, we expect the element to not be found, but the search + // should return the index of the original element. + if (element_level > 0) { + const t8_linearidx_t element_id_at_previous_level + = scheme->element_get_linear_id (tree_class, leaf_element, element_level - 1); + + const t8_locidx_t search_index + = t8_forest_bin_search_upper (leafs, element_id_at_previous_level, element_level - 1); + // We expect the leaf element to be found at position ielement + EXPECT_EQ (search_index, ielement) + << "Found wrong position of level " << element_level - 1 << " leaf element with id " + << element_id_at_previous_level << ". Expected: " << ielement << " got: " << search_index; + } + + // Construct an element that is definitely not in the array and + // does not have an element of smaller id in the array. We expect -1 as return. + // We take the first element of the forest and subtract 1 from its id. + if (ielement == num_elements_in_tree - 1) { + const t8_linearidx_t element_not_found_id = element_id + 1; + // Double check for possible conversion error, if element_id == MAX_POSSIBLE_VALUE. In that case, the + // test logic fails. Should we ever run into this case, we need to rewrite this test accordingly. + SC_CHECK_ABORTF (element_not_found_id > 0, "Invalid element id %li\n", element_not_found_id); + const t8_locidx_t search_index = t8_forest_bin_search_upper (leafs, element_not_found_id, element_level); + EXPECT_EQ (search_index, -1) << "Wrong return value for element that should not be in array. Expectec -1."; + } + } + } +} + +/** Test the t8_forest_bin_search_lower function. + * We iterate through all elements of a forest. + * For each element E of level L, we call t8_forest_bin_search_lower on the forest's element array and expect + * the element to be found. + * For each element we then build one case where we search for an element that is not contained but will + * match a different element in the element array: + * We compute the linear Id of E at level L+1 (if not exceeding the maxlevel) and search for the L+1 element with this id. + * We expect E to be found since it fulfills that its id is <= than the id we search for. + * We then build one case per tree where we search for an element that is not contained and will not match + * any other element: + * We compute the linear Id of the first element in the tree and subtract 1 from it (if it is not zero). + * We expect the search to not find anything. +*/ static void t8_test_forest_bin_search_lower (t8_forest_t forest) { @@ -112,6 +196,7 @@ t8_test_forest_bin_search_lower (t8_forest_t forest) // We expect the leaf element to be found at position ielement EXPECT_EQ (search_index, ielement) << "Found wrong position of leaf element. Expected: " << ielement << " got: " << search_index; + // If we increase the level, we expect the element to not be found, but the search // should return the index of the original element. if (element_level < scheme->get_maxlevel (tree_class)) { @@ -140,7 +225,7 @@ t8_test_forest_bin_search_lower (t8_forest_t forest) } } -TEST_P (t8_bin_search_tester, bin_search_lower) +TEST_P (t8_bin_search_tester, bin_search_lower_uniform) { t8_test_forest_bin_search_lower (forest); } @@ -150,6 +235,16 @@ TEST_P (t8_bin_search_tester, bin_search_lower_adapt) t8_test_forest_bin_search_lower (forest_adapt); } +TEST_P (t8_bin_search_tester, bin_search_upper_uniform) +{ + t8_test_forest_bin_search_upper (forest); +} + +TEST_P (t8_bin_search_tester, bin_search_upper_adapt) +{ + t8_test_forest_bin_search_upper (forest_adapt); +} + // TODO: Add these lambda to common headers since it is reused /* Define a lambda to beatify gtest output for tuples . From af5344853d5404a984d02efebf6015202e0c71a4 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Tue, 3 Feb 2026 11:25:27 +0100 Subject: [PATCH 33/41] fix upper test --- test/t8_forest/t8_gtest_bin_search.cxx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/t8_forest/t8_gtest_bin_search.cxx b/test/t8_forest/t8_gtest_bin_search.cxx index 9dde7d2d3e..607cb7302e 100644 --- a/test/t8_forest/t8_gtest_bin_search.cxx +++ b/test/t8_forest/t8_gtest_bin_search.cxx @@ -128,16 +128,18 @@ t8_test_forest_bin_search_upper (t8_forest_t forest) // If we increase the level, we expect the element to not be found, but the search // should return the index of the original element. - if (element_level > 0) { - const t8_linearidx_t element_id_at_previous_level - = scheme->element_get_linear_id (tree_class, leaf_element, element_level - 1); + if (element_level < scheme->get_maxlevel (tree_class)) { + t8_debugf ("Computing element for level %i, Max is %i\n", element_level, T8_DLINE_MAXLEVEL); + // TODO: The maxlevel eventually should be element dependent. I know that an element dependent maxlevel function was developed in a different branch (by Sandro?) + const t8_linearidx_t element_id_at_next_level + = scheme->element_get_linear_id (tree_class, leaf_element, element_level + 1); const t8_locidx_t search_index - = t8_forest_bin_search_upper (leafs, element_id_at_previous_level, element_level - 1); + = t8_forest_bin_search_upper (leafs, element_id_at_next_level, element_level + 1); // We expect the leaf element to be found at position ielement EXPECT_EQ (search_index, ielement) - << "Found wrong position of level " << element_level - 1 << " leaf element with id " - << element_id_at_previous_level << ". Expected: " << ielement << " got: " << search_index; + << "Found wrong position of level " << element_level + 1 << " leaf element with id " + << element_id_at_next_level << ". Expected: " << ielement << " got: " << search_index; } // Construct an element that is definitely not in the array and @@ -225,21 +227,25 @@ t8_test_forest_bin_search_lower (t8_forest_t forest) } } +// bin_search_lower test for uniform forest TEST_P (t8_bin_search_tester, bin_search_lower_uniform) { t8_test_forest_bin_search_lower (forest); } +// bin_search_lower test for adaptive forest TEST_P (t8_bin_search_tester, bin_search_lower_adapt) { t8_test_forest_bin_search_lower (forest_adapt); } +// bin_search_upper test for uniform forest TEST_P (t8_bin_search_tester, bin_search_upper_uniform) { t8_test_forest_bin_search_upper (forest); } +// bin_search_upper test for adaptive forest TEST_P (t8_bin_search_tester, bin_search_upper_adapt) { t8_test_forest_bin_search_upper (forest_adapt); From 8ce9d58ec79db53aeee472457f96955893e0848c Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Tue, 3 Feb 2026 13:25:57 +0100 Subject: [PATCH 34/41] put commonly used print function in header --- test/t8_forest/t8_gtest_bin_search.cxx | 12 ------------ test/t8_forest/t8_gtest_element_is_leaf.cxx | 10 ---------- test/t8_gtest_macros.hxx | 11 +++++++++++ 3 files changed, 11 insertions(+), 22 deletions(-) diff --git a/test/t8_forest/t8_gtest_bin_search.cxx b/test/t8_forest/t8_gtest_bin_search.cxx index 607cb7302e..5093238c8e 100644 --- a/test/t8_forest/t8_gtest_bin_search.cxx +++ b/test/t8_forest/t8_gtest_bin_search.cxx @@ -251,18 +251,6 @@ TEST_P (t8_bin_search_tester, bin_search_upper_adapt) t8_test_forest_bin_search_upper (forest_adapt); } -// TODO: Add these lambda to common headers since it is reused - -/* Define a lambda to beatify gtest output for tuples . - * This will set the correct level and cmesh name as part of the test case name. */ -auto pretty_print_eclass_scheme_and_level - = [] (const testing::TestParamInfo, int>> &info) { - std::string scheme = t8_scheme_to_string[std::get<0> (std::get<0> (info.param))]; - std::string eclass = t8_eclass_to_string[std::get<1> (std::get<0> (info.param))]; - std::string level = std::string ("_level_") + std::to_string (std::get<1> (info.param)); - return scheme + "_" + eclass + level; - }; - INSTANTIATE_TEST_SUITE_P (t8_gtest_bin_search, t8_bin_search_tester, testing::Combine (AllSchemes, testing::Range (0, T8_IS_LEAF_MAX_LVL)), pretty_print_eclass_scheme_and_level); diff --git a/test/t8_forest/t8_gtest_element_is_leaf.cxx b/test/t8_forest/t8_gtest_element_is_leaf.cxx index 6247f66971..40cecf476f 100644 --- a/test/t8_forest/t8_gtest_element_is_leaf.cxx +++ b/test/t8_forest/t8_gtest_element_is_leaf.cxx @@ -169,16 +169,6 @@ TEST_P (element_is_leaf_hybrid, element_is_leaf_adapt) t8_test_element_is_leaf_for_forest (forest_adapt); } -/* Define a lambda to beatify gtest output for tuples . - * This will set the correct level and cmesh name as part of the test case name. */ -auto pretty_print_eclass_scheme_and_level - = [] (const testing::TestParamInfo, int>> &info) { - std::string scheme = t8_scheme_to_string[std::get<0> (std::get<0> (info.param))]; - std::string eclass = t8_eclass_to_string[std::get<1> (std::get<0> (info.param))]; - std::string level = std::string ("_level_") + std::to_string (std::get<1> (info.param)); - return scheme + "_" + eclass + level; - }; - INSTANTIATE_TEST_SUITE_P (t8_gtest_element_is_leaf, element_is_leaf, testing::Combine (AllSchemes, testing::Range (0, T8_IS_LEAF_MAX_LVL)), pretty_print_eclass_scheme_and_level); diff --git a/test/t8_gtest_macros.hxx b/test/t8_gtest_macros.hxx index 6b8cfd7a7e..ea7046fed7 100644 --- a/test/t8_gtest_macros.hxx +++ b/test/t8_gtest_macros.hxx @@ -33,6 +33,7 @@ #include #include #include +#include /** * lambda to pass to an INSTANTIATE_TEST_SUITE_P to print the current cmesh_example_base @@ -41,6 +42,16 @@ inline auto print_eclass = [] (const testing::TestParamInfo &info) { return t8_eclass_to_string[info.param]; }; +/** Define a lambda to beautify gtest output for tuples . + * This will set the correct level and cmesh name as part of the test case name. */ +auto pretty_print_eclass_scheme_and_level + = [] (const testing::TestParamInfo, int>> &info) { + std::string scheme = t8_scheme_to_string[std::get<0> (std::get<0> (info.param))]; + std::string eclass = t8_eclass_to_string[std::get<1> (std::get<0> (info.param))]; + std::string level = std::string ("_level_") + std::to_string (std::get<1> (info.param)); + return scheme + "_" + eclass + level; + }; + /** * Initializes everything needed for the t8code testsuite. * MPI is initialized with MPI_COMM_WORLD and SC with loglevel SC_LP_PRODUCTION. From 425b28e90b75406f73c4ce12e27b210aa5c6c103 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Tue, 3 Feb 2026 13:40:04 +0100 Subject: [PATCH 35/41] Implemented test for lower and upper bindary serach --- test/t8_forest/t8_gtest_bin_search.cxx | 256 +++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 test/t8_forest/t8_gtest_bin_search.cxx diff --git a/test/t8_forest/t8_gtest_bin_search.cxx b/test/t8_forest/t8_gtest_bin_search.cxx new file mode 100644 index 0000000000..5093238c8e --- /dev/null +++ b/test/t8_forest/t8_gtest_bin_search.cxx @@ -0,0 +1,256 @@ +/* + 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) 2015 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. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include "test/t8_cmesh_generator/t8_cmesh_example_sets.hxx" +#include +#include + +/* In these tests we check the t8_forest_bin_search_lower, t8_forest_bin_search_upper + * and t8_forest_bin_search_first_descendant_ancestor functions. + * Iterating over all cmesh test cases, we create a uniform and an adaptive forest. + * For each forest, we + * TODO: Fill out comment + */ + +/* Maximum uniform level for forest. */ + +#if T8_TEST_LEVEL_INT >= 1 +#define T8_IS_LEAF_MAX_LVL 3 +#else +#define T8_IS_LEAF_MAX_LVL 4 +#endif + +// TODO: ADd this class to common headers since it is reused +class t8_bin_search_tester: public testing::TestWithParam, int>> { + protected: + void + SetUp () override + { + /* Construct a cmesh */ + const int scheme_id = std::get<0> (std::get<0> (GetParam ())); + scheme = create_from_scheme_id (scheme_id); + const t8_eclass_t tree_class = std::get<1> (std::get<0> (GetParam ())); + const int level = std::get<1> (GetParam ()); + t8_cmesh_t cmesh = t8_cmesh_new_from_class (tree_class, sc_MPI_COMM_WORLD); + + // Construct a uniform forest + forest = t8_forest_new_uniform (cmesh, scheme, level, 0, sc_MPI_COMM_WORLD); + t8_forest_ref (forest); + int maxlevel = 7; + const int recursive_adapt = 1; + // Construct an adaptive forest + forest_adapt = t8_forest_new_adapt (forest, t8_test_adapt_first_child, recursive_adapt, 0, &maxlevel); + } + + void + TearDown () override + { + if (forest != NULL) { + t8_forest_unref (&forest); + } + if (forest_adapt != NULL) { + t8_forest_unref (&forest_adapt); + } + } + + t8_forest_t forest { NULL }; + t8_forest_t forest_adapt { NULL }; + const t8_scheme *scheme; +}; + +/** Test the t8_forest_bin_search_upper function. + * We iterate through all elements of a forest. + * For each element E of level L, we call t8_forest_bin_search_lower on the forest's element array and expect + * the element to be found. + * For each element we then build one case where we search for an element that is not contained but will + * match a different element in the element array: + * We compute the linear Id of E at level L-1 (if it is >=0 ) and search for the L-1 element with this id. + * We expect E to be found since it fulfills that its id is >= than the id we search for. + * We then build one case per tree where we search for an element that is not contained and will not match + * any other element: + * We compute the linear Id of the last element in the tree and add 1 to it (if it is not zero). + * We expect the search to not find anything. +*/ +static void +t8_test_forest_bin_search_upper (t8_forest_t forest) +{ + const t8_locidx_t num_local_trees = t8_forest_get_num_local_trees (forest); + + const t8_scheme *scheme = t8_forest_get_scheme (forest); + for (t8_locidx_t itree = 0; itree < num_local_trees; ++itree) { + const t8_locidx_t num_elements_in_tree = t8_forest_get_tree_num_leaf_elements (forest, itree); + const t8_eclass_t tree_class = t8_forest_get_tree_class (forest, itree); + const t8_element_array_t *leafs = t8_forest_tree_get_leaf_elements (forest, itree); + + /* Iterate over all the tree's leaf elements, check whether the leaf + * is correctly identified by t8_forest_element_is_leaf, + * build its parent and its first child (if they exist), and verify + * that t8_forest_element_is_leaf returns false. */ + for (t8_locidx_t ielement = 0; ielement < num_elements_in_tree; ++ielement) { + const t8_element_t *leaf_element = t8_forest_get_leaf_element_in_tree (forest, itree, ielement); + const int element_level = scheme->element_get_level (tree_class, leaf_element); + const t8_linearidx_t element_id = scheme->element_get_linear_id (tree_class, leaf_element, element_level); + + /* Search for a linear element id in a sorted array of + * elements. If the element does not exist, return the largest index i + * such that the element at position i has a smaller id than the given one. + * If no such i exists, return -1. */ + const t8_locidx_t search_index = t8_forest_bin_search_upper (leafs, element_id, element_level); + // We expect the leaf element to be found at position ielement + EXPECT_EQ (search_index, ielement) << "Found wrong position of leaf element. Expected: " << ielement + << " got: " << search_index; + + // If we increase the level, we expect the element to not be found, but the search + // should return the index of the original element. + if (element_level < scheme->get_maxlevel (tree_class)) { + t8_debugf ("Computing element for level %i, Max is %i\n", element_level, T8_DLINE_MAXLEVEL); + // TODO: The maxlevel eventually should be element dependent. I know that an element dependent maxlevel function was developed in a different branch (by Sandro?) + const t8_linearidx_t element_id_at_next_level + = scheme->element_get_linear_id (tree_class, leaf_element, element_level + 1); + + const t8_locidx_t search_index + = t8_forest_bin_search_upper (leafs, element_id_at_next_level, element_level + 1); + // We expect the leaf element to be found at position ielement + EXPECT_EQ (search_index, ielement) + << "Found wrong position of level " << element_level + 1 << " leaf element with id " + << element_id_at_next_level << ". Expected: " << ielement << " got: " << search_index; + } + + // Construct an element that is definitely not in the array and + // does not have an element of smaller id in the array. We expect -1 as return. + // We take the first element of the forest and subtract 1 from its id. + if (ielement == num_elements_in_tree - 1) { + const t8_linearidx_t element_not_found_id = element_id + 1; + // Double check for possible conversion error, if element_id == MAX_POSSIBLE_VALUE. In that case, the + // test logic fails. Should we ever run into this case, we need to rewrite this test accordingly. + SC_CHECK_ABORTF (element_not_found_id > 0, "Invalid element id %li\n", element_not_found_id); + const t8_locidx_t search_index = t8_forest_bin_search_upper (leafs, element_not_found_id, element_level); + EXPECT_EQ (search_index, -1) << "Wrong return value for element that should not be in array. Expectec -1."; + } + } + } +} + +/** Test the t8_forest_bin_search_lower function. + * We iterate through all elements of a forest. + * For each element E of level L, we call t8_forest_bin_search_lower on the forest's element array and expect + * the element to be found. + * For each element we then build one case where we search for an element that is not contained but will + * match a different element in the element array: + * We compute the linear Id of E at level L+1 (if not exceeding the maxlevel) and search for the L+1 element with this id. + * We expect E to be found since it fulfills that its id is <= than the id we search for. + * We then build one case per tree where we search for an element that is not contained and will not match + * any other element: + * We compute the linear Id of the first element in the tree and subtract 1 from it (if it is not zero). + * We expect the search to not find anything. +*/ +static void +t8_test_forest_bin_search_lower (t8_forest_t forest) +{ + const t8_locidx_t num_local_trees = t8_forest_get_num_local_trees (forest); + + const t8_scheme *scheme = t8_forest_get_scheme (forest); + for (t8_locidx_t itree = 0; itree < num_local_trees; ++itree) { + const t8_locidx_t num_elements_in_tree = t8_forest_get_tree_num_leaf_elements (forest, itree); + const t8_eclass_t tree_class = t8_forest_get_tree_class (forest, itree); + const t8_element_array_t *leafs = t8_forest_tree_get_leaf_elements (forest, itree); + + /* Iterate over all the tree's leaf elements, check whether the leaf + * is correctly identified by t8_forest_element_is_leaf, + * build its parent and its first child (if they exist), and verify + * that t8_forest_element_is_leaf returns false. */ + for (t8_locidx_t ielement = 0; ielement < num_elements_in_tree; ++ielement) { + const t8_element_t *leaf_element = t8_forest_get_leaf_element_in_tree (forest, itree, ielement); + const int element_level = scheme->element_get_level (tree_class, leaf_element); + const t8_linearidx_t element_id = scheme->element_get_linear_id (tree_class, leaf_element, element_level); + + /* Search for a linear element id in a sorted array of + * elements. If the element does not exist, return the largest index i + * such that the element at position i has a smaller id than the given one. + * If no such i exists, return -1. */ + const t8_locidx_t search_index = t8_forest_bin_search_lower (leafs, element_id, element_level); + // We expect the leaf element to be found at position ielement + EXPECT_EQ (search_index, ielement) << "Found wrong position of leaf element. Expected: " << ielement + << " got: " << search_index; + + // If we increase the level, we expect the element to not be found, but the search + // should return the index of the original element. + if (element_level < scheme->get_maxlevel (tree_class)) { + t8_debugf ("Computing element for level %i, Max is %i\n", element_level, T8_DLINE_MAXLEVEL); + // TODO: The maxlevel eventually should be element dependent. I know that an element dependent maxlevel function was developed in a different branch (by Sandro?) + const t8_linearidx_t element_id_at_next_level + = scheme->element_get_linear_id (tree_class, leaf_element, element_level + 1); + + const t8_locidx_t search_index + = t8_forest_bin_search_lower (leafs, element_id_at_next_level, element_level + 1); + // We expect the leaf element to be found at position ielement + EXPECT_EQ (search_index, ielement) + << "Found wrong position of level " << element_level + 1 << " leaf element with id " + << element_id_at_next_level << ". Expected: " << ielement << " got: " << search_index; + } + + // Construct an element that is definitely not in the array and + // does not have an element of smaller id in the array. We expect -1 as return. + // We take the first element of the forest and subtract 1 from its id. + if (ielement == 0 && element_id > 0) { + const t8_linearidx_t element_not_found_id = element_id - 1; + const t8_locidx_t search_index = t8_forest_bin_search_lower (leafs, element_not_found_id, element_level); + EXPECT_EQ (search_index, -1) << "Wrong return value for element that should not be in array. Expectec -1."; + } + } + } +} + +// bin_search_lower test for uniform forest +TEST_P (t8_bin_search_tester, bin_search_lower_uniform) +{ + t8_test_forest_bin_search_lower (forest); +} + +// bin_search_lower test for adaptive forest +TEST_P (t8_bin_search_tester, bin_search_lower_adapt) +{ + t8_test_forest_bin_search_lower (forest_adapt); +} + +// bin_search_upper test for uniform forest +TEST_P (t8_bin_search_tester, bin_search_upper_uniform) +{ + t8_test_forest_bin_search_upper (forest); +} + +// bin_search_upper test for adaptive forest +TEST_P (t8_bin_search_tester, bin_search_upper_adapt) +{ + t8_test_forest_bin_search_upper (forest_adapt); +} + +INSTANTIATE_TEST_SUITE_P (t8_gtest_bin_search, t8_bin_search_tester, + testing::Combine (AllSchemes, testing::Range (0, T8_IS_LEAF_MAX_LVL)), + pretty_print_eclass_scheme_and_level); From f9149b0ebfaf5d723b21563ff7c297a93cf1e3cc Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Tue, 3 Feb 2026 13:41:32 +0100 Subject: [PATCH 36/41] Move commonly used adapt function to own header --- test/CMakeLists.txt | 3 +- test/t8_gtest_adapt_callbacks.cxx | 58 +++++++++++++++++++++++++++++++ test/t8_gtest_adapt_callbacks.hxx | 45 ++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 test/t8_gtest_adapt_callbacks.cxx create mode 100644 test/t8_gtest_adapt_callbacks.hxx diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1394dc245a..fbb7aad592 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -137,6 +137,7 @@ add_t8_cpp_test( NAME t8_gtest_cmesh_bounding_box_serial SOUR add_t8_cpp_test( NAME t8_gtest_shmem_parallel SOURCES t8_data/t8_gtest_shmem.cxx ) add_t8_cpp_test( NAME t8_gtest_data_pack_parallel SOURCES t8_data/t8_gtest_data_handler.cxx t8_data/t8_data_handler_specs.cxx) +add_t8_cpp_test( NAME t8_gtest_bin_search_parallel SOURCES t8_forest/t8_gtest_bin_search.cxx t8_gtest_adapt_callbacks.cxx ) add_t8_cpp_test( NAME t8_gtest_element_volume_serial SOURCES t8_forest/t8_gtest_element_volume.cxx ) add_t8_cpp_test( NAME t8_gtest_search_parallel SOURCES t8_forest/t8_gtest_search.cxx ) add_t8_cpp_test( NAME t8_gtest_half_neighbors_parallel SOURCES t8_forest/t8_gtest_half_neighbors.cxx ) @@ -149,7 +150,7 @@ add_t8_cpp_test( NAME t8_gtest_ghost_and_owner_parallel SOURCES t8_for add_t8_cpp_test( NAME t8_gtest_balance_parallel SOURCES t8_forest/t8_gtest_balance.cxx ) add_t8_cpp_test( NAME t8_gtest_forest_commit_parallel SOURCES t8_forest/t8_gtest_forest_commit.cxx ) add_t8_cpp_test( NAME t8_gtest_forest_face_normal_serial SOURCES t8_forest/t8_gtest_forest_face_normal.cxx ) -add_t8_cpp_test( NAME t8_gtest_element_is_leaf_serial SOURCES t8_forest/t8_gtest_element_is_leaf.cxx ) +add_t8_cpp_test( NAME t8_gtest_element_is_leaf_serial SOURCES t8_forest/t8_gtest_element_is_leaf.cxx t8_gtest_adapt_callbacks.cxx ) add_t8_cpp_test( NAME t8_gtest_partition_data_parallel SOURCES t8_forest/t8_gtest_partition_data.cxx ) 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 ) diff --git a/test/t8_gtest_adapt_callbacks.cxx b/test/t8_gtest_adapt_callbacks.cxx new file mode 100644 index 0000000000..bab9f26a08 --- /dev/null +++ b/test/t8_gtest_adapt_callbacks.cxx @@ -0,0 +1,58 @@ +/* + 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_gtest_adapt_callbacks.cxx +* Provide forest adapt callback functions that we use in our tests. +*/ + +#include + +/* Adapt a forest such that always the first child of a + * family is refined and no other elements. This results in a highly + * imbalanced forest. + * + * This adapt callbacks requires an integer as forest user data. + * This integer is the maximum refinement level. + */ +int +t8_test_adapt_first_child (t8_forest_t forest, [[maybe_unused]] t8_forest_t forest_from, + [[maybe_unused]] t8_locidx_t which_tree, const t8_eclass_t eclass, + [[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[]) +{ + T8_ASSERT (!is_family || (is_family && num_elements == scheme->element_get_num_children (eclass, elements[0]))); + + int level = scheme->element_get_level (eclass, elements[0]); + + /* we set a maximum refinement level as forest user data */ + int maxlevel = *(int *) t8_forest_get_user_data (forest); + if (level >= maxlevel) { + /* Do not refine after the maxlevel */ + return 0; + } + int child_id = scheme->element_get_child_id (eclass, elements[0]); + if (child_id == 1) { + return 1; + } + return 0; +} diff --git a/test/t8_gtest_adapt_callbacks.hxx b/test/t8_gtest_adapt_callbacks.hxx new file mode 100644 index 0000000000..584015a3f1 --- /dev/null +++ b/test/t8_gtest_adapt_callbacks.hxx @@ -0,0 +1,45 @@ +/* + 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_gtest_adapt_callbacks.hxx +* Provide forest adapt callback functions that we use in our tests. +*/ + +#ifndef T8_GTEST_ADAPT_CALLBACKS +#define T8_GTEST_ADAPT_CALLBACKS + +#include +#include + +/* Adapt a forest such that always the first child of a + * family is refined and no other elements. This results in a highly + * imbalanced forest. + * + * This adapt callbacks requires an integer as forest user data. + * This integer is the maximum refinement level. + */ +int +t8_test_adapt_first_child (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t which_tree, + 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[]); + +#endif /* T8_GTEST_ADAPT_CALLBACKS */ From d92d6d9a39376e9b273aaa3218c7c0217a11abdc Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Tue, 3 Feb 2026 13:42:14 +0100 Subject: [PATCH 37/41] put commonly used pretty print test function in header --- test/t8_gtest_macros.hxx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/t8_gtest_macros.hxx b/test/t8_gtest_macros.hxx index 6b8cfd7a7e..ea7046fed7 100644 --- a/test/t8_gtest_macros.hxx +++ b/test/t8_gtest_macros.hxx @@ -33,6 +33,7 @@ #include #include #include +#include /** * lambda to pass to an INSTANTIATE_TEST_SUITE_P to print the current cmesh_example_base @@ -41,6 +42,16 @@ inline auto print_eclass = [] (const testing::TestParamInfo &info) { return t8_eclass_to_string[info.param]; }; +/** Define a lambda to beautify gtest output for tuples . + * This will set the correct level and cmesh name as part of the test case name. */ +auto pretty_print_eclass_scheme_and_level + = [] (const testing::TestParamInfo, int>> &info) { + std::string scheme = t8_scheme_to_string[std::get<0> (std::get<0> (info.param))]; + std::string eclass = t8_eclass_to_string[std::get<1> (std::get<0> (info.param))]; + std::string level = std::string ("_level_") + std::to_string (std::get<1> (info.param)); + return scheme + "_" + eclass + level; + }; + /** * Initializes everything needed for the t8code testsuite. * MPI is initialized with MPI_COMM_WORLD and SC with loglevel SC_LP_PRODUCTION. From 648c614a4d686e4eb8a8c8c46c6d8cb867689b54 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Tue, 24 Feb 2026 13:19:58 +0100 Subject: [PATCH 38/41] Made all leafs into leaves --- test/t8_forest/t8_gtest_bin_search.cxx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/t8_forest/t8_gtest_bin_search.cxx b/test/t8_forest/t8_gtest_bin_search.cxx index 5093238c8e..8d342134d7 100644 --- a/test/t8_forest/t8_gtest_bin_search.cxx +++ b/test/t8_forest/t8_gtest_bin_search.cxx @@ -106,7 +106,7 @@ t8_test_forest_bin_search_upper (t8_forest_t forest) for (t8_locidx_t itree = 0; itree < num_local_trees; ++itree) { const t8_locidx_t num_elements_in_tree = t8_forest_get_tree_num_leaf_elements (forest, itree); const t8_eclass_t tree_class = t8_forest_get_tree_class (forest, itree); - const t8_element_array_t *leafs = t8_forest_tree_get_leaf_elements (forest, itree); + const t8_element_array_t *leaves = t8_forest_tree_get_leaf_elements (forest, itree); /* Iterate over all the tree's leaf elements, check whether the leaf * is correctly identified by t8_forest_element_is_leaf, @@ -121,7 +121,7 @@ t8_test_forest_bin_search_upper (t8_forest_t forest) * elements. If the element does not exist, return the largest index i * such that the element at position i has a smaller id than the given one. * If no such i exists, return -1. */ - const t8_locidx_t search_index = t8_forest_bin_search_upper (leafs, element_id, element_level); + const t8_locidx_t search_index = t8_forest_bin_search_upper (leaves, element_id, element_level); // We expect the leaf element to be found at position ielement EXPECT_EQ (search_index, ielement) << "Found wrong position of leaf element. Expected: " << ielement << " got: " << search_index; @@ -135,7 +135,7 @@ t8_test_forest_bin_search_upper (t8_forest_t forest) = scheme->element_get_linear_id (tree_class, leaf_element, element_level + 1); const t8_locidx_t search_index - = t8_forest_bin_search_upper (leafs, element_id_at_next_level, element_level + 1); + = t8_forest_bin_search_upper (leaves, element_id_at_next_level, element_level + 1); // We expect the leaf element to be found at position ielement EXPECT_EQ (search_index, ielement) << "Found wrong position of level " << element_level + 1 << " leaf element with id " @@ -150,7 +150,7 @@ t8_test_forest_bin_search_upper (t8_forest_t forest) // Double check for possible conversion error, if element_id == MAX_POSSIBLE_VALUE. In that case, the // test logic fails. Should we ever run into this case, we need to rewrite this test accordingly. SC_CHECK_ABORTF (element_not_found_id > 0, "Invalid element id %li\n", element_not_found_id); - const t8_locidx_t search_index = t8_forest_bin_search_upper (leafs, element_not_found_id, element_level); + const t8_locidx_t search_index = t8_forest_bin_search_upper (leaves, element_not_found_id, element_level); EXPECT_EQ (search_index, -1) << "Wrong return value for element that should not be in array. Expectec -1."; } } @@ -179,7 +179,7 @@ t8_test_forest_bin_search_lower (t8_forest_t forest) for (t8_locidx_t itree = 0; itree < num_local_trees; ++itree) { const t8_locidx_t num_elements_in_tree = t8_forest_get_tree_num_leaf_elements (forest, itree); const t8_eclass_t tree_class = t8_forest_get_tree_class (forest, itree); - const t8_element_array_t *leafs = t8_forest_tree_get_leaf_elements (forest, itree); + const t8_element_array_t *leaves = t8_forest_tree_get_leaf_elements (forest, itree); /* Iterate over all the tree's leaf elements, check whether the leaf * is correctly identified by t8_forest_element_is_leaf, @@ -194,7 +194,7 @@ t8_test_forest_bin_search_lower (t8_forest_t forest) * elements. If the element does not exist, return the largest index i * such that the element at position i has a smaller id than the given one. * If no such i exists, return -1. */ - const t8_locidx_t search_index = t8_forest_bin_search_lower (leafs, element_id, element_level); + const t8_locidx_t search_index = t8_forest_bin_search_lower (leaves, element_id, element_level); // We expect the leaf element to be found at position ielement EXPECT_EQ (search_index, ielement) << "Found wrong position of leaf element. Expected: " << ielement << " got: " << search_index; @@ -208,7 +208,7 @@ t8_test_forest_bin_search_lower (t8_forest_t forest) = scheme->element_get_linear_id (tree_class, leaf_element, element_level + 1); const t8_locidx_t search_index - = t8_forest_bin_search_lower (leafs, element_id_at_next_level, element_level + 1); + = t8_forest_bin_search_lower (leaves, element_id_at_next_level, element_level + 1); // We expect the leaf element to be found at position ielement EXPECT_EQ (search_index, ielement) << "Found wrong position of level " << element_level + 1 << " leaf element with id " @@ -220,7 +220,7 @@ t8_test_forest_bin_search_lower (t8_forest_t forest) // We take the first element of the forest and subtract 1 from its id. if (ielement == 0 && element_id > 0) { const t8_linearidx_t element_not_found_id = element_id - 1; - const t8_locidx_t search_index = t8_forest_bin_search_lower (leafs, element_not_found_id, element_level); + const t8_locidx_t search_index = t8_forest_bin_search_lower (leaves, element_not_found_id, element_level); EXPECT_EQ (search_index, -1) << "Wrong return value for element that should not be in array. Expectec -1."; } } From 4ae1bcc5db9472bdba2a7e99c3db6729ef828185 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Tue, 24 Feb 2026 13:22:51 +0100 Subject: [PATCH 39/41] Apply suggestions from code review Co-authored-by: David Knapp --- test/t8_forest/t8_gtest_bin_search.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/t8_forest/t8_gtest_bin_search.cxx b/test/t8_forest/t8_gtest_bin_search.cxx index 8d342134d7..92ccd41b9c 100644 --- a/test/t8_forest/t8_gtest_bin_search.cxx +++ b/test/t8_forest/t8_gtest_bin_search.cxx @@ -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) 2015 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 From aa0257497428c2d72a2746cf1e0f477505abb9a4 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Tue, 24 Feb 2026 13:28:42 +0100 Subject: [PATCH 40/41] Update include path --- test/t8_forest/t8_gtest_bin_search.cxx | 2 +- test/t8_schemes/t8_gtest_is_ancestor.cxx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/t8_forest/t8_gtest_bin_search.cxx b/test/t8_forest/t8_gtest_bin_search.cxx index 8d342134d7..1ed0f4d076 100644 --- a/test/t8_forest/t8_gtest_bin_search.cxx +++ b/test/t8_forest/t8_gtest_bin_search.cxx @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include #include diff --git a/test/t8_schemes/t8_gtest_is_ancestor.cxx b/test/t8_schemes/t8_gtest_is_ancestor.cxx index 3f12cd7bf8..fec76341b4 100644 --- a/test/t8_schemes/t8_gtest_is_ancestor.cxx +++ b/test/t8_schemes/t8_gtest_is_ancestor.cxx @@ -31,7 +31,7 @@ */ #include -#include +#include #include #include #include "t8_gtest_dfs_base.hxx" From 468ea5a00037e960d1cbbcea498760d5db812c07 Mon Sep 17 00:00:00 2001 From: Johannes Holke Date: Tue, 24 Feb 2026 15:38:28 +0100 Subject: [PATCH 41/41] Added test for ancestor search --- test/t8_forest/t8_gtest_bin_search.cxx | 117 +++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 8 deletions(-) diff --git a/test/t8_forest/t8_gtest_bin_search.cxx b/test/t8_forest/t8_gtest_bin_search.cxx index a608ece8ff..cb6527bb7d 100644 --- a/test/t8_forest/t8_gtest_bin_search.cxx +++ b/test/t8_forest/t8_gtest_bin_search.cxx @@ -34,8 +34,10 @@ /* In these tests we check the t8_forest_bin_search_lower, t8_forest_bin_search_upper * and t8_forest_bin_search_first_descendant_ancestor functions. * Iterating over all cmesh test cases, we create a uniform and an adaptive forest. - * For each forest, we - * TODO: Fill out comment + * For each forest, we iterate over all elements and initialize searches. + * One search for the element E itself. + * One search for an element that is not in the array but for which element E will be found. + * Additionally, we issue one search per tree where no element at all will be found. */ /* Maximum uniform level for forest. */ @@ -128,9 +130,8 @@ t8_test_forest_bin_search_upper (t8_forest_t forest) // If we increase the level, we expect the element to not be found, but the search // should return the index of the original element. - if (element_level < scheme->get_maxlevel (tree_class)) { + if (scheme->element_is_refinable (tree_class, leaf_element)) { t8_debugf ("Computing element for level %i, Max is %i\n", element_level, T8_DLINE_MAXLEVEL); - // TODO: The maxlevel eventually should be element dependent. I know that an element dependent maxlevel function was developed in a different branch (by Sandro?) const t8_linearidx_t element_id_at_next_level = scheme->element_get_linear_id (tree_class, leaf_element, element_level + 1); @@ -151,7 +152,7 @@ t8_test_forest_bin_search_upper (t8_forest_t forest) // test logic fails. Should we ever run into this case, we need to rewrite this test accordingly. SC_CHECK_ABORTF (element_not_found_id > 0, "Invalid element id %li\n", element_not_found_id); const t8_locidx_t search_index = t8_forest_bin_search_upper (leaves, element_not_found_id, element_level); - EXPECT_EQ (search_index, -1) << "Wrong return value for element that should not be in array. Expectec -1."; + EXPECT_EQ (search_index, -1) << "Wrong return value for element that should not be in array. Expected -1."; } } } @@ -201,9 +202,8 @@ t8_test_forest_bin_search_lower (t8_forest_t forest) // If we increase the level, we expect the element to not be found, but the search // should return the index of the original element. - if (element_level < scheme->get_maxlevel (tree_class)) { + if (scheme->element_is_refinable (tree_class, leaf_element)) { t8_debugf ("Computing element for level %i, Max is %i\n", element_level, T8_DLINE_MAXLEVEL); - // TODO: The maxlevel eventually should be element dependent. I know that an element dependent maxlevel function was developed in a different branch (by Sandro?) const t8_linearidx_t element_id_at_next_level = scheme->element_get_linear_id (tree_class, leaf_element, element_level + 1); @@ -221,12 +221,101 @@ t8_test_forest_bin_search_lower (t8_forest_t forest) if (ielement == 0 && element_id > 0) { const t8_linearidx_t element_not_found_id = element_id - 1; const t8_locidx_t search_index = t8_forest_bin_search_lower (leaves, element_not_found_id, element_level); - EXPECT_EQ (search_index, -1) << "Wrong return value for element that should not be in array. Expectec -1."; + EXPECT_EQ (search_index, -1) << "Wrong return value for element that should not be in array. Expected -1."; } } } } +/** Test the t8_forest_bin_search_upper function. + * We iterate through all elements of a forest. + * For each element E of level L, we call t8_forest_bin_search_lower on the forest's element array and expect + * the element to be found. + * For each element we then build one case where we search for an element that is not contained but will + * match a different element in the element array: + * We compute the linear Id of E at level L-1 (if it is >=0 ) and search for the L-1 element with this id. + * We expect E to be found since it fulfills that its id is >= than the id we search for. + * We then build one case per tree where we search for an element that is not contained and will not match + * any other element: + * We compute the linear Id of the last element in the tree and add 1 to it (if it is not zero). + * We expect the search to not find anything. +*/ +static void +t8_test_forest_bin_search_first_descendant_ancestor (t8_forest_t forest) +{ + const t8_locidx_t num_local_trees = t8_forest_get_num_local_trees (forest); + + const t8_scheme *scheme = t8_forest_get_scheme (forest); + for (t8_locidx_t itree = 0; itree < num_local_trees; ++itree) { + const t8_locidx_t num_elements_in_tree = t8_forest_get_tree_num_leaf_elements (forest, itree); + const t8_eclass_t tree_class = t8_forest_get_tree_class (forest, itree); + const t8_element_array_t *leaves = t8_forest_tree_get_leaf_elements (forest, itree); + t8_element_t *search_element; + scheme->element_new (tree_class, 1, &search_element); + + /* Iterate over all the tree's leaf elements, check whether the leaf + * is correctly identified by t8_forest_element_is_leaf, + * build its parent and its first child (if they exist), and verify + * that t8_forest_element_is_leaf returns false. */ + for (t8_locidx_t ielement = 0; ielement < num_elements_in_tree; ++ielement) { + const t8_element_t *leaf_element = t8_forest_get_leaf_element_in_tree (forest, itree, ielement); + const int element_level = scheme->element_get_level (tree_class, leaf_element); + const t8_linearidx_t element_id = scheme->element_get_linear_id (tree_class, leaf_element, element_level); + + /* Search for a linear element id in a sorted array of + * elements. If the element does not exist, return the largest index i + * such that the element at position i has a smaller id than the given one. + * If no such i exists, return -1. */ + const t8_element_t *element_found; + const t8_locidx_t search_index + = t8_forest_bin_search_first_descendant_ancestor (leaves, leaf_element, &element_found); + // We expect the leaf element to be found at position ielement + EXPECT_EQ (search_index, ielement) << "Found wrong position of leaf element. Expected: " << ielement + << " got: " << search_index; + EXPECT_TRUE (scheme->element_is_equal (tree_class, element_found, leaf_element)); + + // If we increase the level, we expect the element to not be found, but the search + // should return the index of the original element. + if (scheme->element_is_refinable (tree_class, leaf_element)) { + t8_debugf ("Computing element for level %i, Max is %i\n", element_level, T8_DLINE_MAXLEVEL); + scheme->element_get_child (tree_class, leaf_element, 0, search_element); + const t8_locidx_t search_index + = t8_forest_bin_search_first_descendant_ancestor (leaves, search_element, &element_found); + // We expect the leaf element to be found at position ielement + EXPECT_EQ (search_index, ielement) << "Found wrong position of level " << element_level + 1 << " leaf element " + << ". Expected: " << ielement << " got: " << search_index; + EXPECT_TRUE (scheme->element_is_equal (tree_class, element_found, leaf_element)); + } + + // Construct an element that is definitely not in the array and + // does not have an element of smaller id in the array. We expect -1 as return. + // We take the first element of the forest and subtract 1 from its id. + if (ielement == num_elements_in_tree - 1) { + const t8_linearidx_t element_not_found_id = element_id + 1; + // We need to check that this Id corresponds to a valid element id. + // To do so, we must compute the number of elements at the given refinement level, + // and check that element_not_found_id is smaller. + // We do this by building the root element and calling element_count_leaves. + // Note that we do not need to check this in the upper/lower tests, since we do not generate an actual element in them. + scheme->element_set_linear_id (tree_class, search_element, 0, 0); + const t8_linearidx_t max_valid_id + = (t8_linearidx_t) scheme->element_count_leaves (tree_class, search_element, element_level); + if (element_not_found_id < max_valid_id) { + // Double check for possible conversion error, if element_id == MAX_POSSIBLE_VALUE. In that case, the + // test logic fails. Should we ever run into this case, we need to rewrite this test accordingly. + SC_CHECK_ABORTF (element_not_found_id > 0, "Invalid element id %li\n", element_not_found_id); + scheme->element_set_linear_id (tree_class, search_element, element_level, element_not_found_id); + const t8_locidx_t search_index + = t8_forest_bin_search_first_descendant_ancestor (leaves, search_element, &element_found); + EXPECT_EQ (search_index, -1) << "Wrong return value for element that should not be in array. Expected -1."; + EXPECT_EQ (element_found, nullptr); + } + } + } + scheme->element_destroy (tree_class, 1, &search_element); + } +} + // bin_search_lower test for uniform forest TEST_P (t8_bin_search_tester, bin_search_lower_uniform) { @@ -251,6 +340,18 @@ TEST_P (t8_bin_search_tester, bin_search_upper_adapt) t8_test_forest_bin_search_upper (forest_adapt); } +// bin_search_first_descendant_ancestor test for uniform forest +TEST_P (t8_bin_search_tester, bin_search_first_descendant_ancestor_uniform) +{ + t8_test_forest_bin_search_first_descendant_ancestor (forest); +} + +// bin_search_first_descendant_ancestor test for adaptive forest +TEST_P (t8_bin_search_tester, bin_search_first_descendant_ancestor_adapt) +{ + t8_test_forest_bin_search_first_descendant_ancestor (forest_adapt); +} + INSTANTIATE_TEST_SUITE_P (t8_gtest_bin_search, t8_bin_search_tester, testing::Combine (AllSchemes, testing::Range (0, T8_IS_LEAF_MAX_LVL)), pretty_print_eclass_scheme_and_level);