diff --git a/src/t8.h b/src/t8.h index 6528ce6e13..8a0bfd5b0a 100644 --- a/src/t8.h +++ b/src/t8.h @@ -161,6 +161,7 @@ typedef enum { T8_MPI_CMESH_UNIFORM_BOUNDS_END, /**< Used for cmesh uniform bounds computation. */ T8_MPI_TEST_ELEMENT_PACK_TAG, /**< Used for testing mpi pack and unpack functionality */ T8_MPI_PFC_TAG, /**< Used for data exchange during partition for coarsening. */ + T8_MPI_BOUNDARY_CONDITION_SYNC_TAG, /**< Used for the global synchronization of boundary conditions. */ T8_MPI_TAG_LAST /**< Dummy last MPI tag. */ } t8_MPI_tag_t; diff --git a/src/t8_cmesh/t8_cmesh.cxx b/src/t8_cmesh/t8_cmesh.cxx index 27c7e7cd79..73a6459993 100644 --- a/src/t8_cmesh/t8_cmesh.cxx +++ b/src/t8_cmesh/t8_cmesh.cxx @@ -686,7 +686,7 @@ t8_cmesh_bcast (const t8_cmesh_t cmesh_in, const int root, sc_MPI_Comm comm) t8_cmesh_struct_t cmesh; t8_gloidx_t num_trees_per_eclass[T8_ECLASS_COUNT]; size_t stash_elem_counts[3]; - int pre_commit; /* True, if cmesh on root is not committed yet. */ + int pre_commit; /** True if cmesh on root is not committed yet. */ #if T8_ENABLE_DEBUG sc_MPI_Comm comm; #endif @@ -768,6 +768,9 @@ t8_cmesh_bcast (const t8_cmesh_t cmesh_in, const int root, sc_MPI_Comm comm) if (meta_info.cmesh.profile != nullptr) { t8_cmesh_set_profiling (cmesh_in, 1); } + if (meta_info.cmesh.boundary_condition_handler != nullptr) { + t8_cmesh_add_boundary_condition_handler (cmesh_out); + } for (iclass = 0; iclass < T8_ECLASS_COUNT; iclass++) { cmesh_out->num_trees_per_eclass[iclass] = meta_info.num_trees_per_eclass[iclass]; cmesh_out->num_local_trees_per_eclass[iclass] = meta_info.num_trees_per_eclass[iclass]; @@ -793,6 +796,8 @@ t8_cmesh_bcast (const t8_cmesh_t cmesh_in, const int root, sc_MPI_Comm comm) cmesh_out->committed = 1; } } + /* Broadcast boundary conditions */ + cmesh_out->boundary_condition_handler->bcast (root, comm); cmesh_out->mpirank = mpirank; cmesh_out->mpisize = mpisize; diff --git a/src/t8_cmesh/t8_cmesh_boundary_conditions/internal/t8_cmesh_boundary_condition_handler.cxx b/src/t8_cmesh/t8_cmesh_boundary_conditions/internal/t8_cmesh_boundary_condition_handler.cxx index 20800df38e..0313efc18b 100644 --- a/src/t8_cmesh/t8_cmesh_boundary_conditions/internal/t8_cmesh_boundary_condition_handler.cxx +++ b/src/t8_cmesh/t8_cmesh_boundary_conditions/internal/t8_cmesh_boundary_condition_handler.cxx @@ -25,11 +25,14 @@ * Implementation context of \ref t8_cmesh_boundary_condition_handler.hxx */ +#include #include #include #include #include +#include + using namespace detail; #if T8_ENABLE_DEBUG @@ -91,3 +94,103 @@ t8_cmesh_boundary_condition_handler::get_boundary_condition_attribute_key () con { return T8_CMESH_BOUNDARY_CONDITION_ATTRIBUTE_KEY; } + +std::vector +t8_cmesh_boundary_condition_handler::serialize_map () const +{ + std::vector serial_data; + /* Fill serial_data with strings only. The hashes can be re-generated locally. */ + for (const auto &[key, string] : t8_cmesh_boundary_condition_handler::m_boundary_conditions) { + serial_data.insert (serial_data.end (), string.begin (), string.end ()); + /* Null terminate strings to be able to split them again later. */ + serial_data.push_back ('\0'); + } + serial_data.shrink_to_fit (); + return serial_data; +} + +void +t8_cmesh_boundary_condition_handler::unpack_map (std::vector &serial_data, bool overwrite) +{ + if (overwrite) { + m_boundary_conditions.clear (); + } + + /* Iterate over stings. */ + for (const char *string = serial_data.data (); string < serial_data.data () + serial_data.size (); + string += std::strlen (string) + 1) { + /* Interpret c string as std::string, rehash it and insert it. */ + std::string value (string); + t8_cmesh_boundary_condition_handler::m_boundary_conditions.try_emplace ( + t8_cmesh_boundary_condition_handler::hash_boundary_condition_name (value), std::move (value)); + } +} + +void +t8_cmesh_boundary_condition_handler::synchronize (sc_MPI_Comm comm) +{ + T8_ASSERT (comm != sc_MPI_COMM_NULL); + + int rank, mpisize; + sc_MPI_Comm_rank (comm, &rank); + sc_MPI_Comm_size (comm, &mpisize); + + if (mpisize == 1) { + /* Nothing to do. */ + return; + } + + /* Prepare the sendbuffer. */ + const std::vector send_buffer = t8_cmesh_boundary_condition_handler::serialize_map (); + + /* Communicate the local sizes */ + const int local_size = static_cast (send_buffer.size ()); + std::vector sizes (mpisize); + MPI_Allgather (&local_size, 1, sc_MPI_INT, sizes.data (), 1, sc_MPI_INT, comm); + + /* Compute the offsets for the data and create receive buffer. */ + std::vector offsets (mpisize); + int total_size = 0; + for (int rank = 0; rank < mpisize; ++rank) { + offsets[rank] = total_size; + total_size += sizes[rank]; + } + std::vector recv_buffer (total_size); + + /* Communicate. */ + MPI_Allgatherv (send_buffer.data (), local_size, MPI_BYTE, recv_buffer.data (), sizes.data (), offsets.data (), + MPI_BYTE, comm); + + /* Unpack the data. */ + t8_cmesh_boundary_condition_handler::unpack_map (recv_buffer, false); +} + +void +t8_cmesh_boundary_condition_handler::bcast (int main_rank, sc_MPI_Comm comm) +{ + int rank; + sc_MPI_Comm_rank (comm, &rank); + + /* Buffer for sending and receiving */ + std::vector buffer; + + /* Serialize data. */ + if (rank == main_rank) { + buffer = t8_cmesh_boundary_condition_handler::serialize_map (); + } + + /* Prepare buffer length. */ + int size = static_cast (buffer.size ()); + sc_MPI_Bcast (&size, 1, sc_MPI_INT, main_rank, comm); + if (rank != main_rank) { + buffer.resize (size); + } + + /* Broadcast data */ + sc_MPI_Bcast (buffer.data (), size, sc_MPI_BYTE, main_rank, comm); + + /* Add data to local map. */ + if (rank != main_rank) { + t8_cmesh_boundary_condition_handler::unpack_map (buffer, true); + } +} diff --git a/src/t8_cmesh/t8_cmesh_boundary_conditions/internal/t8_cmesh_boundary_condition_handler.hxx b/src/t8_cmesh/t8_cmesh_boundary_conditions/internal/t8_cmesh_boundary_condition_handler.hxx index 167bd3dba4..6c1c91ec09 100644 --- a/src/t8_cmesh/t8_cmesh_boundary_conditions/internal/t8_cmesh_boundary_condition_handler.hxx +++ b/src/t8_cmesh/t8_cmesh_boundary_conditions/internal/t8_cmesh_boundary_condition_handler.hxx @@ -73,6 +73,8 @@ struct t8_cmesh_boundary_condition_handler using boundary_condition_hash = T8Type; public: + /**************************************** CONSTRUCTORS & ASSIGNMENT OPERATORS ****************************************/ + /** * Standard constructor. Associates the handler with a cmesh * \param [in] cmesh @@ -81,6 +83,42 @@ struct t8_cmesh_boundary_condition_handler { } + /** + * Copy constructor. + * \param [in] other The other. + */ + t8_cmesh_boundary_condition_handler (const t8_cmesh_boundary_condition_handler &other) = default; + + /** + * Move constructor. + * \param [in] other The other. + */ + t8_cmesh_boundary_condition_handler (t8_cmesh_boundary_condition_handler &&other) noexcept = default; + + /** + * Copy assignment operator. + * \param [in] other The other. + * \return A copy of this. + */ + t8_cmesh_boundary_condition_handler & + operator= (const t8_cmesh_boundary_condition_handler &other) + = default; + + /** + * Move assignment operator. + * \param [in] other The other. + * \return A reference to a moved version of this. + */ + t8_cmesh_boundary_condition_handler & + operator= (t8_cmesh_boundary_condition_handler &&other) noexcept + = default; + + /** + * The destructor. + */ + ~t8_cmesh_boundary_condition_handler () = default; + + /**************************************** BOUNDARY CONDITION SETUP ****************************************/ /** * Applies boundary conditions to the faces of a cmesh cell. * @@ -90,21 +128,69 @@ struct t8_cmesh_boundary_condition_handler * as the eclass of the cell has faces. */ template - requires std::convertible_to, std::string_view> + requires std::convertible_to, std::string_view> inline void add_boundary_conditions (t8_gloidx_t gtreeid, TStringRange boundary_conditions) { std::vector hashes; hashes.reserve (std::size (boundary_conditions)); for (const auto &boundary_condition : boundary_conditions) { - const boundary_condition_hash hash = hash_boundary_condition_name (boundary_condition); - m_boundary_conditions.try_emplace (hash, boundary_condition); + const std::string_view boundary_condition_view = boundary_condition; + const boundary_condition_hash hash = hash_boundary_condition_name (boundary_condition_view); + [[maybe_unused]] const auto inserted = m_boundary_conditions.try_emplace (hash, boundary_condition_view); +#if T8_ENABLE_DEBUG + if (inserted.second) { + t8_debugf ("Registered boundary condition %.*s\n", static_cast (boundary_condition_view.size ()), + boundary_condition_view.data ()); + } +#endif hashes.emplace_back (std::move (hash)); } t8_cmesh_set_attribute (m_cmesh, gtreeid, t8_get_package_id (), get_boundary_condition_attribute_key (), hashes.data (), sizeof (boundary_condition_hash) * hashes.size (), 0); } + /** + * Adds a boundary condition name to this handler without registering it to a tree. + * Mostly needed for debugging and testing reasons. + * Boundary conditions added via \ref add_boundary_conditions do not need to be registered explicitly. + * \tparam TString A string-like object. + * \param [in] boundary_condition The name of the boundary condition. + */ + template + requires std::convertible_to + inline void + register_boundary_condition (TString &&boundary_condition) + { + const std::string boundary_condition_string { boundary_condition }; + const boundary_condition_hash hash = hash_boundary_condition_name (boundary_condition_string); + + const auto inserted = m_boundary_conditions.try_emplace (hash, std::move (boundary_condition_string)); + +#if T8_ENABLE_DEBUG + if (inserted.second) { + const std::string_view boundary_condition_view = boundary_condition; + t8_debugf ("Registered boundary condition %.*s\n", static_cast (boundary_condition_view.size ()), + boundary_condition_view.data ()); + } +#endif + } + + /** + * Updates the internal cmesh. The boundary condition handler can only be given to uncommitted cmeshes. + * \param [in] new_cmesh The new cmesh. + */ + inline void + set_cmesh (t8_cmesh_t new_cmesh) + { + T8_ASSERT (t8_cmesh_is_initialized (new_cmesh)); + T8_ASSERTF (t8_cmesh_is_committed (new_cmesh, 0), + "The boundary condition handler can only be set for uncommitted cmeshes.\n"); + m_cmesh = new_cmesh; + } + + /**************************************** BOUNDARY CONDITION RETRIEVAL ****************************************/ + /** * Retrieves the boundary conditions of a cmesh cell. * @@ -195,6 +281,23 @@ struct t8_cmesh_boundary_condition_handler return std::nullopt; } + /** + * Get all registered boundary condition names. + * \return A vector containing all registered boundary condition names. + */ + inline std::vector + get_registered_boundary_conditions () const + { + std::vector boundary_conditions; + boundary_conditions.reserve (m_boundary_conditions.size ()); + for (const auto &boundary_condition : m_boundary_conditions) { + boundary_conditions.push_back (boundary_condition.second); + } + return boundary_conditions; + } + + /**************************************** HELPER FUNCTIONS ****************************************/ + #if T8_ENABLE_DEBUG /** Verifies the proper attribution of boundary conditions. Can only be called on a cmesh * during commit. @@ -234,9 +337,9 @@ struct t8_cmesh_boundary_condition_handler * \return The hash of the name. */ inline boundary_condition_hash - hash_boundary_condition_name (const std::string &boundary_condition_name) const + hash_boundary_condition_name (const std::string_view &boundary_condition_name) const { - return boundary_condition_hash (std::hash {}(boundary_condition_name)); + return boundary_condition_hash (std::hash {}(boundary_condition_name)); } /** @@ -267,6 +370,45 @@ struct t8_cmesh_boundary_condition_handler return m_boundary_conditions.at (hash); } + /**************************************** MPI HELPER FUNCTIONS ****************************************/ + + public: + /** + * Synchronizes the contents of the boundary condition handler across all processes. + * \param [in] comm The communicator to use. + */ + void + synchronize (sc_MPI_Comm comm); + + /** + * Broadcasts the boundary conditions from \a main_rank to all other ranks. + * \param [in] main_rank The main rank from which to broadcast. + * \param [in] comm The communicator to use. + */ + void + bcast (int main_rank, sc_MPI_Comm comm); + + /** + * Converts the contents of m_boundary_conditions into a serial vector of chars. + * The keys are omitted and only the strings are serialized. + * In the serialized vector, the individual strings are null-terminated. + * \return The serialized map. + */ + std::vector + serialize_map () const; + + /** + * Unpacks and integrates the \a serial_data into m_boundary_conditions. + * It either merges the already existing data or overwrites the complete map if \a overwrite is set to true. + * \param [in] serial_data The data to unpack and integrate. + * \param [in] overwrite Overwrites the data in this handler if true. Merges the data with the existing data on false. + */ + void + unpack_map (std::vector &serial_data, bool overwrite); + + /**************************************** MEMBERS ****************************************/ + + private: /** The associated cmesh of this struct */ t8_cmesh_t m_cmesh; diff --git a/src/t8_cmesh/t8_cmesh_boundary_conditions/t8_cmesh_boundary_conditions.hxx b/src/t8_cmesh/t8_cmesh_boundary_conditions/t8_cmesh_boundary_conditions.hxx index 8561fdc6d9..a61d72dce4 100644 --- a/src/t8_cmesh/t8_cmesh_boundary_conditions/t8_cmesh_boundary_conditions.hxx +++ b/src/t8_cmesh/t8_cmesh_boundary_conditions/t8_cmesh_boundary_conditions.hxx @@ -45,7 +45,7 @@ * as the eclass of the cell has faces. */ template - requires std::convertible_to, std::string_view> + requires std::convertible_to, std::string_view> void t8_cmesh_set_boundary_conditions (t8_cmesh_t cmesh, t8_gloidx_t gtreeid, TStringRange boundary_conditions) { diff --git a/src/t8_cmesh/t8_cmesh_internal/t8_cmesh_commit.cxx b/src/t8_cmesh/t8_cmesh_internal/t8_cmesh_commit.cxx index 289b9a0b3d..68d7f9845d 100644 --- a/src/t8_cmesh/t8_cmesh_internal/t8_cmesh_commit.cxx +++ b/src/t8_cmesh/t8_cmesh_internal/t8_cmesh_commit.cxx @@ -36,6 +36,7 @@ #include #include #include +#include /** * A struct to hold the information about a ghost facejoin. @@ -503,6 +504,18 @@ t8_cmesh_commit_partitioned_new (t8_cmesh_t cmesh, sc_MPI_Comm comm) } sc_MPI_Allreduce (&id1, &cmesh->num_trees, 1, T8_MPI_GLOIDX, sc_MPI_SUM, comm); + /* Communicate the boundary conditions. + * Since some processes can have no trees, + * we have to communicate if boundary conditions were applied first. */ + int boundary_conditions_applied_locally = cmesh->boundary_condition_handler != nullptr; + int boundary_conditions_applied_globally = 0; + sc_MPI_Allreduce (&boundary_conditions_applied_locally, &boundary_conditions_applied_globally, 1, sc_MPI_INT, + sc_MPI_MAX, comm); + if (boundary_conditions_applied_globally && cmesh->boundary_condition_handler == nullptr) { + t8_cmesh_add_boundary_condition_handler (cmesh); + } + cmesh->boundary_condition_handler->synchronize (comm); + #if T8_ENABLE_DEBUG sc_flops_shot (&fi, &snapshot); sc_stats_set1 (&stats[2], snapshot.iwtime, "cmesh_commit_end"); @@ -583,6 +596,15 @@ t8_cmesh_commit (t8_cmesh_t cmesh, sc_MPI_Comm comm) cmesh->geometry_handler->ref (); } + /* Copy the boundary condition handler if available. */ + T8_ASSERT (cmesh->boundary_condition_handler == nullptr); + if (cmesh->set_from->boundary_condition_handler != nullptr) { + cmesh->boundary_condition_handler + = new detail::t8_cmesh_boundary_condition_handler (*cmesh->set_from->boundary_condition_handler); + /* Assign handler to new cmesh. */ + cmesh->boundary_condition_handler->set_cmesh (cmesh); + } + #if T8_ENABLE_DEBUG /* Copy negative volume check from set_from */ cmesh->negative_volume_check = cmesh->set_from->negative_volume_check; diff --git a/src/t8_data/t8_static_vector.hxx b/src/t8_data/t8_static_vector.hxx index 56f1895865..18a575ee0b 100644 --- a/src/t8_data/t8_static_vector.hxx +++ b/src/t8_data/t8_static_vector.hxx @@ -90,6 +90,25 @@ class t8_static_vector { } } + /** + * Creates a static vector from a range. + * + * \tparam TRange The type of the input range. + * \param [in] range The elements to store in the vector. + * + * \note The number of elements in the range must not exceed the vector capacity. + */ + template + requires std::convertible_to, TType> + constexpr t8_static_vector (TRange&& range) + { + T8_ASSERT (std::ranges::size (range) <= TCapacity); + + for (const auto& value : range) { + m_data[m_size++] = value; + } + } + /** * Returns the current number of elements stored in the vector. * @@ -292,6 +311,31 @@ class t8_static_vector { return *this; } + /** + * Assigns the contents of a range to the vector. + * + * \tparam TRange The type of the input range. + * \param [in] range The elements to copy into the vector. + * \return A reference to this vector. + * + * \note The number of elements in the range must not exceed the vector capacity. + */ + template + requires std::convertible_to, TType> + constexpr t8_static_vector& + operator= (TRange&& range) + { + T8_ASSERT (std::ranges::size (range) <= TCapacity); + + m_size = 0; + + for (const auto& value : range) { + m_data[m_size++] = value; + } + + return *this; + } + /** * Returns a reference to the element at the given index. * diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fdc8409356..8eb1989447 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -139,7 +139,7 @@ add_t8_cpp_test( NAME t8_gtest_compute_first_element_serial SOUR add_t8_cpp_test( NAME t8_gtest_multiple_attributes_parallel SOURCES t8_cmesh/t8_gtest_multiple_attributes.cxx ) add_t8_cpp_test( NAME t8_gtest_attribute_gloidx_array_serial SOURCES t8_cmesh/t8_gtest_attribute_gloidx_array.cxx ) add_t8_cpp_test( NAME t8_gtest_cmesh_bounding_box_serial SOURCES t8_cmesh/t8_gtest_cmesh_bounding_box.cxx ) -add_t8_cpp_test( NAME t8_gtest_cmesh_boundary_conditions_serial SOURCES t8_cmesh/t8_gtest_cmesh_boundary_conditions.cxx ) +add_t8_cpp_test( NAME t8_gtest_cmesh_boundary_conditions_parallel SOURCES t8_cmesh/t8_gtest_cmesh_boundary_conditions.cxx ) 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) diff --git a/test/t8_cmesh/t8_gtest_cmesh_boundary_conditions.cxx b/test/t8_cmesh/t8_gtest_cmesh_boundary_conditions.cxx index aac4d4836d..4ee9d1cda1 100644 --- a/test/t8_cmesh/t8_gtest_cmesh_boundary_conditions.cxx +++ b/test/t8_cmesh/t8_gtest_cmesh_boundary_conditions.cxx @@ -64,6 +64,13 @@ * Test fixture for the cmesh boundary condition module. It applies boundary conditions * to single tree cmeshes in accordance to their face number. */ + +/** Variable for setting hex boundary conditions. */ +constexpr std::array single_hex_bcs = { "bc_0", "bc_1", "bc_2", "bc_3", "bc_4", "bc_5" }; + +/** C Version of \ref single_hex_bcs. */ +const char *single_hex_bcs_c[6] = { "bc_0", "bc_1", "bc_2", "bc_3", "bc_4", "bc_5" }; + struct t8_cmesh_single_tree_bc: public testing::TestWithParam { protected: @@ -71,7 +78,7 @@ struct t8_cmesh_single_tree_bc: public testing::TestWithParam SetUp () override { eclass = GetParam (); - boundary_conditions = { "bc_0", "bc_1", "bc_2", "bc_3", "bc_4", "bc_5" }; + boundary_conditions = single_hex_bcs; boundary_conditions.resize (static_cast (t8_eclass_num_faces[eclass])); t8_cmesh_init (&cmesh); t8_cmesh_set_tree_class (cmesh, 0, eclass); @@ -85,7 +92,7 @@ struct t8_cmesh_single_tree_bc: public testing::TestWithParam t8_cmesh_unref (&cmesh); } - t8_boundary_conditions boundary_conditions; + t8_boundary_conditions boundary_conditions; t8_cmesh_t cmesh; t8_eclass eclass; }; @@ -243,14 +250,30 @@ TEST (t8_gtest_cmesh_boundary_conditions, test_hybrid_hypercube_boundary_conditi TEST (t8_gtest_cmesh_boundary_conditions, test_boundary_condition_c_interface) { /* Create a cmesh with one hex and apply boundary conditions */ - const char *boundary_conditions[6] = { "bc_0", "bc_1", "bc_2", "bc_3", "bc_4", "bc_5" }; t8_cmesh_t cmesh; t8_cmesh_init (&cmesh); t8_cmesh_set_tree_class (cmesh, 0, T8_ECLASS_HEX); - t8_cmesh_set_boundary_conditions (cmesh, 0, boundary_conditions, 6); + t8_cmesh_set_boundary_conditions (cmesh, 0, single_hex_bcs_c, 6); t8_cmesh_commit (cmesh, sc_MPI_COMM_WORLD); - /* Only check if the cmesh tree is local to our process. */ + /* Check if the cmesh tree is local to our process. */ + ASSERT_EQ (t8_cmesh_get_num_local_trees (cmesh), 1); + /* Some variables for retrieving and checking the boundary conditions. */ + const char *retrieved_boundary_conditions[6]; + const char *retrieved_single_boundary_condition; + size_t length = 0; + + /* Retrieve boundary conditions via t8_cmesh_get_boundary_conditions and t8_cmesh_get_boundary_condition() and check them. */ + t8_cmesh_get_boundary_conditions (cmesh, 0, retrieved_boundary_conditions, &length); + for (size_t i_boundary_condition = 0; i_boundary_condition < length; ++i_boundary_condition) { + /* Check t8_cmesh_get_boundary_conditions */ + EXPECT_STREQ (single_hex_bcs_c[i_boundary_condition], retrieved_boundary_conditions[i_boundary_condition]); + + /* Check t8_cmesh_get_boundary_condition */ + t8_cmesh_get_boundary_condition (cmesh, 0, i_boundary_condition, &retrieved_single_boundary_condition); + EXPECT_STREQ (single_hex_bcs_c[i_boundary_condition], retrieved_single_boundary_condition); + } + if (t8_cmesh_get_num_local_trees (cmesh)) { /* Some variables for retrieving and checking the boundary conditions. */ const char *retrieved_boundary_conditions[6]; @@ -261,11 +284,11 @@ TEST (t8_gtest_cmesh_boundary_conditions, test_boundary_condition_c_interface) t8_cmesh_get_boundary_conditions (cmesh, 0, retrieved_boundary_conditions, &length); for (size_t i_boundary_condition = 0; i_boundary_condition < length; ++i_boundary_condition) { /* Check t8_cmesh_get_boundary_conditions */ - EXPECT_STREQ (boundary_conditions[i_boundary_condition], retrieved_boundary_conditions[i_boundary_condition]); + EXPECT_STREQ (single_hex_bcs_c[i_boundary_condition], retrieved_boundary_conditions[i_boundary_condition]); /* Check t8_cmesh_get_boundary_condition */ t8_cmesh_get_boundary_condition (cmesh, 0, i_boundary_condition, &retrieved_single_boundary_condition); - EXPECT_STREQ (boundary_conditions[i_boundary_condition], retrieved_single_boundary_condition); + EXPECT_STREQ (single_hex_bcs_c[i_boundary_condition], retrieved_single_boundary_condition); } } @@ -315,8 +338,8 @@ TEST (t8_gtest_cmesh_boundary_conditions, test_boundary_condition_c_interface) ASSERT_TRUE (retrieved_boundary_conditions[iface] != nullptr); ASSERT_TRUE (retrieved_single_boundary_condition != nullptr); - EXPECT_STREQ (retrieved_boundary_conditions[iface], boundary_conditions[iface]); - EXPECT_STREQ (retrieved_single_boundary_condition, boundary_conditions[iface]); + EXPECT_STREQ (retrieved_boundary_conditions[iface], single_hex_bcs_c[iface]); + EXPECT_STREQ (retrieved_single_boundary_condition, single_hex_bcs_c[iface]); } } } @@ -324,3 +347,217 @@ TEST (t8_gtest_cmesh_boundary_conditions, test_boundary_condition_c_interface) t8_forest_unref (&forest); } + +/** + * Checks if the registered boundary conditions of the \a handler match with some \a testing_conditions. + * \param [in] handler The handler to check. + * \param [in] testing_conditions The conditions which should be registered in \a handler. + */ +static void +check_boundary_conditions (detail::t8_cmesh_boundary_condition_handler &handler, + std::vector testing_conditions) +{ + auto retrieved_conditions = handler.get_registered_boundary_conditions (); + ASSERT_EQ (retrieved_conditions.size (), testing_conditions.size ()); + + std::ranges::sort (retrieved_conditions); + std::ranges::sort (testing_conditions); + for (size_t i_bc = 0; i_bc < retrieved_conditions.size (); ++i_bc) { + EXPECT_EQ (retrieved_conditions[i_bc], testing_conditions[i_bc]); + } +} + +/** + * This test registers boundary conditions on rank 0 and broadcasts them to the other ranks. + * In the end we check, if every rank hast the boundary conditions. + */ +TEST (t8_gtest_cmesh_boundary_conditions, test_boundary_condition_broadcast) +{ + /* Get MPI info. */ + sc_MPI_Comm comm = sc_MPI_COMM_WORLD; + int rank; + sc_MPI_Comm_rank (comm, &rank); + + /* We create the same vector with 20 boundary conditions on each rank. */ + std::vector testing_boundary_conditions; + size_t num_boundary_conditions = 20; + testing_boundary_conditions.reserve (num_boundary_conditions); + for (size_t i_bc = 0; i_bc < num_boundary_conditions; ++i_bc) { + testing_boundary_conditions.emplace_back ("testing_boundary_condition_" + std::to_string (i_bc)); + } + + /* We create a handler and register the boundary conditions only on rank 0. */ + detail::t8_cmesh_boundary_condition_handler handler (nullptr); + if (rank == 0) { + for (const auto &boundary_condition : testing_boundary_conditions) { + handler.register_boundary_condition (boundary_condition); + } + } + + /* Broadcast the conditions. */ + handler.bcast (0, comm); + + /* Check if every rank has all conditions. */ + check_boundary_conditions (handler, testing_boundary_conditions); +} + +/** + * This test creates boundary conditions for each rank. + * Then we synchronize all bcs and check that all boundary conditions are available + * on all ranks. + */ +TEST (t8_gtest_cmesh_boundary_conditions, test_boundary_condition_synchronize) +{ + /* Get MPI info */ + sc_MPI_Comm comm = sc_MPI_COMM_WORLD; + int rank, mpisize; + sc_MPI_Comm_rank (comm, &rank); + sc_MPI_Comm_size (comm, &mpisize); + + /* Create the boundary conditions. + * For each rank, we create 6 bcs. + * The last rank gets no boundary conditions as a special testing case. + * Each rank gets his own 6 boundary conditions as well as 2 boundary conditions + * of the next rank. This way each rank (except the first, last and second to last) + * has 2 shared bcs with rank - 1, 2 unique bcs and 2 shared bcs with rank + 1. + * Rank 0 and mpisize - 2 have 4 unique bcs and 2 shared ones (if there are more than 2 ranks). + * Rank mpisize - 1 has no boundary conditions. */ + std::vector testing_boundary_conditions; + const size_t num_boundary_conditions_per_rank = 6; + /* Since the last rank gets no bcs we use mpisize - 1 and since every rank gets 2 additional bcs we also add 2. */ + size_t num_boundary_conditions = (mpisize - 1) * num_boundary_conditions_per_rank + 2; + /* If we are serial we just apply 6 boundary conditions. */ + if (mpisize == 1) + num_boundary_conditions = num_boundary_conditions_per_rank; + testing_boundary_conditions.reserve (num_boundary_conditions); + for (size_t i_bc = 0; i_bc < num_boundary_conditions; ++i_bc) { + testing_boundary_conditions.emplace_back ("testing_boundary_condition_" + std::to_string (i_bc)); + } + + /* Assign all bcs. */ + detail::t8_cmesh_boundary_condition_handler handler (nullptr); + size_t bc_min = rank * num_boundary_conditions_per_rank; + size_t bc_max = (rank + 1) * num_boundary_conditions_per_rank + 2; + if (mpisize == 1) + bc_max = num_boundary_conditions_per_rank; + /* All ranks except the last one assign bcs. */ + if (rank != mpisize - 1 || mpisize == 1) { + for (size_t i_bc = bc_min; i_bc < bc_max; ++i_bc) { + handler.register_boundary_condition (testing_boundary_conditions[i_bc]); + } + } + + /* Synchronize the conditions. */ + handler.synchronize (comm); + + /* Check if every rank has all conditions. */ + check_boundary_conditions (handler, testing_boundary_conditions); +} + +/** + * We create a cmesh on one rank and broadcast it. The bcs should be available on all ranks. + */ +TEST (t8_gtest_cmesh_boundary_conditions, test_boundary_condition_broadcasted_cmesh) +{ + /* Get MPI info */ + sc_MPI_Comm comm = sc_MPI_COMM_WORLD; + int rank, mpisize; + sc_MPI_Comm_rank (comm, &rank); + sc_MPI_Comm_size (comm, &mpisize); + + /* Create cmesh and apply boundary conditions just on rank 0. */ + const t8_boundary_conditions boundary_conditions = { "bc_0", "bc_1", "bc_2", "bc_3", "bc_4", "bc_5" }; + t8_cmesh_t cmesh = NULL; + if (rank == 0) { + t8_cmesh_init (&cmesh); + t8_cmesh_set_tree_class (cmesh, 0, T8_ECLASS_HEX); + t8_cmesh_set_boundary_conditions (cmesh, 0, boundary_conditions); + } + cmesh = t8_cmesh_bcast (cmesh, 0, comm); + t8_cmesh_commit (cmesh, comm); + + ASSERT_EQ (t8_cmesh_get_num_local_trees (cmesh), 1); + /* Retrieve and test boundary conditions on all ranks. */ + const auto retrieved_boundary_conditions = t8_cmesh_get_boundary_conditions (cmesh, 0); + ASSERT_EQ (retrieved_boundary_conditions.size (), single_hex_bcs.size ()); + for (size_t i_boundary_condition = 0; i_boundary_condition < single_hex_bcs.size (); ++i_boundary_condition) { + /* Check t8_cmesh_get_boundary_conditions */ + EXPECT_EQ (single_hex_bcs[i_boundary_condition], retrieved_boundary_conditions[i_boundary_condition]); + } + t8_cmesh_destroy (&cmesh); +} + +/** + * We create a distributed cmesh on multiple ranks. The bcs are distributed as well. + * After committing, the boundary conditions should be available on every rank. + */ +TEST (t8_gtest_cmesh_boundary_conditions, test_boundary_condition_distributed_cmesh) +{ + /* Get MPI info */ + sc_MPI_Comm comm = sc_MPI_COMM_WORLD; + int rank, mpisize; + sc_MPI_Comm_rank (comm, &rank); + sc_MPI_Comm_size (comm, &mpisize); + + /* The three boundary conditions test three things: + * 1. test_boundary_condition is equal on every rank and should not be duplicated. + * 2. tree_id: gtreeid is unique to each process and tree and should be available everywhere afterwards. + * 3. rank: rank is unique to each rank an tests, that every rank contributed his part. */ + const t8_boundary_conditions boundary_conditions + = { "test_boundary_condition", "tree_id: ", "rank: " + std::to_string (rank) }; + + /* Every rank gets 5 trees except the last one, which gets 0. + * If there is only one rank, it still gets 5 trees. */ + constexpr size_t num_trees_per_rank = 5; + t8_gloidx_t min_tree_id; + t8_gloidx_t max_tree_id; + if (mpisize == 1 || rank != mpisize - 1) { + min_tree_id = rank * num_trees_per_rank; + max_tree_id = (rank + 1) * num_trees_per_rank - 1; + } + else { + min_tree_id = 1; /* min tree id has to be bigger than max tree id for t8_cmesh_set_partition_range */ + max_tree_id = 0; + } + + /* Create the distributed cmesh. */ + t8_cmesh_t cmesh; + t8_cmesh_init (&cmesh); + t8_cmesh_set_dimension (cmesh, 2); + for (t8_gloidx_t itree = min_tree_id; itree <= max_tree_id; ++itree) { + t8_cmesh_set_tree_class (cmesh, itree, T8_ECLASS_TRIANGLE); + auto current_bcs = boundary_conditions; + current_bcs[1] += std::to_string (itree); + t8_cmesh_set_boundary_conditions (cmesh, itree, current_bcs); + } + t8_cmesh_set_partition_range (cmesh, 3, min_tree_id, max_tree_id); + t8_cmesh_commit (cmesh, comm); + + /* Retrieve boundary conditions and compute how many there should be. */ + auto registered_boundary_conditions + = t8_cmesh_get_boundary_condition_handler (cmesh)->get_registered_boundary_conditions (); + std::ranges::sort (registered_boundary_conditions); + const t8_gloidx_t num_trees_in_boundary_conditions + = mpisize == 1 ? num_trees_per_rank : (mpisize - 1) * num_trees_per_rank; + const t8_gloidx_t num_ranks_in_boundary_conditions = mpisize == 1 ? 1 : mpisize - 1; + + /* Check the common boundary condition. */ + ASSERT_FALSE (std::find (registered_boundary_conditions.begin (), registered_boundary_conditions.end (), + "test_boundary_condition") + == registered_boundary_conditions.end ()); + + /* Check that all bcs from all ranks (except the last one) are there. */ + for (int irank = 0; irank < num_ranks_in_boundary_conditions; ++irank) { + ASSERT_FALSE (std::find (registered_boundary_conditions.begin (), registered_boundary_conditions.end (), + "rank: " + std::to_string (irank)) + == registered_boundary_conditions.end ()); + } + + /* Check that all tree id bcs are there. */ + for (int itree = 0; itree < num_trees_in_boundary_conditions; ++itree) { + ASSERT_FALSE (std::find (registered_boundary_conditions.begin (), registered_boundary_conditions.end (), + "tree_id: " + std::to_string (itree)) + == registered_boundary_conditions.end ()); + } + t8_cmesh_destroy (&cmesh); +}