Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
DLR-AMR
GitHub Repository: DLR-AMR/t8code
Path: blob/main/tutorials/general/t8_step6_stencil.cxx
903 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 types in parallel.

  Copyright (C) 2023 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.
*/

/* See also: https://github.com/DLR-AMR/t8code/wiki/Step-6-Computing-stencils
 *
 * This is step6 of the t8code tutorials using the C++ interface of t8code.
 * In the following we will store data in the individual elements of our forest.
 * To do this, we will create a uniform forest in 2D, which will get adapted,
 * partitioned, balanced and create ghost elements all in one go.
 * After adapting the forest we build a data array and gather data for
 * the local elements. Next, we exchange the data values of the ghost elements and compute
 * various stencils resp. finite differences. Finally, vtu files are stored with three
 * custom data fields.
 *
 * How you can experiment here:
 *   - Look at the paraview output files of the adapted forest.
 *   - Change the adaptation criterion as you wish to adapt elements or families as desired.
 *   - Store even more data per element, for instance the coordinates of its midpoint.
 *   - Extend this step to 3D.
 *  */

#include <cmath>
#include <t8.h>                                 /* General t8code header, always include this. */
#include <t8_types/t8_vec.hxx>                  /* Basic operations on 3D vectors. */
#include <t8_cmesh/t8_cmesh.h>                  /* cmesh definition and basic interface. */
#include <t8_forest/t8_forest_general.h>        /* forest definition and basic interface. */
#include <t8_forest/t8_forest_io.h>             /* save forest */
#include <t8_forest/t8_forest_geometrical.h>    /* geometrical information of the forest */
#include <t8_cmesh/t8_cmesh_examples.h>         /* A collection of exemplary cmeshes */
#include <t8_schemes/t8_default/t8_default.hxx> /* default refinement scheme. */
#include <tutorials/general/t8_step3.h>

T8_EXTERN_C_BEGIN ();

/* The data that we want to store for each element. */
struct data_per_element
{
  /* The first three data fields are not necessary for our
   * computations in this step, but are left here for reference. */
  int level;
  double volume;
  double midpoint[3];

  /* Element length in x- and y-direction. */
  double dx;
  double dy;

  /* `Height` which is filled according to the position of the element
   * in the computational domain. */
  double height;

  /* Storage for our finite difference computations. */
  double schlieren;
  double curvature;
};

/* In this function we first allocate a new uniformly refined forest at given
 * refinement level. Then a second forest is created, where user data for the
 * adaption call (cf. step 3) is registered.  The second forest inherits all
 * properties of the first ("root") forest and deallocates it. The final
 * adapted and committed forest is returned back to the calling scope. */
static t8_forest_t
t8_step6_build_forest (sc_MPI_Comm comm, int dim, int level)
{
  t8_cmesh_t cmesh = t8_cmesh_new_periodic (comm, dim);

  const t8_scheme *scheme = t8_scheme_new_default ();
  struct t8_step3_adapt_data adapt_data = {
    { 0.0, 0.0, 0.0 }, /* Midpoints of the sphere. */
    0.5,               /* Refine if inside this radius. */
    0.7                /* Coarsen if outside this radius. */
  };
  /* Start with a uniform forest. */
  t8_forest_t forest = t8_forest_new_uniform (cmesh, scheme, level, 0, comm);
  t8_forest_t forest_apbg;

  /* Adapt, partition, balance and create ghost elements all in one go.
   * See step 3 and 4 for more details. */
  t8_forest_init (&forest_apbg);
  t8_forest_set_user_data (forest_apbg, &adapt_data);
  t8_forest_set_adapt (forest_apbg, forest, t8_step3_adapt_callback, 0);
  t8_forest_set_partition (forest_apbg, NULL, 0);
  t8_forest_set_balance (forest_apbg, NULL, 0);
  t8_forest_set_ghost (forest_apbg, 1, T8_GHOST_FACES);
  t8_forest_commit (forest_apbg);

  return forest_apbg;
}

/* Allocate and fill the element data array with `heights` from an arbitrary
 * mathematical function. Returns a pointer to the array which is then owned
 * by the calling scope. */
static struct data_per_element *
t8_step6_create_element_data (t8_forest_t forest)
{
  /* Check that the forest is a committed. */
  T8_ASSERT (t8_forest_is_committed (forest));

  /* Get the number of local elements of forest. */
  t8_locidx_t num_local_elements = t8_forest_get_local_num_leaf_elements (forest);
  /* Get the number of ghost elements of forest. */
  t8_locidx_t num_ghost_elements = t8_forest_get_num_ghosts (forest);
  /* Get the scheme of the forest */
  const t8_scheme *scheme = t8_forest_get_scheme (forest);

  /* Build an array of our data that is as long as the number of elements plus the number of ghosts. */
  struct data_per_element *element_data = T8_ALLOC (struct data_per_element, num_local_elements + num_ghost_elements);

  /* Get the number of trees that have elements of this process. */
  t8_locidx_t num_local_trees = t8_forest_get_num_local_trees (forest);

  /* Loop over all local trees in the forest. */
  for (t8_locidx_t itree = 0, current_index = 0; itree < num_local_trees; ++itree) {
    t8_eclass_t tree_class = t8_forest_get_tree_class (forest, itree);

    /* Get the number of elements of this tree. */
    t8_locidx_t num_elements_in_tree = t8_forest_get_tree_num_leaf_elements (forest, itree);

    /* Loop over all local elements in the tree. */
    for (t8_locidx_t ielement = 0; ielement < num_elements_in_tree; ++ielement, ++current_index) {
      const t8_element_t *element = t8_forest_get_leaf_element_in_tree (forest, itree, ielement);

      /* Pointer to our current element data struct. */
      struct data_per_element *edat = &element_data[current_index];

      edat->level = scheme->element_get_level (tree_class, element);
      edat->volume = t8_forest_element_volume (forest, itree, element);
      t8_forest_element_centroid (forest, itree, element, edat->midpoint);

      /* Compute vertex coordinates. */
      double verts[4][3] {};
      scheme->element_get_vertex_reference_coords (tree_class, element, 0, verts[0]);
      scheme->element_get_vertex_reference_coords (tree_class, element, 1, verts[1]);
      scheme->element_get_vertex_reference_coords (tree_class, element, 2, verts[2]);
      /* Not needed: scheme->element_get_vertex_reference_coords (tree_class, element, 3, verts[3]); */

      edat->dx = verts[1][0] - verts[0][0];
      edat->dy = verts[2][1] - verts[0][1];

      /* Shift x and y to the center since the domain is [0,1] x [0,1]. */
      const double x = edat->midpoint[0] - 0.5;
      const double y = edat->midpoint[1] - 0.5;
      const double r = sqrt (x * x + y * y) * 20.0;  // scaled radius

      /* Some 'interesting' height function. */
      edat->height = sin (2.0 * r) / r;
    }
  }

  return element_data;
}

/* Gather the 3x3 stencil for each element and compute finite difference approximations
 * for schlieren and curvature of the stored heights in the elements. */
static void
t8_step6_compute_stencil (t8_forest_t forest, struct data_per_element *element_data)
{
  /* Check that forest is a committed, that is valid and usable, forest. */
  T8_ASSERT (t8_forest_is_committed (forest));

  /* Get the number of trees that have elements of this process. */
  t8_locidx_t num_local_trees = t8_forest_get_num_local_trees (forest);
  /* Get the scheme of the forest */
  const t8_scheme *scheme = t8_forest_get_scheme (forest);

  double stencil[3][3] {};
  double dx[3] = { 0 };
  double dy[3] = { 0 };

  /* Loop over all local trees in the forest. For each local tree the element
   * data (level, midpoint[3], dx, dy, volume, height, schlieren, curvature) of
   * each element is calculated and stored into the element data array. */
  for (t8_locidx_t itree = 0, current_index = 0; itree < num_local_trees; ++itree) {
    t8_eclass_t tree_class = t8_forest_get_tree_class (forest, itree);

    t8_locidx_t num_elements_in_tree = t8_forest_get_tree_num_leaf_elements (forest, itree);

    /* Loop over all local elements in the tree. */
    for (t8_locidx_t ielement = 0; ielement < num_elements_in_tree; ++ielement, ++current_index) {
      const t8_element_t *element = t8_forest_get_leaf_element_in_tree (forest, itree, ielement);

      /* Gather center point of the 3x3 stencil. */
      stencil[1][1] = element_data[current_index].height;
      dx[1] = element_data[current_index].dx;
      dy[1] = element_data[current_index].dy;

      /* Loop over all faces of an element. */
      int num_faces = scheme->element_get_num_faces (tree_class, element);
      for (int iface = 0; iface < num_faces; iface++) {
        int num_neighbors;        /**< Number of neighbors for each face */
        int *dual_faces;          /**< The face indices of the neighbor elements */
        t8_locidx_t *neighids;    /**< Indices of the neighbor elements */
        t8_element_t **neighbors; /*< Neighboring elements. */
        t8_eclass_t neigh_class;  /*< Neighboring elements tree class. */

        /* Collect all neighbors at the current face. */
        t8_forest_leaf_face_neighbors (forest, itree, element, &neighbors, iface, &dual_faces, &num_neighbors,
                                       &neighids, &neigh_class, 1);

        /* Retrieve the `height` of the face neighbor. Account for two neighbors in case
           of a non-conforming interface by computing the average. */
        double height = 0.0;
        if (num_neighbors > 0) {
          for (int ineigh = 0; ineigh < num_neighbors; ineigh++) {
            height = height + element_data[neighids[ineigh]].height;
          }
          height = height / num_neighbors;
        }

        /* Fill in the neighbor information of the 3x3 stencil. */
        switch (iface) {
        case 0:  // NORTH
          stencil[0][1] = height;
          dx[0] = element_data[neighids[0]].dx;
          break;
        case 1:  // SOUTH
          stencil[2][1] = height;
          dx[2] = element_data[neighids[0]].dx;
          break;
        case 2:  // WEST
          stencil[1][0] = height;
          dy[0] = element_data[neighids[0]].dy;
          break;
        case 3:  // EAST
          stencil[1][2] = height;
          dy[2] = element_data[neighids[0]].dy;
          break;
        }

        if (num_neighbors > 0) {
          /* Free allocated memory. */
          scheme->element_destroy (tree_class, num_neighbors, neighbors);
          T8_FREE (neighbors);
          T8_FREE (dual_faces);
          T8_FREE (neighids);
        }
      }

      /* Prepare finite difference computations. The code also accounts for non-conforming interfaces. */
      const double xslope_m = 0.5 / (dx[0] + dx[1]) * (stencil[1][1] - stencil[0][1]);
      const double xslope_p = 0.5 / (dx[1] + dx[2]) * (stencil[2][1] - stencil[1][1]);

      const double yslope_m = 0.5 / (dy[0] + dy[1]) * (stencil[1][1] - stencil[1][0]);
      const double yslope_p = 0.5 / (dy[1] + dy[2]) * (stencil[1][2] - stencil[1][1]);

      const double xslope = 0.5 * (xslope_m + xslope_p);
      const double yslope = 0.5 * (yslope_m + yslope_p);

      /* TODO: Probably still not optimal at non-conforming interfaces. */
      const double xcurve = (xslope_p - xslope_m) * 4 / (dx[0] + 2.0 * dx[1] + dx[2]);
      const double ycurve = (yslope_p - yslope_m) * 4 / (dy[0] + 2.0 * dy[1] + dy[2]);

      /* Compute schlieren and curvature norm. */
      element_data[current_index].schlieren = sqrt (xslope * xslope + yslope * yslope);
      element_data[current_index].curvature = sqrt (xcurve * xcurve + ycurve * ycurve);
    }
  }
}

/* Each process has computed the data entries for its local elements.
 * In order to get the values for the ghost elements, we use t8_forest_ghost_exchange_data.
 * Calling this function will fill all the ghost entries of our element data array with the
 * value on the process that owns the corresponding element. */
static void
t8_step6_exchange_ghost_data (t8_forest_t forest, struct data_per_element *data)
{
  sc_array *sc_array_wrapper;
  t8_locidx_t num_elements = t8_forest_get_local_num_leaf_elements (forest);
  t8_locidx_t num_ghosts = t8_forest_get_num_ghosts (forest);

  /* t8_forest_ghost_exchange_data expects an sc_array (of length num_local_elements + num_ghosts).
   * We wrap our data array to an sc_array. */
  sc_array_wrapper = sc_array_new_data (data, sizeof (struct data_per_element), num_elements + num_ghosts);

  /* Carry out the data exchange. The entries with indices > num_local_elements will get overwritten. */
  t8_forest_ghost_exchange_data (forest, sc_array_wrapper);

  /* Destroy the wrapper array. This will not free the data memory since we used sc_array_new_data. */
  sc_array_destroy (sc_array_wrapper);
}

/* Write the forest as vtu and also write the element's volumes in the file.
 *
 * t8code supports writing element based data to vtu as long as its stored
 * as doubles. Each of the data fields to write has to be provided in its own
 * array of length num_local_elements.
 * We support two types: T8_VTK_SCALAR - One double per element.
 *                  and  T8_VTK_VECTOR - Three doubles per element.
 */
static void
t8_step6_output_data_to_vtu (t8_forest_t forest, struct data_per_element *data, const char *prefix)
{
  t8_locidx_t num_elements = t8_forest_get_local_num_leaf_elements (forest);

  /* We need to allocate a new array to store the data on their own.
   * These arrays have one entry per local element. */
  double *heights = T8_ALLOC (double, num_elements);
  double *schlieren = T8_ALLOC (double, num_elements);
  double *curvature = T8_ALLOC (double, num_elements);

  /* The number of user defined data fields to write. */
  const int num_data = 3;

  /* For each user defined data field we need one t8_vtk_data_field_t variable. */
  t8_vtk_data_field_t vtk_data[num_data];
  /* Set the type of this variable. Since we have one value per element, we pick T8_VTK_SCALAR. */
  vtk_data[0].type = T8_VTK_SCALAR;
  /* The name of the field as should be written to the file. */
  strcpy (vtk_data[0].description, "height");
  vtk_data[0].data = heights;
  /* Copy the element's volumes from our data array to the output array. */
  for (t8_locidx_t ielem = 0; ielem < num_elements; ++ielem) {
    heights[ielem] = data[ielem].height;
  }

  vtk_data[1].type = T8_VTK_SCALAR;
  strcpy (vtk_data[1].description, "schlieren");
  vtk_data[1].data = schlieren;
  for (t8_locidx_t ielem = 0; ielem < num_elements; ++ielem) {
    schlieren[ielem] = data[ielem].schlieren;
  }

  vtk_data[2].type = T8_VTK_SCALAR;
  strcpy (vtk_data[2].description, "curvature");
  vtk_data[2].data = curvature;
  for (t8_locidx_t ielem = 0; ielem < num_elements; ++ielem) {
    curvature[ielem] = data[ielem].curvature;
  }

  {
    /* Write user defined data to vtu file. */
    const int write_treeid = 1;
    const int write_mpirank = 1;
    const int write_level = 1;
    const int write_element_id = 1;
    const int write_ghosts = 0;
    t8_forest_write_vtk_ext (forest, prefix, write_treeid, write_mpirank, write_level, write_element_id, write_ghosts,
                             0, 0, num_data, vtk_data);
  }

  T8_FREE (heights);
  T8_FREE (schlieren);
  T8_FREE (curvature);
}

int
t8_step6_main (int argc, char **argv)
{
  int mpiret;
  sc_MPI_Comm comm;
  t8_forest_t forest;

  /* The prefix for our output files. */
  const char *prefix_forest_with_data = "t8_step6_stencil";

  /* The uniform refinement level of the forest. */
  const int dim = 2;

#if T8_ENABLE_DEBUG
  /* Set lower refinement level. Otherwise the program is painfully slow. */
  const int level = 3;
#else
  const int level = 6;
#endif

  /* The array that will hold our per element data. */
  data_per_element *data;

  /*
   * Initialization.
   */

  /* Initialize MPI. This has to happen before we initialize sc or t8code. */
  mpiret = sc_MPI_Init (&argc, &argv);
  /* Error check the MPI return value. */
  SC_CHECK_MPI (mpiret);

  /* Initialize the sc library, has to happen before we initialize t8code. */
  sc_init (sc_MPI_COMM_WORLD, 1, 1, NULL, SC_LP_ESSENTIAL);
  /* Initialize t8code with log level SC_LP_PRODUCTION. See sc.h for more info on the log levels. */
  t8_init (SC_LP_PRODUCTION);

  /* We will use MPI_COMM_WORLD as a communicator. */
  comm = sc_MPI_COMM_WORLD;

  /* Initialize an adapted forest with periodic boundaries. */
  forest = t8_step6_build_forest (comm, dim, level);

  /*
   * Data handling and computation.
   */

  /* Build data array and gather data for the local elements. */
  data = t8_step6_create_element_data (forest);

  /* Exchange the neighboring data at MPI process boundaries. */
  t8_step6_exchange_ghost_data (forest, data);

  /* Compute stencil. */
  t8_step6_compute_stencil (forest, data);

  /* Exchange the neighboring data at MPI process boundaries again.
   * This ensures that also the computed schlieren and curvature
   * data are properly written to the ghost elements.
   * For the sake of this example this step is only necessary in order
   * to visualize the values on the ghost cells in the vtu output later. */
  t8_step6_exchange_ghost_data (forest, data);

  /* Output the data to vtu files. */
  t8_step6_output_data_to_vtu (forest, data, prefix_forest_with_data);
  t8_global_productionf (" [step6] Wrote forest and data to %s*.\n", prefix_forest_with_data);

  /*
   * Clean-up
   */

  /* Free the data array. */
  T8_FREE (data);

  /* Destroy the forest. */
  t8_forest_unref (&forest);
  t8_global_productionf (" [step6] Destroyed forest.\n");

  sc_finalize ();

  mpiret = sc_MPI_Finalize ();
  SC_CHECK_MPI (mpiret);

  return 0;
}

T8_EXTERN_C_END ();