diff --git a/src/t8_forest/t8_forest_private.cxx b/src/t8_forest/t8_forest_private.cxx index ce3e91879d..49d3367eda 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. @@ -93,4 +101,123 @@ 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_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)); + }); + + /* 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 (); + } +} + +/** \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_ancestor (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 = 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; + } + } + /* 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 = 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; + } + } + // No ancestor or descendant was found + *element_found = nullptr; + return -1; +} + T8_EXTERN_C_END (); diff --git a/src/t8_forest/t8_forest_private.h b/src/t8_forest/t8_forest_private.h index e01f2a5162..0414a343e8 100644 --- a/src/t8_forest/t8_forest_private.h +++ b/src/t8_forest/t8_forest_private.h @@ -200,7 +200,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). @@ -213,6 +213,35 @@ 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 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). + * \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); + +/** \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_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. * \param [in] forest The forest. 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 9cd1a2281e..5e3b1a5bac 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 @@ -191,6 +191,40 @@ struct t8_default_scheme_common: public t8_scheme_helpersunderlying ().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 a6cbc3fb4c..fdaf9510c4 100644 --- a/src/t8_schemes/t8_scheme.hxx +++ b/src/t8_schemes/t8_scheme.hxx @@ -509,6 +509,21 @@ struct 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] tree_class The eclass of the current tree. + * \param [in] element_A An element of class \a eclass in scheme \a scheme. + * \param [in] element_B An element of class \a eclass in scheme \a scheme. + * \return True if and only if \a element_A is an ancestor of \a element_B. + */ + bool + element_is_ancestor (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 @@ -537,8 +552,9 @@ struct 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. diff --git a/src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx b/src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx index ea573238d4..c11e37cc3a 100644 --- a/src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx +++ b/src/t8_schemes/t8_standalone/t8_standalone_implementation.hxx @@ -608,6 +608,54 @@ struct t8_standalone_scheme: public t8_scheme_helpers level(B) then A cannot be an ancestor. + - Otherwise compute the ancestor of B at level(A) + - Compare the computed ancestor with A. + */ + T8_ASSERT (element_is_valid (element_A)); + T8_ASSERT (element_is_valid (element_B)); + + const t8_standalone_element *el_B = (const t8_standalone_element *) element_B; + + const int level_A = element_get_level (element_A); + const int level_B = element_get_level (element_B); + + if (level_A > level_B) { + // A is finer than B and thus cannot be an ancestor. + return false; + } + + // Compute the ancestor of B at level_A and compare it with A + t8_element_t *ancestor; + element_new (1, &ancestor); + + t8_standalone_element *ancestor_casted = (t8_standalone_element *) ancestor; + + element_get_ancestor (el_B, level_A, ancestor_casted); + + const bool is_ancestor = element_is_equal (ancestor, element_A); + + element_destroy (1, &ancestor); + + // Return true if A == ancestor + // Return false if A != ancestor + return is_ancestor; + } + /** Compute the nearest common ancestor of two elements. That is, * the element with highest level that still has both given elements as * descendants. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f559cbc0b9..ec207ad849 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -135,6 +135,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 ) @@ -147,7 +148,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 ) @@ -182,6 +183,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_forest/t8_gtest_bin_search.cxx b/test/t8_forest/t8_gtest_bin_search.cxx new file mode 100644 index 0000000000..cb6527bb7d --- /dev/null +++ b/test/t8_forest/t8_gtest_bin_search.cxx @@ -0,0 +1,357 @@ +/* + This file is part of t8code. + t8code is a C library to manage a collection (a forest) of multiple + connected adaptive space-trees of general element classes in parallel. + + Copyright (C) 2026 the developers + + t8code is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + t8code is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with t8code; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#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 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. */ + +#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 *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, + * 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 (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; + + // 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); + 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 (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 " + << 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 (leaves, element_not_found_id, element_level); + EXPECT_EQ (search_index, -1) << "Wrong return value for element that should not be in array. Expected -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 *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, + * 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 (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; + + // 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); + 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 (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 " + << 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 (leaves, element_not_found_id, element_level); + 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) +{ + 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); +} + +// 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); diff --git a/test/t8_forest/t8_gtest_element_is_leaf.cxx b/test/t8_forest/t8_gtest_element_is_leaf.cxx index 8d5164daa1..dfd779e293 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; -} struct element_is_leaf: public testing::TestWithParam, int>> { @@ -194,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_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 */ diff --git a/test/t8_gtest_macros.hxx b/test/t8_gtest_macros.hxx index 0d957449e3..1e5eccbc84 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. 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..fec76341b4 --- /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); diff --git a/test/t8_schemes/t8_gtest_nca.cxx b/test/t8_schemes/t8_gtest_nca.cxx index 5465c879bb..a8521d31a0 100644 --- a/test/t8_schemes/t8_gtest_nca.cxx +++ b/test/t8_schemes/t8_gtest_nca.cxx @@ -81,6 +81,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 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)); } } } @@ -125,6 +129,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 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)); } } } @@ -179,6 +187,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 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)); if (!scheme->element_is_equal (tree_class, check_nca, check)) { level_a = scheme->element_get_level (tree_class, desc_a); @@ -307,6 +319,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 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)); } } }