Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions mesh_handle/element.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,22 @@ class element: public TCompetences<element<TMeshClass, 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.
Expand Down
24 changes: 24 additions & 0 deletions mesh_handle/mesh.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@
#include <t8_forest/t8_forest_ghost/t8_forest_ghost.h>
#include <t8_forest/t8_forest_ghost/t8_forest_ghost_definition_base.hxx>
#include <t8_forest/t8_forest_iterate.h>
#include <t8_forest/t8_forest_subelement.hxx>
#include <vector>
#include <functional>
#include <memory>
#include <span>
#include <type_traits>
#include <algorithm>

namespace t8_mesh_handle
{
Expand Down Expand Up @@ -239,6 +241,28 @@ class mesh: public TMeshCompetencePack::template apply<mesh<TElementCompetencePa
return t8_forest_is_balanced (m_forest);
}

/** Check if the mesh is conformal, i.e. no element has hanging nodes on any of its faces.
* The check uses \ref element::has_hanging_node and thus uses cached neighbors.
* This function is collective and has to be called on all processes.
* \return True if the mesh is conformal on all processes, false otherwise.
*/
bool
is_conformal () const
{
if (this->get_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.
Expand Down
36 changes: 36 additions & 0 deletions test/mesh_handle/t8_gtest_ghost.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,16 @@ along with t8code; if not, write to the Free Software Foundation, Inc.,
#include <mesh_handle/competences/cache_element_competences.hxx>
#include <mesh_handle/competence_pack.hxx>
#include <mesh_handle/constructor_wrappers.hxx>
#include "t8_gtest_common.hxx"
#include <t8_cmesh/t8_cmesh.h>
#include <t8_cmesh/t8_cmesh_examples.h>
#include <t8_forest/t8_forest_general.h>
#include <t8_forest/t8_forest_balance.h>
#include <t8_forest/t8_forest_ghost/t8_forest_ghost.h>
#include <t8_schemes/t8_default/t8_default.hxx>
#include <t8_forest/t8_forest_subelement.hxx>
#include <vector>
#include <algorithm>

/** Parametrized test fixture for the ghost tests. */
struct t8_mesh_ghost_test: public testing::TestWithParam<std::tuple<t8_eclass_t, int>>
Expand Down Expand Up @@ -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<t8_mesh_handle::all_cache_element_competences>;
auto mesh
= t8_mesh_handle::handle_hypercube_uniform_default<mesh_class> (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_class>);
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 <typename TUnderlying>
struct cache_neighbors_overwrite: public t8_mesh_handle::cache_neighbors<TUnderlying>
Expand Down
Loading