Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
DLR-AMR
GitHub Repository: DLR-AMR/t8code
Path: blob/main/example/advect/t8_advection.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 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_statistics.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_geometrical.h>
#include <t8_forest/t8_forest_profiling.h>
#include <t8_forest/t8_forest_iterate.h>
#include <t8_forest/t8_forest_partition.h>
#include <t8_forest/t8_forest_ghost.h>
#include <example/common/t8_example_common.hxx>
#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 <t8_types/t8_vec.hxx>

#define MAX_FACES 8 /* The maximum number of faces of an element */
/* TODO: This is not memory efficient. If we run out of memory, we can optimize here. */

/* Enum for statistics. */
typedef enum {
  ADVECT_ADAPT = 0,       /* adapt runtime */
  ADVECT_PARTITION,       /* partition runtime */
  ADVECT_PARTITION_PROCS, /* number of processes sent to in partition */
  ADVECT_PARTITION_DATA,  /* data partitioning runtime */
  ADVECT_BALANCE,         /* balance runtime */
  ADVECT_BALANCE_ROUNDS,  /* number of rounds in balance */
  ADVECT_GHOST,           /* ghost runtime */
  ADVECT_GHOST_SENT,      /* number of ghosts sent to other processes */
  ADVECT_GHOST_EXCHANGE,  /* ghost exchange runtime */
  ADVECT_GHOST_WAIT,      /* ghost exchange waittime */
  ADVECT_REPLACE,         /* forest_iterate_replace runtime */
  ADVECT_IO,              /* vtk runtime */
  ADVECT_ELEM_AVG,        /* average global number of elements (per time step) */
  ADVECT_INIT,            /* initialization runtime */
  ADVECT_AMR,             /* AMR runtime (adapt+partition+ghost+balance) including data exchange (partition/ghost) */
  ADVECT_NEIGHS,          /* neighbor finding runtime */
  ADVECT_FLUX,            /* flux computation runtime */
  ADVECT_DUMMY,           /* dummy operations to increase load (see -s option) */
  ADVECT_SOLVE,           /* solver runtime */
  ADVECT_TOTAL,           /* overall runtime */
  ADVECT_ERROR_INF,       /* l_infty error */
  ADVECT_ERROR_2,         /* L_2 error */
  ADVECT_VOL_LOSS,        /* The loss in volume (region with LS < 0) in percent */
  ADVECT_NUM_STATS        /* The number of statistics that we measure */
} advect_stats_t;

/* Names of statistics that we measure */
const char *advect_stat_names[ADVECT_NUM_STATS] = { "adapt",
                                                    "partition",
                                                    "partition_procs_sent",
                                                    "partition_data",
                                                    "balance",
                                                    "balance_rounds",
                                                    "ghost",
                                                    "ghost_sent",
                                                    "ghost_exchange",
                                                    "ghost_exchange_wait",
                                                    "replace",
                                                    "vtk_print",
                                                    "number_elements",
                                                    "init",
                                                    "AMR",
                                                    "neighbor_finding",
                                                    "flux_computation",
                                                    "dummy_ops",
                                                    "solve",
                                                    "total",
                                                    "l_infty_error",
                                                    "L_2",
                                                    "volume_loss_[%]" };

/** The description of the problem configuration.
 *  We store all necessary parameters, such as the initial level-set function, the flow function,
 *  data needed for adaptation etc.
 *  We also store the current forest and element data here.
 */
typedef struct
{
  t8_flow_function_3d_fn u;      /**< Fluid field */
  t8_example_level_set_fn phi_0; /**< Initial condition for phi */
  void *udata_for_phi;           /**< User data passed to phi */
  t8_forest_t forest;            /**< The forest in use */
  t8_forest_t forest_adapt;      /**< The forest after adaptation */
  sc_array_t *element_data;      /**< Array of type t8_advect_element_data_t of length
                              num_local_elements + num_ghosts */
  /* TODO: shorten element_data by number of ghosts */
  sc_array_t
    *element_data_adapt; /**< element_data for the adapted forest, used during adaptation to interpolate values */
  /* We store the phi values in an extra array, since this data must exist for ghost
   * element as well and is communicated with other processes in ghost_exchange. */
  sc_array_t *phi_values;       /**< For each element and ghost its phi value. */
  sc_array_t *phi_values_adapt; /**< phi values for the adapted forest, used during adaptation to interpolate values. */
  sc_MPI_Comm comm;             /**< MPI communicator used */
  sc_statinfo_t stats[ADVECT_NUM_STATS]; /**< Runtimes and other statistics. */
  double t;                              /**< Current simulation time */
  double T;                              /**< End time */
  double cfl;                            /**< CFL number */
  double delta_t;                        /**< Current time step */
  double min_grad, max_grad;             /**< bounds for refinement */
  double min_vol;                        /**< minimum element volume at level 'level' */
  double band_width;                     /**< width of the refinement band */
  int num_time_steps;                    /**< Number of time steps computed so far.
                                        (If delta_t is constant then t = num_time_steps * delta_t) */
  int vtk_count;                         /**< If vtk output is enabled, count the number of pvtu files written. */
  int level;                             /**< Initial refinement level */
  int maxlevel;                          /**< Maximum refinement level */
  int volume_refine;                     /**< If >= refine elements only if their volume is greater
                                       than the minimum volume at level 'level + volume_refine' */
  int dim;                               /**< The dimension of the mesh */
  int dummy_op;                          /**< If true, we carry out more (but useless) operations
                                     per element, in order to simulate more computation load */
} t8_advect_problem_t;

/** The per element data */
typedef struct
{
  t8_3D_point midpoint;           /**< coordinates of element midpoint in R^3 */
  double vol;                     /**< Volume of this element */
  double phi_new;                 /**< Value of solution at midpoint in next time step */
  double *fluxes[MAX_FACES];      /**< The fluxes to each neighbor at a given face */
  int flux_valid[MAX_FACES];      /**< If > 0, this flux was computed, if 0 memory was allocated
                                                   for this flux, but not computed. If < 0, no memory was allocated. */
  int level;                      /**< The refinement level of the element. */
  int num_faces;                  /**< The number of faces */
  int num_neighbors[MAX_FACES];   /**< Number of neighbors for each face */
  int *dual_faces[MAX_FACES];     /**< The face indices of the neighbor elements */
  t8_locidx_t *neighs[MAX_FACES]; /**< Indices of the neighbor elements */
  int8_t neigh_level[MAX_FACES];  /**< The level of the face neighbors at this face. */
} t8_advect_element_data_t;

/* Return the phi value of a given local or ghost element.
 * 0 <= ielement < num_elements + num_ghosts
 */
static double
t8_advect_element_get_phi (const t8_advect_problem_t *problem, t8_locidx_t ielement)
{
  return *((double *) t8_sc_array_index_locidx (problem->phi_values, ielement));
}

/* Set the phi value of an element to a given entry */
static void
t8_advect_element_set_phi (const t8_advect_problem_t *problem, t8_locidx_t ielement, double phi)
{
  *((double *) t8_sc_array_index_locidx (problem->phi_values, ielement)) = phi;
}

/* Set the phi value of an element in the adapted forest to a given entry */
static void
t8_advect_element_set_phi_adapt (const t8_advect_problem_t *problem, t8_locidx_t ielement, double phi)
{
  *((double *) t8_sc_array_index_locidx (problem->phi_values_adapt, ielement)) = phi;
}

/* Adapt the forest. We refine if the level-set function is close to zero
 * and coarsen if it is larger than a given threshold. */
static int
t8_advect_adapt (t8_forest_t forest, t8_forest_t forest_from, t8_locidx_t ltree_id, const t8_eclass_t tree_class,
                 t8_locidx_t lelement_id, const t8_scheme *scheme, const int is_family,
                 [[maybe_unused]] const int num_elements, t8_element_t *elements[])
{
  t8_advect_problem_t *problem;
  t8_advect_element_data_t *elem_data;
  double band_width, elem_diam;
  int level;
  t8_locidx_t offset;
  double phi;
  double vol_thresh;
  static int seed = 10000;

  srand (seed++);
  /* Get a pointer to the problem from the user data pointer of forest */
  problem = (t8_advect_problem_t *) t8_forest_get_user_data (forest);
  /* Get the element's level */
  level = scheme->element_get_level (tree_class, elements[0]);
  if (level == problem->maxlevel && !is_family) {
    /* It is not possible to refine this level */
    return 0;
  }
  /* Compute the volume threshold. Elements larger than this and
   * close to the 0 level-set are refined */
  if (problem->volume_refine >= 0) {
    vol_thresh = problem->min_vol / (1 << (problem->dim * problem->volume_refine));
  }
  else {
    vol_thresh = 0;
  }
  /* Get the value of phi at this element */
  offset = t8_forest_get_tree_element_offset (forest_from, ltree_id);
  phi = t8_advect_element_get_phi (problem, lelement_id + offset);

  /* Get a pointer to the element data */
  elem_data = (t8_advect_element_data_t *) t8_sc_array_index_locidx (problem->element_data, lelement_id + offset);

  /* Refine if close to levelset, coarsen if not */
  band_width = problem->band_width;
  elem_diam = t8_forest_element_diam (forest_from, ltree_id, elements[0]);
  if (fabs (phi) > 2 * band_width * elem_diam) {
    /* coarsen if this is a family and level is not too small */
    return -(is_family && level > problem->level);
  }
  else if (fabs (phi) < band_width * elem_diam && elem_data->vol > vol_thresh) {
    /* refine if level is not too large */
    return level < problem->maxlevel;
  }
  return 0;
}

/* Compute the total volume of the elements with negative phi value */
static double
t8_advect_level_set_volume (const t8_advect_problem_t *problem)
{
  t8_locidx_t num_local_elements, ielem;
  t8_advect_element_data_t *elem_data;
  double volume = 0, global_volume = 0;
  double phi;

  num_local_elements = t8_forest_get_local_num_leaf_elements (problem->forest);

  for (ielem = 0; ielem < num_local_elements; ielem++) {
    elem_data = (t8_advect_element_data_t *) t8_sc_array_index_locidx (problem->element_data, ielem);
    phi = t8_advect_element_get_phi (problem, ielem);
    if (phi < 0) {
      volume += elem_data->vol;
    }
  }
  sc_MPI_Allreduce (&volume, &global_volume, 1, sc_MPI_DOUBLE, sc_MPI_SUM, problem->comm);
  return global_volume;
}

/* Compute the relative l_infty error of the stored phi values compared to a
 * given analytical function at time problem->t */
static double
t8_advect_l_infty_rel (const t8_advect_problem_t *problem, t8_example_level_set_fn analytical_sol, double distance)
{
  t8_locidx_t num_local_elements, ielem;
  t8_advect_element_data_t *elem_data;
  double phi;
  double ana_sol;
  double error[2] = { -1, 0 }, el_error, global_error[2];

  num_local_elements = t8_forest_get_local_num_leaf_elements (problem->forest);
  for (ielem = 0; ielem < num_local_elements; ielem++) {
    elem_data = (t8_advect_element_data_t *) t8_sc_array_index_locidx (problem->element_data, ielem);

    /* Compute the analytical solution */
    ana_sol = analytical_sol (elem_data->midpoint, problem->t, problem->udata_for_phi);
    if (fabs (ana_sol) < distance) {
      /* Compute the error as the stored value at the midpoint of this element
       * minus the solution at this midpoint */
      phi = t8_advect_element_get_phi (problem, ielem);
      el_error = fabs ((phi - ana_sol));
      error[0] = SC_MAX (error[0], el_error);
      /* Compute the l_infty norm of the analytical solution */
      error[1] = SC_MAX (error[1], ana_sol);
    }
  }
  /* Compute the maximum of the error among all processes */
  sc_MPI_Allreduce (&error, &global_error, 2, sc_MPI_DOUBLE, sc_MPI_MAX, problem->comm);

  /* Return the relative error, that is the l_infty error divided by
   * the l_infty norm of the analytical solution */
  return global_error[0] / global_error[1];
}

static double
t8_advect_l_2_rel (const t8_advect_problem_t *problem, t8_example_level_set_fn analytical_sol, double distance)
{
  t8_locidx_t num_local_elements, ielem, count = 0;
  t8_advect_element_data_t *elem_data;
  double phi;
  double diff, ana_sol;
  double error[2] = { 0, 0 }, el_error, global_error[2];

  num_local_elements = t8_forest_get_local_num_leaf_elements (problem->forest);
  for (ielem = 0; ielem < num_local_elements; ielem++) {
    elem_data = (t8_advect_element_data_t *) t8_sc_array_index_locidx (problem->element_data, ielem);
    /* Compute the analytical solution */
    ana_sol = analytical_sol (elem_data->midpoint, problem->t, problem->udata_for_phi);
    if (fabs (ana_sol) < distance) {
      count++;
      /* Compute the error as the stored value at the midpoint of this element minus the solution at this midpoint */
      phi = t8_advect_element_get_phi (problem, ielem);
      diff = fabs (phi - ana_sol);
      el_error = diff * diff * elem_data->vol;
      error[0] += el_error;
      error[1] += ana_sol * ana_sol * elem_data->vol;
    }
  }
  t8_debugf ("[advect] L_2 %e  %e\n", error[0], error[1]);
  t8_debugf ("[advect] L_2 %i elems\n", count);
  /* Compute the maximum of the error among all processes */
  sc_MPI_Allreduce (&error, &global_error, 2, sc_MPI_DOUBLE, sc_MPI_SUM, problem->comm);

  /* Return the relative error, that is the l_infty error divided by the l_infty norm of the analytical solution */
  return sqrt (global_error[0]) / sqrt (global_error[1]);
}

static double
t8_advect_flux_upwind_1d (const t8_advect_problem_t *problem, const t8_locidx_t el_plus, const t8_locidx_t el_minus,
                          int face)
{
  t8_3D_point x_j_half;
  int idim;
  t8_3D_vec u_at_x_j_half;
  double phi;
  int sign;
  t8_advect_element_data_t *el_data_plus;

  /*
   *    | --x-- | --x-- |   Two elements, midpoints marked with 'x'
   *       x_j     x_j+1
   *          x_j_half
   */
  /* Compute x_j_half */
  el_data_plus = (t8_advect_element_data_t *) t8_sc_array_index_locidx (problem->element_data, el_plus);
  for (idim = 0; idim < 3; idim++) {
    x_j_half[idim] = (el_data_plus->midpoint[idim] - (idim == 0 ? el_data_plus->vol / 2 : 0));
  }
  /* Compute u at the interval boundary. */
  problem->u (x_j_half, problem->t, u_at_x_j_half);
  /* In 1D we are only interested in the first coordinate of u */

  sign = face == 0 ? -1 : 1;
  if (sign * u_at_x_j_half[0] >= 0) {
    /* we have outflow */
    phi = -t8_advect_element_get_phi (problem, el_plus);
  }
  else {
    /* we have inflow */
    /* el_minus may be negative, in this case, el_plus is at the boundary and we use phi = 0. */
    phi = el_minus >= 0 ? t8_advect_element_get_phi (problem, el_minus) : 0;
  }
  return u_at_x_j_half[0] * phi;
}

/* Compute the flux across a given face between two elements */
/* face is the face number as seen from el_data_plus */
/* This works also if element_plus hangs on element_minus.
 * It does not work if it hangs the other way around. */
static double
t8_advect_flux_upwind (const t8_advect_problem_t *problem, double el_plus_phi, double el_minus_phi, t8_locidx_t ltreeid,
                       const t8_element_t *element_plus, int face)
{
  t8_3D_point face_center;
  t8_3D_vec u_at_face_center;
  t8_3D_vec normal;
  double area, normal_times_u;

  /*
   *    | --x-- | --x-- |   Two elements, midpoints marked with 'x'
   *       x_j     x_j+1
   *          face_center
   */

  /* Compute the center coordinate of the face */
  t8_forest_element_face_centroid (problem->forest, ltreeid, element_plus, face, face_center.data ());
  /* Compute u at the face center. */
  problem->u (face_center, problem->t, u_at_face_center);
  /* Compute the normal of the element at this face */
  t8_forest_element_face_normal (problem->forest, ltreeid, element_plus, face, normal.data ());
  /* Compute the area of the face */
  area = t8_forest_element_face_area (problem->forest, ltreeid, element_plus, face);

  /* Compute the dot-product of u and the normal vector */
  normal_times_u = t8_dot (normal, u_at_face_center);

  if (normal_times_u >= 0) {
    return -el_plus_phi * normal_times_u * area;
  }
  else {
    /* u flows into the element_plus */
    return -el_minus_phi * normal_times_u * area;
  }
}

/* Compute the flux if the element has hanging neighbors:
 *
 *  x -- x ---- x
 *  |    |      |
 *  x -- x      |
 *  |    |      |
 *  x -- x ---- x
 *
 * neighs  el_hang
 *
 */
int a = 0;
static double
t8_advect_flux_upwind_hanging (const t8_advect_problem_t *problem, t8_locidx_t iel_hang, t8_locidx_t ltreeid,
                               const t8_element_t *element_hang, int face, int adapted_or_partitioned)
{
  int i, num_face_children, child_face;
  const t8_scheme *scheme = t8_forest_get_scheme (problem->forest);
  t8_eclass eclass;
  t8_element_t **face_children;
  t8_advect_element_data_t *neigh_data;
  double flux = 0;
  int dual_face;
  t8_locidx_t neigh_id;
  int neigh_is_ghost;
  t8_advect_element_data_t *el_hang;
  double phi_plus, phi_minus;

  /* Get a pointer to the element */
  el_hang = (t8_advect_element_data_t *) t8_sc_array_index_locidx (problem->element_data, iel_hang);
  /* Get the eclass for the tree of the element */
  eclass = t8_forest_get_tree_class (problem->forest, ltreeid);
  /* Compute the children of the element at the face */
  num_face_children = scheme->element_get_num_face_children (eclass, element_hang, face);
  T8_ASSERT (num_face_children == el_hang->num_neighbors[face]);

  face_children = T8_ALLOC (t8_element_t *, num_face_children);
  scheme->element_new (eclass, num_face_children, face_children);
  scheme->element_get_children_at_face (eclass, element_hang, face, face_children, num_face_children, NULL);

  /* Store the phi value of el_hang. We use it as the phi value of the
   * children to compute the flux */
  phi_plus = t8_advect_element_get_phi (problem, iel_hang);

  for (i = 0; i < num_face_children; i++) {
    child_face = scheme->element_face_get_child_face (eclass, element_hang, face, i);
    /* Get a pointer to the neighbor's element data */
    neigh_id = el_hang->neighs[face][i];
    neigh_data = (t8_advect_element_data_t *) t8_sc_array_index_locidx (problem->element_data, neigh_id);
    neigh_is_ghost = neigh_id >= t8_forest_get_local_num_leaf_elements (problem->forest);
    phi_minus = t8_advect_element_get_phi (problem, neigh_id);
    /* Compute the flux */
    el_hang->fluxes[face][i]
      = t8_advect_flux_upwind (problem, phi_plus, phi_minus, ltreeid, face_children[i], child_face);
    /* Set the flux of the neighbor element */
    dual_face = el_hang->dual_faces[face][i];
    if (!adapted_or_partitioned && !neigh_is_ghost) {

      if (neigh_data->flux_valid[dual_face] < 0) {
        /* We need to allocate the fluxes */
        neigh_data->fluxes[dual_face] = T8_ALLOC (double, 1);
      }
      SC_CHECK_ABORT (dual_face < neigh_data->num_faces, "num\n");
      // SC_CHECK_ABORT (neigh_data->num_neighbors[dual_face] == 1, "entry\n");
      neigh_data->num_neighbors[dual_face] = 1;
      neigh_data->fluxes[dual_face][0] = -el_hang->fluxes[face][i];
      neigh_data->flux_valid[dual_face] = 1;
    }

    flux += el_hang->fluxes[face][i];
  }

  el_hang->flux_valid[face] = 1;
  /* clean-up */
  scheme->element_destroy (eclass, num_face_children, face_children);
  T8_FREE (face_children);

  a = 2;
  return flux;
}

/* If an element is at the domain boundary, we encode boundary conditions
 * by setting a phi value for an imaginative neighbor element.
 * We currently set the phi value to the value of the element itself. */
static void
t8_advect_boundary_set_phi (const t8_advect_problem_t *problem, t8_locidx_t ielement, double *boundary_phi)
{

  *boundary_phi = t8_advect_element_get_phi (problem, ielement);
}

static void
t8_advect_advance_element (t8_advect_problem_t *problem, t8_locidx_t lelement)
{
  int iface, ineigh;
  double flux_sum = 0;
  int num_faces;
  double phi;
  t8_advect_element_data_t *elem;

  /* Get a pointer to the element */
  elem = (t8_advect_element_data_t *) t8_sc_array_index_locidx (problem->element_data, lelement);
  /* Get the phi value of the element */
  phi = t8_advect_element_get_phi (problem, lelement);
  num_faces = elem->num_faces;
  /* Sum all the fluxes */
  for (iface = 0; iface < num_faces; iface++) {
    for (ineigh = 0; ineigh < SC_MAX (1, elem->num_neighbors[iface]); ineigh++) {
      flux_sum += elem->fluxes[iface][ineigh];
    }
  }
  /* Phi^t = dt/dx * (f_(j-1/2) - f_(j+1/2)) + Phi^(t-1) */
  elem->phi_new = (problem->delta_t / elem->vol) * flux_sum + phi;
}

/* Compute element midpoint and vol and store at element_data field. */
static void
t8_advect_compute_element_data (t8_advect_problem_t *problem, t8_advect_element_data_t *elem_data,
                                const t8_element_t *element, const t8_locidx_t ltreeid)
{
  /* Compute the midpoint coordinates of element */
  t8_forest_element_centroid (problem->forest, ltreeid, element, elem_data->midpoint.data ());
  /* Compute the length of this element */
  elem_data->vol = t8_forest_element_volume (problem->forest, ltreeid, element);
}

/* Replace callback to decide how to interpolate a refined or coarsened element.
 * If an element is refined, each child gets the phi value of its parent.
 * If elements are coarsened, the parent gets the average phi value of the children.
 */
/* outgoing are the old elements and incoming the new ones */
/* TODO: If coarsening, weight the phi values by volume of the children:
 *       phi_E = sum (phi_Ei *vol(E_i)/vol(E))
 *       Similar formula for refining?
 */
static void
t8_advect_replace (t8_forest_t forest_old, t8_forest_t forest_new, t8_locidx_t which_tree, const t8_eclass_t tree_class,
                   const t8_scheme *scheme, int refine, int num_outgoing, t8_locidx_t first_outgoing, int num_incoming,
                   t8_locidx_t first_incoming)
{
  t8_advect_problem_t *problem;
  t8_advect_element_data_t *elem_data_in, *elem_data_out;
  t8_locidx_t first_incoming_data, first_outgoing_data;
  int i, iface;
  double phi_old;

  /* Get the problem description */

  problem = (t8_advect_problem_t *) t8_forest_get_user_data (forest_new);
  T8_ASSERT (forest_old == problem->forest);
  T8_ASSERT (forest_new == problem->forest_adapt);
  /* Get pointers to the element data */
  first_incoming_data = first_incoming + t8_forest_get_tree_element_offset (forest_new, which_tree);
  first_outgoing_data = first_outgoing + t8_forest_get_tree_element_offset (forest_old, which_tree);
  elem_data_out = (t8_advect_element_data_t *) t8_sc_array_index_locidx (problem->element_data, first_outgoing_data);
  elem_data_in
    = (t8_advect_element_data_t *) t8_sc_array_index_locidx (problem->element_data_adapt, first_incoming_data);

  /* Get the old phi value (used in the cases with num_outgoing = 1) */
  phi_old = t8_advect_element_get_phi (problem, first_outgoing_data);
  if (refine == 0) {
    T8_ASSERT (num_incoming == num_outgoing && num_incoming == 1);
    /* The element is not changed, copy phi and vol */
    memcpy (elem_data_in, elem_data_out, sizeof (t8_advect_element_data_t));
    t8_advect_element_set_phi_adapt (problem, first_incoming_data, phi_old);
#if T8_ENABLE_DEBUG
    /* Get a pointer to the new element */
    const t8_element_t *element
      = t8_forest_get_leaf_element_in_tree (problem->forest_adapt, which_tree, first_incoming);
    /* Debug check number of faces */
    T8_ASSERT (elem_data_in->num_faces == scheme->element_get_num_faces (tree_class, element));
#endif
    /* Set the neighbor entries to uninitialized */
    for (iface = 0; iface < elem_data_in->num_faces; iface++) {
      elem_data_in->num_neighbors[iface] = 0;
      elem_data_in->flux_valid[iface] = -1;
      elem_data_in->dual_faces[iface] = NULL;
      elem_data_in->fluxes[iface] = NULL;
      elem_data_in->neighs[iface] = NULL;
    }
  }
  else if (refine == 1) {
    T8_ASSERT (num_outgoing == 1);
#if T8_ENABLE_DEBUG
    /* Ensure that the number of incoming elements matches the
     * number of children of the outgoing element. */
    const t8_element_t *element_outgoing = t8_forest_get_leaf_element_in_tree (forest_old, which_tree, first_outgoing);
    const int num_children = scheme->element_get_num_children (tree_class, element_outgoing);
    T8_ASSERT (num_incoming == num_children);
#endif
    /* The old element is refined, we copy the phi values and compute the new midpoints */
    for (i = 0; i < num_incoming; i++) {
      /* Get a pointer to the new element */
      const t8_element_t *element
        = t8_forest_get_leaf_element_in_tree (problem->forest_adapt, which_tree, first_incoming + i);
      /* Compute midpoint and vol of the new element */
      t8_advect_compute_element_data (problem, elem_data_in + i, element, which_tree);
      t8_advect_element_set_phi_adapt (problem, first_incoming_data + i, phi_old);
      /* Set the neighbor entries to uninitialized */
      const int num_new_faces = scheme->element_get_num_faces (tree_class, element);
      elem_data_in[i].num_faces = num_new_faces;
      for (iface = 0; iface < num_new_faces; iface++) {
        elem_data_in[i].num_neighbors[iface] = 0;
        elem_data_in[i].flux_valid[iface] = -1;
        elem_data_in[i].dual_faces[iface] = NULL;
        elem_data_in[i].fluxes[iface] = NULL;
        elem_data_in[i].neighs[iface] = NULL;
      }
      /* Update the level */
      elem_data_in[i].level = elem_data_out->level + 1;
    }
  }
  else {
    double phi = 0;
    T8_ASSERT (refine = -1);
    T8_ASSERT (num_incoming == 1);
#if T8_ENABLE_DEBUG
    /* Ensure that the number of outgoing elements matches the
     * number of siblings of the first outgoing element. */
    const t8_element_t *element_outgoing = t8_forest_get_leaf_element_in_tree (forest_old, which_tree, first_outgoing);
    const int num_siblings = scheme->element_get_num_siblings (tree_class, element_outgoing);
    T8_ASSERT (num_outgoing == num_siblings);
#endif
    /* The old elements form a family which is coarsened. We compute the average
     * phi value and set it as the new phi value */
    /* Get a pointer to the new element */
    const t8_element_t *element
      = t8_forest_get_leaf_element_in_tree (problem->forest_adapt, which_tree, first_incoming);
    /* Compute midpoint and vol of the new element */
    t8_advect_compute_element_data (problem, elem_data_in, element, which_tree);

    /* Compute average of phi */
    for (i = 0; i < num_outgoing; i++) {
      phi += t8_advect_element_get_phi (problem, first_outgoing_data + i);
    }
    phi /= num_outgoing;
    t8_advect_element_set_phi_adapt (problem, first_incoming_data, phi);
    /* Set the neighbor entries to uninitialized */
    elem_data_in->num_faces = elem_data_out[0].num_faces;
    T8_ASSERT (elem_data_in->num_faces == scheme->element_get_num_faces (tree_class, element));
    for (iface = 0; iface < elem_data_in->num_faces; iface++) {
      elem_data_in->num_neighbors[iface] = 0;
      elem_data_in->flux_valid[iface] = -1;
      elem_data_in->dual_faces[iface] = NULL;
      elem_data_in->fluxes[iface] = NULL;
      elem_data_in->neighs[iface] = NULL;
    }
    /* update the level */
    elem_data_in->level = elem_data_out->level - 1;
  }
}

static void
t8_advect_problem_elements_destroy (t8_advect_problem_t *problem)
{

  t8_locidx_t lelement, num_local_elem;
  int iface;
  t8_advect_element_data_t *elem_data;

  num_local_elem = t8_forest_get_local_num_leaf_elements (problem->forest);
  T8_ASSERT (num_local_elem <= (t8_locidx_t) problem->element_data->elem_count);
  /* destroy all elements */
  for (lelement = 0; lelement < num_local_elem; lelement++) {
    elem_data = (t8_advect_element_data_t *) t8_sc_array_index_locidx (problem->element_data, lelement);
    for (iface = 0; iface < elem_data->num_faces; iface++) {
      /* TODO: make face number dim independent */
      if (elem_data->num_neighbors[iface] > 0) {
        T8_FREE (elem_data->neighs[iface]);
        T8_FREE (elem_data->dual_faces[iface]);
        T8_FREE (elem_data->fluxes[iface]);
        elem_data->num_neighbors[iface] = 0;
        elem_data->flux_valid[iface] = -1;
      }
      else {
        T8_FREE (elem_data->fluxes[iface]);
      }
    }
  }
}

/* Adapt the forest and interpolate the phi values to the new grid,
 * compute the new u values on the grid */
static void
t8_advect_problem_adapt (t8_advect_problem_t *problem, int measure_time)
{
  t8_locidx_t num_elems_p_ghosts, num_elems;
  double adapt_time, balance_time = 0, ghost_time;
  t8_locidx_t ghost_sent;
  int balance_rounds, did_balance = 0;
  double replace_time;

  /* Adapt the forest, but keep the old one */
  t8_forest_ref (problem->forest);
  t8_forest_init (&problem->forest_adapt);
  /* Enable profiling to measure the runtime */
  t8_forest_set_profiling (problem->forest_adapt, 1);
  /* Set the user data pointer of the new forest */
  t8_forest_set_user_data (problem->forest_adapt, problem);
  /* Set the adapt function */
  t8_forest_set_adapt (problem->forest_adapt, problem->forest, t8_advect_adapt, 0);
  if (problem->maxlevel - problem->level > 1) {
    /* We also want to balance the forest if the difference in refinement levels is greater 1 */
    t8_forest_set_balance (problem->forest_adapt, NULL, 1);
    did_balance = 1;
  }
  /* We also want ghost elements in the new forest */
  t8_forest_set_ghost (problem->forest_adapt, 1, T8_GHOST_FACES);
  /* Commit the forest, adaptation and balance happens here */
  t8_forest_commit (problem->forest_adapt);

  /* Store the runtimes in problems stats */
  if (measure_time) {
    adapt_time = t8_forest_profile_get_adapt_time (problem->forest_adapt);
    ghost_time = t8_forest_profile_get_ghost_time (problem->forest_adapt, &ghost_sent);
    if (did_balance) {
      balance_time = t8_forest_profile_get_balance_time (problem->forest_adapt, &balance_rounds);
    }
    sc_stats_accumulate (&problem->stats[ADVECT_ADAPT], adapt_time);
    sc_stats_accumulate (&problem->stats[ADVECT_GHOST], ghost_time);
    sc_stats_accumulate (&problem->stats[ADVECT_GHOST_SENT], ghost_sent);
    if (did_balance) {
      sc_stats_accumulate (&problem->stats[ADVECT_BALANCE], balance_time);
      sc_stats_accumulate (&problem->stats[ADVECT_BALANCE_ROUNDS], balance_rounds);
    }
    /* We want to count all runs over the solver time as one */
    problem->stats[ADVECT_ADAPT].count = 1;
    problem->stats[ADVECT_BALANCE].count = 1;
    problem->stats[ADVECT_GHOST].count = 1;
    problem->stats[ADVECT_GHOST_SENT].count = 1;
  }

  /* Allocate new memory for the element_data of the advected forest */
  num_elems = t8_forest_get_local_num_leaf_elements (problem->forest_adapt);
  num_elems_p_ghosts = num_elems + t8_forest_get_num_ghosts (problem->forest_adapt);
  problem->element_data_adapt = sc_array_new_count (sizeof (t8_advect_element_data_t), num_elems);
  problem->phi_values_adapt = sc_array_new_count ((problem->dummy_op ? 2 : 1) * sizeof (double), num_elems_p_ghosts);
  /* We now call iterate_replace in which we interpolate the new element data.
   * It is necessary that the old and new forest only differ by at most one level.
   * We guarantee this by calling adapt non-recursively and calling balance without repartitioning. */
  replace_time = -sc_MPI_Wtime ();
  t8_forest_iterate_replace (problem->forest_adapt, problem->forest, t8_advect_replace);
  replace_time += sc_MPI_Wtime ();
  if (measure_time) {
    sc_stats_accumulate (&problem->stats[ADVECT_REPLACE], replace_time);
    sc_stats_accumulate (&problem->stats[ADVECT_AMR], ghost_time + adapt_time + balance_time + replace_time);
    problem->stats[ADVECT_REPLACE].count = 1;
    problem->stats[ADVECT_AMR].count = 1;
  }
  /* clean the old element data */
  t8_advect_problem_elements_destroy (problem);
  sc_array_destroy (problem->element_data);
  sc_array_destroy (problem->phi_values);
  /* Free memory for the forest */
  t8_forest_unref (&problem->forest);
  /* Set the forest to the adapted one */
  problem->forest = problem->forest_adapt;
  problem->forest_adapt = NULL;
  /* Set the elem data to the adapted elem data */
  problem->element_data = problem->element_data_adapt;
  problem->element_data_adapt = NULL;
  /* Set the phi values to the adapted phi values */
  problem->phi_values = problem->phi_values_adapt;
  problem->phi_values_adapt = NULL;
}

/* Re-partition the forest and element data of a problem */
static void
t8_advect_problem_partition (t8_advect_problem_t *problem, int measure_time)
{
  t8_forest_t forest_partition;
  sc_array_t data_view, data_view_new, phi_view, phi_view_new;
  sc_array_t *new_data, *new_phi;
  t8_locidx_t num_local_elements, num_local_elements_new;
  t8_locidx_t num_ghosts_new;
  int procs_sent;
  t8_locidx_t ghost_sent;
  double partition_time, ghost_time;

  /* Partition the forest and create its ghost layer */
  /* ref the current forest, since we still need access to it */
  t8_forest_ref (problem->forest);
  t8_forest_init (&forest_partition);
  /* Enable profiling to measure runtime */
  t8_forest_set_profiling (forest_partition, 1);
  /* Partition the forest and create ghosts */
  t8_forest_set_partition (forest_partition, problem->forest, 0);
  t8_forest_set_ghost (forest_partition, 1, T8_GHOST_FACES);
  t8_forest_commit (forest_partition);

  /* Add runtimes to internal stats */
  if (measure_time) {
    partition_time = t8_forest_profile_get_partition_time (forest_partition, &procs_sent);
    ghost_time = t8_forest_profile_get_ghost_time (forest_partition, &ghost_sent);
    sc_stats_accumulate (&problem->stats[ADVECT_PARTITION], partition_time);
    sc_stats_accumulate (&problem->stats[ADVECT_GHOST], ghost_time);
    sc_stats_accumulate (&problem->stats[ADVECT_AMR], ghost_time + partition_time);
    /* We want to count all runs over the solver time as one */
    problem->stats[ADVECT_PARTITION].count = 1;
    problem->stats[ADVECT_GHOST].count = 1;
  }
  /* Partition the data */
  num_local_elements = t8_forest_get_local_num_leaf_elements (problem->forest);
  num_local_elements_new = t8_forest_get_local_num_leaf_elements (forest_partition);
  num_ghosts_new = t8_forest_get_num_ghosts (forest_partition);
  /* Create a view array of the entries for the local elements */
  sc_array_init_view (&data_view, problem->element_data, 0, num_local_elements);
  sc_array_init_view (&phi_view, problem->phi_values, 0, num_local_elements);
  /* Allocate the data array for the partitioned elements */
  new_data = sc_array_new_count (sizeof (t8_advect_element_data_t), num_local_elements_new + num_ghosts_new);
  new_phi = sc_array_new_count (sizeof (double), num_local_elements_new + num_ghosts_new);
  /* Create a view array of the entries for the local elements */
  sc_array_init_view (&data_view_new, new_data, 0, num_local_elements_new);
  sc_array_init_view (&phi_view_new, new_phi, 0, num_local_elements_new);
  /* Perform the data partition */
  partition_time = -sc_MPI_Wtime ();
  t8_forest_partition_data (problem->forest, forest_partition, &data_view, &data_view_new);
  t8_forest_partition_data (problem->forest, forest_partition, &phi_view, &phi_view_new);
  partition_time += sc_MPI_Wtime ();
  if (measure_time) {
    sc_stats_accumulate (&problem->stats[ADVECT_PARTITION_DATA], partition_time);
    sc_stats_accumulate (&problem->stats[ADVECT_AMR], partition_time);
    problem->stats[ADVECT_PARTITION_DATA].count = 1;
    problem->stats[ADVECT_AMR].count = 1;
    t8_debugf ("status ghost: %f\n\n", ghost_time);
  }

  /* destroy the old forest and the element data */
  t8_advect_problem_elements_destroy (problem);
  t8_forest_unref (&problem->forest);
  problem->forest = forest_partition;
  sc_array_destroy (problem->element_data);
  problem->element_data = new_data;
  sc_array_destroy (problem->phi_values);
  problem->phi_values = new_phi;
}

static t8_cmesh_t
t8_advect_create_cmesh (sc_MPI_Comm comm, int cube_type, const char *mshfile, int level, int dim, int use_cad_geometry)
{
  if (mshfile != NULL) {
    /* Load from .msh file and partition */
    t8_cmesh_t cmesh, cmesh_partition;
    T8_ASSERT (mshfile != NULL);

    cmesh = t8_cmesh_from_msh_file (mshfile, 0, comm, dim, 0, use_cad_geometry);
    /* The partitioning of the cad geometry is not yet available */
    if (use_cad_geometry) {
      t8_productionf ("cmesh was not partitioned. Partitioning is not yet "
                      "available with the curved geometry\n");
      return cmesh;
    }
    /* partition this cmesh according to the initial refinement level */
    t8_cmesh_init (&cmesh_partition);
    t8_cmesh_set_partition_uniform (cmesh_partition, level, t8_scheme_new_default ());
    t8_cmesh_set_derive (cmesh_partition, cmesh);
    t8_cmesh_commit (cmesh_partition, comm);
    return cmesh_partition;
  }
  else {
    if (cube_type == 7) {
      return t8_cmesh_new_periodic_hybrid (comm);
    }
    else if (cube_type == 8) {
      return t8_cmesh_new_hypercube_hybrid (comm, 0, 1);
    }
    else if (cube_type == 9) {
      return t8_cmesh_new_hypercube (T8_ECLASS_PYRAMID, comm, 0, 0, 0);
    }
    else {
      T8_ASSERT (T8_ECLASS_ZERO <= cube_type && cube_type < T8_ECLASS_COUNT);
      return t8_cmesh_new_hypercube ((t8_eclass_t) cube_type, comm, 0, 0, 1);
    }
  }
}

static t8_flow_function_3d_fn
t8_advect_choose_flow (int flow_arg)
{
  switch (flow_arg) {
  case 1:
    return t8_flow_constant_one_x_vec;
  case 2:
    return t8_flow_constant_one_xyz_vec;
  case 3:
    return t8_flow_incomp_cube_flow;
  case 4:
    return t8_flow_rotation_2d;
  case 5:
    return t8_flow_around_circle;
  case 6:
    return t8_flow_stokes_flow_sphere_shell;
  case 7:
    return t8_flow_around_circle_with_angular_velocity;
  default:
    SC_ABORT ("Wrong argument for flow parameter.\n");
  }
  return NULL; /* prevents compiler warning */
}

static t8_advect_problem_t *
t8_advect_problem_init (t8_cmesh_t cmesh, t8_flow_function_3d_fn u, t8_example_level_set_fn phi_0, void *ls_data,
                        int level, int maxlevel, double T, double cfl, sc_MPI_Comm comm, double band_width, int dim,
                        int dummy_op, int volume_refine)
{
  t8_advect_problem_t *problem;
  const t8_scheme *default_scheme = t8_scheme_new_default ();
  int i;

  T8_ASSERT (1 <= dim && dim <= 3);

  /* allocate problem */
  problem = T8_ALLOC (t8_advect_problem_t, 1);
  /* Fill problem parameters */
  problem->u = u;                         /* flow field */
  problem->phi_0 = phi_0;                 /* initial condition */
  problem->udata_for_phi = ls_data;       /* user data pointer passed to phi */
  problem->level = level;                 /* minimum refinement level */
  problem->maxlevel = maxlevel;           /* maximum allowed refinement level */
  problem->t = 0;                         /* start time */
  problem->T = T;                         /* end time */
  problem->delta_t = -1;                  /* delta_t, invalid value */
  problem->min_grad = 2;                  /* Coarsen an element if the gradient is smaller */
  problem->max_grad = 4;                  /* Refine an element if the gradient is larger */
  problem->min_vol = -1;                  /* Invalid start entry */
  problem->volume_refine = volume_refine; /* If greater or equal zero, refine elem only if
                                                   volume is larger than min_vol. */
  problem->cfl = cfl;                     /* cfl number  */
  problem->num_time_steps = 0;            /* current time step */
  problem->comm = comm;                   /* MPI communicator */
  problem->vtk_count = 0;                 /* number of pvtu files written */
  problem->band_width = band_width;       /* width of the refinement band around 0 level-set */
  problem->dim = dim;                     /* dimension of the mesh */
  problem->dummy_op = dummy_op;           /* If true, emulate more computational load per element */

  for (i = 0; i < ADVECT_NUM_STATS; i++) {
    sc_stats_init (&problem->stats[i], advect_stat_names[i]);
  }

  /* Construct uniform forest with ghosts */

  problem->forest = t8_forest_new_uniform (cmesh, default_scheme, level, 1, comm);

  /* Initialize the element array with num_local_elements + num_ghosts entries. */

  problem->element_data
    = sc_array_new_count (sizeof (t8_advect_element_data_t), t8_forest_get_local_num_leaf_elements (problem->forest));
  problem->element_data_adapt = NULL;

  /* initialize the phi array */
  problem->phi_values
    = sc_array_new_count ((dummy_op ? 2 : 1) * sizeof (double), t8_forest_get_local_num_leaf_elements (problem->forest)
                                                                  + t8_forest_get_num_ghosts (problem->forest));
  problem->phi_values_adapt = NULL;
  return problem;
}

/* Project the solution at the last time step to the forest.
 * Also set the fluxes to invalid */
static void
t8_advect_project_element_data (t8_advect_problem_t *problem)
{
  t8_locidx_t num_local_elements, ielem;
  t8_advect_element_data_t *elem_data;
  int iface;

  num_local_elements = t8_forest_get_local_num_leaf_elements (problem->forest);
  for (ielem = 0; ielem < num_local_elements; ielem++) {
    elem_data = (t8_advect_element_data_t *) t8_sc_array_index_locidx (problem->element_data, ielem);
    /* Currently the mesh does not change, thus the projected value is
     * just the computed value */
    t8_advect_element_set_phi (problem, ielem, elem_data->phi_new);
    /* Set all fluxes to invalid */
    for (iface = 0; iface < elem_data->num_faces; iface++) {
      if (elem_data->flux_valid[iface] >= 0) {
        /* If they are allocated (>= 0), set to allocated and not computed (=0) */
        elem_data->flux_valid[iface] = 0;
      }
    }
  }
}

static void
t8_advect_problem_init_elements (t8_advect_problem_t *problem)
{
  t8_locidx_t itree, ielement, idata;
  t8_locidx_t num_trees, num_elems_in_tree;
  t8_element_t **neighbors;
  int iface, ineigh;
  t8_advect_element_data_t *elem_data;
  const t8_scheme *scheme = t8_forest_get_scheme (problem->forest);
  t8_eclass_t neigh_eclass;
  double speed, max_speed = 0, min_diam = -1, delta_t, min_delta_t;
  t8_3D_vec u;
  double diam;
  double min_vol = 1e9;

  num_trees = t8_forest_get_num_local_trees (problem->forest);
  /* maximum possible delta_t value */
  min_delta_t = problem->T - problem->t;
  for (itree = 0, idata = 0; itree < num_trees; itree++) {
    const t8_eclass_t tree_class = t8_forest_get_tree_class (problem->forest, itree);
    num_elems_in_tree = t8_forest_get_tree_num_leaf_elements (problem->forest, itree);
    for (ielement = 0; ielement < num_elems_in_tree; ielement++, idata++) {
      const t8_element_t *element = t8_forest_get_leaf_element_in_tree (problem->forest, itree, ielement);
      elem_data = (t8_advect_element_data_t *) t8_sc_array_index_locidx (problem->element_data, idata);
      /* Initialize the element's midpoint and volume */
      t8_advect_compute_element_data (problem, elem_data, element, itree);
      /* Compute the minimum diameter */
      diam = t8_forest_element_diam (problem->forest, itree, element);
      T8_ASSERT (diam > 0);
      min_diam = min_diam < 0 ? diam : SC_MIN (min_diam, diam);
      /* Compute the maximum velocity */
      problem->u (elem_data->midpoint, problem->t, u);
      speed = t8_norm (u);
      max_speed = SC_MAX (max_speed, speed);

      /* Compute minimum necessary time step */
      delta_t = problem->T - problem->t;
      if (speed > 0) {
        delta_t = problem->cfl * diam / speed;
      }
      min_delta_t = SC_MIN (delta_t, min_delta_t);
      if (problem->volume_refine >= 0 && problem->min_vol <= 0) {
        /* Compute the minimum volume */
        min_vol = SC_MIN (min_vol, elem_data->vol);
      }
      /* Set the initial condition */
      t8_advect_element_set_phi (problem, idata, problem->phi_0 (elem_data->midpoint, 0, problem->udata_for_phi));
      /* Set the level */
      elem_data->level = scheme->element_get_level (tree_class, element);
      /* Set the faces */
      elem_data->num_faces = scheme->element_get_num_faces (tree_class, element);
      for (iface = 0; iface < elem_data->num_faces; iface++) {
        /* Compute the indices of the face neighbors */

        t8_forest_leaf_face_neighbors (problem->forest, itree, element, &neighbors, iface,
                                       &elem_data->dual_faces[iface], &elem_data->num_neighbors[iface],
                                       &elem_data->neighs[iface], &neigh_eclass, 1);
        for (ineigh = 0; ineigh < elem_data->num_neighbors[iface]; ineigh++) {
          elem_data->neigh_level[iface] = scheme->element_get_level (neigh_eclass, neighbors[ineigh]);
        }

        if (elem_data->num_neighbors[iface] > 0) {
          scheme->element_destroy (neigh_eclass, elem_data->num_neighbors[iface], neighbors);
          T8_FREE (neighbors);
          //t8_global_essentialf("alloc face %i of elem %i\n", iface, ielement);
          elem_data->fluxes[iface] = T8_ALLOC (double, elem_data->num_neighbors[iface]);
        }
        else {
          elem_data->fluxes[iface] = T8_ALLOC (double, 1);
        }
        elem_data->flux_valid[iface] = 0;
      }
    }
  }
  /* Exchange ghost values */
  t8_forest_ghost_exchange_data (problem->forest, problem->phi_values);

  /* Compute the timestep, this has to be done globally */
  sc_MPI_Allreduce (&min_delta_t, &problem->delta_t, 1, sc_MPI_DOUBLE, sc_MPI_MIN, problem->comm);
  if (problem->volume_refine >= 0 && problem->min_vol <= 0) {
    /* Compute the minimum volume.
     * Only in first run and only if volume refinement is active */
    sc_MPI_Allreduce (&min_vol, &problem->min_vol, 1, sc_MPI_DOUBLE, sc_MPI_MIN, problem->comm);
  }
  t8_global_essentialf ("[advect] min diam %g max flow %g  delta_t = %g\n", min_diam, max_speed, problem->delta_t);
}

static void
t8_advect_write_vtk (t8_advect_problem_t *problem)
{
  double *u_and_phi_array[4];
  t8_3D_vec u_temp;
  t8_locidx_t num_local_elements, ielem;
  t8_vtk_data_field_t vtk_data[5];
  t8_advect_element_data_t *elem_data;
  char fileprefix[BUFSIZ];
  int idim;
  double phi;

  /* Allocate num_local_elements doubles to store u and phi values */
  num_local_elements = t8_forest_get_local_num_leaf_elements (problem->forest);
  /* phi */
  u_and_phi_array[0] = T8_ALLOC_ZERO (double, num_local_elements);
  /* phi_0 */
  u_and_phi_array[1] = T8_ALLOC_ZERO (double, num_local_elements);
  /* phi - phi_0 */
  u_and_phi_array[2] = T8_ALLOC_ZERO (double, num_local_elements);
  /* u */
  u_and_phi_array[3] = T8_ALLOC_ZERO (double, 3 * (num_local_elements));

  /* Fill u and phi arrays with their values */
  for (ielem = 0; ielem < num_local_elements; ielem++) {
    elem_data = (t8_advect_element_data_t *) t8_sc_array_index_locidx (problem->element_data, ielem);
    phi = t8_advect_element_get_phi (problem, ielem);
    u_and_phi_array[0][ielem] = phi;
    u_and_phi_array[1][ielem] = problem->phi_0 (elem_data->midpoint, problem->t, problem->udata_for_phi);
    u_and_phi_array[2][ielem] = phi - u_and_phi_array[1][ielem];
    problem->u (elem_data->midpoint, problem->t, u_temp);
    for (idim = 0; idim < 3; idim++) {
      u_and_phi_array[3][3 * ielem + idim] = u_temp[idim];
    }
  }

  /* Write meta data for vtk */
  snprintf (vtk_data[0].description, BUFSIZ, "Num. Solution");
  vtk_data[0].type = T8_VTK_SCALAR;
  vtk_data[0].data = u_and_phi_array[0];
  snprintf (vtk_data[1].description, BUFSIZ, "Ana. Solution");
  vtk_data[1].type = T8_VTK_SCALAR;
  vtk_data[1].data = u_and_phi_array[1];
  snprintf (vtk_data[2].description, BUFSIZ, "Error");
  vtk_data[2].type = T8_VTK_SCALAR;
  vtk_data[2].data = u_and_phi_array[2];
  snprintf (vtk_data[3].description, BUFSIZ, "Flow");
  vtk_data[3].type = T8_VTK_VECTOR;
  vtk_data[3].data = u_and_phi_array[3];
  /* Write filename */
  snprintf (fileprefix, BUFSIZ, "advection_%03i", problem->vtk_count);
  /* Write vtk files */
  if (t8_forest_write_vtk_ext (problem->forest, fileprefix, 1, 1, 1, 1, 0, 0, 0, 4, vtk_data)) {
    t8_debugf ("[Advect] Wrote pvtu to files %s\n", fileprefix);
  }
  else {
    t8_errorf ("[Advect] Error writing to files %s\n", fileprefix);
  }
  /* clean-up */
  T8_FREE (u_and_phi_array[0]);
  T8_FREE (u_and_phi_array[1]);
  T8_FREE (u_and_phi_array[2]);
  T8_FREE (u_and_phi_array[3]);
  problem->vtk_count++;
}

#if T8_ENABLE_DEBUG
static void
t8_advect_print_phi (t8_advect_problem_t *problem)
{
  t8_locidx_t ielement;
  t8_locidx_t num_local_els;
  char buffer[BUFSIZ] = "";
  double phi;

  num_local_els = t8_forest_get_local_num_leaf_elements (problem->forest);
  for (ielement = 0; ielement < (t8_locidx_t) problem->element_data->elem_count; ielement++) {
    phi = t8_advect_element_get_phi (problem, ielement);
    snprintf (buffer + strlen (buffer), BUFSIZ - strlen (buffer), "%.2f |%s ", phi,
              ielement == num_local_els - 1 ? "|" : "");
  }
  t8_debugf ("\t%s\n", buffer);
  /* reset buffer */
  buffer[0] = '\0';
}
#endif

static void
t8_advect_problem_destroy (t8_advect_problem_t **pproblem)
{
  t8_advect_problem_t *problem;

  T8_ASSERT (pproblem != NULL);
  problem = *pproblem;
  if (problem == NULL) {
    return;
  }
  /* destroy elements */
  t8_advect_problem_elements_destroy (problem);
  /* Free the element array */
  sc_array_destroy (problem->element_data);
  if (problem->element_data_adapt != NULL) {
    sc_array_destroy (problem->element_data_adapt);
  }
  sc_array_destroy (problem->phi_values);
  if (problem->phi_values_adapt != NULL) {
    sc_array_destroy (problem->phi_values_adapt);
  }
  /* Unref the forest */
  t8_forest_unref (&problem->forest);
  /* Free the problem and set pointer to NULL */
  T8_FREE (problem);
  *pproblem = NULL;
}

static void
t8_advect_solve (t8_cmesh_t cmesh, t8_flow_function_3d_fn u, t8_example_level_set_fn phi_0, void *ls_data,
                 const int level, const int maxlevel, double T, double cfl, sc_MPI_Comm comm, int adapt_freq,
                 int no_vtk, int vtk_freq, double band_width, int dim, int dummy_op, int volume_refine)
{
  t8_advect_problem_t *problem;
  int iface, ineigh;
  t8_locidx_t itree, ielement, lelement;
  t8_advect_element_data_t *elem_data, *neigh_data = NULL;
  double flux;
  double l_infty, L_2;
  int modulus, time_steps;
  int num_faces;
  int done = 0;
  int adapted_or_partitioned = 0;
  int dual_face;
  t8_element_t **neighs;
  t8_eclass_t neigh_eclass;
  double total_time, solve_time = 0;
  double ghost_exchange_time, ghost_waittime, neighbor_time, flux_time;
  double vtk_time = 0;
  double start_volume, end_volume;
  int hanging, neigh_is_ghost;
  t8_locidx_t neigh_index = -1;
  double phi_plus, phi_minus;

  /* Initialize problem */
  /* start timing */
  total_time = -sc_MPI_Wtime ();
  problem = t8_advect_problem_init (cmesh, u, phi_0, ls_data, level, maxlevel, T, cfl, comm, band_width, dim, dummy_op,
                                    volume_refine);
  t8_advect_problem_init_elements (problem);
  const t8_scheme *scheme = t8_forest_get_scheme (problem->forest);

  if (maxlevel > level) {
    int ilevel;

    for (ilevel = problem->level; ilevel < problem->maxlevel; ilevel++) {
      /* initial adapt */
      t8_advect_problem_adapt (problem, 0);
      /* repartition */
      t8_advect_problem_partition (problem, 0);
      /* Re initialize the elements */
      t8_advect_problem_init_elements (problem);
    }
    adapted_or_partitioned = 1;
  }
  start_volume = t8_advect_level_set_volume (problem);
  t8_global_essentialf ("[advect] Start volume %e\n", start_volume);

#if T8_ENABLE_DEBUG
  t8_advect_print_phi (problem);
#endif

  /* Set initialization runtime */
  sc_stats_set1 (&problem->stats[ADVECT_INIT], total_time + sc_MPI_Wtime (), advect_stat_names[ADVECT_INIT]);

  time_steps = (int) (T / problem->delta_t);
  t8_global_essentialf ("[advect] Starting with Computation. Level %i."
                        " Adaptive levels %i."
                        " End time %g. delta_t %g. cfl %g. %i time steps.\n",
                        level, maxlevel - level, T, problem->delta_t, problem->cfl, time_steps);
  T8_ASSERT (problem->delta_t > 0);
  T8_ASSERT (time_steps > 0);
  /* Controls how often we print the time step to stdout */
  modulus = SC_MAX (1, time_steps / 10);
  for (problem->num_time_steps = 0; !done; problem->num_time_steps++, problem->t += problem->delta_t) {
    if (problem->num_time_steps % modulus == modulus - 1) {
      t8_global_essentialf ("[advect] Step %i  %" T8_GLOIDX_FORMAT " elems\n", problem->num_time_steps + 1,
                            t8_forest_get_global_num_leaf_elements (problem->forest));
    }
    /* Time loop */

    /* Print vtk */
    if (!no_vtk && problem->num_time_steps % vtk_freq == 0) {
      vtk_time -= sc_MPI_Wtime ();
      t8_advect_write_vtk (problem);
      vtk_time += sc_MPI_Wtime ();
    }
    /* Measure element count */
    sc_stats_accumulate (&problem->stats[ADVECT_ELEM_AVG], t8_forest_get_global_num_leaf_elements (problem->forest));

    solve_time -= sc_MPI_Wtime ();
    for (itree = 0, lelement = 0; itree < t8_forest_get_num_local_trees (problem->forest); itree++) {
      /* tree loop */
      /* Get the scheme of this tree */
      for (ielement = 0; ielement < t8_forest_get_tree_num_leaf_elements (problem->forest, itree);
           ielement++, lelement++) {
        /* element loop */
        /* Get a pointer to the element data */
        elem_data = (t8_advect_element_data_t *) t8_sc_array_index_locidx (problem->element_data, lelement);
        const t8_element_t *elem = t8_forest_get_leaf_element_in_tree (problem->forest, itree, ielement);
        num_faces = elem_data->num_faces;
        /* Compute left and right flux */
        for (iface = 0; iface < num_faces; iface++) {
          if (elem_data->flux_valid[iface] <= 0 || adapted_or_partitioned) {

            /* Compute flux at this face */
            if (adapted_or_partitioned) {
              /* We changed the mesh, so that we have to calculate the neighbor
               * indices again. */
              if (elem_data->num_neighbors[iface] > 0) {
                T8_FREE (elem_data->neighs[iface]);
                T8_FREE (elem_data->dual_faces[iface]);
                elem_data->flux_valid[iface] = -1;
              }
              T8_FREE (elem_data->fluxes[iface]);
              neighbor_time = -sc_MPI_Wtime ();
              t8_forest_leaf_face_neighbors (problem->forest, itree, elem, &neighs, iface,
                                             &elem_data->dual_faces[iface], &elem_data->num_neighbors[iface],
                                             &elem_data->neighs[iface], &neigh_eclass, 1);
              for (ineigh = 0; ineigh < elem_data->num_neighbors[iface]; ineigh++) {
                elem_data->neigh_level[iface] = scheme->element_get_level (neigh_eclass, neighs[ineigh]);
              }

              T8_ASSERT (neighs != NULL || elem_data->num_neighbors[iface] == 0);
              if (neighs != NULL) {
                scheme->element_destroy (neigh_eclass, elem_data->num_neighbors[iface], neighs);

                T8_FREE (neighs);
              }

              /* Allocate flux storage */
              elem_data->fluxes[iface] = T8_ALLOC (double, SC_MAX (1, elem_data->num_neighbors[iface]));
              elem_data->flux_valid[iface] = 0;

              neighbor_time += sc_MPI_Wtime ();
              sc_stats_accumulate (&problem->stats[ADVECT_NEIGHS], neighbor_time);
              /* We want to count all runs over the solver time as one */
              problem->stats[ADVECT_NEIGHS].count = 1;
            }

            /* sensible default */
            neigh_data = NULL;
            neigh_is_ghost = 0;
            /* Compute whether this is a hanging face
             * and whether the first neighbor is a ghost */
            if (elem_data->num_neighbors[iface] >= 1) {

              neigh_index = elem_data->neighs[iface][0];
              neigh_is_ghost = neigh_index >= t8_forest_get_local_num_leaf_elements (problem->forest);
              hanging = elem_data->level != elem_data->neigh_level[iface];
            }
            else {
              hanging = 0;
              neigh_is_ghost = 0;
            }
            flux_time = -sc_MPI_Wtime ();
            if (problem->dim == 1) {
              if (elem_data->num_neighbors[iface] == 0) {
                T8_ASSERT (elem_data->num_neighbors[iface] <= 0);
                /* This is a boundary */
                neigh_index = -1;
              }
              flux = t8_advect_flux_upwind_1d (problem, lelement, neigh_index, iface);
              elem_data->fluxes[iface][0] = flux;
              elem_data->flux_valid[iface] = 1;
            }
            else {
              T8_ASSERT (problem->dim == 2 || problem->dim == 3);
              /* Check whether the flux for the neighbor element was computed */
              /* Get a pointer to the neighbor element */
              if (elem_data->num_neighbors[iface] >= 1 && !neigh_is_ghost) {
                neigh_data = (t8_advect_element_data_t *) t8_sc_array_index_locidx (problem->element_data, neigh_index);
              }

              /* Get the phi value at the current element */
              phi_plus = t8_advect_element_get_phi (problem, lelement);
              if (elem_data->num_neighbors[iface] == 1) {
                dual_face = elem_data->dual_faces[iface][0];
                /* There is exactly one face-neighbor */
                /* get the phi value at the neighbor element */
                phi_minus = t8_advect_element_get_phi (problem, neigh_index);
                flux = t8_advect_flux_upwind (problem, phi_plus, phi_minus, itree, elem, iface);

                elem_data->flux_valid[iface] = 1;
                elem_data->fluxes[iface][0] = flux;

                /* If this face is not hanging, we can set the
                 * flux of the neighbor element as well */
                if (!adapted_or_partitioned && !neigh_is_ghost && !hanging) {
                  if (neigh_data->flux_valid[dual_face] < 0) {
                    neigh_data->fluxes[dual_face] = T8_ALLOC (double, 1);
                    neigh_data->dual_faces[dual_face] = T8_ALLOC (int, 1);
                    neigh_data->neighs[dual_face] = T8_ALLOC (t8_locidx_t, 1);
                  }
                  SC_CHECK_ABORT (dual_face < neigh_data->num_faces, "num\n");
                  //         SC_CHECK_ABORT (neigh_data->num_neighbors[dual_face] == 1, "dual face\n");
                  neigh_data->fluxes[dual_face][0] = -flux;
                  neigh_data->dual_faces[dual_face][0] = iface;
                  neigh_data->neighs[dual_face][0] = lelement;
                  neigh_data->flux_valid[dual_face] = 1;
                }
              }
              else if (elem_data->num_neighbors[iface] > 1) {
                flux = t8_advect_flux_upwind_hanging (problem, lelement, itree, elem, iface, adapted_or_partitioned);
              }
              else {
                /* This element is at the domain boundary */
                /* We enforce outflow boundary conditions */
                T8_ASSERT (elem_data->num_neighbors[iface] <= 0);
                t8_advect_boundary_set_phi (problem, lelement, &phi_minus);

                flux = t8_advect_flux_upwind (problem, phi_plus, phi_minus, itree, elem, iface);

                elem_data->flux_valid[iface] = 1;
                elem_data->fluxes[iface][0] = flux;
              }
            }
            flux_time += sc_MPI_Wtime ();

            sc_stats_accumulate (&problem->stats[ADVECT_FLUX], flux_time);
            /* We want to count all runs over the solver time as one */
            problem->stats[ADVECT_FLUX].count = 1;
          }
        }
        if (problem->dummy_op) {
          /* simulate more load per element */
          int i, j;
          double *phi_values;
          double dummy_time = -sc_MPI_Wtime ();
          phi_values = (double *) t8_sc_array_index_locidx (problem->phi_values, ielement);
          phi_values[1] = 0;
          for (i = 1; i < 5; i++) {
            phi_values[1] *= i;
            for (j = 0; j < 5; j++) {
              phi_values[1] += pow (i, j);
            }
          }
          dummy_time += sc_MPI_Wtime ();
          sc_stats_accumulate (&problem->stats[ADVECT_DUMMY], dummy_time);
          problem->stats[ADVECT_DUMMY].count = 1;
        }
        /* Compute time step */
        t8_advect_advance_element (problem, lelement);
      }
    }
    adapted_or_partitioned = 0;
    /* Store the advanced phi value in each element */
    t8_advect_project_element_data (problem);
    solve_time += sc_MPI_Wtime ();
    if (maxlevel > level) {
      /* Adapt the mesh after adapt_freq time steps */
      if (problem->num_time_steps % adapt_freq == adapt_freq - 1) {
        adapted_or_partitioned = 1;
        t8_advect_problem_adapt (problem, 1);
        t8_advect_problem_partition (problem, 1);
      }
    }

    /* Exchange ghost values */
    ghost_exchange_time = -sc_MPI_Wtime ();
    t8_forest_ghost_exchange_data (problem->forest, problem->phi_values);
    ghost_exchange_time += sc_MPI_Wtime ();
    sc_stats_accumulate (&problem->stats[ADVECT_GHOST_EXCHANGE], ghost_exchange_time);
    ghost_waittime = t8_forest_profile_get_ghostexchange_waittime (problem->forest);
    sc_stats_accumulate (&problem->stats[ADVECT_GHOST_WAIT], ghost_waittime);
    /* We want to count all runs over the solver time as one */
    problem->stats[ADVECT_GHOST_EXCHANGE].count = 1;
    problem->stats[ADVECT_GHOST_WAIT].count = 1;

    if (problem->t + problem->delta_t > problem->T) {
      /* Ensure that the last time step is always the given end time */
      problem->delta_t = problem->T - problem->t;
    }
    /* Check whether we are finished */
    if (problem->t >= problem->T) {
      done = 1;
    }
  } /* End element loop */
  if (!no_vtk) {
    vtk_time -= sc_MPI_Wtime ();
    /* Print last time step vtk */
    t8_advect_write_vtk (problem);
    vtk_time += sc_MPI_Wtime ();
  }
  /* Compute runtime */
  total_time += sc_MPI_Wtime ();
  sc_stats_set1 (&problem->stats[ADVECT_TOTAL], total_time, advect_stat_names[ADVECT_TOTAL]);
  sc_stats_set1 (&problem->stats[ADVECT_SOLVE], solve_time, advect_stat_names[ADVECT_SOLVE]);
  sc_stats_set1 (&problem->stats[ADVECT_IO], vtk_time, advect_stat_names[ADVECT_IO]);
  /* Compute volume loss */

  end_volume = t8_advect_level_set_volume (problem);
  t8_global_essentialf ("[advect] End volume %e\n", end_volume);

  sc_stats_set1 (&problem->stats[ADVECT_VOL_LOSS], 100 * (1 - end_volume / start_volume),
                 advect_stat_names[ADVECT_VOL_LOSS]);

  /* Compute l_infty error */
  l_infty = t8_advect_l_infty_rel (problem, phi_0, 0.025);
  L_2 = t8_advect_l_2_rel (problem, phi_0, 0.025);
  t8_global_essentialf ("[advect] Done. t = %g \t l_infty error:\t%e\tL_2:\t%e\n", problem->t, l_infty, L_2);

  sc_stats_set1 (&problem->stats[ADVECT_ERROR_INF], l_infty, advect_stat_names[ADVECT_ERROR_INF]);
  sc_stats_set1 (&problem->stats[ADVECT_ERROR_2], L_2, advect_stat_names[ADVECT_ERROR_2]);
  sc_stats_compute (problem->comm, ADVECT_NUM_STATS, problem->stats);
  sc_stats_print (t8_get_package_id (), SC_LP_ESSENTIAL, ADVECT_NUM_STATS, problem->stats, 1, 1);
  /* clean-up */
  t8_advect_problem_destroy (&problem);
}

int
main (int argc, char *argv[])
{
  int mpiret;
  sc_options_t *opt;
  char help[BUFSIZ];
  const char *mshfile = NULL;
  int level, reflevel, dim, cube_type, dummy_op;
  int parsed, helpme, no_vtk, vtk_freq, adapt_freq;
  int volume_refine;
  int flow_arg, use_cad_geometry;
  double T, cfl, band_width;
  t8_levelset_sphere_data_t ls_data;
  /* brief help message */

  /* long help message */

  snprintf (help, BUFSIZ,
            "This program solves the advection equation on "
            "a given geometry.\n");
  mpiret = sc_MPI_Init (&argc, &argv);
  SC_CHECK_MPI (mpiret);

  sc_init (sc_MPI_COMM_WORLD, 1, 1, NULL, SC_LP_ESSENTIAL);
#if T8_ENABLE_DEBUG
  t8_init (SC_LP_DEBUG);
#else
  t8_init (SC_LP_ESSENTIAL);
#endif

  /* initialize command line argument parser */
  opt = sc_options_new (argv[0]);

  sc_options_add_switch (opt, 'h', "help", &helpme, "Display a short help message.");
  sc_options_add_int (opt, 'u', "flow", &flow_arg, 0,
                      "Choose the flow field u.\n"
                      "\t\t1 - Constant 1 in x-direction.\n"
                      "\t\t2 - Constant 1 in x,y, and z.\n"
                      "\t\t3 - A turbulent flow in a cube with zero outflow.\n"
                      "\t\t\tIt reverses direction at t = 0.5.\n"
                      "\t\t4 - 2D rotation around (0.5,0.5).\n"
                      "\t\t5 - 2D flow around circle at (0.5,0.5)"
                      "with radius 0.15.\n"
                      "\t\t6 - A solution to the stokes equation on a spherical shell.\n"
                      "\t\t7 - Flow past a rotating cylinder of radius of 0.5"
                      " around the z-axis. This flow is defined for a specific"
                      " mesh, which can be generated with Gmsh and the .geo"
                      " files 't8_advection_generate_channel.geo' and"
                      " 't8_advection_generate_channel_2d.geo'. These meshes"
                      " can also be used with the curved geometry.\n");
  sc_options_add_int (opt, 'l', "level", &level, 0, "The minimum refinement level of the mesh.");
  sc_options_add_int (opt, 'r', "rlevel", &reflevel, 0, "The number of adaptive refinement levels.");
  sc_options_add_int (opt, 'e', "elements", &cube_type, -1,
                      "If specified the coarse mesh is a hypercube\n\t\t\t\t     consisting of the"
                      " following elements:\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 - triangle/quad (hybrid 2d).\n"
                      "\t\t8 - tet/hex/prism (hybrid 3d).\n"
                      "\t\t9 - pyramid.\n");
  sc_options_add_string (opt, 'f', "mshfile", &mshfile, NULL,
                         "If specified, the cmesh is constructed from a .msh file with "
                         "the given prefix.\n\t\t\t\t     The files must end in .msh "
                         "and be in ASCII format version 2. -d must be specified.");
  sc_options_add_int (opt, 'd', "dim", &dim, -1, "In combination with -f: The dimension of the mesh. 1 <= d <= 3.");

  sc_options_add_switch (opt, 'c', "cad", &use_cad_geometry,
                         "In combination with -f: Use the cad geometry, only viable if a "
                         ".brep file of the same name is present.");

  sc_options_add_double (opt, 'T', "end-time", &T, 1, "The duration of the simulation. Default: 1");

  sc_options_add_double (opt, 'C', "CFL", &cfl, 1, "The cfl number to use. Default: 1");
  sc_options_add_double (opt, 'b', "band-width", &band_width, 1,
                         "Control the width of the refinement band around\n"
                         "\t\t\t\t     the zero level-set. Default 1.");

  sc_options_add_int (opt, 'a', "adapt-freq", &adapt_freq, 1,
                      "Controls how often the mesh is readapted. "
                      "A value of i means, every i-th time step.");

  sc_options_add_int (opt, 'v', "vtk-freq", &vtk_freq, 1,
                      "How often the vtk output is produced "
                      "\n\t\t\t\t     (after how many time steps). "
                      "A value of 0 is equivalent to using -o.");

  sc_options_add_switch (opt, 'o', "no-vtk", &no_vtk,
                         "Suppress vtk output. "
                         "Overwrites any -v setting.");

  sc_options_add_switch (opt, 's', "simulate", &dummy_op,
                         "Simulate more load per element. "
                         "In each iteration, useless dummy operations\n "
                         "\t\t\t\t     are performed per element. Decreases the "
                         "performance!");
  sc_options_add_double (opt, 'X', "Xcoord", &ls_data.M[0], 0.6,
                         "The X-Coordinate of the middlepoint"
                         "of the sphere. Default is 0.6.");
  sc_options_add_double (opt, 'Y', "Ycoord", &ls_data.M[1], 0.6,
                         "The Y-Coordinate of the middlepoint"
                         "of the sphere. Default is 0.6.");
  sc_options_add_double (opt, 'Z', "Zcoord", &ls_data.M[2], 0.6,
                         "The Z-Coordinate of the middlepoint"
                         "of the sphere. Default is 0.6.");
  sc_options_add_double (opt, 'R', "Radius", &ls_data.radius, 0.25,
                         "The radius of the Sphere."
                         "Default is 0.25.");

  sc_options_add_int (opt, 'V', "volume-refine", &volume_refine, -1,
                      "Refine elements close to the 0 level-set only "
                      "if their volume is smaller than the l+V-times refined\n"
                      "\t\t\t\t     smallest element int the mesh.");

  parsed = sc_options_parse (t8_get_package_id (), SC_LP_ERROR, opt, argc, argv);
  if (helpme) {
    /* display help message and usage */
    t8_global_essentialf ("%s\n", help);
    sc_options_print_usage (t8_get_package_id (), SC_LP_ERROR, opt, NULL);
  }
  else if (parsed >= 0 && 1 <= flow_arg && flow_arg <= 7 && 0 <= level && 0 <= reflevel && 0 <= vtk_freq
           && ((mshfile != NULL && 0 < dim && dim <= 3) || (1 <= cube_type && cube_type <= 9)) && band_width >= 0) {
    t8_cmesh_t cmesh;
    t8_flow_function_3d_fn u;

    if (mshfile == NULL) {
      switch (cube_type) {
      case 7:
        dim = 2;
        break;
      case 8:
        dim = 3;
        break;
      case 9:
        dim = 3;
        break;
      default:
        dim = t8_eclass_to_dimension[cube_type];
        T8_ASSERT (cube_type < 7);
      }
    }
    /* Set level-set midpoint coordinates to zero for unused dimensions. */
    if (cube_type == 2 || cube_type == 3 || cube_type == 7) {
      ls_data.M[2] = 0;
    }
    if (cube_type == 1) {
      ls_data.M[1] = ls_data.M[2] = 0;
    }

    cmesh = t8_advect_create_cmesh (sc_MPI_COMM_WORLD, cube_type, mshfile, level, dim, use_cad_geometry);
    u = t8_advect_choose_flow (flow_arg);
    if (!no_vtk) {
      t8_cmesh_vtk_write_file (cmesh, "advection_cmesh");
    }
    /* Computation */
    t8_advect_solve (cmesh, u, t8_levelset_sphere, &ls_data, level, level + reflevel, T, cfl, sc_MPI_COMM_WORLD,
                     adapt_freq, no_vtk, vtk_freq, band_width, dim, dummy_op, volume_refine);
  }
  else {
    /* wrong usage */
    t8_global_productionf ("\n\tERROR:Wrong usage.\n\n");
    sc_options_print_usage (t8_get_package_id (), SC_LP_ERROR, opt, NULL);
  }

  sc_options_destroy (opt);
  sc_finalize ();
  mpiret = sc_MPI_Finalize ();
  SC_CHECK_MPI (mpiret);
  return 0;
}