Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
DLR-AMR
GitHub Repository: DLR-AMR/t8code
Path: blob/main/example/forest/t8_test_ghost.cxx
901 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) 2015 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.
*/

#include <sc_options.h>
#include <sc_refcount.h>
#include <t8_eclass.h>
#include <t8_schemes/t8_default/t8_default.hxx>
#include <t8_forest/t8_forest_general.h>
#include <t8_forest/t8_forest_io.h>
#include <t8_forest/t8_forest_profiling.h>
#include <t8_cmesh/t8_cmesh.h>
#include <t8_cmesh/t8_cmesh_io/t8_cmesh_readmshfile.h>
#include <t8_vtk/t8_vtk_writer.h>
#include <t8_cmesh/t8_cmesh_examples.h>
#include <example/common/t8_example_common.hxx>

typedef enum {
  REFINE_THIRD = 0, /* Refine every third element */
  REFINE_P8,        /* Refine every 0-th, 3rd, 5-th, and 6-th child */
  REFINE_SPHERE     /* Refine along a sphere */
} refine_method_t;

/* Refine every 0-th, 3rd, 5-th and 6-th child.
 * This function comes from the timings2.c example of p4est.
 */
int
t8_refine_p8est ([[maybe_unused]] t8_forest_t forest, [[maybe_unused]] t8_forest_t forest_from,
                 [[maybe_unused]] t8_locidx_t which_tree, const t8_eclass_t tree_class,
                 [[maybe_unused]] t8_locidx_t lelement_id, const t8_scheme *scheme,
                 [[maybe_unused]] const int is_family, [[maybe_unused]] const int num_elements,
                 t8_element_t *elements[])
{

  T8_ASSERT (!is_family || num_elements == scheme->element_get_num_children (tree_class, elements[0]));

  const int id = scheme->element_get_child_id (tree_class, elements[0]);
  return (id == 0 || id == 3 || id == 5 || id == 6);
}

/* Refine every third element. */
static int
t8_adapt_every_third_element ([[maybe_unused]] t8_forest_t forest, [[maybe_unused]] t8_forest_t forest_from,
                              [[maybe_unused]] t8_locidx_t which_tree, const t8_eclass_t tree_class,
                              [[maybe_unused]] t8_locidx_t lelement_id, const t8_scheme *scheme,
                              [[maybe_unused]] const int is_family, [[maybe_unused]] const int num_elements,
                              t8_element_t *elements[])
{
  T8_ASSERT (!is_family || num_elements == scheme->element_get_num_children (tree_class, elements[0]));
  const int level = scheme->element_get_level (tree_class, elements[0]);
  if (scheme->element_get_linear_id (tree_class, elements[0], level) % 3 == 0) {
    return 1;
  }
  return 0;
}

/* Prepare a forest for level set controlled refinement around a sphere */
static void
t8_test_ghost_set_levelset_data (t8_forest_t forest_adapt, const int min_level, const int max_level,
                                 const double midpoint[3], const double radius, const double band_width)
{
  /* Build the struct containing all information about the levelset refinement */
  t8_example_level_set_struct_t *levelSetData;
  t8_levelset_sphere_data_t *sphere_data;
  /* Allocate memory */
  levelSetData = T8_ALLOC (t8_example_level_set_struct_t, 1);
  sphere_data = T8_ALLOC (t8_levelset_sphere_data_t, 1);

  /* Set the midpount of the sphere */
  sphere_data->M[0] = midpoint[0];
  sphere_data->M[1] = midpoint[1];
  sphere_data->M[2] = midpoint[2];
  /* Set the radius */
  sphere_data->radius = radius;

  /* Set levelSetData members */
  levelSetData->L = t8_levelset_sphere;
  levelSetData->band_width = band_width;
  levelSetData->max_level = max_level;
  levelSetData->min_level = min_level;
  levelSetData->t = 0;
  levelSetData->udata = sphere_data;
  /* Attach levelSetData to forest */
  t8_forest_set_user_data (forest_adapt, levelSetData);
}

/* Clean up the data allocated in t8_test_ghost_set_levelset_data
 * for level-set refinement */
static void
t8_test_ghost_clean_levelset_data (t8_forest_t forest)
{
  t8_example_level_set_struct_t *data = (t8_example_level_set_struct_t *) t8_forest_get_user_data (forest);
  T8_FREE (data->udata);
  T8_FREE (data);
}

static void
t8_test_ghost_refine_and_partition (t8_cmesh_t cmesh, const int level, sc_MPI_Comm comm, const int partition_cmesh,
                                    const int ghost_version, const int max_level, const int no_vtk,
                                    const refine_method_t refine_method)
{
  t8_forest_t forest, forest_ghost;
  t8_cmesh_t cmesh_partition;
  t8_forest_adapt_t adapt_fn;

  if (!no_vtk) {
    t8_cmesh_vtk_write_file (cmesh, "test_ghost_cmesh0");
  }
  if (partition_cmesh) {
    /* partition the initial cmesh according to a uniform forest */
    t8_cmesh_init (&cmesh_partition);
    t8_cmesh_set_derive (cmesh_partition, cmesh);
    t8_cmesh_set_partition_uniform (cmesh_partition, level, t8_scheme_new_default ());
    t8_cmesh_commit (cmesh_partition, comm);
  }
  else {
    /* do not partition the initial cmesh */
    cmesh_partition = cmesh;
  }
  if (!no_vtk) {
    t8_cmesh_vtk_write_file (cmesh_partition, "test_ghost_cmesh1");
  }
  forest = t8_forest_new_uniform (cmesh_partition, t8_scheme_new_default (), level, 1, comm);

  /* adapt (if desired), partition and create ghosts for the forest */
  t8_forest_init (&forest_ghost);
  if (max_level > level) {
    int r;

    /* Chose refinement function */
    switch (refine_method) {
    case REFINE_THIRD:
      adapt_fn = t8_adapt_every_third_element;
      break;
    case REFINE_P8:
      adapt_fn = t8_refine_p8est;
      break;
    case REFINE_SPHERE:
      adapt_fn = t8_common_adapt_level_set;
      break;
    default:
      SC_ABORT_NOT_REACHED (); /* Invalid value */
    }

    /* If the forest should be refined and refinement method is the levelset-sphere
     * function, then we need to set the correct refinement data for the forest_ghost */
    if (refine_method == REFINE_SPHERE) {
      const double midpoint[3] = { 0.4, 0.4, 0.4 };
      const double radius = 0.2;
      const double band_width = 2;
      /* If levelset refinement is used, we need to set the user data
       * pointer of the forest appropriately */
      t8_test_ghost_set_levelset_data (forest, level, max_level, midpoint, radius, band_width);
    }
    /* Refine the forest */
    for (r = level; r < max_level; r++) {
      if (refine_method == REFINE_SPHERE) {
        /* Copy the user data to the forest that should be refined */
        t8_forest_set_user_data (forest_ghost, t8_forest_get_user_data (forest));
      }
      t8_forest_set_adapt (forest_ghost, forest, adapt_fn, 0);
      t8_forest_commit (forest_ghost);
      forest = forest_ghost;
      t8_forest_init (&forest_ghost);
    }

    /* If we used a level-set function to refine, we need to clean-up our allocated data */
    if (refine_method == REFINE_SPHERE) {
      t8_test_ghost_clean_levelset_data (forest);
    }
  }

  /* Set the forest for partitioning */
  t8_forest_set_partition (forest_ghost, forest, 0);
  /* Activate ghost creation */
  t8_forest_set_ghost_ext (forest_ghost, 1, T8_GHOST_FACES, ghost_version);
  /* Activate timers */
  t8_forest_set_profiling (forest_ghost, 1);

  /* partition the forest and create ghosts */
  t8_forest_commit (forest_ghost);
  if (!no_vtk) {
    t8_forest_write_vtk (forest_ghost, "test_ghost");
    t8_global_productionf ("Output vtk to test_ghost.pvtu\n");
  }
  /* print ghosts */
  t8_forest_ghost_print (forest_ghost);

  t8_forest_print_profile (forest_ghost);
  t8_forest_unref (&forest_ghost);
}

/* Build a forest on a 2d or 3d brick connectivity,
 * refine and partition it and for each of these stages construct
 * the ghost layer. */
static void
t8_test_ghost_brick (int dim, int x, int y, int z, int periodic_x, int periodic_y, int periodic_z, int level,
                     sc_MPI_Comm comm, int ghost_version, int max_level, int no_vtk, refine_method_t refine_method)
{
  t8_cmesh_t cmesh;

  if (dim == 2) {
    cmesh = t8_cmesh_new_brick_2d (x, y, periodic_x, periodic_y, comm);
  }
  else {
    T8_ASSERT (dim == 3);
    cmesh = t8_cmesh_new_brick_3d (x, y, z, periodic_x, periodic_y, periodic_z, comm);
  }

  t8_test_ghost_refine_and_partition (cmesh, level, comm, 1, ghost_version, max_level, no_vtk, refine_method);
}

/* Build a forest on a hypercube mesh
 * and refine the first tree of a process once.
 * Create ghost layer and print it.
 * partition the forest, create ghost layer and print it. */
static void
t8_test_ghost_hypercube (t8_eclass_t eclass, int level, sc_MPI_Comm comm, int ghost_version, int max_level, int no_vtk,
                         refine_method_t refine_method)
{
  t8_cmesh_t cmesh;

  if (eclass < T8_ECLASS_COUNT) {
    // Build a hypercube out of the given eclass
    cmesh = t8_cmesh_new_hypercube (eclass, comm, 0, 0, 0);
  }
  else if (eclass == T8_ECLASS_COUNT) {
    // Build a 3D hybrid hypercube with tets, hexes and prisms
    cmesh = t8_cmesh_new_hypercube_hybrid (comm, 0, 0);
  }

  if (eclass != T8_ECLASS_VERTEX && eclass != T8_ECLASS_PYRAMID) {
    t8_test_ghost_refine_and_partition (cmesh, level, comm, 1, ghost_version, max_level, no_vtk, refine_method);
  }
  else {
    t8_cmesh_destroy (&cmesh);
  }
}

/* Build a forest on a cmesh read from a .msh file.
 * and refine the first tree of a process once.
 * Create ghost layer and print it.
 * partition the forest, create ghost layer and print it. */
static void
t8_test_ghost_msh_file (const char *fileprefix, int level, int dim, sc_MPI_Comm comm, int ghost_version, int max_level,
                        int no_vtk, refine_method_t refine_method)
{
  t8_cmesh_t cmesh;

  cmesh = t8_cmesh_from_msh_file (fileprefix, 0, comm, dim, 0, 0);
  t8_test_ghost_refine_and_partition (cmesh, level, comm, 1, ghost_version, max_level, no_vtk, refine_method);
}

/* Build a forest on the tet_test cmesh that has all face-to-face combinations.
 * This is useful for testing and debugging.
 */
static void
t8_test_ghost_tet_test (int level, sc_MPI_Comm comm, int ghost_version, int max_level, int no_vtk,
                        refine_method_t refine_method)
{
  t8_cmesh_t cmesh;

  cmesh = t8_cmesh_new_tet_orientation_test (comm);
  t8_test_ghost_refine_and_partition (cmesh, level, comm, 1, ghost_version, max_level, no_vtk, refine_method);
}

int
main (int argc, char **argv)
{
  int mpiret, parsed, eclass_int, level, helpme;
  int x_dim, y_dim, z_dim, periodic;
  int test_tet;
  int dim, no_vtk, refine_levels, ghost_version;
  int max_level;
  int refine_method_int;
  refine_method_t refine_method;
  sc_options_t *opt;
  const char *prefix;
  char usage[BUFSIZ];
  char help[BUFSIZ];
  int sreturnA, sreturnB;

  sreturnA = snprintf (usage, BUFSIZ, "Usage:\t%s <OPTIONS>", basename (argv[0]));
  sreturnB = snprintf (help, BUFSIZ, "help string\n%s\n", usage);

  if (sreturnA > BUFSIZ || sreturnB > BUFSIZ) {
    /* The usage string or help message was truncated */
    /* Note: gcc >= 7.1 prints a warning if we
     * do not check the return value of snprintf. */
    t8_debugf ("Warning: Truncated usage string and help message to '%s' and '%s'\n", usage, help);
  }
  mpiret = sc_MPI_Init (&argc, &argv);
  SC_CHECK_MPI (mpiret);
  sc_init (sc_MPI_COMM_WORLD, 1, 1, NULL, SC_LP_ESSENTIAL);
  t8_init (SC_LP_STATISTICS);
  /*
   * COMMAND LINE OPTION SETUP
   */
  opt = sc_options_new (argv[0]);
  /* Level -l */
  sc_options_add_int (opt, 'l', "level", &level, 0, "The refinement level of the mesh.");
  /* enable/disable vtk -o */
  sc_options_add_switch (opt, 'o', "no-vtk", &no_vtk, "disable vtk output");
  /* input .msh file (prefix) -f */
  sc_options_add_string (opt, 'f', "prefix", &prefix, "",
                         "Prefix of a"
                         " .msh file.");
  /* dimension of .msh file (if -f is given) -d */
  sc_options_add_int (opt, 'd', "dim", &dim, 2, "If a .msh file is read, the dimension must be specified.");
  /* if given, create brick cmesh with x,y(,z) many quads/cubes. -x, -y, -z */
  sc_options_add_int (opt, 'x', "x-dim", &x_dim, 0, "Number of brick mesh cells in x direction.");
  sc_options_add_int (opt, 'y', "y-dim", &y_dim, 0, "Number of brick mesh cells in y direction.");
  sc_options_add_int (opt, 'z', "z-dim", &z_dim, 0,
                      "Number of brick mesh cells in z direction. If specified, then the mesh is automatically 3d.");
  /* If brick mesh is generated, define periodicity. -p */
  sc_options_add_int (opt, 'p', "periodic", &periodic, 0,
                      "Periodicity of brick mesh. A three (two) digit decimal number zyx. If digit i is nonzero then "
                      "the representative coordinate direction of the brick mesh is periodic.");
  /* Use a cmesh that tests all tet-to-tet face-connections */
  sc_options_add_switch (opt, 't', "test-tet", &test_tet, "Use a cmesh that tests all tet face-to-face connections.");
  /* Provide a refinement level for every third element, -r */
  sc_options_add_int (opt, 'r', "refine-level", &refine_levels, 0,
                      "Refine <INT> times every third element in the uniform forest before creating the ghost layer."
                      "Default is 0 (no refinement).");
  /* Change the refinement rule -R */
  sc_options_add_int (opt, 'R', "refine-method", &refine_method_int, 0,
                      "Chose the method of refinement.\n"
                      "\t\t0 - Refine every third element.\n"
                      "\t\t1 - Refine every 0-th, 3rd, 5-th, and 6-th element.\n"
                      "\t\t2 - Refine along a sphere with midpoint (0.3, 0.3, 0.3) and radius 0.2.");
  /* Change the version of ghost algorithm used -g */
  sc_options_add_int (opt, 'g', "ghost-version", &ghost_version, 3,
                      "Change the ghost algorithm that is used.\n"
                      "\t\t1 - Iterative and only for balanced forests. (only if refine <= 1)\n"
                      "\t\t2 - Iterative, also for unbalanced forests.\n"
                      "\t\t3 - Top-down search, for unbalanced forests (default).");
  /* Use a hypercube mesh -e */
  sc_options_add_int (opt, 'e', "elements", &eclass_int, 2,
                      "If neither -f nor -x,-y,-z, or -t are used, a cubical mesh is the type of elements to use.\n"
                      "\t\t0 - vertex\n\t\t1 - line\n\t\t2 - quad\n"
                      "\t\t3 - triangle\n\t\t4 - hexahedron\n"
                      "\t\t5 - tetrahedron\n\t\t6 - prism\n\t\t7 - pyramid\n"
                      "\t\t8 - hex/tet/prism hybrid");
  /* Print help -h */
  sc_options_add_switch (opt, 'h', "help", &helpme, "Display a short help message.");
  /*
   * END OF COMMAND LINE OPTION SETUP
   */
  /* parse command line options */
  parsed = sc_options_parse (t8_get_package_id (), SC_LP_DEFAULT, opt, argc, argv);
  refine_method = (refine_method_t) refine_method_int;
  /* check for wrong usage of arguments */
  if (parsed < 0 || parsed != argc || x_dim < 0 || y_dim < 0 || z_dim < 0 || dim < 2 || dim > 3
      || eclass_int < T8_ECLASS_VERTEX || eclass_int > T8_ECLASS_COUNT || refine_method_int < 0
      || refine_method_int > REFINE_SPHERE || ghost_version < 1 || ghost_version > 3
      || (ghost_version == 1 && refine_levels >= 2)) {
    sc_options_print_usage (t8_get_package_id (), SC_LP_ERROR, opt, NULL);
    return 1;
  }
  if (helpme) {
    /* Print help string and then exit */
    t8_global_productionf ("%s\n", help);
    sc_options_print_usage (t8_get_package_id (), SC_LP_ERROR, opt, NULL);
  }
  else {
    max_level = level + refine_levels;
    /* Setup coarse mesh and start computation */
    if (x_dim == 0 && !strcmp (prefix, "") && test_tet == 0) {
      /* If neither of -x, -f, -t are given, we use a hypercube mesh */
      t8_global_productionf ("Testing ghost on a hypercube cmesh with %s elements\n",
                             eclass_int < T8_ECLASS_COUNT ? t8_eclass_to_string[eclass_int] : "hybrid");
      t8_test_ghost_hypercube ((t8_eclass_t) eclass_int, level, sc_MPI_COMM_WORLD, ghost_version, max_level, no_vtk,
                               refine_method);
    }
    else if (x_dim > 0) {
      /* If -x is given, we create a brick mesh (-y must be given as well) */
      int x_per, y_per, z_per;
      if (y_dim <= 0 || z_dim < 0) {
        t8_global_productionf ("\tERROR: Wrong usage\n");
        return 1;
      }
      dim = z_dim != 0 ? 3 : 2; /* Compute dimension */
      /* Periodic is a three digit number,
       * 000, 001, 010, 011, 100, 101, 110, 111
       * the first digit defines the x periodicity, second y, third z */
      x_per = periodic % 10;
      y_per = periodic / 10 % 10;
      z_per = periodic / 100 % 10;
      t8_global_productionf ("Testing ghost on a %i x %i x %i brick mesh in %iD\n", x_dim, y_dim, z_dim, dim);
      t8_test_ghost_brick (dim, x_dim, y_dim, z_dim, x_per, y_per, z_per, level, sc_MPI_COMM_WORLD, ghost_version,
                           max_level, no_vtk, refine_method);
    }
    else if (test_tet) {
      /* Create the test tetrahedra cmesh */
      t8_global_productionf ("Testing ghost on tet-test cmesh.\n");
      t8_global_productionf ("vtk output disabled.\n");
      t8_test_ghost_tet_test (level, sc_MPI_COMM_WORLD, ghost_version, max_level, no_vtk, refine_method);
    }
    else {
      /* A triangle or tetgen file collection must be given (-f) */
      T8_ASSERT (strcmp (prefix, ""));
      T8_ASSERT (dim == 2 || dim == 3);
      t8_global_productionf ("Testing ghost on cmesh read from %s.msh\n", prefix);
      t8_test_ghost_msh_file (prefix, level, dim, sc_MPI_COMM_WORLD, ghost_version, max_level, no_vtk, refine_method);
    }
  }

  /* Clean-up */
  sc_options_destroy (opt);
  sc_finalize ();
  mpiret = sc_MPI_Finalize ();
  SC_CHECK_MPI (mpiret);
  return 0;
}