/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* file:     coarsen_3d.c                                                   */
/*                                                                          */
/* description:  coarsening of 3 dim. hierarchical meshes;                  */
/*               file contains all routines depending on DIM == 3;          */
/*                                                                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  authors:   Alfred Schmidt                                               */
/*             Zentrum fuer Technomathematik                                */
/*             Fachbereich 3 Mathematik/Informatik                          */
/*             Universitaet Bremen                                          */
/*             Bibliothekstr. 2                                             */
/*             D-28359 Bremen, Germany                                      */
/*                                                                          */
/*             Kunibert G. Siebert                                          */
/*             Institut fuer Mathematik                                     */
/*             Universitaet Augsburg                                        */
/*             Universitaetsstr. 14                                         */
/*             D-86159 Augsburg, Germany                                    */
/*                                                                          */
/*  http://www.mathematik.uni-freiburg.de/IAM/ALBERTA                       */
/*                                                                          */
/*  (c) by A. Schmidt and K.G. Siebert (1996-2003)                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/

/****************************************************************************/
/*  coarsen_element_3d:  coarsens a single element of the coarsening patch; */
/*  dof's in the interior of the element are removed and dof's in the faces */
/*  of the element are removed if neighbour has been coarsend or if the face*/
/*  is part of the domain's boundary                                        */
/****************************************************************************/

static void
coarsen_element_3d(MESH *mesh, RC_LIST_EL *coarse_list, int is_periodic)
{
  EL       *el = coarse_list->el_info.el, *child[2];
  EL       *neigh;
  int      dir, el_type, i, node;

  child[0] = el->child[0]; child[1] = el->child[1];
  el_type = coarse_list->el_info.el_type;

/****************************************************************************/
/*  Information about patch neighbours is still valid! But edge and face    */
/*  dof's in a common face of patch neighbours have to be removed           */
/****************************************************************************/

  for (dir = 0; dir < 2; dir++) {
    FLAGS adm_flags = ADM_FLAGS_DFLT;
    
    neigh =
      coarse_list->neigh[dir] ? coarse_list->neigh[dir]->el_info.el : NULL;

    if (is_periodic && neigh && neigh->child[0] &&
	el->dof[0] != neigh->dof[0] && el->dof[0] != neigh->dof[1]) {
      /* fake "no neighbour", but do not free the DOF-indices in this
       * run; this will be done when the we are called again with el
       * == neigh; then our childs have been gone and free_dof() will
       * be called without ADM_PERIODIC, the DOF-indices will be freed
       * then.
       */
      adm_flags |= ADM_PERIODIC;
      neigh = NULL;
    }

    if (!neigh || !neigh->child[0]) {
/****************************************************************************/
/*  boundary face or  neigh has been coarsend: free the dof's in the face   */
/****************************************************************************/

      if (mesh->n_dof[EDGE]) {
	node = mesh->node[EDGE] + n_child_edge_3d[el_type][0][dir];
	free_dof(child[0]->dof[node], mesh, EDGE, adm_flags);
      }
      if (mesh->n_dof[FACE]) {
	node = mesh->node[FACE] + n_child_face_3d[el_type][0][dir];
	free_dof(child[0]->dof[node], mesh, FACE, adm_flags);
	node = mesh->node[FACE] + n_child_face_3d[el_type][1][dir];
	free_dof(child[1]->dof[node], mesh, FACE, adm_flags);
      }
    }
  }  /*       for (dir = 0; dir < 2; dir++)                                 */

/****************************************************************************/
/*  finally remove the interior dof's: in the common face of child[0] and   */
/*  child[1] and at the two barycenter                                      */
/****************************************************************************/

  if (mesh->n_dof[FACE])
  {
    node = mesh->node[FACE];
    free_dof(child[0]->dof[node], mesh, FACE, ADM_FLAGS_DFLT);
  }


  if (mesh->n_dof[CENTER])
  {
    node = mesh->node[CENTER];
    for (i = 0; i < 2; i++)
      free_dof(child[i]->dof[node], mesh, CENTER, ADM_FLAGS_DFLT);
  }

/****************************************************************************/
/*  get new data on parent and transfer data from children to parent        */
/****************************************************************************/

  el->child[0] = NULL;
  el->child[1] = (EL *) AI_get_leaf_data(mesh);

  if (el->child[1] &&
      ((MESH_MEM_INFO *)mesh->mem_info)->leaf_data_info->coarsen_leaf_data)
    ((MESH_MEM_INFO *)mesh->mem_info)->
      leaf_data_info->coarsen_leaf_data(el, child);

  free_element(child[0], mesh);
  free_element(child[1], mesh);

  el->mark++;
  
  mesh->n_elements--;
  mesh->n_hier_elements -= 2;

  return;
}

/****************************************************************************/
/*  get_coarse_patch_3d:  gets the patch for coarsening starting on element */
/*  el_info->el in direction of neighbour [3-dir]; returns 1 if a boundary  */
/*  reached and 0 if we come back to the starting element.                  */
/*                                                                          */
/*  We complete the loop also in the case of a incompatible                 */
/*  coarsening patch since then all marks of patch elements are reset by    */
/*  do_coarse_patch() and this minimizes calls of traverse_neighbour();     */
/*  if we reach a boundary while looping around the edge we loop back to    */
/*  the starting element before we return                                   */
/****************************************************************************/
 
static bool get_coarse_patch_3d(const EL_INFO *el_info, DOF *edge[2], int dir,
				RC_LIST_EL coarse_list[], int *n_neigh,
				int *is_periodic)
{
  FUNCNAME("get_coarse_patch_3d");
  static const U_CHAR next_el[6][2] = {{3,2},{1,3},{1,2},{0,3},{0,2},{0,1}};
  const EL_INFO *neigh_info;
  EL  *el, *neigh;
  int i, j, k, opp_v, edge_no;

  el = el_info->el;

  if ((neigh = el_info->neigh[3-dir]) == NULL)
    return true;

  opp_v = el_info->opp_vertex[3-dir];

  neigh_info = traverse_neighbour(stack_3d, el_info, 3-dir);
  DEBUG_TEST_EXIT(neigh == neigh_info->el,
	      "neigh %d and neigh_info->el %d are not identical\n", 
	      INDEX(neigh), INDEX(neigh_info->el));

  while (neigh != el) {

/****************************************************************************/
/*  we have to go back to the starting element via opp_v values             */
/*  correct information is produce by AI_set_neighs_on_patch()              */
/****************************************************************************/
    coarse_list[*n_neigh].opp_vertex[0] = opp_v;  
    coarse_list[*n_neigh].el_info       = *neigh_info;

    for (j = 0; j < N_VERTICES_3D; j++)
      if (neigh->dof[j][0] == edge[0][0])  break;
    for (k = 0; k < N_VERTICES_3D; k++)
      if (neigh->dof[k][0] == edge[1][0])  break;

    DEBUG_TEST_EXIT(j < N_VERTICES_3D  &&  k < N_VERTICES_3D,
       "dof %d or dof %d not found on element %d with nodes (%d %d %d %d)\n", 
		edge[0][0], edge[1][0], INDEX(neigh), neigh->dof[0][0],
		neigh->dof[1][0], neigh->dof[2][0], neigh->dof[3][0]);

    if (neigh->dof[j] != edge[0])
      *is_periodic = true;

    edge_no = edge_of_vertices_3d[j][k];
    coarse_list[*n_neigh].flags =
      (edge_no == 0) ? RCLE_COARSE_EDGE_COMPAT : RCLE_NONE;

    ++*n_neigh;

    /***************************************************************
     *
     * finished with current neighbour, now look out for the next.
     *
     **************************************************************/

/****************************************************************************/
/*  get the direction of the next neighbour				    */
/****************************************************************************/

    if (next_el[edge_no][0] != opp_v)
      i = next_el[edge_no][0];
    else
      i = next_el[edge_no][1];

    opp_v = neigh_info->opp_vertex[i];
    if ((neigh = neigh_info->neigh[i]))
    {
      neigh_info = traverse_neighbour(stack_3d, neigh_info, i);
      DEBUG_TEST_EXIT(neigh == neigh_info->el,
	"neigh %d and neigh_info->el %d are not identical\n", 
		  INDEX(neigh), INDEX(neigh_info->el));
    }
    else
      break;
  }

  if (neigh == el)  
    return false; /* boundary not reached */

/****************************************************************************/
/*  the domain's boundary is reached; loop back to the starting el          */
/****************************************************************************/

  i = *n_neigh-1;
  opp_v = coarse_list[i].opp_vertex[0];
  do {
    DEBUG_TEST_EXIT(neigh_info->neigh[opp_v]  &&  i > 0,
		"while looping back domains boundary was reached or i == 0\n");
    opp_v = coarse_list[i--].opp_vertex[0];
    neigh_info = traverse_neighbour(stack_3d, neigh_info, opp_v);
  } while(neigh_info->el != el);

  return true; /* boundary reached */
}


/****************************************************************************/
/*  coarse_patch_3d: first rebuild the dofs on the parents then do          */
/*  restriction of data (if possible) and finally coarsen the patch elements*/
/****************************************************************************/

static void coarse_patch_3d(MESH *mesh, RC_LIST_EL *coarse_list,
			    int n_neigh, int bound, int is_periodic)
{
  EL   *el = coarse_list->el_info.el;
  REAL *new_coord = el->new_coord;
  int  i, node;

/****************************************************************************/
/*  reactivate DOFs                                                         */
/****************************************************************************/
  if (!is_periodic) {
    for (i = 0; i < n_neigh; i++) {
      AI_reactivate_dof(mesh, coarse_list[i].el_info.el, NULL, NULL);
    }
  } else {
    DOF *edge_twins[N_EDGES_3D] = { NULL, };
    DOF *face_twins[N_FACES_3D] = { NULL, };
    EL  *neigh;
    int dir;
    
    /* inherit periodic DOF indices from the first element in the list */
    edge_twins[0] = el->dof[mesh->node[EDGE+0]];
    for (i = 0; i < n_neigh; i++) {
      /* Inherit the face indices from _both_ neighbours,
       * AI_reactivate_dof() will check whether those are really
       * set. Otherwise periodic DOFs would not be set correctly on
       * "closed" (interior) patches
       */
      for (dir = 0; dir < 2; dir ++) {
	RC_LIST_EL *rcl_neigh = coarse_list[i].neigh[dir];
	if (rcl_neigh) {
	  neigh = rcl_neigh->el_info.el;
	  face_twins[3-dir] =
	    neigh->dof[mesh->node[FACE+coarse_list[i].opp_vertex[dir]]];
	} else {
	  face_twins[3-dir] = NULL;
	}
      }
      AI_reactivate_dof(mesh, el, edge_twins, face_twins);
    }
  }

  if (call_coarse_restrict_3d)
/****************************************************************************/
/*  restrict dof vectors to the parents on the patch                        */
/****************************************************************************/
    coarse_restrict(mesh, ((MESH_MEM_INFO *)mesh->mem_info)->dvlist,
		    coarse_list, n_neigh);

  if (call_coarse_restrict_np_3d) {
    if (is_periodic) {
      RC_LIST_EL *coarse_list_np = get_rc_list(mesh);
      int n_neigh_np, n_left = n_neigh;
    
      n_neigh_np = AI_split_rc_list_3d(coarse_list, coarse_list_np, n_left);
      do {
	coarse_restrict(mesh, ((MESH_MEM_INFO *)mesh->mem_info)->dvlist_np,
			coarse_list_np + n_neigh - n_left, n_neigh_np);
	if ((n_left -= n_neigh_np)) {
	  n_neigh_np =
	    AI_split_rc_list_3d(NULL,
				coarse_list_np + n_neigh - n_left, n_left);
	}
      } while (n_left);
    
      free_rc_list(mesh, coarse_list_np);
    } else {
      coarse_restrict(mesh, ((MESH_MEM_INFO *)mesh->mem_info)->dvlist_np,
		      coarse_list, n_neigh);
    }
  }

/****************************************************************************/
/*  and now start to coarsen the patch:                                     */
/****************************************************************************/
/*  remove dof's of the coarsening edge                                     */
/****************************************************************************/

  free_dof(el->child[0]->dof[3], mesh, VERTEX, ADM_FLAGS_DFLT);
  mesh->n_vertices--;
  mesh->per_n_vertices--;

  if (mesh->n_dof[EDGE]) {
    node = mesh->node[EDGE]+2;
    free_dof(el->child[0]->dof[node], mesh, EDGE, ADM_FLAGS_DFLT);
    free_dof(el->child[1]->dof[node], mesh, EDGE, ADM_FLAGS_DFLT);
  }

  if (is_periodic) {
    DOF *vdof;

    /* The code below relies on not looping twice through the same
     * periodic wall; if there is a boundary, then coarse_list[0] must
     * be a boundary element as well as coarse_list[n_neigh-1].
     */
    mesh->n_edges--;
    mesh->n_faces--;

    vdof = el->child[0]->dof[3];
    for (i = 1; i < n_neigh; i++) {
      el = coarse_list[i].el_info.el;
      if (el->child[0]->dof[3] != vdof) {
	vdof = el->child[0]->dof[3];
	free_dof(vdof, mesh, VERTEX, ADM_PERIODIC);
	mesh->n_vertices--;

	if (mesh->n_dof[EDGE]) {
	  node = mesh->node[EDGE]+2;
	  free_dof(el->child[0]->dof[node], mesh, EDGE, ADM_PERIODIC);
	  free_dof(el->child[1]->dof[node], mesh, EDGE, ADM_PERIODIC);
	}
	mesh->n_edges -= 1 + 1;
	mesh->n_faces--;
      }
    }
  }
  

/*--------------------------------------------------------------------------*/
/*--- and now coarsen single elements                                    ---*/
/*--------------------------------------------------------------------------*/

  for (i = 0; i < n_neigh; i++) {
    coarsen_element_3d(mesh, coarse_list+i, is_periodic);
    if (is_periodic && new_coord) {
      el = coarse_list[i].el_info.el;
      /* If this is a periodic mesh then the new_coord pointers may
       * be different, free all of them.
       */
      if (el->new_coord != new_coord) {
	free_real_d(mesh, new_coord);
	new_coord = coarse_list[i].el_info.el->new_coord;
      }
    }
    coarse_list[i].el_info.el->new_coord = NULL;
  }

  /* free also the last (or only) new_coord memory */
  if (new_coord)
    free_real_d(mesh, new_coord);

/****************************************************************************/
/*  if the coarsening edge is an interior edge there are  n_neigh + 1 edges */
/*  and 2*n_neigh + 1 faces removed; if it is a boundary edge it is one     */
/*  more edge and one more face                                             */
/****************************************************************************/

  mesh->n_edges -= n_neigh + 1 + !!bound;
  mesh->per_n_edges -= n_neigh + 1 + !!bound;
  mesh->n_faces -= 2*n_neigh + !!bound;
  mesh->per_n_faces -= 2*n_neigh + !!bound;

  return;
}

/*--------------------------------------------------------------------------*/
/*  Propagate coarsening information over the whole hierarchy		    */
/*  by POSTORDER traversal of the hierarchy tree               		    */
/*  leaves:      'increment' coarsening mark                  		    */
/*  inner nodes: set coarsening mark to                                     */
/*               min(0,child[0].mark+1,child[1].mark+1)                     */
/*--------------------------------------------------------------------------*/

static void spread_coarsen_mark_fct_3d(const EL_INFO *el_info, void *data)
{
  EL      *el = el_info->el;
  S_CHAR  mark;

  if (el->child[0])
  {  
/*--------------------------------------------------------------------------*/
/* interior node of the tree                                                */
/*--------------------------------------------------------------------------*/
    mark = MAX(el->child[0]->mark, el->child[1]->mark);
    el->mark = MIN(mark + 1, 0);
  }
  else
  {
/*--------------------------------------------------------------------------*/
/* leaf node of the tree                                                    */
/*--------------------------------------------------------------------------*/
    if (el->mark < 0)  el->mark -= 1;
  }
  return;
}

static void spread_coarsen_mark_3d(MESH *mesh)
{
  mesh_traverse(mesh, -1, CALL_EVERY_EL_POSTORDER, 
		spread_coarsen_mark_fct_3d, NULL);
  return;
}

/*--------------------------------------------------------------------------*/
/*  cleanup_after_coarsen_3d:                                 		    */
/*  resets the element marks                                                */
/*--------------------------------------------------------------------------*/

static void cleanup_after_coarsen_fct_3d(const EL_INFO *el_info, void *data)
{
  EL       *el = el_info->el;
  el->mark = MAX(el->mark, 0);
}

static void cleanup_after_coarsen_3d(MESH *mesh)
{
  mesh_traverse(mesh, -1, CALL_LEAF_EL, cleanup_after_coarsen_fct_3d, NULL);
  return;
}

/*--------------------------------------------------------------------------*/
/*  can_coarsen_patch_3d:  if patch can be coarsened return true, else false*/
/*  and reset the element marks                                             */
/*--------------------------------------------------------------------------*/

static int can_coarsen_patch_3d(MESH *mesh, RC_LIST_EL *coarse_list,
				int n_neigh)
{
  /* FUNCNAME("can_coarsen_patch_3d"); */
  int      i, j;
  EL       *el;

  for (i = 0; i < n_neigh; i++)
  {
    el = coarse_list[i].el_info.el;

    if (el->mark >= 0  ||  el->child[0] == NULL)
    {
/*--------------------------------------------------------------------------*/
/*  element must not be coarsened or element is a leaf element; reset the   */
/*  the coarsening flag on all those elements that have to be coarsened with*/
/*  this element                                                            */
/*--------------------------------------------------------------------------*/
      el->mark = 0;
      for (j = 0; j < n_neigh; j++)
	if (coarse_list[j].flags & RCLE_COARSE_EDGE_COMPAT)
	  coarse_list[j].el_info.el->mark = 0;
      return(false);
    }
    else if (el->child[0]->mark >= 0  ||  el->child[1]->mark >= 0)
    {
/*--------------------------------------------------------------------------*/
/*  one of the element's children must not be coarsened; reset the          */
/*  coarsening flag on all those elements that have to be coarsened with    */
/*  this element                                                            */
/*--------------------------------------------------------------------------*/
      el->mark = 0;
      for (j = 0; j < n_neigh; j++)
	if (coarse_list[j].flags & RCLE_COARSE_EDGE_COMPAT)
	  coarse_list[j].el_info.el->mark = 0;
      return(false);
    }
    else if (el->child[0]->child[0]  ||  el->child[1]->child[0])
    {
/*--------------------------------------------------------------------------*/
/*  one of the element's children is not a leaf element;                    */
/*  element may be coarsened after coarsening one of the children; try again*/
/*--------------------------------------------------------------------------*/
      do_more_coarsen_3d = true;
      return(false);
    }
#if 0 /* removed, DK */
    else
    {
/*--------------------------------------------------------------------------*/
/*  either one element is a macro element or we can coarsen the patch       */
/*--------------------------------------------------------------------------*/
      if (!(coarse_list[i].flags & RCLE_COARSE_EDGE_COMPAT))
      {
	MACRO_EL  *mel;

	for (mel = mesh->first_macro_el; mel; mel = mel->next)
	  if (mel->el == el)  break;

	DEBUG_TEST_EXIT(mel, "incompatible coarsening patch found\n");
      }
    }
#endif
  }

  return(true);
}


/*--------------------------------------------------------------------------*/
/*  coarsen_fct_3d:  get the coarsening patch, starting on el_info->el,     */
/*  checks whether patch can be coarsened and calls coarse_patch_3d if      */
/*  possible                                                                */
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
/*  coarse_patch_3d:  gets the coarsening patch via get_coarse_patch_3d()   */
/*  (starting on el_info->el), checks whether patch can be coarsened or not */
/*  via can_coarsen_patch_3d(); in the first case the patch is refined by   */
/*  coarse_patch_3d(); otherwise element markers for patch elements are     */
/*  reset by can_coarsen_patch_3d() and we have to return :-(               */
/*--------------------------------------------------------------------------*/

static void coarsen_fct_3d(const EL_INFO *el_info)
{
  EL         *el = el_info->el;
  MESH       *mesh = el_info->mesh;
  DOF        *edge[2];
  int        n_neigh, bound = false, is_periodic = false;
  EL_INFO    *elinfo = (EL_INFO *) el_info;
  int        n_vertices = mesh->n_vertices;
  int        n_edges = mesh->n_edges;
  int        n_faces = mesh->n_faces;  
  RC_LIST_EL *coarse_list, *coarse_list_orig;

  if (el->mark >= 0)  return;    /* el must not be coarsened, return :-(    */
  if (!(el->child[0])) return;   /* single leaves don't get coarsened       */

  if (el->child[0]->mark >= 0  || el->child[1]->mark >= 0)
  {
/*--------------------------------------------------------------------------*/
/*  one of the children must not be coarsend; return :-(                    */
/*--------------------------------------------------------------------------*/
    el->mark = 0;
    return;
  }

  if (el->child[0]->child[0] || el->child[1]->child[0])
  {
/*--------------------------------------------------------------------------*/
/*  one of the children is not a leaf element; try again later on           */
/*--------------------------------------------------------------------------*/
    do_more_coarsen_3d = true;
    return;
  }

/*--------------------------------------------------------------------------*/
/*  get a list for storing all elements at the coarsening edge and fill it  */
/*--------------------------------------------------------------------------*/
  coarse_list = coarse_list_orig = get_rc_list(mesh);

/*--------------------------------------------------------------------------*/
/*  give the refinement edge the right orientation                          */
/*--------------------------------------------------------------------------*/

  if (el->dof[0][0] < el->dof[1][0]) {
    edge[0] = el->dof[0];
    edge[1] = el->dof[1];
  } else {
    edge[1] = el->dof[0];
    edge[0] = el->dof[1];
  }

  coarse_list[0].el_info = *el_info;
  coarse_list[0].flags   = RCLE_COARSE_EDGE_COMPAT;
  n_neigh = 1;
  
  coarse_list[0].opp_vertex[0] = 0;

  bound = false;
  if (get_coarse_patch_3d(elinfo, edge, 0,
			  coarse_list, &n_neigh, &is_periodic)) {
    if (is_periodic) {
      AI_reverse_rc_list_3d(coarse_list, n_neigh, edge);
    }
    get_coarse_patch_3d(elinfo, edge, 1, coarse_list, &n_neigh, &is_periodic);
    bound = true;
  } else if (is_periodic) {
    coarse_list = AI_rotate_rc_list_3d(coarse_list, n_neigh, edge);
  }

/*--------------------------------------------------------------------------*/
/*  fill neighbour information inside the patch in the refinement list      */
/*--------------------------------------------------------------------------*/
  AI_set_neighs_on_patch_3d(coarse_list, n_neigh, bound);

/*--------------------------------------------------------------------------*/
/*  check wether we can coarsen the patch or not                            */
/*--------------------------------------------------------------------------*/
  if (can_coarsen_patch_3d(mesh, coarse_list, n_neigh)) {
    coarse_patch_3d(mesh, coarse_list, n_neigh, bound, is_periodic);
  }

  free_rc_list(mesh, coarse_list_orig);

  if (n_vertices < 0) {
    mesh->n_vertices =
      mesh->per_n_vertices = -1;
  }
  
  if (n_edges < 0) {
    mesh->n_edges =
      mesh->per_n_edges = -1;
  }

  if (n_faces < 0) {
    mesh->n_faces =
      mesh->per_n_faces = -1;
  }

  return;
}


/*--------------------------------------------------------------------------*/
/*  coarsen_3d:        							    */
/*  traversal routine for recursive coarsening of a triangulation in 3d     */
/*--------------------------------------------------------------------------*/

static U_CHAR coarsen_3d(MESH *mesh, FLAGS fill_flags)
{
  int           n_elements;
  const EL_INFO *el_info;

  fill_flags |= CALL_EVERY_EL_POSTORDER | FILL_NEIGH;

  n_elements = mesh->n_elements;

  call_coarse_restrict_3d =
    count_coarse_restrict(mesh, AI_get_dof_vec_list(mesh), false);
  if (mesh->is_periodic)
    call_coarse_restrict_np_3d =
      count_coarse_restrict(mesh, AI_get_dof_vec_list_np(mesh), true);

  spread_coarsen_mark_3d(mesh);

  stack_3d = get_traverse_stack();
  do
  {
    do_more_coarsen_3d = false;
    el_info = traverse_first(stack_3d, mesh, -1, fill_flags);
    while (el_info)
    {
      coarsen_fct_3d(el_info);
      el_info = traverse_next(stack_3d, el_info);
    }
  } while (do_more_coarsen_3d);
  free_traverse_stack(stack_3d);
 
  cleanup_after_coarsen_3d(mesh);

  n_elements -= mesh->n_elements;

  return(n_elements ? MESH_COARSENED : 0);
}
