diff --git a/benchmarks/t8_time_prism_adapt.cxx b/benchmarks/t8_time_prism_adapt.cxx index 2d7784fe07..8853845315 100644 --- a/benchmarks/t8_time_prism_adapt.cxx +++ b/benchmarks/t8_time_prism_adapt.cxx @@ -181,7 +181,7 @@ main (int argc, char **argv) if (sreturnA > BUFSIZ || sreturnB > BUFSIZ) { /* The usage string or help message was truncated */ - /* Note: gcc >= 7.1 prints a warning if we + /* Note: gcc >= 7.1 prints a warning if we * do not check the return value of snprintf. */ t8_debugf ("Warning: Truncated usage string and help message to '%s' and '%s'\n", usage, help); } diff --git a/example/forest/t8_test_face_iterate.cxx b/example/forest/t8_test_face_iterate.cxx index d354094aa7..6b987f871c 100644 --- a/example/forest/t8_test_face_iterate.cxx +++ b/example/forest/t8_test_face_iterate.cxx @@ -212,7 +212,7 @@ main (int argc, char **argv) if (sreturnA > BUFSIZ || sreturnB > BUFSIZ) { /* The usage string or help message was truncated */ - /* Note: gcc >= 7.1 prints a warning if we + /* Note: gcc >= 7.1 prints a warning if we * do not check the return value of snprintf. */ t8_debugf ("Warning: Truncated usage string and help message to '%s' and '%s'\n", usage, help); } diff --git a/example/forest/t8_test_ghost.cxx b/example/forest/t8_test_ghost.cxx index e1a8bdd215..4121ba77fb 100644 --- a/example/forest/t8_test_ghost.cxx +++ b/example/forest/t8_test_ghost.cxx @@ -303,7 +303,7 @@ main (int argc, char **argv) if (sreturnA > BUFSIZ || sreturnB > BUFSIZ) { /* The usage string or help message was truncated */ - /* Note: gcc >= 7.1 prints a warning if we + /* Note: gcc >= 7.1 prints a warning if we * do not check the return value of snprintf. */ t8_debugf ("Warning: Truncated usage string and help message to '%s' and '%s'\n", usage, help); } diff --git a/example/forest/t8_test_ghost_large_level_diff.cxx b/example/forest/t8_test_ghost_large_level_diff.cxx index 08115b1393..d6fd2c69bd 100644 --- a/example/forest/t8_test_ghost_large_level_diff.cxx +++ b/example/forest/t8_test_ghost_large_level_diff.cxx @@ -219,7 +219,7 @@ main (int argc, char *argv[]) if (sreturn >= BUFSIZ) { /* The help message was truncated */ - /* Note: gcc >= 7.1 prints a warning if we + /* Note: gcc >= 7.1 prints a warning if we * do not check the return value of snprintf. */ t8_debugf ("Warning: Truncated help message to '%s'\n", help); } diff --git a/example/remove/t8_example_empty_trees.cxx b/example/remove/t8_example_empty_trees.cxx index e2a95c3fd7..476f68c581 100644 --- a/example/remove/t8_example_empty_trees.cxx +++ b/example/remove/t8_example_empty_trees.cxx @@ -115,7 +115,7 @@ main (int argc, char **argv) if (sreturnA > BUFSIZ || sreturnB > BUFSIZ) { /* The usage string or help message was truncated */ - /* Note: gcc >= 7.1 prints a warning if we + /* Note: gcc >= 7.1 prints a warning if we * do not check the return value of snprintf. */ t8_debugf ("Warning: Truncated usage string and help message to '%s' and '%s'\n", usage, help); } diff --git a/src/t8_forest/t8_forest.cxx b/src/t8_forest/t8_forest.cxx index 004a313113..23e98768df 100644 --- a/src/t8_forest/t8_forest.cxx +++ b/src/t8_forest/t8_forest.cxx @@ -83,7 +83,7 @@ t8_forest_is_incomplete_family (const t8_forest_t forest, const t8_locidx_t ltre scheme->element_new (tree_class, 1, &element_parent_current); scheme->element_new (tree_class, 1, &element_compare); - /* We first assume that we have an (in)complete family with the size of array elements. + /* We first assume that we have an (in)complete family with the size of array elements. * In the following we try to disprove this. */ int family_size = elements_size; @@ -92,9 +92,9 @@ t8_forest_is_incomplete_family (const t8_forest_t forest, const t8_locidx_t ltre const int child_id_current = scheme->element_get_child_id (tree_class, elements[0]); scheme->element_get_parent (tree_class, elements[0], element_parent_current); - /* Elements of the current family could already be passed, so that + /* Elements of the current family could already be passed, so that * the element/family currently under consideration can no longer be coarsened. - * Also, there may be successors of a hypothetical previous family member + * Also, there may be successors of a hypothetical previous family member * that would be overlapped after coarsening. * */ if (child_id_current > 0 && el_considered > 0) { @@ -136,12 +136,12 @@ t8_forest_is_incomplete_family (const t8_forest_t forest, const t8_locidx_t ltre T8_ASSERT (family_size > 0); T8_ASSERT (family_size >= 0 && family_size <= elements_size); - /* There may be successors of a hypothetical later family member (with index + /* There may be successors of a hypothetical later family member (with index * family_size in this family) that would be overlapped after coarsening. */ if (family_size < elements_size) { /* Get level of element after last element of current possible family */ const int level = scheme->element_get_level (tree_class, elements[family_size]); - /* Only elements with higher level then level of current element, can get + /* Only elements with higher level then level of current element, can get * potentially be overlapped. */ if (level > level_current) { /* Compare ancestors */ @@ -164,7 +164,7 @@ t8_forest_is_incomplete_family (const t8_forest_t forest, const t8_locidx_t ltre const int num_siblings = scheme->element_get_num_siblings (tree_class, elements[0]); T8_ASSERT (family_size <= num_siblings); /* If the first/last element at a process boundary is not the first/last - * element of a possible family, we are not guaranteed to consider all + * element of a possible family, we are not guaranteed to consider all * family members.*/ if (el_considered == 0 && child_id_current > 0 && ltree_id == 0 && forest->mpirank > 0) { return 0; @@ -300,9 +300,9 @@ t8_forest_no_overlap ([[maybe_unused]] t8_forest_t forest) * More detailed: * Let e_a and e_b be two elements. * If the level of e_a is equal to the level of the nca of e_a and e_b, - * then e_b is a descendant of e_a. + * then e_b is a descendant of e_a. * If the level of e_b is equal to the level of the nca of e_a and e_b, - * then e_a is a descendant of e_b. + * then e_a is a descendant of e_b. * Thus e_a and e_b overlap in both cases. * Note: If e_a equals e_b, e_a is the descendant of e_b and vice versa. * */ @@ -1305,9 +1305,9 @@ t8_forest_tree_shared ([[maybe_unused]] t8_forest_t forest, [[maybe_unused]] int else { SC_ABORT ("For incomplete trees the method t8_forest_last_tree_shared aka " "t8_forest_tree_shared(forest, 1) is not implemented.\n"); - /* TODO: If last_local_tree is 0 of the current process and it gets 0 as the - * first_local_tree of the bigger process, then it cannot be said whether - * the tree with id 0 is shared or not, since the bigger process could also + /* TODO: If last_local_tree is 0 of the current process and it gets 0 as the + * first_local_tree of the bigger process, then it cannot be said whether + * the tree with id 0 is shared or not, since the bigger process could also * carry an empty forest. */ } /* If global_neighbour_tree_idx == forest->first_local_tree tree is shared */ @@ -1960,7 +1960,7 @@ t8_forest_element_is_leaf (const t8_forest_t forest, const t8_element_t *element T8_ASSERT (t8_forest_is_committed (forest)); T8_ASSERT (t8_forest_tree_is_local (forest, local_tree)); - /* We get the array of the tree's elements and then search in the array of elements for our + /* We get the array of the tree's elements and then search in the array of elements for our * element candidate. */ /* Get the array */ const t8_element_array_t *elements = t8_forest_get_tree_leaf_element_array (forest, local_tree); @@ -1982,7 +1982,7 @@ t8_forest_element_is_leaf (const t8_forest_t forest, const t8_element_t *element /* The element was not found. */ return 0; } - /* An element was found but it may not be the candidate element. + /* An element was found but it may not be the candidate element. * To identify whether the element was found, we compare these two. */ const t8_element_t *check_element = t8_element_array_index_locidx (elements, search_result); T8_ASSERT (check_element != NULL); @@ -2323,7 +2323,7 @@ t8_forest_element_find_owner_old (t8_forest_t forest, t8_gloidx_t gtreeid, t8_el return proc; } else { - /* Get the next owning process. Its first descendant is in fact an element of the tree. + /* Get the next owning process. Its first descendant is in fact an element of the tree. * If it is bigger than the descendant we look for, then proc is the owning process of element. */ proc_next = *(int *) sc_array_index (owners_of_tree, 1); if (*(t8_linearidx_t *) t8_shmem_array_index (forest->global_first_desc, (size_t) proc_next) @@ -2704,6 +2704,8 @@ t8_forest_init (t8_forest_t *pforest) forest->incomplete_trees = -1; forest->set_partition_offset = 0; forest->set_first_global_element = -1; + forest->set_weighted_partitioning = 0; + forest->weight_function = nullptr; } int @@ -2966,9 +2968,9 @@ t8_forest_comm_global_num_leaf_elements (t8_forest_t forest) * Check if any tree in a forest refines irregularly. * An irregular refining tree is a tree with an element that does not * refine into 2^dim children. For example the default implementation - * of pyramids. + * of pyramids. * \note This function is MPI collective - * + * * \param[in] forest The forest to check * \return Non-zero if any tree refines irregular */ diff --git a/src/t8_forest/t8_forest_general.h b/src/t8_forest/t8_forest_general.h index 2e5b047aaa..4ac34fde53 100644 --- a/src/t8_forest/t8_forest_general.h +++ b/src/t8_forest/t8_forest_general.h @@ -48,11 +48,18 @@ typedef enum { T8_GHOST_VERTICES /**< Consider all vertex (codimension 3) and edge and face neighbors. */ } t8_ghost_type_t; -/** This typedef is needed as a helper construct to +/** This typedef is needed as a helper construct to * properly be able to define a function that returns * a pointer to a void fun(void) function. \see t8_forest_get_user_function. */ typedef void (*t8_generic_function_pointer) (void); + +/** + * The prototype a weight function for the partition algorithm. + * The function should be pure, and return a positive weight given a forest, a local tree index and an element index within the local tree + */ +typedef double (t8_weight_fcn_t) (t8_forest_t, t8_locidx_t, t8_locidx_t); + T8_EXTERN_C_BEGIN (); /** Callback function prototype to replace one set of elements with another. @@ -77,12 +84,12 @@ T8_EXTERN_C_BEGIN (); * \param [in] first_incoming The tree local index of the first incoming element. * 0 <= first_incom < new_which_tree->num_elements * - * If an element is being refined, \a refine and \a num_outgoing will be 1 and + * If an element is being refined, \a refine and \a num_outgoing will be 1 and * \a num_incoming will be the number of children. - * If a family is being coarsened, \a refine will be -1, \a num_outgoing will be - * the number of family members and \a num_incoming will be 1. - * If an element is being removed, \a refine and \a num_outgoing will be 1 and - * \a num_incoming will be 0. + * If a family is being coarsened, \a refine will be -1, \a num_outgoing will be + * the number of family members and \a num_incoming will be 1. + * If an element is being removed, \a refine and \a num_outgoing will be 1 and + * \a num_incoming will be 0. * Else \a refine will be 0 and \a num_outgoing and \a num_incoming will both be 1. * \see t8_forest_iterate_replace */ @@ -96,7 +103,7 @@ typedef void (*t8_forest_replace_t) (t8_forest_t forest_old, t8_forest_t forest_ * form a family and we decide whether this family should be coarsened * or only the first element should be refined. * Otherwise \a is_family must equal zero and we consider the first entry - * of the element array for refinement. + * of the element array for refinement. * Entries of the element array beyond the first \a num_elements are undefined. * \param [in] forest The forest to which the new elements belong. * \param [in] forest_from The forest that is adapted. @@ -121,7 +128,7 @@ typedef int (*t8_forest_adapt_t) (t8_forest_t forest, t8_forest_t forest_from, t /** Create a new forest with reference count one. * This forest needs to be specialized with the t8_forest_set_* calls. - * Currently it is mandatory to either call the functions \see t8_forest_set_mpicomm, + * Currently it is mandatory to either call the functions \see t8_forest_set_mpicomm, * \ref t8_forest_set_cmesh, and \ref t8_forest_set_scheme, * or to call one of \ref t8_forest_set_copy, \ref t8_forest_set_adapt, or * \ref t8_forest_set_partition. It is illegal to mix these calls, or to @@ -159,7 +166,7 @@ t8_forest_is_committed (t8_forest_t forest); * \param [in] forest The forest to consider. * \return True if \a forest has no elements which are inside each other. * \note This function is collective, but only checks local overlapping on each process. - * \see t8_forest_partition_test_boundary_element if you also want to test for + * \see t8_forest_partition_test_boundary_element if you also want to test for * global overlap across the process boundaries. */ int @@ -324,6 +331,15 @@ t8_forest_get_user_function (const t8_forest_t forest); void t8_forest_set_partition (t8_forest_t forest, const t8_forest_t set_from, int set_for_coarsening); +/** Set a user-defined weight function to guide the partitioning. + * \param [in, out] forest The forest. + * \param [in] weight_callback A callback function defining element weights for the partitioning. + * \pre \a weight_callback must be free of side-effects, the behavior is undefined otherwise + * \note If \a weight_callback is null, then all the elements are assumed to have the same weight + */ +void +t8_forest_set_partition_weight_function (t8_forest_t forest, t8_weight_fcn_t *weight_callback); + /** Set a source forest to be balanced during commit. * A forest is said to be balanced if each element has face neighbors of level * at most +1 or -1 of the element's level. @@ -379,8 +395,8 @@ t8_forest_set_ghost_ext (t8_forest_t forest, int do_ghost, t8_ghost_type_t ghost /** * Use assertions and document that the forest_set (..., from) and - * set_load are mutually exclusive. - * + * set_load are mutually exclusive. + * * TODO: Unused function -> remove? */ void @@ -452,7 +468,7 @@ t8_forest_get_eclass (const t8_forest_t forest, const t8_locidx_t ltreeid); /** * Check whether a given tree id belongs to a local tree in a forest. - * + * * \param [in] forest The forest. * \param [in] local_tree A tree id. * \return True if and only if the id \a local_tree belongs to a local tree of \a forest. @@ -467,8 +483,8 @@ t8_forest_tree_is_local (const t8_forest_t forest, const t8_locidx_t local_tree) * \param [in] forest The forest. * \param [in] gtreeid The global id of a tree. * \return The tree's local id in \a forest, if it is a local tree. - * A negative number if not. Ghosts trees are not considered - * as local. + * A negative number if not. Ghosts trees are not considered + * as local. * \see t8_forest_get_local_or_ghost_id for ghost trees. * \see https://github.com/DLR-AMR/t8code/wiki/Tree-indexing for more details about tree indexing. */ @@ -522,7 +538,7 @@ t8_forest_get_coarse_tree (t8_forest_t forest, t8_locidx_t ltreeid); /** * Query whether a given element is a leaf in a forest. - * + * * \param [in] forest The forest. * \param [in] element An element of a local tree in \a forest. * \param [in] local_tree A local tree id of \a forest. @@ -585,7 +601,7 @@ t8_forest_leaf_face_neighbors (t8_forest_t forest, t8_locidx_t ltreeid, const t8 t8_element_t **pneighbor_leaves[], int face, int *dual_faces[], int *num_neighbors, t8_locidx_t **pelement_indices, t8_eclass_t *pneigh_eclass, int forest_is_balanced); -/** Like \ref t8_forest_leaf_face_neighbors but also provides information about the global neighbors and the orientation. +/** Like \ref t8_forest_leaf_face_neighbors but also provides information about the global neighbors and the orientation. * \param [in] forest The forest. Must have a valid ghost layer. * \param [in] ltreeid A local tree id. * \param [in] leaf A leaf in tree \a ltreeid of \a forest. @@ -603,8 +619,8 @@ t8_forest_leaf_face_neighbors (t8_forest_t forest, t8_locidx_t ltreeid, const t8 * \param [in] forest_is_balanced True if we know that \a forest is balanced, false * otherwise. * \param [out] gneigh_tree The global tree IDs of the neighbor trees. - * \param [out] orientation If not NULL on input, the face orientation is computed and stored here. - * Thus, if the face connection is an inter-tree connection the orientation of the tree-to-tree connection is stored. + * \param [out] orientation If not NULL on input, the face orientation is computed and stored here. + * Thus, if the face connection is an inter-tree connection the orientation of the tree-to-tree connection is stored. * Otherwise, the value 0 is stored. * All other parameters and behavior are identical to \ref t8_forest_leaf_face_neighbors. * \note If there are no face neighbors, then *neighbor_leaves = NULL, num_neighbors = 0, @@ -854,7 +870,7 @@ t8_forest_element_face_neighbor (t8_forest_t forest, t8_locidx_t ltreeid, const /** * TODO: Can be removed since it is unused. - * + * * \param[in] forest The forest. */ void @@ -862,16 +878,16 @@ t8_forest_iterate (t8_forest_t forest); /** Query whether a batch of points lies inside an element. For bilinearly interpolated elements. * \note For 2D quadrilateral elements this function is only an approximation. It is correct - * if the four vertices lie in the same plane, but it may produce only approximate results if + * if the four vertices lie in the same plane, but it may produce only approximate results if * the vertices do not lie in the same plane. * \param [in] forest The forest. * \param [in] ltreeid The forest local id of the tree in which the element is. * \param [in] element The element. * \param [in] points 3-dimensional coordinates of the points to check * \param [in] num_points The number of points to check - * \param [in, out] is_inside An array of length \a num_points, filled with 0/1 on output. True (non-zero) if a \a point - * lies within an \a element, false otherwise. The return value is also true if the point - * lies on the element boundary. Thus, this function may return true for different leaf + * \param [in, out] is_inside An array of length \a num_points, filled with 0/1 on output. True (non-zero) if a \a point + * lies within an \a element, false otherwise. The return value is also true if the point + * lies on the element boundary. Thus, this function may return true for different leaf * elements, if they are neighbors and the point lies on the common boundary. * \param [in] tolerance Tolerance that we allow the point to not exactly match the element. * If this value is larger we detect more points. diff --git a/src/t8_forest/t8_forest_partition.cxx b/src/t8_forest/t8_forest_partition.cxx index 06ee9aa8ef..04341a3f70 100644 --- a/src/t8_forest/t8_forest_partition.cxx +++ b/src/t8_forest/t8_forest_partition.cxx @@ -37,7 +37,7 @@ T8_EXTERN_C_BEGIN (); /** * For each tree that we send elements from to other processes, - * we send the information stored in this struct to the other process + * we send the information stored in this struct to the other process */ typedef struct { @@ -253,7 +253,7 @@ t8_forest_partition_test_boundary_element ([[maybe_unused]] const t8_forest_t fo return; } if (forest->mpirank == forest->mpisize - 1) { - /* The last process can only share a tree with process rank-1. + /* The last process can only share a tree with process rank-1. * However, this is already tested by process rank-1. */ return; } @@ -262,7 +262,7 @@ t8_forest_partition_test_boundary_element ([[maybe_unused]] const t8_forest_t fo /* The first tree on process rank+1 is not shared with current rank, nothing to do */ return; } - /* The first tree on process rank+1 may be shared but empty. + /* The first tree on process rank+1 may be shared but empty. * Thus, the first descendant id of rank+1 is not of the first local tree. */ if (local_tree_num_elements == 0) { /* check if first not shared tree of process rank+1 contains elements */ @@ -472,30 +472,45 @@ t8_forest_partition_create_tree_offsets (t8_forest_t forest) } } +void +t8_forest_set_partition_weight_function (t8_forest_t forest, t8_weight_fcn_t *weight_fcn) +{ + forest->set_weighted_partitioning = 1; + forest->weight_function = weight_fcn; +} + +// Compute forest->element_offsets according to the weight function, if provided /* Calculate the new element_offset for forest from * the element in forest->set_from assuming a partition without element weights */ static void t8_forest_partition_compute_new_offset (t8_forest_t forest) { - t8_forest_t forest_from; - sc_MPI_Comm comm; - t8_gloidx_t new_first_element_id; - int i, mpiret, mpisize; - T8_ASSERT (t8_forest_is_initialized (forest)); T8_ASSERT (forest->set_from != NULL); + T8_ASSERT (forest->element_offsets == NULL); - forest_from = forest->set_from; - comm = forest->mpicomm; + // Preparation: Define frequently used forest properties as own local variables. + t8_forest_t forest_from = forest->set_from; + sc_MPI_Comm comm = forest_from->mpicomm; + const int mpirank = forest_from->mpirank; + const int mpisize = forest_from->mpisize; + const t8_gloidx_t global_num_leaf_elements = forest_from->global_num_leaf_elements; + const t8_weight_fcn_t *weight_fcn = forest->weight_function; - T8_ASSERT (forest->element_offsets == NULL); - /* Set the shmem array type to comm */ + // Initialize the shmem array. t8_shmem_init (comm); t8_shmem_set_type (comm, T8_SHMEM_BEST_TYPE); - /* Initialize the shmem array */ - t8_shmem_array_init (&forest->element_offsets, sizeof (t8_gloidx_t), forest->mpisize + 1, comm); - mpiret = sc_MPI_Comm_size (comm, &mpisize); - SC_CHECK_MPI (mpiret); + t8_shmem_array_init (&forest->element_offsets, sizeof (t8_gloidx_t), mpisize + 1, comm); + + // If the global number of elements is zero, all element offsets are too and we can leave this function. + if (global_num_leaf_elements == 0) { + if (t8_shmem_array_start_writing (forest->element_offsets)) { + t8_gloidx_t *element_offsets = t8_shmem_array_get_gloidx_array_for_writing (forest->element_offsets); + std::fill_n (element_offsets, mpisize + 1, t8_gloidx_t { 0 }); + } + t8_shmem_array_end_writing (forest->element_offsets); + return; + } // Define vector of manually set element offsets. std::vector custom_element_offsets; @@ -508,21 +523,15 @@ t8_forest_partition_compute_new_offset (t8_forest_t forest) SC_CHECK_MPI (retval); } - if (t8_shmem_array_start_writing (forest->element_offsets)) { - if (forest_from->global_num_leaf_elements > 0) { - + // If no weight function is provided, compute the offsets solely based on the number of elements. + if (forest->set_weighted_partitioning == 0) { + if (t8_shmem_array_start_writing (forest->element_offsets)) { t8_gloidx_t *element_offsets = t8_shmem_array_get_gloidx_array_for_writing (forest->element_offsets); // Distinguish whether custom element offsets were set: if (forest->set_partition_offset == 0) { - - // Compute element offsets - for (i = 0; i < mpisize; i++) { - /* Calculate the first element index for each process. We convert to doubles to prevent overflow */ - new_first_element_id - = (((double) i * (long double) forest_from->global_num_leaf_elements) / (double) mpisize); - T8_ASSERT (0 <= new_first_element_id && new_first_element_id < forest_from->global_num_leaf_elements); - element_offsets[i] = new_first_element_id; + for (int i = 0; i < mpisize; ++i) { + element_offsets[i] = std::floor (static_cast (global_num_leaf_elements) * i / mpisize); } } else { @@ -531,19 +540,83 @@ t8_forest_partition_compute_new_offset (t8_forest_t forest) std::copy_n (custom_element_offsets.data (), static_cast (mpisize), element_offsets); } // The last entry is the same in both cases. - element_offsets[forest->mpisize] = forest->global_num_leaf_elements; + element_offsets[mpisize] = global_num_leaf_elements; } - else { - t8_gloidx_t *element_offsets = t8_shmem_array_get_gloidx_array_for_writing (forest->element_offsets); - for (i = 0; i <= mpisize; i++) { - element_offsets[i] = 0; + t8_shmem_array_end_writing (forest->element_offsets); + } + else { + // Else: Weighted load balancing: + // ------------------------------ + + // Function definition: Sum of the weights on the local partition. + double const partition_weight = [&] () { + double local_sum = 0.; + for (t8_locidx_t ltreeid = 0; ltreeid < t8_forest_get_num_local_trees (forest_from); ++ltreeid) { + for (t8_locidx_t ielm = 0; ielm < t8_forest_get_tree_num_leaf_elements (forest_from, ltreeid); ++ielm) { + local_sum += weight_fcn (forest_from, ltreeid, ielm); + } + } + return local_sum; + }(); + + // Function definition: Partial sum of the partition weights of all lower-rank processes (excluding the local rank). + double const partition_weight_offset = [&] () { + double local_offset = 0.; + double local_partition_weight = partition_weight; // because MPI does not like const variables + sc_MPI_Exscan (&local_partition_weight, &local_offset, 1, sc_MPI_DOUBLE, sc_MPI_SUM, comm); + return mpirank > 0 ? local_offset : 0; // because the result of MPI_Exscan is undefined on rank 0 + }(); + + // Function definition: Complete sum of the partition weights. + double const forest_weight = [&] () { + double total_weight = partition_weight_offset + partition_weight; + sc_MPI_Bcast (&total_weight, 1, sc_MPI_DOUBLE, mpisize - 1, comm); + return total_weight; + }(); + + // The [rank_begin, rank_end) slice of the new offsets will land in the local partition. + int const rank_begin = std::ceil (mpisize * partition_weight_offset / forest_weight); + int const rank_end = std::ceil (mpisize * (partition_weight_offset + partition_weight) / forest_weight); + std::vector local_offsets (rank_end - rank_begin, 0); + + // Prepare computation of local element offsets. + double accumulated_weight = partition_weight_offset; + t8_gloidx_t global_elm_idx = t8_forest_get_first_local_leaf_element_id (forest_from); + int iproc = rank_begin; + + // Loop over local trees and their elements: + // - Add element weight to accumulated sum. + // - If accumulated weight is above ideal bound for proc iproc, set local offset and go to next process. + for (t8_locidx_t ltreeid = 0; ltreeid < t8_forest_get_num_local_trees (forest_from); ++ltreeid) { + for (t8_locidx_t ielm = 0; ielm < t8_forest_get_tree_num_leaf_elements (forest_from, ltreeid); ++ielm) { + T8_ASSERT (0 <= global_elm_idx && global_elm_idx < global_num_leaf_elements); + accumulated_weight += weight_fcn (forest_from, ltreeid, ielm); + while (accumulated_weight + > forest_weight * iproc / mpisize) { // there may be empty partitions, hence while and not if + T8_ASSERT (rank_begin <= iproc && iproc < rank_end); + local_offsets[iproc - rank_begin] = global_elm_idx; + ++iproc; + } + ++global_elm_idx; } } + T8_ASSERT (iproc == rank_end); // i.e. local_offsets has been filled properly + + // Allgather local offsets into the global element offset array. + t8_shmem_array_allgatherv (local_offsets.data (), local_offsets.size (), T8_MPI_GLOIDX, forest->element_offsets, + T8_MPI_GLOIDX, comm); + + // Add first and last entry to element offsets. + if (t8_shmem_array_start_writing (forest->element_offsets)) { + t8_gloidx_t *element_offsets = t8_shmem_array_get_gloidx_array_for_writing (forest->element_offsets); + element_offsets[0] = 0; + element_offsets[mpisize] = global_num_leaf_elements; + } + t8_shmem_array_end_writing (forest->element_offsets); } - t8_shmem_array_end_writing (forest->element_offsets); // In case the partition-for-coarsening flag is set, correct the partitioning if a family of elements is - // split across process boundaries + // split across process boundaries. if (forest->set_for_coarsening != 0) { t8_forest_pfc_correction_offsets (forest); } diff --git a/src/t8_forest/t8_forest_partition.h b/src/t8_forest/t8_forest_partition.h index d74f102419..6b0bda0e43 100644 --- a/src/t8_forest/t8_forest_partition.h +++ b/src/t8_forest/t8_forest_partition.h @@ -36,8 +36,7 @@ T8_EXTERN_C_BEGIN (); /** * Populate a forest with the partitioned elements of forest->set_from. - * Currently the elements are distributed evenly (each element has the same weight). - * + * * \param [in,out] forest The forest. */ void @@ -45,12 +44,12 @@ t8_forest_partition (t8_forest_t forest); /** * Create a new forest that gathers a given forest on one process. - * + * * This functionality is mostly required for comparison purposes and sanity checks within the testing framework. - * + * * \param[in] forest_from the forest that should be gathered on one rank * \param[in] gather_rank the rank of the process the forest will be gathered on - * + * * \return The gathered forest: The same as \a forest_from, but all elements are on rank \a gather_rank. */ t8_forest_t @@ -58,9 +57,9 @@ t8_forest_new_gather (const t8_forest_t forest_from, const int gather_rank); /** * Manually set the partition offset of the current process. - * + * * If set, the next partitioning of the forest will use the manually defined element offsets. - * + * * \param[in,out] forest the considered forest * \param[in] first_global_element the global ID that will become the first local element */ @@ -101,13 +100,13 @@ t8_forest_partition_create_first_desc (t8_forest_t forest); void t8_forest_partition_create_tree_offsets (t8_forest_t forest); -/** \brief Re-Partition an array accordingly to a partitioned forest. - * +/** \brief Re-Partition an array accordingly to a partitioned forest. + * * \param[in] forest_from The forest before the partitioning step. * \param[in] forest_to The partitioned forest of \a forest_from. * \param[in] data_in A pointer to an sc_array_t holding data (one value per element) accordingly to \a forest_from. * \param[in,out] data_out A pointer to an already allocated sc_array_t capable of holding data accordingly to \a forest_to. - * + * * \note \a data_in has to be of size equal to the number of local elements of \a forest_from * \a data_out has to be already allocated and has to be of size equal to the number of local elements of \a forest_to. */ diff --git a/src/t8_forest/t8_forest_types.h b/src/t8_forest/t8_forest_types.h index ca59c5ef07..02aa51277b 100644 --- a/src/t8_forest/t8_forest_types.h +++ b/src/t8_forest/t8_forest_types.h @@ -46,7 +46,7 @@ typedef struct t8_forest_ghost *t8_forest_ghost_t; /**< Defined below */ * The latter 3 can be combined, in which case the order is * 1. Adapt, 2. Partition, 3. Balance. * We store the methods in an int8_t and use these defines to - * distinguish between them. + * distinguish between them. */ typedef int8_t t8_forest_from_t; @@ -73,9 +73,11 @@ typedef struct t8_forest t8_gloidx_t set_first_global_element; /**< If set_partition_offset is true, the global ID of the first local element after partitioning.*/ - int set_level; /**< Level to use in new construction. */ - int set_for_coarsening; /**< Change partition to allow + int set_level; /**< Level to use in new construction. */ + int set_for_coarsening; /**< Change partition to allow for one round of coarsening */ + int set_weighted_partitioning; /**< Flag indicating whether a weighting function is used for partitioning.*/ + t8_weight_fcn_t *weight_function; /**< Pointer to user defined element weight function. Can be null. */ sc_MPI_Comm mpicomm; /**< MPI communicator to use. */ t8_cmesh_t cmesh; /**< Coarse mesh to use. */ @@ -87,7 +89,7 @@ typedef struct t8_forest int dimension; /**< Dimension inferred from \b cmesh. */ int incomplete_trees; /**< Flag to check whether the forest has (potential) incomplete trees. A tree is incomplete if an element has been removed from it. - Once an element got removed, the flag sets to 1 (true) and stays. + Once an element got removed, the flag sets to 1 (true) and stays. For a committed forest this flag is either true on all ranks or false on all ranks. */ @@ -112,8 +114,8 @@ typedef struct t8_forest int mpisize; /**< Number of MPI processes. */ int mpirank; /**< Number of this MPI process. */ - t8_gloidx_t first_local_tree; /**< The global index of the first local tree on this process. - If first_local_tree is larger than last_local_tree then + t8_gloidx_t first_local_tree; /**< The global index of the first local tree on this process. + If first_local_tree is larger than last_local_tree then this processor/forest is empty. See https://github.com/DLR-AMR/t8code/wiki/Tree-indexing */ t8_gloidx_t last_local_tree; /**< The global index of the last local tree on this process. @@ -194,9 +196,9 @@ typedef struct t8_profile } t8_profile_struct_t; -/** +/** * This struct stores various information about a forest's ghost elements and ghost trees. - * + * */ typedef struct t8_forest_ghost { diff --git a/src/t8_netcdf.c b/src/t8_netcdf.c index faf8a34423..2b5c74fa23 100644 --- a/src/t8_netcdf.c +++ b/src/t8_netcdf.c @@ -46,7 +46,7 @@ t8_netcdf_create_integer_var (const char *var_name, const char *var_long_name, c sc_array_t *var_data) { t8_netcdf_variable_type_t var_type; - /* Check whether 32-bit (4-byte) integer or 64-bit integer data sholud be written */ + /* Check whether 32-bit (4-byte) integer or 64-bit integer data should be written */ var_type = (var_data->elem_size > 4) ? T8_NETCDF_INT64 : T8_NETCDF_INT; return t8_netcdf_create_var (var_type, var_name, var_long_name, var_unit, var_data); } diff --git a/test/t8_forest/t8_gtest_forest_commit.cxx b/test/t8_forest/t8_gtest_forest_commit.cxx index 4137d38545..b944fb68f1 100644 --- a/test/t8_forest/t8_gtest_forest_commit.cxx +++ b/test/t8_forest/t8_gtest_forest_commit.cxx @@ -114,28 +114,24 @@ t8_test_forest_commit_abp (t8_forest_t forest, int maxlevel) static t8_forest_t t8_test_forest_commit_abp_3step (t8_forest_t forest, int maxlevel) { + /* adapt the forest */ t8_forest_t forest_adapt; - t8_forest_t forest_partition; -#if T8_TEST_LEVEL_INT < 2 - t8_forest_t forest_balance; - t8_forest_init (&forest_balance); -#endif - t8_forest_init (&forest_adapt); - t8_forest_init (&forest_partition); - - /* adapt the forest */ t8_forest_set_user_data (forest_adapt, &maxlevel); t8_forest_set_adapt (forest_adapt, forest, t8_test_adapt_balance, 1); t8_forest_commit (forest_adapt); #if T8_TEST_LEVEL_INT < 2 /* balance the forest */ + t8_forest_t forest_balance; + t8_forest_init (&forest_balance); t8_forest_set_balance (forest_balance, forest_adapt, 0); t8_forest_commit (forest_balance); #endif /* partition the forest */ + t8_forest_t forest_partition; + t8_forest_init (&forest_partition); #if T8_TEST_LEVEL_INT < 2 t8_forest_set_partition (forest_partition, forest_balance, 0); #else @@ -148,7 +144,6 @@ t8_test_forest_commit_abp_3step (t8_forest_t forest, int maxlevel) TEST_P (forest_commit, test_forest_commit) { - t8_forest_t forest; t8_forest_t forest_ada_bal_part; t8_forest_t forest_abp_3part; diff --git a/test/t8_forest/t8_gtest_weighted_partitioning.cxx b/test/t8_forest/t8_gtest_weighted_partitioning.cxx new file mode 100644 index 0000000000..5e178d2c6d --- /dev/null +++ b/test/t8_forest/t8_gtest_weighted_partitioning.cxx @@ -0,0 +1,232 @@ +/* + 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 In this file, we test the weighted-partitioning feature of t8code. + * + * It allows defining a weighting function that assigns a weight to each element + * in the forest. The partitioning will then try to establish a (as far as possible) + * ideal balancing of the weights, rather than the element numbers as "usual". + * + * The following steps are performed per example cmesh and scheme, with sanity + * checks validating each of them: + * + * (1.) A uniform, partitioned base forest is created. + * + * (2.) In a first test, the weighting function is equal to one for all elements. + * This allows a simple sanity check: The resulting partition has to be the + * same as without assigning weights. + * + * (3.) Next, we test a non-uniform weight function. As an example, we defined the + * weight function such that an element's weight is simply its global element id. + * This allows to "manually" compute the expected element offsets to then compare + * them. + */ + +// testing +#include +#include +#include "test/t8_cmesh_generator/t8_cmesh_example_sets.hxx" +#include +#include + +// t8code +#include +#include +#include +#include +#include + +/** + * Test class for validating t8code's weighted partitioning. See file description for details on the testing strategy. +*/ +class t8_test_weighted_partitioning_test: public testing::TestWithParam> { + + protected: + /** + * Setup routine of the test suite. + */ + void + SetUp () override + { + // Get scheme. + const int scheme_id = std::get<0> (GetParam ()); + scheme = create_from_scheme_id (scheme_id); + + // Construct cmesh. + cmesh = std::get<1> (GetParam ())->cmesh_create (); + + // Skip empty meshes. + if (t8_cmesh_is_empty (cmesh)) { + GTEST_SKIP (); + } + } + + /** + * Tear down routine of the test suite. + * + * Makes sure to unref the cmesh and the scheme. + */ + void + TearDown () override + { + t8_cmesh_destroy (&cmesh); + scheme->unref (); + } + + // Member variables: + const t8_scheme *scheme; + t8_cmesh_t cmesh; +}; + +/** + * Main function of testing suite. See doxygen file description for details on the testing strategy. +*/ +TEST_P (t8_test_weighted_partitioning_test, test_weighted_partitioning) +{ + + // ------------------------------------------- + // ----- (1.) Create uniform base forest ----- + // ------------------------------------------- + + t8_debugf ("Create uniform base forest.\n"); + + // Initial uniform refinement level + const int level = 2; + + // Increase reference counters of cmesh and scheme to avoid reaching zero. + t8_cmesh_ref (cmesh); + scheme->ref (); + + // Create initial, uniform base forest. + t8_forest_t base_forest = t8_forest_new_uniform (cmesh, scheme, level, 0, sc_MPI_COMM_WORLD); + + // --------------------------------------------- + // ----- (2.) Test 1: All weights the same ----- + // --------------------------------------------- + t8_debugf ("Create forest with uniform partition weights.\n"); + + // Initialize forest + t8_forest_t forest_uniform_weights; + t8_forest_init (&forest_uniform_weights); + + // Define uniform weight fcn. + t8_weight_fcn_t *uniform_weight_fcn = [] (t8_forest_t, t8_locidx_t, t8_locidx_t) -> double { return 1; }; + + // Prepare partitioning. + t8_forest_set_partition (forest_uniform_weights, base_forest, 0); + t8_forest_set_partition_weight_function (forest_uniform_weights, uniform_weight_fcn); + + // Commit forest (without destroying base_forest). + t8_forest_ref (base_forest); + t8_forest_commit (forest_uniform_weights); + + // Create partitioned forest without weights for comparison. + t8_forest_t forest_no_weights; + t8_forest_init (&forest_no_weights); + t8_forest_set_partition (forest_no_weights, base_forest, 0); + t8_forest_ref (base_forest); + t8_forest_commit (forest_no_weights); + + // Make sure they are the same. + EXPECT_FOREST_EQ (forest_uniform_weights, forest_no_weights); + + // --------------------------------------------- + // ----- (3.) Test 2: Non-uniform weights ------ + // --------------------------------------------- + t8_debugf ("Create forest with non-uniform partition weights.\n"); + + // (3a.) Apply weighted partitioning: + // ---------------------------------- + + // Define non-uniform weight fcn: For this test, we pick the element number as element weight. + t8_weight_fcn_t *weight_fcn = [] (t8_forest_t forest, t8_locidx_t ltree_id, t8_locidx_t ele_in_tree) -> double { + const t8_gloidx_t gelem_id = t8_forest_get_first_local_leaf_element_id (forest) + + t8_forest_get_tree_element_offset (forest, ltree_id) + ele_in_tree; + return gelem_id; + }; + + // Prepare forest. + t8_forest_t forest_weighted; + t8_forest_init (&forest_weighted); + t8_forest_set_partition (forest_weighted, base_forest, 0); + t8_forest_set_partition_weight_function (forest_weighted, weight_fcn); + + // Commit forest (without destroying base_forest). + t8_forest_ref (base_forest); + t8_forest_commit (forest_weighted); + + // (3b.) Determine expected partitioning: + // -------------------------------------- + + // Preparation: MPI variables and global number of leaf elements. + const int mpirank = base_forest->mpirank; + const int mpisize = base_forest->mpisize; + const t8_gloidx_t num_global_leaves = t8_forest_get_global_num_leaf_elements (base_forest); + + // Total sum of the weights for this case (Gauss sum formula for num_global_leaves - 1) + const double total_weight_sum = num_global_leaves * (num_global_leaves - 1) / 2; + + // "Manually" compute the element offsets expected for this distribution. + // Note: For simplicity, every process computes all offsets here, so no MPI communication is required. + std::vector element_offsets (mpisize + 1); + double sum = 0.0; + int on_rank = 0; + element_offsets[0] = 0; + for (t8_gloidx_t ielem = 0; ielem < num_global_leaves; ielem++) { + + // Add weight (which matches element number here) + sum += ielem; + + // If we are above ideal bound for current process, add offset to vector and go to next rank. + if (sum > total_weight_sum * (on_rank + 1) / mpisize) { + on_rank++; + std::fill (element_offsets.begin () + on_rank, element_offsets.end (), ielem); + t8_global_productionf ("Computed offset for rank %i: %li \n", on_rank, element_offsets[on_rank]); + } + } + // The last entry is always the global number of elements. + element_offsets[mpisize] = num_global_leaves; + + // Determine number of local elements. + t8_locidx_t num_local_leaves = element_offsets[mpirank + 1] - element_offsets[mpirank]; + + // (3c.) Comparison: + // ----------------- + + // Make sure the number of local leaves match. + EXPECT_EQ (t8_forest_get_local_num_leaf_elements (forest_weighted), num_local_leaves); + + // --------------------------- + // ----- Memory clean-up ----- + // --------------------------- + + // Destroy the forests. + t8_forest_unref (&base_forest); + t8_forest_unref (&forest_uniform_weights); + t8_forest_unref (&forest_no_weights); + t8_forest_unref (&forest_weighted); +} + +// Instantiate parameterized test to be run for all schemes and example cmeshes. +INSTANTIATE_TEST_SUITE_P (t8_gtest_weighted_partitioning, t8_test_weighted_partitioning_test, + testing::Combine (AllSchemeCollections, AllCmeshsParam), pretty_print_base_example_scheme); diff --git a/test/t8_forest_incomplete/t8_gtest_empty_global_tree.cxx b/test/t8_forest_incomplete/t8_gtest_empty_global_tree.cxx index dc8489bc16..f7dd31cfff 100644 --- a/test/t8_forest_incomplete/t8_gtest_empty_global_tree.cxx +++ b/test/t8_forest_incomplete/t8_gtest_empty_global_tree.cxx @@ -28,11 +28,11 @@ #include #include -/** In this test, we are given a forest with 3 global trees. - * We adapt the forest so that all 6 compositions of empty - * global trees are the result of it. +/** In this test, we are given a forest with 3 global trees. + * We adapt the forest so that all 6 compositions of empty + * global trees are the result of it. * Therefore, \a testcase runs from 0 to 5. - * We do this twice. Once we partition the forest in the same call. + * We do this twice. Once we partition the forest in the same call. * The second time, we do the adapting and partitioning separately. * The two resulting forests must be equal. * */ diff --git a/test/t8_forest_incomplete/t8_gtest_empty_local_tree.cxx b/test/t8_forest_incomplete/t8_gtest_empty_local_tree.cxx index 80316d8e1b..d88d41845e 100644 --- a/test/t8_forest_incomplete/t8_gtest_empty_local_tree.cxx +++ b/test/t8_forest_incomplete/t8_gtest_empty_local_tree.cxx @@ -31,11 +31,11 @@ #define MAX_NUM_RANKS 8 -/* In this test, a partitioned forest with one global tree and at - * least so many elements, such that each process has at least one +/* In this test, a partitioned forest with one global tree and at + * least so many elements, such that each process has at least one * local element is given. Let x be the number of mpi ranks. * There are 2^x many ways to empty these x local trees. - * + * * Example: * x = 3 * instances - binary representation @@ -43,13 +43,13 @@ * 1 - 0 1 * 2 - 1 0 * 3 - 1 1 - * We remove all elements from rank with id i if the i`th bit + * We remove all elements from rank with id i if the i`th bit * in the current instances is 0. - * + * * Note, this test runs only on two to maxmal 8 ranks. - * - * We adapt the given forest twice. - * The first time, we partition the forest in the same call. + * + * We adapt the given forest twice. + * The first time, we partition the forest in the same call. * The second time, we do the adapting and partitioning separately. * The two resulting forests must be equal. */ @@ -82,7 +82,7 @@ class DISABLED_local_tree: public testing::TestWithParam { t8_forest_t forest; }; -/** This structure contains a bitset with all +/** This structure contains a bitset with all * local trees on all processes to be removed. */ struct t8_trees_to_remove @@ -90,7 +90,7 @@ struct t8_trees_to_remove std::bitset remove; }; -/** Remove every element of rank i if the i`th bit in +/** Remove every element of rank i if the i`th bit in * the current instance \a remove is 0. */ static int t8_adapt_remove (t8_forest_t forest, t8_forest_t forest_from, [[maybe_unused]] t8_locidx_t which_tree, diff --git a/test/t8_forest_incomplete/t8_gtest_iterate_replace.cxx b/test/t8_forest_incomplete/t8_gtest_iterate_replace.cxx index 2f9ce5e483..074d485ee4 100644 --- a/test/t8_forest_incomplete/t8_gtest_iterate_replace.cxx +++ b/test/t8_forest_incomplete/t8_gtest_iterate_replace.cxx @@ -71,7 +71,7 @@ struct t8_return_data int *callbacks; }; -/** Inside the callback of iterate_replace we compare \a refine +/** Inside the callback of iterate_replace we compare \a refine * with the according return value of the callback of forest_adapt. * If true, we check the parameter \a num_outgoing, \a first_outgoing * \a num_incoming and \a first_incoming for correctness. */ diff --git a/tutorials/features/t8_features_curved_meshes.cxx b/tutorials/features/t8_features_curved_meshes.cxx index d7c7ad8927..58eb3a622c 100644 --- a/tutorials/features/t8_features_curved_meshes.cxx +++ b/tutorials/features/t8_features_curved_meshes.cxx @@ -23,7 +23,7 @@ /* See also: https://github.com/DLR-AMR/t8code/wiki/Feature---Curved-meshes * * This is a feature tutorial about curved meshes. - * In the following we will generate an input mesh and input geometry in Gmsh. + * In the following we will generate an input mesh and input geometry in Gmsh. * Furthermore, we will read those files in and build a curved forest with them. * As refinement criterion we will define a wall which is moving through the mesh * and we will refine elements based on their neighboring geometrical geometries. @@ -51,7 +51,7 @@ #include /* std::string */ #include /* std::array */ -/* We use this data to control to which level the elements at which +/* We use this data to control to which level the elements at which * geometry get refined. */ struct t8_naca_geometry_adapt_data { @@ -61,7 +61,7 @@ struct t8_naca_geometry_adapt_data int *levels; /** Array with refinement levels */ }; -/** +/** * The adaptation callback function. This function will be called once for each element * and the return value decides whether this element should be refined or not. * return > 0 -> This element should get refined. @@ -72,11 +72,11 @@ struct t8_naca_geometry_adapt_data * return > 0 -> The first element should get refined. * return = 0 -> The first element should not get refined. * return < 0 -> The whole family should get coarsened. - * + * * In this case, the function retrieves the geometry information of the tree the element belongs to. * Based on that the function looks whether the tree is linked to a specific geometry * and if this element touches this geometry. If true, it returns 1. Otherwise it returns 0. - * + * * \param [in] forest The current forest that is in construction. * \param [in] forest_from The forest from which we adapt the current forest (in our case, the uniform forest) * \param [in] which_tree The process local id of the current tree. @@ -130,9 +130,9 @@ t8_naca_geometry_adapt_callback (t8_forest_t forest, t8_forest_t forest_from, t8 return 0; } -/** +/** * The geometry refinement function. Here, we refine all elements, which touch certain geometries. - * + * * \param [in] forest The forest that has to be refined * \param [in] fileprefix The prefix of the msh and brep file. * Influences only the vtu file name. @@ -172,7 +172,7 @@ t8_naca_geometry_refinement (t8_forest_t forest, const std::string &fileprefix, geometries.data (), /* Array with geometry indices */ levels.data () /* Array with refinement levels */ }; - /* Adapt and balance the forest. + /* Adapt and balance the forest. * Note, that we have to hand the adapt data to the forest before the commit. */ t8_forest_init (&forest_new); t8_forest_set_adapt (forest_new, forest, t8_naca_geometry_adapt_callback, 1); @@ -204,7 +204,7 @@ struct t8_naca_plane_adapt_data int rlevel; /* The max refinement level */ }; -/** +/** * The adaptation callback function. This function will be called once for each element * and the return value decides whether this element should be refined or not. * return > 0 -> This element should get refined. @@ -215,12 +215,12 @@ struct t8_naca_plane_adapt_data * return > 0 -> The first element should get refined. * return = 0 -> The first element should not get refined. * return < 0 -> The whole family should get coarsened. - * + * * In this case the function checks whether the element or family is in a certain proximity to a refinement plane. * If true and the element does not have a max level, 1 is returned. If a family of elements - * is too far away and the level is not below or equal the min level threshold -1 is returned. + * is too far away and the level is not below or equal the min level threshold -1 is returned. * Otherwise 0 is returned. - * + * * \param [in] forest The current forest that is in construction. * \param [in] forest_from The forest from which we adapt the current forest (in our case, the uniform forest) * \param [in] which_tree The process local id of the current tree. @@ -264,10 +264,10 @@ t8_naca_plane_adapt_callback (t8_forest_t forest, t8_forest_t forest_from, t8_lo return 0; } -/** +/** * The plane refinement function. Here we create a refinement loop, which moves a plane * through the mesh and refines it near the plane. - * + * * \param [in] forest The forest which has to be refined. * \param [in] fileprefix The prefix of the msh and brep file. * Influences only the vtu file name. @@ -300,7 +300,7 @@ t8_naca_plane_refinement (t8_forest_t forest, const std::string &fileprefix, int /* Moving plane loop */ while (adapt_data.t < steps) { - /* Adapt and balance the forest. + /* Adapt and balance the forest. * Note, that we have to hand the adapt data to the forest before the commit. */ t8_forest_init (&forest_new); t8_forest_set_adapt (forest_new, forest, t8_naca_plane_adapt_callback, 1); diff --git a/tutorials/general/t8_step4_partition_balance_ghost.cxx b/tutorials/general/t8_step4_partition_balance_ghost.cxx index a7d97f858a..5092a79294 100644 --- a/tutorials/general/t8_step4_partition_balance_ghost.cxx +++ b/tutorials/general/t8_step4_partition_balance_ghost.cxx @@ -24,30 +24,30 @@ * * This is step4 of the t8code tutorials. * After generating a coarse mesh (step1), building a uniform forest - * on it (step2) and adapting this forest (step3) + * on it (step2) and adapting this forest (step3) * we will now learn how to control the forest creation in more detail, * how to partition and balance a forest and how to generate a layer of ghost elements. - * + * * Partition: Each forest is distributed among the MPI processes. Partitioning a forest means - * to change this distribution in order to maintain a load balance. After we + * to change this distribution in order to maintain a load balance. After we * applied partition to a forest the new forest will have equal numbers of elements * on each process (+- 1). * In our example we start with a uniform forest from a cmesh. This forest is constructed - * such that each process has the same number of elements. + * such that each process has the same number of elements. * We then adapt this forest, thus refining and coarsening its elements. This changes the * number of elements on each process and will almost always result in a load imbalance. - * You can verify this yourself by printing the process local number on each process for the + * You can verify this yourself by printing the process local number on each process for the * adapted forest (for example with t8_productionf). * In order to reestablish a load balance, we will construct a new forest from the adapted * one via the partition feature. - * - * Ghost: Many applications require a layer of ghost elements. Ghost element of a process are + * + * Ghost: Many applications require a layer of ghost elements. Ghost element of a process are * those elements that are not local to this process but have a (face) neighbor element that is. * Telling a forest to create a ghost layer will gather all necessary information and give * us access to the ghost elements. * t8code does not create a layer of ghost elements by default, thus our initial uniform forest * and the adapted forest will not have one. - * + * * Balance: A forest fulfills the balance property if (and only if) for each element its (face) neighbors * have refinement level at most +1 or -1 in comparison to the element's level. * The balance property is often broken after adaptation. The Balance algorithm iterates through @@ -65,7 +65,7 @@ * Note that balance changes the local number of elements and thus may also change the load balance * and hence require repartitioning. * Balance is usually the most expensive of t8code's mesh manipulation algorithms. - * + * * How you can experiment here: * Partition: * - Test the program with different numbers of processes and compare the local @@ -79,7 +79,7 @@ * on treeid to only view those elements with treeid = -1. * Balance: * - View the unbalanced and balanced forest vtu files and compare them. - * You can color the elements by level. + * You can color the elements by level. * - Exercise: Apply balance to forest_adapt and not to the twice adapted forest. * - Set the no_repartition flag of t8_forest_set_balance to true and observe how the * partition of the resulting forest changes. @@ -100,25 +100,25 @@ T8_EXTERN_C_BEGIN (); * However, t8code offers us more control over the creation of forests. * For example we can control whether or not a forest should have a ghost layer, * be balanced/partitioned from another forest, etc. - * + * * Usually, there are three steps involved in creating a forest: * 1. Initialize the forest with t8_forest_init. - * This function will prepare a forest by setting default values for its members and + * This function will prepare a forest by setting default values for its members and * initializing necessary structures. * 2. Set all properties that the forest should have. * Here we can for example set a cmesh and refinement scheme or specify that the * forest should be adapted/partitioned/balanced from another forest etc. * See the t8_forest_set functions in t8_forest.h * The order in which you call the set functions does not matter. - * 3. Commit the forest with t8_forest_commit. + * 3. Commit the forest with t8_forest_commit. * In this step the forest is actually created after setting all - * desired properties. The forest cannot be changed after it was committed. - * + * desired properties. The forest cannot be changed after it was committed. + * * The t8_forest_new functions are just wrappers around this process. */ /* In this function we create a new forest that repartitions a given forest - * and has a layer of ghost elements. + * and has a layer of ghost elements. */ static t8_forest_t t8_step4_partition_ghost (t8_forest_t forest) @@ -135,7 +135,7 @@ t8_step4_partition_ghost (t8_forest_t forest) * This will change the distribution of the forest elements among the processes * in such a way that afterwards each process has the same number of elements * (+- 1 if the number of elements is not divisible by the number of processes). - * + * * The third 0 argument is the flag 'partition_for_coarsening' which is currently not * implemented. Once it is, this will ensure that a family of elements will not be split * across multiple processes and thus one level coarsening is always possible (see also the @@ -145,7 +145,7 @@ t8_step4_partition_ghost (t8_forest_t forest) /* Tell the new_forest to create a ghost layer. * This will gather those face neighbor elements of process local element that reside * on a different process. - * + * * We currently support ghost mode T8_GHOST_FACES that creates face neighbor ghost elements * and will in future also support other modes for edge/vertex neighbor ghost elements. */ @@ -156,7 +156,7 @@ t8_step4_partition_ghost (t8_forest_t forest) return new_forest; } -/* In this function we adapt a forest as in step3 and balance it. +/* In this function we adapt a forest as in step3 and balance it. * In our main program the input forest is already adapted and then the resulting twice adapted forest will be unbalanced. */ static t8_forest_t diff --git a/tutorials/general/t8_step5_element_data.cxx b/tutorials/general/t8_step5_element_data.cxx index a496799497..200bcfe8d0 100644 --- a/tutorials/general/t8_step5_element_data.cxx +++ b/tutorials/general/t8_step5_element_data.cxx @@ -23,22 +23,22 @@ /* See also: https://github.com/DLR-AMR/t8code/wiki/Step-5---Store-element-data * * This is step5 of the t8code tutorials. - * In the following we will store data in the individual elements of our forest. - * To do this, we will again create a uniform forest, which will get adapted as in step4, + * In the following we will store data in the individual elements of our forest. + * To do this, we will again create a uniform forest, which will get adapted as in step4, * with the difference that we partition, balance and create ghost elements all in the same step. - * After adapting the forest we will learn how to build a data array and gather data for + * After adapting the forest we will learn how to build a data array and gather data for * the local elements. Furthermore, we exchange the data values of the ghost elements and * output the volume data to vtu. * * How you can experiment here: * - Look at the paraview output files of the adapted forest. - * You can apply a clip filter to look into the cube. Also you can apply (in addition) + * You can apply a clip filter to look into the cube. Also you can apply (in addition) * the threshold filter to display only elements with certain properties. * But at first you may just want to enter the tooltip selection mode 'Hover Cells On' * to display cell information when hover over them. * - Change the adaptation criterion as you wish to adapt elements or families as desired. * - Store even more data per element, for instance the coordinates of its midpoint. - * You can again apply the threshold filter to your new data. Don't forget to write the + * You can again apply the threshold filter to your new data. Don't forget to write the * data into the output file. * */ @@ -104,7 +104,7 @@ t8_step5_create_element_data (t8_forest_t forest) /* Now we need to build an array of our data that is as long as the number * of elements plus the number of ghosts. You can use any allocator such as - * new, malloc or the t8code provide allocation macro T8_ALLOC. + * new, malloc or the t8code provide allocation macro T8_ALLOC. * Note that in the latter case you need * to use T8_FREE in order to free the memory. */ @@ -185,7 +185,7 @@ t8_step5_exchange_ghost_data (t8_forest_t forest, struct t8_step5_data_per_eleme } /* Write the forest as vtu and also write the element's volumes in the file. - * + * * t8code supports writing element based data to vtu as long as its stored * as doubles. Each of the data fields to write has to be provided in its own * array of length num_local_elements. @@ -215,7 +215,7 @@ t8_step5_output_data_to_vtu (t8_forest_t forest, struct t8_step5_data_per_elemen } { /* To write user defined data, we need the extended output function t8_forest_vtk_write_file - * from t8_forest_vtk.h. Despite writing user data, it also offers more control over which + * from t8_forest_vtk.h. Despite writing user data, it also offers more control over which * properties of the forest to write. */ int write_treeid = 1; int write_mpirank = 1; diff --git a/tutorials/general/t8_step5_element_data_c_interface.c b/tutorials/general/t8_step5_element_data_c_interface.c index fb98104d7d..378e4e686d 100644 --- a/tutorials/general/t8_step5_element_data_c_interface.c +++ b/tutorials/general/t8_step5_element_data_c_interface.c @@ -23,22 +23,22 @@ /* See also: https://github.com/DLR-AMR/t8code/wiki/Step-5---Store-element-data * * This is step5 of the t8code tutorials using the C interface of t8code. - * In the following we will store data in the individual elements of our forest. - * To do this, we will again create a uniform forest, which will get adapted as in step4, + * In the following we will store data in the individual elements of our forest. + * To do this, we will again create a uniform forest, which will get adapted as in step4, * with the difference that we partition, balance and create ghost elements all in the same step. - * After adapting the forest we will learn how to build a data array and gather data for + * After adapting the forest we will learn how to build a data array and gather data for * the local elements. Furthermore, we exchange the data values of the ghost elements and * output the volume data to vtu. * * How you can experiment here: * - Look at the paraview output files of the adapted forest. - * You can apply a clip filter to look into the cube. Also you can apply (in addition) + * You can apply a clip filter to look into the cube. Also you can apply (in addition) * the threshold filter to display only elements with certain properties. * But at first you may just want to enter the tooltip selection mode 'Hover Cells On' * to display cell information when hover over them. * - Change the adaptation criterion as you wish to adapt elements or families as desired. * - Store even more data per element, for instance the coordinates of its midpoint. - * You can again apply the threshold filter to your new data. Don't forget to write the + * You can again apply the threshold filter to your new data. Don't forget to write the * data into the output file. * */ @@ -81,7 +81,7 @@ t8_step5_build_forest (sc_MPI_Comm comm, int level) t8_forest_init (&forest_apbg); t8_forest_set_user_data (forest_apbg, &adapt_data); t8_forest_set_adapt (forest_apbg, forest, t8_step3_adapt_callback, 0); - t8_forest_set_partition (forest_apbg, NULL, 0); + t8_forest_set_partition (forest_apbg, NULL, 0, nullptr); t8_forest_set_balance (forest_apbg, NULL, 0); t8_forest_set_ghost (forest_apbg, 1, T8_GHOST_FACES); t8_forest_commit (forest_apbg); @@ -106,7 +106,7 @@ t8_step5_create_element_data (t8_forest_t forest) /* Now we need to build an array of our data that is as long as the number * of elements plus the number of ghosts. You can use any allocator such as - * new, malloc or the t8code provide allocation macro T8_ALLOC. + * new, malloc or the t8code provide allocation macro T8_ALLOC. * Note that in the latter case you need * to use T8_FREE in order to free the memory. */ @@ -188,7 +188,7 @@ t8_step5_exchange_ghost_data (t8_forest_t forest, struct t8_step5_data_per_eleme } /* Write the forest as vtu and also write the element's volumes in the file. - * + * * t8code supports writing element based data to vtu as long as its stored * as doubles. Each of the data fields to write has to be provided in its own * array of length num_local_elements. @@ -218,7 +218,7 @@ t8_step5_output_data_to_vtu (t8_forest_t forest, struct t8_step5_data_per_elemen } { /* To write user defined data, we need to extended output function t8_forest_vtk_write_file - * from t8_forest_vtk.h. Despite writing user data, it also offers more control over which + * from t8_forest_vtk.h. Despite writing user data, it also offers more control over which * properties of the forest to write. */ int write_treeid = 1; int write_mpirank = 1; diff --git a/tutorials/general/t8_step6_stencil.cxx b/tutorials/general/t8_step6_stencil.cxx index 7bb63d0fb5..9804375b5c 100644 --- a/tutorials/general/t8_step6_stencil.cxx +++ b/tutorials/general/t8_step6_stencil.cxx @@ -23,10 +23,10 @@ /* See also: https://github.com/DLR-AMR/t8code/wiki/Step-6-Computing-stencils * * This is step6 of the t8code tutorials using the C++ interface of t8code. - * In the following we will store data in the individual elements of our forest. - * To do this, we will create a uniform forest in 2D, which will get adapted, + * In the following we will store data in the individual elements of our forest. + * To do this, we will create a uniform forest in 2D, which will get adapted, * partitioned, balanced and create ghost elements all in one go. - * After adapting the forest we build a data array and gather data for + * After adapting the forest we build a data array and gather data for * the local elements. Next, we exchange the data values of the ghost elements and compute * various stencils resp. finite differences. Finally, vtu files are stored with three * custom data fields. @@ -299,7 +299,7 @@ t8_step6_exchange_ghost_data (t8_forest_t forest, struct data_per_element *data) } /* Write the forest as vtu and also write the element's volumes in the file. - * + * * t8code supports writing element based data to vtu as long as its stored * as doubles. Each of the data fields to write has to be provided in its own * array of length num_local_elements.