Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
DLR-AMR
GitHub Repository: DLR-AMR/t8code
Path: blob/main/test/mesh_handle/t8_gtest_ghost.cxx
909 views
/*
This file is part of t8code.
t8code is a C library to manage a collection (a forest) of multiple
connected adaptive space-trees of general element classes in parallel.

Copyright (C) 2025 the developers

t8code is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

t8code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with t8code; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

/**
 * \file t8_gtest_ghost.cxx
 * Tests if the ghost elements and the \ref t8_mesh_handle::element::get_face_neighbor implementation work as intended.
 */

#include <gtest/gtest.h>
#include <test/t8_gtest_macros.hxx>
#include <t8.h>

#include <mesh_handle/mesh.hxx>
#include <mesh_handle/competences.hxx>
#include <mesh_handle/competence_pack.hxx>
#include <mesh_handle/constructor_wrappers.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.h>
#include <t8_schemes/t8_default/t8_default.hxx>
#include <vector>

/** Parametrized test fixture for the ghost tests. */
struct t8_mesh_ghost_test: public testing::TestWithParam<std::tuple<t8_eclass_t, int>>
{
 protected:
  void
  SetUp () override
  {
    eclass = std::get<0> (GetParam ());
    level = std::get<1> (GetParam ());
  }

  t8_eclass_t eclass;
  int level;
};

/** Check the implementation of ghosts and all functions accessible by ghosts. */
TEST_P (t8_mesh_ghost_test, check_ghosts)
{
  using mesh_class = t8_mesh_handle::mesh<t8_mesh_handle::all_cache_competences>;
  auto mesh = t8_mesh_handle::handle_hypercube_uniform_default<mesh_class> (eclass, level, sc_MPI_COMM_WORLD, true,
                                                                            true, false);

  EXPECT_EQ (mesh->get_num_ghosts (), t8_forest_get_num_ghosts (mesh->get_forest ()));
  if ((mesh->get_dimension () > 1) && (mesh->get_num_local_elements () > 1)) {
    // Ensure that we actually have ghost elements in this test.
    EXPECT_GT (mesh->get_num_ghosts (), 0);
  }
  else {
    GTEST_SKIP () << "Skipping test as no ghost elements are created for 1D or single element meshes.";
  }

  // Check functions for ghost elements.
  const t8_locidx_t num_local_elements = mesh->get_num_local_elements ();
  const t8_locidx_t num_ghost_elements = mesh->get_num_ghosts ();
  for (t8_locidx_t ighost = num_local_elements; ighost < num_local_elements + num_ghost_elements; ++ighost) {
    EXPECT_EQ (ighost, (*mesh)[ighost].get_element_handle_id ());
    EXPECT_TRUE ((*mesh)[ighost].is_ghost_element ());
    EXPECT_EQ (level, (*mesh)[ighost].get_level ());
    EXPECT_LE (0, (*mesh)[ighost].get_num_faces ());
    EXPECT_LE (0, (*mesh)[ighost].get_num_vertices ());
    EXPECT_LE (0, (*mesh)[ighost].get_volume ());
    EXPECT_LE (0, (*mesh)[ighost].get_diameter ());
    for (const auto& coordinate : (*mesh)[ighost].get_centroid ()) {
      EXPECT_TRUE (coordinate >= 0.0 && coordinate <= 1.0);
    }
    for (int ivertex = 0; ivertex < (*mesh)[ighost].get_num_vertices (); ++ivertex) {
      for (const auto& coordinate : (*mesh)[ighost].get_vertex_coordinates (ivertex)) {
        EXPECT_TRUE (coordinate >= 0.0 && coordinate <= 1.0);
      }
    }
    // Check face related functions exemplary for first face.
    EXPECT_LE (0, (*mesh)[ighost].get_face_area (0));
    for (const auto& coordinate : (*mesh)[ighost].get_face_centroid (0)) {
      EXPECT_TRUE (coordinate >= 0.0 && coordinate <= 1.0);
    }
    for (const auto& coordinate : (*mesh)[ighost].get_face_normal (0)) {
      EXPECT_TRUE (coordinate >= -1 && coordinate <= 1);
    }
    // Check exemplary that caches work for ghost elements.
    EXPECT_TRUE ((*mesh)[ighost].volume_cache_filled ());
    EXPECT_LE (0, (*mesh)[ighost].get_volume ());
  }
}

/** Check that the function \ref t8_mesh_handle::element::get_face_neighbors of the handle works as intended (equal results to forest).*/
TEST_P (t8_mesh_ghost_test, compare_neighbors_to_forest)
{
  const t8_scheme* scheme = t8_scheme_new_default ();
  t8_forest_t forest = t8_forest_new_uniform (t8_cmesh_new_hypercube (eclass, sc_MPI_COMM_WORLD, 0, 1, 0), scheme,
                                              level, 1, sc_MPI_COMM_WORLD);

  const t8_mesh_handle::mesh<> mesh (forest);
  EXPECT_EQ (mesh.get_num_ghosts (), t8_forest_get_num_ghosts (forest));
  if ((mesh.get_dimension () > 1) && (mesh.get_num_local_elements () > 1)) {
    // Ensure that we have ghost elements in this test.
    EXPECT_GT (mesh.get_num_ghosts (), 0);
  }

  // Iterate over the elements of the forest and of the mesh handle simultaneously and compare results.
  auto mesh_iterator = mesh.cbegin ();
  for (t8_locidx_t itree = 0; itree < t8_forest_get_num_local_trees (forest); ++itree) {
    for (t8_locidx_t ielem = 0; ielem < t8_forest_get_tree_num_leaf_elements (forest, itree); ++ielem) {
      // --- Compare elements. ---
      EXPECT_EQ (mesh_iterator->get_local_tree_id (), itree);
      EXPECT_EQ (mesh_iterator->get_local_element_id (), ielem);
      // --- Compare neighbors. ---
      const t8_element_t* elem = t8_forest_get_leaf_element_in_tree (forest, itree, ielem);
      const int num_faces = scheme->element_get_num_faces (t8_forest_get_tree_class (forest, itree), elem);
      EXPECT_EQ (mesh_iterator->get_num_faces (), num_faces);
      for (int iface = 0; iface < num_faces; iface++) {
        // --- Get neighbors from forest. ---
        t8_element_t** neighbors;
        int num_neighbors;
        const int forest_is_balanced = t8_forest_is_balanced (forest);
        t8_eclass_t neigh_eclass;
        int* dual_faces;
        t8_locidx_t* neigh_ids;
        t8_forest_leaf_face_neighbors (forest, itree, elem, &neighbors, iface, &dual_faces, &num_neighbors, &neigh_ids,
                                       &neigh_eclass, forest_is_balanced);
        // --- Get neighbors from mesh element. ---
        std::vector<int> dual_faces_handle;
        auto neighbors_handle = mesh_iterator->get_face_neighbors (iface, dual_faces_handle);
        // --- Compare results. ---
        EXPECT_EQ (num_neighbors, (int) neighbors_handle.size ());
        EXPECT_EQ (dual_faces_handle, std::vector<int> (dual_faces, dual_faces + num_neighbors));
        for (int ineighs = 0; ineighs < num_neighbors; ineighs++) {
          EXPECT_EQ (neighbors_handle[ineighs]->get_element_handle_id (), neigh_ids[ineighs]);
        }
        for (int ineigh = 0; ineigh < num_neighbors; ineigh++) {
          EXPECT_EQ (scheme->element_get_shape (neigh_eclass, neighbors[ineigh]),
                     neighbors_handle[ineigh]->get_shape ());
        }
        // Free memory.
        if (num_neighbors > 0) {
          scheme->element_destroy (neigh_eclass, num_neighbors, neighbors);
          T8_FREE (neigh_ids);
          T8_FREE (neighbors);
          T8_FREE (dual_faces);
        }
      }
      // Evolve mesh iterator.
      mesh_iterator++;
    }
  }
}

/** 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>
{
 public:
  /** Overwrites the cache variables for the a \ref face.
   * \param [in] face              Face for which the cache should be overwritten.
   * \param [in] neighbors         New cache vector for the neighbors.
   * \param [in] dual_faces        New cache vector for the dual faces.
   */
  void
  overwrite_cache (int face, std::vector<const TUnderlying*> neighbors, std::vector<int> dual_faces) const
  {
    this->m_neighbors[face] = neighbors;
    this->m_dual_faces[face] = dual_faces;
  }
};

/** Use child of \ref t8_mesh_handle::cache_neighbors to check that the cache is actually set 
 * and accessed correctly. This is done by modifying the cache variables to a unrealistic values and 
 * checking that the functionality actually outputs this unrealistic value.
 */
TEST_P (t8_mesh_ghost_test, cache_neighbors)
{
  using mesh_class = t8_mesh_handle::mesh<t8_mesh_handle::competence_pack<cache_neighbors_overwrite>>;
  using element_class = typename mesh_class::element_class;
  const auto mesh = t8_mesh_handle::handle_hypercube_uniform_default<mesh_class> (eclass, level, sc_MPI_COMM_WORLD,
                                                                                  true, true, false);
  EXPECT_TRUE (element_class::has_face_neighbor_cache ());

  if (mesh->get_num_local_elements () == 0) {
    GTEST_SKIP () << "No local elements in the mesh to test the cache functionality.";
  }
  const std::vector<const element_class*> unrealistic_neighbors
    = { &((*mesh)[0]), &((*mesh)[mesh->get_num_local_elements () - 1]) };
  const std::vector<int> unrealistic_dual_faces = { 100, 1012000 };
  for (auto it = mesh->cbegin (); it != mesh->cend (); ++it) {
    // Check that cache is empty at the beginning.
    EXPECT_FALSE (it->neighbor_cache_filled_any ());
    it->fill_face_neighbor_cache ();
    for (int iface = 0; iface < it->get_num_faces (); iface++) {
      EXPECT_TRUE (it->neighbor_cache_filled (iface));
      std::vector<int> dual_faces;
      auto neighbors = it->get_face_neighbors (iface, dual_faces);
      // Overwrite cache with unrealistic values.
      it->overwrite_cache (iface, unrealistic_neighbors, unrealistic_dual_faces);
      EXPECT_TRUE (it->neighbor_cache_filled (iface));
      neighbors = it->get_face_neighbors (iface, dual_faces);
      // --- Compare results. ---
      EXPECT_EQ (neighbors, unrealistic_neighbors);
      EXPECT_EQ (dual_faces, unrealistic_dual_faces);
    }
  }
}

INSTANTIATE_TEST_SUITE_P (t8_gtest_ghost, t8_mesh_ghost_test, testing::Combine (AllEclasses, testing::Range (1, 2)));