Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
DLR-AMR
GitHub Repository: DLR-AMR/t8code
Path: blob/main/src/t8_cmesh/t8_cmesh_helpers.cxx
915 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) 2023, 2024 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_cmesh_helpers.cxx
 *
 * Collection of cmesh helper routines.
 */

#include <t8.h>
#include <t8_cmesh/t8_cmesh.h>
#include <t8_eclass.h>
#include <t8_cmesh/t8_cmesh_internal/t8_cmesh_types.h>
#include <t8_cmesh/t8_cmesh_internal/t8_cmesh_stash.h>
#include <t8_cmesh/t8_cmesh_helpers.h>
#include <vector>
#include <map>

void
t8_cmesh_set_join_by_vertices (t8_cmesh_t cmesh, const t8_gloidx_t ntrees, const t8_eclass_t *eclasses,
                               const double *vertices, int **connectivity, const int do_both_directions)
{
  /* If `connectivity` is NULL then the following array gets freed at the end of this routine. */
  int *conn = T8_ALLOC (int, ntrees *T8_ECLASS_MAX_FACES * 3);
  for (int i = 0; i < ntrees * T8_ECLASS_MAX_FACES * 3; i++) {
    conn[i] = -1;
  }

  /* Compute minimum and maximum of the cmesh domain. */
  double min_coord = vertices[0];
  double max_coord = vertices[0];

  for (int itree = 0; itree < ntrees; itree++) {
    const t8_eclass_t eclass = eclasses[itree];
    const int nverts = t8_eclass_num_vertices[eclass];
    const int edim = t8_eclass_to_dimension[eclass];

    for (int ivert = 0; ivert < nverts; ivert++) {
      for (int icoord = 0; icoord < edim; icoord++) {
        const double coord
          = vertices[T8_3D_TO_1D (ntrees, T8_ECLASS_MAX_CORNERS, T8_ECLASS_MAX_DIM, itree, ivert, icoord)];

        if (coord < min_coord) {
          min_coord = coord;
        }

        if (coord > max_coord) {
          max_coord = coord;
        }
      }
    }
  }

  /* Scaled tolerance to decide if two doubles are equal. */
  const double tolerance = 10.0 * T8_PRECISION_EPS * std::abs (max_coord - min_coord);

  /* Setup hash table `faces` mapping a hash key to a pair containing `(itree, iface)`. */
  std::multimap<unsigned long, std::pair<int, int>> faces;

  /* `num_bins` should be more than enough for (almost) all cases.
   * I.e., 2^P4EST_QMAXLEVEL =~ 1.073e9.
   */
  const double num_bins = 1e9; /* Number of bins. */
  const double inverse_bin_size = num_bins / (max_coord - min_coord);

  for (int itree = 0; itree < ntrees; itree++) {
    const t8_eclass_t eclass = eclasses[itree];

    /* Get the number of faces of this element. */
    const int nfaces = t8_eclass_num_faces[eclass];

    /* Loop over all faces of the current cmesh element. */
    for (int iface = 0; iface < nfaces; iface++) {
      /* Get the number of vertices per face of this element. */
      const int nface_verts = t8_eclass_num_vertices[t8_eclass_face_types[eclass][iface]];

      /* Compute the hash key. The idea is to convert the rescaled vertices of a tree face to
       * long integers and add them up. We apply a bit of seeding by also adding `icoord`. See below. */
      unsigned long hash = 0;

      /* Loop over the vertices of the current element's face. */
      for (int iface_vert = 0; iface_vert < nface_verts; iface_vert++) {
        /* Map from a face vertex id to the element vertex id. */
        const int ivert = t8_face_vertex_to_tree_vertex[eclass][iface][iface_vert];

        for (int icoord = 0; icoord < T8_ECLASS_MAX_DIM; icoord++) {
          const int index = T8_3D_TO_1D (ntrees, T8_ECLASS_MAX_CORNERS, T8_ECLASS_MAX_DIM, itree, ivert, icoord);
          const double rescaled = (vertices[index] - min_coord) * inverse_bin_size;

          /* Simple hash function. */
          hash = hash + icoord + static_cast<unsigned long> (rescaled + 0.5);
        }
      }

      /* Loop over all pre-registered faces with the same hash. */
      auto range = faces.equal_range (hash);
      for (auto it = range.first; it != range.second; ++it) {
        /* Query potentially neighboring `itree` and `iface`. */
        const int neigh_itree = std::get<0> (it->second);
        const int neigh_iface = std::get<1> (it->second);

        /* Retrieve the potentially neighboring element class. */
        const t8_eclass_t neigh_eclass = eclasses[neigh_itree];

        /* Get the number of vertices per face of potentially neighboring element. */
        const int neigh_nface_verts = t8_eclass_num_vertices[t8_eclass_face_types[neigh_eclass][neigh_iface]];

        /* If the number of face vertices do not match we can skip. */
        if (nface_verts != neigh_nface_verts) {
          continue;
        }

        /* The order of the encountered face vertices is needed for computing
         * the orientation later on. Prepare the array for that here. */
        int face_vert_order[T8_ECLASS_MAX_EDGES_2D];
        for (int i = 0; i < T8_ECLASS_MAX_EDGES_2D; i++) {
          face_vert_order[i] = -1;
        }

        int match_count = 0; /* This tracks the number of matching vertices. */
        /* Loop over the vertices of the current element's face. */
        for (int iface_vert = 0; iface_vert < nface_verts; iface_vert++) {
          /* Map from a face vertex id to the element vertex id. */
          const int ivert = t8_face_vertex_to_tree_vertex[eclass][iface][iface_vert];

          /* Loop over the vertices of the potentially neighboring element's face. */
          for (int neigh_iface_vert = 0; neigh_iface_vert < neigh_nface_verts; neigh_iface_vert++) {
            /* Map from a face vertex id to the element vertex id. */
            const int neigh_ivert = t8_face_vertex_to_tree_vertex[neigh_eclass][neigh_iface][neigh_iface_vert];

            int match_count_per_coord = 0; /* Tracks the matching of x, y and z coordinates of two vertices. */
            for (int icoord = 0; icoord < T8_ECLASS_MAX_DIM; icoord++) {
              /* Retrieve the x, y or z component of the face vertex
               * coordinate from the vertices array. */

              const double face_vert
                = vertices[T8_3D_TO_1D (ntrees, T8_ECLASS_MAX_CORNERS, T8_ECLASS_MAX_DIM, itree, ivert, icoord)];
              const double neigh_face_vert = vertices[T8_3D_TO_1D (ntrees, T8_ECLASS_MAX_CORNERS, T8_ECLASS_MAX_DIM,
                                                                   neigh_itree, neigh_ivert, icoord)];

              /* Compare the coordinates with some tolerance. */
              if (std::abs (face_vert - neigh_face_vert) < tolerance) {
                match_count_per_coord++;
              }
            }

            /* In case all x, y and z components match we increase the match_count variable. */
            if (match_count_per_coord == T8_ECLASS_MAX_DIM) {
              match_count++;
              /* Store the encountered face vertex order for later use. */
              face_vert_order[iface_vert] = neigh_iface_vert;
              continue;
            }
          }
        }

        /* If the number of matching face vertices is equal to the actual number of the face's vertices
         * we interpret this as a face-to-face connection between two elements. */
        if (match_count == nface_verts) {
          /* Compute the orientation of the face-to-face connection.
           * Face corner 0 of the face with the lower face direction connects
           * to a corner of the other face. The number of this corner is the
           * orientation code. */
          int orientation = -1;
          int smaller_bigger_face_condition = -1;

          const int compare = t8_eclass_compare (eclass, neigh_eclass);
          if (compare < 0) {
            /* This tree class is smaller than neigh. tree class. */
            smaller_bigger_face_condition = 1;
          }
          else if (compare > 0) {
            /* This tree class is bigger than neigh. tree class. */
            smaller_bigger_face_condition = 0;
          }
          else {
            /* This tree class is the same as the neigh. tree class. 
               Then the face with the smaller face id is the smaller one. */
            smaller_bigger_face_condition = iface < neigh_iface;
          }

          if (smaller_bigger_face_condition) {
            orientation = face_vert_order[0];
          }
          else {
            for (int iface_vert = 0; iface_vert < nface_verts; iface_vert++) {
              if (0 == face_vert_order[iface_vert]) {
                orientation = iface_vert;
                break;
              }
            }
          }

          /* Store the results. */
          conn[T8_3D_TO_1D (ntrees, T8_ECLASS_MAX_FACES, 3, itree, iface, 0)] = neigh_itree;
          conn[T8_3D_TO_1D (ntrees, T8_ECLASS_MAX_FACES, 3, itree, iface, 1)] = neigh_iface;
          conn[T8_3D_TO_1D (ntrees, T8_ECLASS_MAX_FACES, 3, itree, iface, 2)] = orientation;

          if (do_both_directions) {
            conn[T8_3D_TO_1D (ntrees, T8_ECLASS_MAX_FACES, 3, neigh_itree, neigh_iface, 0)] = itree;
            conn[T8_3D_TO_1D (ntrees, T8_ECLASS_MAX_FACES, 3, neigh_itree, neigh_iface, 1)] = iface;
            conn[T8_3D_TO_1D (ntrees, T8_ECLASS_MAX_FACES, 3, neigh_itree, neigh_iface, 2)] = orientation;
          }

          break;
        }
      } /* Loop over faces with identical hash. */

      /* Register the current pair of `itree` and `iface` with given `hash` in the hash table. */
      faces.insert (std::make_pair (hash, std::make_pair (itree, iface)));
    } /* Loop over faces. */
  }   /* Loop over trees. */

  /* Transfer the computed face connectivity to the `cmesh` object. */
  if (cmesh != NULL) {
    for (int itree = 0; itree < ntrees; itree++) {
      const t8_eclass_t eclass = eclasses[itree];
      const int nfaces = t8_eclass_num_faces[eclass];

      for (int iface = 0; iface < nfaces; iface++) {
        const int neigh_itree = conn[T8_3D_TO_1D (ntrees, T8_ECLASS_MAX_FACES, 3, itree, iface, 0)];
        const int neigh_iface = conn[T8_3D_TO_1D (ntrees, T8_ECLASS_MAX_FACES, 3, itree, iface, 1)];
        const int orientation = conn[T8_3D_TO_1D (ntrees, T8_ECLASS_MAX_FACES, 3, itree, iface, 2)];

        if (neigh_itree > -1) {
          t8_cmesh_set_join (cmesh, itree, neigh_itree, iface, neigh_iface, orientation);
        }
      }
    }
  }

  /* Pass the `conn` array to the caller if asked for. */
  if (connectivity == NULL) {
    T8_FREE (conn);
  }
  else {
    *connectivity = conn;
  }
}

void
t8_cmesh_set_join_by_stash (t8_cmesh_t cmesh, int **connectivity, const int do_both_directions)
{
  /* Get some pointers to the cmesh's stash */
  const sc_array_t *classes = &(cmesh->stash->classes);
  const sc_array_t *attributes = &(cmesh->stash->attributes);
  const t8_gloidx_t ntrees = classes->elem_count;
  const size_t num_attributes = attributes->elem_count;
  std::vector<t8_eclass_t> eclasses (ntrees);
  std::vector<double> vertices (ntrees * T8_ECLASS_MAX_CORNERS * T8_ECLASS_MAX_DIM, 0.0);

  /* Retrieve the eclasses of the trees from the cmesh's stash */
  for (t8_gloidx_t itree = 0; itree < ntrees; itree++) {
    const t8_stash_class_struct_t *entry = (t8_stash_class_struct_t *) t8_sc_array_index_locidx (classes, itree);
    eclasses[entry->id] = entry->eclass;
  }

  /* Retrieve the vertices of the trees from the stash */
  for (size_t iattribute = 0; iattribute < num_attributes; iattribute++) {
    const t8_stash_attribute_struct_t *entry
      = (t8_stash_attribute_struct_t *) t8_sc_array_index_locidx (attributes, iattribute);
    /* If the attribute is a vertex attribute, copy it into a separate vector */
    if (entry->key == T8_CMESH_VERTICES_ATTRIBUTE_KEY && entry->package_id == t8_get_package_id ()) {
      const size_t index = T8_3D_TO_1D (ntrees, T8_ECLASS_MAX_CORNERS, T8_ECLASS_MAX_DIM, entry->id, 0, 0);
      memcpy (&vertices[index], entry->attr_data, entry->attr_size);
    }
  }

  /* Let t8_cmesh_set_join_by_vertices join the trees */
  t8_cmesh_set_join_by_vertices (cmesh, ntrees, eclasses.data (), vertices.data (), connectivity, do_both_directions);
}