/*--------------------------------------------------------------------------*/
/* ALBERTA:  an Adaptive multi Level finite element toolbox using           */
/*           Bisectioning refinement and Error control by Residual          */
/*           Techniques for scientific Applications                         */
/*                                                                          */
/* www.alberta-fem.de                                                       */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/* file:     coarsen.c                                                      */
/*                                                                          */
/* description:  coarsening of 2 dim. hierarchical meshes;                  */
/*               file contains all routines depending on DIM == 2;          */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  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                                    */
/*                                                                          */
/*             Claus-Justus Heine                                           */
/*             Abteilung fuer Angewandte Mathematik                          */
/*             Universitaet Freiburg                                        */
/*             Hermann-Herder-Str. 10                                       */
/*             79104 Freiburg, Germany                                      */
/*                                                                          */
/*  (c) by A. Schmidt and K.G. Siebert (1996-2003),                         */
/*         C.-J. Heine (2006).                                              */
/*                                                                          */
/*--------------------------------------------------------------------------*/

/****************************************************************************/
/*  coarsen_element_2d: coarsens a single element of the coarsening patch;  */
/*  dofs in the interior of the element are removed; dofs for higher order  */
/*  at the boundary or the coarsening patch still belong to                 */
/*  the parent. Do not remove them from the mesh!!!                         */
/****************************************************************************/

static void coarsen_element_2d(MESH *mesh, EL *el)
{
  FUNCNAME("coarsen_element_2d");
  EL       *child[2];

  child[0] = el->child[0]; 
  child[1] = el->child[1]; 

  DEBUG_TEST_EXIT(child[0]->mark < 0  &&  child[1]->mark < 0,
	      "element %d with children[%d,%d] must not be coarsend!\n",
	      INDEX(el), INDEX(child[0]), INDEX(child[1]));
  
  if (mesh->n_dof[EDGE]) {
/****************************************************************************/
/*  remove dof from common edge of child[0] and child[1]                    */
/****************************************************************************/
    free_dof(child[0]->dof[4], mesh, EDGE, ADM_FLAGS_DFLT);
  }

  if (mesh->n_dof[CENTER]) {
/****************************************************************************/
/*  remove dof from the barycenters of child[0] and child[1]                */
/****************************************************************************/
    int  node = mesh->node[CENTER];

    free_dof(child[0]->dof[node], mesh, CENTER, ADM_FLAGS_DFLT);
    free_dof(child[1]->dof[node], mesh, CENTER, ADM_FLAGS_DFLT);
  }

  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;

  mesh->n_edges--;
  mesh->per_n_edges--;

  return;
}

/****************************************************************************/
/*  AI_coarse_patch_2d: first rebuild the dofs on the parents then do       */
/*  restriction of data (if possible) and finally coarsen the patch elements*/
/*                                                                          */
/*  This routine is also called from submesh.c!                             */
/****************************************************************************/

void AI_coarse_patch_2d(MESH *mesh, RC_LIST_EL coarse_list[], int n_neigh)
{
  EL   *el = coarse_list[0].el_info.el, *neigh = coarse_list[1].el_info.el;
  REAL *new_coord = el->new_coord;
  int  is_periodic = false;

/****************************************************************************/
/*  reactivate DOFs                                                         */
/****************************************************************************/
  AI_reactivate_dof(mesh, el, NULL, NULL);
  if (neigh) {
    if (_AI_rc_list_periodic_neigh_p(coarse_list, coarse_list+1)) {
      DOF *edge_twins[N_EDGES_2D] = { NULL, };

      is_periodic = true;
      edge_twins[2] = el->dof[mesh->node[EDGE]+2];
      AI_reactivate_dof(mesh, neigh, edge_twins, NULL);
    } else {
      AI_reactivate_dof(mesh, neigh, NULL, NULL);
    }
  }

  if (call_coarse_restrict_2d)
/****************************************************************************/
/*  restrict dof vectors to the parents on the patch                        */
/****************************************************************************/
    coarse_restrict(mesh, ((MESH_MEM_INFO *)mesh->mem_info)->dvlist,
		    coarse_list, n_neigh);
  if (neigh && is_periodic && call_coarse_restrict_np_2d) {
    coarse_restrict(mesh, ((MESH_MEM_INFO *)mesh->mem_info)->dvlist_np,
		    coarse_list, 1);
    coarse_restrict(mesh, ((MESH_MEM_INFO *)mesh->mem_info)->dvlist_np,
		    coarse_list+1, 1);
  }
  

/****************************************************************************/
/*  now, remove those dofs in the coarsening edge                           */
/****************************************************************************/
  free_dof(el->child[0]->dof[2], mesh, VERTEX, ADM_FLAGS_DFLT);

  if (mesh->n_dof[EDGE]) {
    free_dof(el->child[0]->dof[3], mesh, EDGE, ADM_FLAGS_DFLT);
    free_dof(el->child[1]->dof[4], mesh, EDGE, ADM_FLAGS_DFLT);
  }

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

  coarsen_element_2d(mesh, el);
  el->new_coord = NULL;

  if (neigh) {
    if (is_periodic) {

      if (new_coord)
	free_real_d(mesh, new_coord);
      new_coord = neigh->new_coord;
      neigh->new_coord = NULL;

      /* now, remove those dofs in the coarsening edge */
      free_dof(neigh->child[0]->dof[2], mesh, VERTEX, ADM_PERIODIC);

      if (mesh->n_dof[EDGE]) {
	free_dof(neigh->child[0]->dof[3], mesh, EDGE, ADM_PERIODIC);
	free_dof(neigh->child[1]->dof[4], mesh, EDGE, ADM_PERIODIC);
      }

      mesh->n_vertices--;
      mesh->n_edges--;
    }
    coarsen_element_2d(mesh, neigh);
  }

/*--------------------------------------------------------------------------*/
/*---  if there is a projected boundary node, free memory                ---*/
/*--------------------------------------------------------------------------*/

  if (new_coord)
    free_real_d(mesh, new_coord);

  mesh->n_vertices--;
  mesh->per_n_vertices--;

  mesh->n_edges--;
  mesh->per_n_edges--;
  
  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_2d(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_2d(MESH *mesh)
{
  mesh_traverse(mesh, -1, CALL_EVERY_EL_POSTORDER, 
		spread_coarsen_mark_fct_2d, NULL);
  return;
}

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

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

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

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

static int  do_coarse_patch_2d(MESH *mesh, RC_LIST_EL *coarse_list, int n_neigh)
{
  /*FUNCNAME("do_coarse_patch_2d"); */
  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 coarsend or element is a leaf element; reset the    */
/*  the coarsening flag on all those elements that have to be coarsend 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 coarsend; 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_2d = 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].flag == 0)
      {
	int        m;

	for(m = 0; m < mesh->n_macro_el; m++)
	  if (mesh->macro_els[m].el == el)  break;

	DEBUG_TEST_EXIT(m < mesh->n_macro_el,
		    "incompatible coarsening patch found\n");
      }
    }
#endif
  }

  return(true);
}


/*--------------------------------------------------------------------------*/
/*  coarsen_fct_2d:  get the coarsen patch, starting on el_info->el, checks */
/*  whether patch can be coarsend and calls AI_coarse_patch_2d if possible  */
/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/
/*  AI_coarse_patch_2d:  gets the coarsening patch via get_coarse_patch_2d()*/
/*  (starting on el_info->el), checks whether patch can be coarsened or not */
/*  via do_coarsen_patch(); in the first case the patch is refined by       */
/*  AI_coarse_patch_2d(); otherwise element marker for patch elements are   */
/*  reset by do_coarsen_patch_2d() and we have to return :-(                */
/*--------------------------------------------------------------------------*/

static void coarsen_fct_2d(const EL_INFO *el_info)
{
  EL         *el = el_info->el;
  MESH       *mesh = el_info->mesh;
  int        n_edges = mesh->n_edges, n_vertices = mesh->n_vertices;
  int        n_neigh;
  RC_LIST_EL coarse_list[2];

  if (el->mark >= 0)  return;    /* el must not be coarsend, 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 coarsened; 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_2d = true;
    return;
  }

  coarse_list[0].el_info = *el_info;
  coarse_list[0].flags = RCLE_COARSE_EDGE_COMPAT;
  n_neigh = 1;
  
  if ((coarse_list[1].el_info.el = el_info->neigh[2])) {
    if (el_info->fill_flag & FILL_COORDS) {
      int orient =
	wall_rel_orientation_2d(el, el_info->neigh[2], 2, 2);
      fill_neigh_el_info(&coarse_list[1].el_info, el_info, 2, orient);
    } else {
      coarse_list[1].el_info.mesh = mesh;
      coarse_list[1].el_info.macro_el = NULL;
      coarse_list[1].el_info.parent = NULL;
      coarse_list[1].el_info.fill_flag = FILL_NOTHING;
    }
    n_neigh = 2;
    coarse_list[1].flags =
      (el_info->opp_vertex[2] == 2) ? RCLE_COARSE_EDGE_COMPAT : RCLE_NONE;
  }

/*--------------------------------------------------------------------------*/
/*  check whether we can coarsen the patch or not                           */
/*--------------------------------------------------------------------------*/
  if (do_coarse_patch_2d(mesh, coarse_list, n_neigh))
  {
    AI_coarse_patch_2d(mesh, coarse_list, n_neigh);
  }

  /* prevent drifting and overflow for extreme cases */
  if (n_vertices < 0) {
    mesh->n_vertices =
      mesh->per_n_vertices = -1;
  }

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

  return;
}


/*--------------------------------------------------------------------------*/
/*  coarsen_2d:        							    */
/*  traversal routine for recursive coarsening of a 2d triangulation        */
/*--------------------------------------------------------------------------*/

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

  fill_flags |= CALL_EVERY_EL_POSTORDER | FILL_NEIGH;

  if (fill_flags & (FILL_COORDS|FILL_PROJECTION)) {
    fill_flags |= FILL_OPP_COORDS;
  }

  n_elements = mesh->n_elements;

  call_coarse_restrict_2d =
    count_coarse_restrict(mesh, AI_get_dof_vec_list(mesh), false);
  if (mesh->is_periodic)
    call_coarse_restrict_np_2d =
      count_coarse_restrict(mesh, AI_get_dof_vec_list_np(mesh), true);

  spread_coarsen_mark_2d(mesh);

  stack = get_traverse_stack();
  do
  {
    do_more_coarsen_2d = false;
    el_info = traverse_first(stack, mesh, -1, fill_flags);
    while (el_info)
    {
      coarsen_fct_2d(el_info);
      el_info = traverse_next(stack, el_info);
    }
  } while (do_more_coarsen_2d);
  free_traverse_stack(stack);
 
  cleanup_after_coarsen_2d(mesh);

  n_elements -= mesh->n_elements;

  return(n_elements ? MESH_COARSENED : 0);
}
