diff --git a/mesh_handle/element.hxx b/mesh_handle/element.hxx index 9adaa615ab..dbc8625b29 100644 --- a/mesh_handle/element.hxx +++ b/mesh_handle/element.hxx @@ -413,6 +413,22 @@ class element: public TCompetences>... { } } + /** Check if the element has a hanging node i.e. if any of its faces has more than one neighbor. + * This is the case if the element has more than one face neighbor at any face. + * This function uses the cached face neighbor defined in TCompetences if available. + * \return True if the element has a hanging node, false otherwise. + */ + bool + has_hanging_node () const + { + for (int iface = 0; iface < get_num_faces (); ++iface) { + if (get_face_neighbors (iface).size () > 1) { + return true; + } + } + return false; + } + /** Convert points from the reference space of this element to the reference space of * its tree / its initial coarse mesh element. * \param [in] ref_coords Points in the element's reference space [0,1]^dim. diff --git a/mesh_handle/mesh.hxx b/mesh_handle/mesh.hxx index c8d4586e62..2c4ae04572 100644 --- a/mesh_handle/mesh.hxx +++ b/mesh_handle/mesh.hxx @@ -38,11 +38,13 @@ #include #include #include +#include #include #include #include #include #include +#include namespace t8_mesh_handle { @@ -239,6 +241,28 @@ class mesh: public TMeshCompetencePack::template applyget_dimension () <= 1) { + return true; // 1D or lower-dimensional meshes are always conformal. + } + SC_CHECK_ABORT (!t8_forest_has_subelements (m_forest), + "The conformity check is not possible for meshes with subelements."); + const bool locally_conformal + = std::all_of (cbegin (), cend (), [] (const element_class& element) { return !element.has_hanging_node (); }); + int local = locally_conformal ? 1 : 0; + int global = 0; + const int mpiret = sc_MPI_Allreduce (&local, &global, 1, sc_MPI_INT, sc_MPI_LAND, t8_forest_get_mpicomm (m_forest)); + SC_CHECK_MPI (mpiret); + return global != 0; + } + // --- Methods to access elements. --- /** * Returns a constant iterator to the first (local) mesh element. diff --git a/test/mesh_handle/t8_gtest_ghost.cxx b/test/mesh_handle/t8_gtest_ghost.cxx index 1dc0896e56..e3a89a9f9a 100644 --- a/test/mesh_handle/t8_gtest_ghost.cxx +++ b/test/mesh_handle/t8_gtest_ghost.cxx @@ -33,13 +33,16 @@ along with t8code; if not, write to the Free Software Foundation, Inc., #include #include #include +#include "t8_gtest_common.hxx" #include #include #include #include #include #include +#include #include +#include /** Parametrized test fixture for the ghost tests. */ struct t8_mesh_ghost_test: public testing::TestWithParam> @@ -179,6 +182,39 @@ TEST_P (t8_mesh_ghost_test, compare_neighbors_to_forest) } } +/** Check the conformity check of the elements and the mesh. The mesh version is compared to the forest. */ +TEST_P (t8_mesh_ghost_test, test_is_conformal) +{ + using mesh_class = t8_mesh_handle::mesh; + auto mesh + = t8_mesh_handle::handle_hypercube_uniform_default (eclass, level, sc_MPI_COMM_WORLD, true, true); + + /* A uniform mesh is always conformal. After this, the neighbor cache is also filled. */ + EXPECT_TRUE (mesh->is_conformal ()); + for (const auto& element : *mesh) { + EXPECT_FALSE (element.has_hanging_node ()); + for (int iface = 0; iface < element.get_num_faces (); ++iface) { + EXPECT_TRUE (element.neighbor_cache_filled (iface)); + } + } + + /* Refining every second element should create hanging nodes (except 0D and 1D, where each face has at most one point). */ + mesh->set_adapt (adapt_callback_refine_second); + mesh->set_ghost (); + mesh->commit (); + EXPECT_EQ (mesh->is_conformal (), t8_forest_is_conforming (mesh->get_forest ())); + if (mesh->get_dimension () > 1) { + EXPECT_FALSE (mesh->is_conformal ()); + const bool any_element_not_conformal + = std::any_of (mesh->cbegin (), mesh->cend (), [] (const auto& element) { return element.has_hanging_node (); }); + int local = any_element_not_conformal ? 1 : 0; + int global = 0; + const int mpiret = sc_MPI_Allreduce (&local, &global, 1, sc_MPI_INT, sc_MPI_LOR, sc_MPI_COMM_WORLD); + SC_CHECK_MPI (mpiret); + EXPECT_TRUE (global != 0); + } +} + /** Child of \ref t8_mesh_handle::cache_neighbors that allows to modify the cache variables for test purposes. */ template struct cache_neighbors_overwrite: public t8_mesh_handle::cache_neighbors