/*--------------------------------------------------------------------------*/
/* 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:     memory_2d.c                                                    */
/*                                                                          */
/*                                                                          */
/* description:  special routines for getting new FE_SPACEs, 2D-Version     */
/*                                                                          */
/*--------------------------------------------------------------------------*/
/*                                                                          */
/*  authors:   Daniel Koester                                               */
/*             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 D. Koester (2005),                                               */
/*         C.-J. Heine (2006).                                              */
/*--------------------------------------------------------------------------*/

/****************************************************************************/
/* LOGICAL_EL_2D:                                                           */
/* This structure defines a MESH element at any point in the hierarchy in   */
/* terms of global indices for vertices, edges, and elements. This          */
/* information is calculated by the routines below. A temporary             */
/* representation of the entire mesh is used to fill DOF pointers on all    */
/* elements. This rather general (and costly) approach was used since this  */
/* information might be useful in other contexts in the future              */
/*                                                                          */
/* The entries are self-explanatory, except for "neigh", which is slightly  */
/* complicated. "neigh" is defined to be the neighbour on the same          */
/* refinement level. If there is no such neighbour due to non-matching      */
/* refinement edges, we take a neighbour one level higher.                  */
/*                                                                          */
/* oppv is special: it encodes the local vertex number of the opposite      */
/* vertex in the neighbour _AND_ the relative orientation of this element   */
/* and the neighbour element. It ranges from 1...3 or -1...-3. It is        */
/* initialized with zero if there is no neighbour.                          */
/****************************************************************************/

typedef struct logical_el_2d LOGICAL_EL_2D;

struct logical_el_2d
{
  int parent;
  int child[2];
  int neigh[N_NEIGH_2D];
  int oppv[N_NEIGH_2D];
  int vertex[N_VERTICES_2D];
  int edge[N_EDGES_2D];
  EL *el;
};

/* Given the oppv of a wall and a local vertex number v w.r.t. that
 * wall on _this_ element compute the local vertex number of the
 * corresponding vertex w.r.t. the neighbour. oppv is negative when
 * the neighbours' orientation differs from our orientation.
 *
 * v takes the values 0 and 1.
 */
#define NEIGH_VERTEX(oppv, v)			\
  (((oppv) > 0 ? ((oppv)-1)+(1-(v)+1) : (-(oppv)-1)+(v)+1) % N_VERTICES_2D)

/****************************************************************************/
/* fill_logical_el_rec_2d(l_els, el, current_el, index):                    */
/* This routine fills basic information on logical els, namely childhood    */
/* relationships, element types, EL pointers. Elements are also counted     */
/* here, and the maximum depth of the refinement tree is determined         */
/* (max_level == maximum number of parent generations above an element).    */
/****************************************************************************/

static void fill_logical_el_rec_2d(LOGICAL_EL_2D *l_els, EL *el,
				   int current_el, int *index,
				   int current_level, int *max_level)
{
  /*FUNCNAME("fill_logical_el_rec_2d"); */
  int i_child, child_ind[2];

  if(current_level > *max_level)
    *max_level = current_level;

  if(el->child[0]) {
    /* Fill basic child information.                                        */

    for(i_child = 0; i_child < 2; i_child++) {
      child_ind[i_child] = *index + i_child;

      l_els[current_el].child[i_child]   = child_ind[i_child];
      l_els[child_ind[i_child]].el       = el->child[i_child];
      l_els[child_ind[i_child]].parent   = current_el;
    }

    *index += 2;

    /* Now recurse to the two children.                                     */

    fill_logical_el_rec_2d(l_els, el->child[0], child_ind[0], index,
			   current_level + 1, max_level);
    fill_logical_el_rec_2d(l_els, el->child[1], child_ind[1], index,
			   current_level + 1, max_level);
  }

  return;
}

/****************************************************************************/
/* fill_connectivity_rec_2d(l_els, current_el, v_index, e_index):           */
/* This routine is the workhorse for filling a LOGICAL_EL array. It sets    */
/* vertex, edge, and neighbour indices on the children of "current_el".     */
/* It is not a simple recursion, but is called for each refinement level.   */
/*  Only thus are we guaranteed to have correct neighbourhood information   */
/* which is vital for setting edge and vertex indices correctly. A pointer  */
/* to each index type is handed through all calls of this routine and       */
/* advanced for each new entity counted.                                    */
/****************************************************************************/

static void fill_connectivity_rec_2d(LOGICAL_EL_2D *l_els, int current_el,
				     int *v_index, int *e_index,
				     int current_level, int desired_level)
{
  FUNCNAME("fill_connectivity_rec_2d");
  int i_child, child_ind[2], opp_v[N_NEIGH_2D] = {-1,-1,-1}, neigh;
  int i, neigh_child[2], new_vertex, parity;

  if(l_els[current_el].child[0] > -1) {
    for(i_child = 0; i_child < 2; i_child++)
      child_ind[i_child] = l_els[current_el].child[i_child];

    if(current_level == desired_level - 1) {

      for(i = 0; i < N_NEIGH_2D; i++)
	opp_v[i] = ABS(l_els[current_el].oppv[i])-1;

      /* Fill edge information. Two edges are handed down from parents.     */

      l_els[child_ind[0]].edge[2] = l_els[current_el].edge[1];
      l_els[child_ind[1]].edge[2] = l_els[current_el].edge[0];

      /* One edge is allocated where the two children of "current_el" meet. */

      l_els[child_ind[0]].edge[1] =
	l_els[child_ind[1]].edge[0] = *e_index;

      *e_index += 1;

      if(l_els[child_ind[0]].edge[0] < 0) {
	l_els[child_ind[0]].edge[0] = *e_index;
	l_els[child_ind[1]].edge[1] = *e_index + 1;

	*e_index += 2;
      }

      /* Fill vertex information. All "current_el" vertices are passed to   */
      /* children.                                                          */

      l_els[child_ind[0]].vertex[0] = l_els[current_el].vertex[2];
      l_els[child_ind[1]].vertex[1] = l_els[current_el].vertex[2];
      l_els[child_ind[0]].vertex[1] = l_els[current_el].vertex[0];
      l_els[child_ind[1]].vertex[0] = l_els[current_el].vertex[1];

      /* One new vertex is shared by the two children and possibly by       */
      /* neighbouring children. The new vertex is passed to neighbours.     */

      new_vertex = false;
      if(l_els[child_ind[0]].vertex[2] < 0) {
	l_els[child_ind[0]].vertex[2] =
	  l_els[child_ind[1]].vertex[2] = *v_index;

	new_vertex = true;
	*v_index += 1;
      }

      /* Set neighbourhood information among children of "current_el".      */

      l_els[child_ind[0]].neigh[1] = child_ind[1];
      l_els[child_ind[1]].neigh[0] = child_ind[0];
      l_els[child_ind[0]].oppv[1] = 0+1;
      l_els[child_ind[1]].oppv[0] = 1+1;

      /* Now spread neighbourhood info, edges, and vertices to the direct   */
      /* neighbours. We might have to traverse down on neighbour elements.  */

      for(i = 0; i < N_NEIGH_2D; i++) {
	neigh = l_els[current_el].neigh[i];

	if(neigh < 0) {
	  continue;
	}

	if (i < 2) {
	  if (l_els[neigh].child[0] > -1) {
	    /* These are faces which do not contain the refinement edge.    */
	    neigh_child[0] = l_els[neigh].child[0];
	    neigh_child[1] = l_els[neigh].child[1];

	    if(opp_v[i] < 2) {
	      l_els[child_ind[1-i]].neigh[2] = neigh_child[1-opp_v[i]];
	      l_els[neigh_child[1-opp_v[i]]].neigh[2] = child_ind[1-i];

	      if (l_els[current_el].oppv[i] < 0) {
		l_els[child_ind[1-i]].oppv[2] = -(2+1);
	      } else {
		l_els[child_ind[1-i]].oppv[2] = 2+1;
	      }
	    } else {
	      l_els[child_ind[1-i]].neigh[2] = neigh;
	      l_els[child_ind[1-i]].oppv[2] = l_els[current_el].oppv[i];
	    }
	  } else {
	    /* To be able to handle periodic DOFs correctly we have to
	     * set the neighbours of the children to their parent's
	     * (current_el) neighbour. Otherwise we are not able to
	     * find the periodic twins of the DOFs across periodic
	     * walls. This should not disturb anything else; if the
	     * neighbour has no children, then the children of
	     * current_el cannot have children, too, because wall
	     * number 2 contains the refinement edge.
	     */
	    l_els[child_ind[1-i]].neigh[2] = neigh;
	    l_els[child_ind[1-i]].oppv[2] = l_els[current_el].oppv[i];
	  }
	} else /*if (l_els[neigh].child[0] > -1) neighbour must have childs */ {
	  int ch0n, ch1n, v0, nv0;

	  neigh_child[0] = l_els[neigh].child[0];
	  neigh_child[1] = l_els[neigh].child[1];

	  if(opp_v[2] != 2) {
	    /* Go down one level along the neighbour's children. */
	    DEBUG_TEST_EXIT(l_els[neigh_child[1-opp_v[2]]].child[0] > -1,
			    "Why did we not find a child here?\n");

	    neigh = neigh_child[1-opp_v[2]];

	    neigh_child[0] = l_els[neigh].child[0];
	    neigh_child[1] = l_els[neigh].child[1];
	  }

	  parity = l_els[current_el].oppv[2] < 0;

	  /* to cope with periodic walls we need the global vertex
	   * number of the neighbour's vertex corresponding to vertex
	   * 0 on this element. However, "neigh" might belong to one
	   * level deeper than "current_el" in which case it does not
	   * carry vertex information yet, so we may need to go up to
	   * its parent. Unluckily the parent of neigh might not even
	   * be a neighbour of our parent (w.r.t. to the "level
	   * neighbourhood" stored in l_els).
	   */
	  v0 = l_els[current_el].vertex[0];
	  if ((nv0 = l_els[neigh].vertex[1-parity]) < 0) {
	    int n_parent = l_els[neigh].parent;
	    int n_ich    = (l_els[n_parent].child[0] == neigh) ? 0 : 1;

	    v0  = l_els[current_el].vertex[parity];
	    nv0 = l_els[n_parent].vertex[2*n_ich];
	  }

	  ch0n = 1-parity; /* neighbour of child0 */
	  ch1n = parity-0; /* neighbour of child1 */

	  l_els[child_ind[0]].neigh[0]   = neigh_child[ch0n];
	  l_els[child_ind[1]].neigh[1]   = neigh_child[ch1n];

	  l_els[child_ind[0]].oppv[0]    = parity ? -(ch0n + 1) : ch0n + 1;
	  l_els[child_ind[1]].oppv[1]    = parity ? -(ch1n + 1) : ch1n + 1;

	  l_els[neigh_child[0]].neigh[0] = child_ind[ch0n];
	  l_els[neigh_child[1]].neigh[1] = child_ind[ch1n];

	  l_els[neigh_child[0]].oppv[0] = parity ? -(ch0n + 1) : ch0n + 1;
	  l_els[neigh_child[1]].oppv[1] = parity ? -(ch1n + 1) : ch1n + 1;

	  /* for periodic walls neither the vertex nor the edges are
	   * handed to the neighbours of the children.
	   */

	  if (v0 == nv0) {

	    l_els[neigh_child[0]].edge[0] = l_els[child_ind[ch0n]].edge[ch0n];
	    l_els[neigh_child[1]].edge[1] = l_els[child_ind[ch1n]].edge[ch1n];

	    if(new_vertex) {
	      l_els[neigh_child[0]].vertex[2] =
		l_els[neigh_child[1]].vertex[2] = *v_index - 1;
	    }

	  }
	}
      }
    }

    /* Now recurse to the two children. */

    if(current_level < desired_level) {
      fill_connectivity_rec_2d(l_els, child_ind[0], v_index, e_index,
			       current_level + 1, desired_level);
      fill_connectivity_rec_2d(l_els, child_ind[1], v_index, e_index,
			       current_level + 1, desired_level);
    }
  }

  return;
}


/****************************************************************************/
/* fill_logical_els_2d(mesh, n_elements, n_vertices, n_edges):              */
/* Return a filled array of LOGICAL_EL_2D elements corresponding to the     */
/* current state of the mesh refinement tree.                               */
/****************************************************************************/

static LOGICAL_EL_2D *fill_logical_els_2d(MESH *mesh, int *n_elements,
					  int *n_vertices, int *n_edges,
					  int *n_macro_vertices)
{
  FUNCNAME("fill_logical_els_2d");
  REAL_D        *coords = ((MESH_MEM_INFO *)mesh->mem_info)->coords;
  int            i, j, n_els = mesh->n_hier_elements, neigh, opp_v;
  LOGICAL_EL_2D *l_els = NULL;
  int            index, v_index = 0, e_index = 0, max_level = 0;
  MACRO_EL      *mel;

  l_els = MEM_ALLOC(n_els, LOGICAL_EL_2D);

  /* First pass: Initialize everything, set some information on macro
   * elements: Neighbour indices and vertices. Make use of the fact
   * that macro coordinates are stored in a simple linear array to get
   * vertices!
   */

  for(i = 0; i < n_els; i++) {
    l_els[i].parent   = -1;
    l_els[i].child[0] = -1;
    l_els[i].child[1] = -1;

    if(i < mesh->n_macro_el) {
      mel = mesh->macro_els + i;

      for(j = 0; j < N_VERTICES_2D; j++)
	l_els[i].vertex[j] = (REAL_D *)(mel->coord[j]) - coords;

      for(j = 0; j < N_NEIGH_2D; j++) {
	if(mel->neigh[j]) {
	  opp_v  = mel->opp_vertex[j];
	  l_els[i].neigh[j] = mel->neigh[j]->index;
	  if (mel->neigh_vertices[j][0] == -1) {
	    if (mel->coord[(j+1)%N_VERTICES_2D]
		==
		mel->neigh[j]->coord[(opp_v+1)%N_VERTICES_2D]) {
	      opp_v = -opp_v-1;
	    } else {
	      ++opp_v;
	    }
	  } else {
	    if (mel->neigh_vertices[j][0] == (opp_v + 1) % N_VERTICES_2D) {
	      opp_v = -opp_v-1;
	    } else {
	      ++opp_v;
	    }
	  }
	  l_els[i].oppv[j] = opp_v;
	} else {
	  l_els[i].neigh[j] = -1;
	  l_els[i].oppv[j] = 0;
	}
      }      

      l_els[i].el = mel->el;
    } else {
      for(j = 0; j < N_NEIGH_2D; j++) {
	l_els[i].neigh[j] = -1;
	l_els[i].oppv[j] = 0;
      }

      for(j = 0; j < N_VERTICES_2D; j++)
	l_els[i].vertex[j] = -1;
    }

    for(j = 0; j < N_EDGES_2D; j++)
      l_els[i].edge[j] = -1;
  }

  /* Second pass: Now that we know vertices and neighbours on the
   * macro triangulation we set edges.
   */

  for(i = 0; i < mesh->n_macro_el; i++) {
    mel = mesh->macro_els + i;
    for(j = 0; j < N_EDGES_2D; j++) {
      if(l_els[i].edge[j] < 0) {
	l_els[i].edge[j]  = e_index;

	neigh = l_els[i].neigh[j];
	if(neigh > -1 && mel->neigh_vertices[j][0] == -1) {
	  opp_v = (int)mesh->macro_els[i].opp_vertex[j];
	  l_els[neigh].edge[opp_v] = e_index;
	}

	e_index++;
      }
    }
  }

  index   = mesh->n_macro_el;
  v_index = ((MESH_MEM_INFO *)mesh->mem_info)->count;

  if (n_macro_vertices) {
    *n_macro_vertices = v_index;
  }

  /* Now we fill basic information about all mesh elements.                 */

  for(i = 0; i < mesh->n_macro_el; i++)
    fill_logical_el_rec_2d(l_els, mesh->macro_els[i].el, i, &index, 0,
			   &max_level);

  /* Connectivity and vertex/edge indices are built in layers for each level*/
  /* in the refinement tree. The reason is the interdepence between         */
  /* neighbour information and vertex/edge information.                     */

  for(i = 1; i <= max_level; i++)
    for(j = 0; j < mesh->n_macro_el; j++)
      fill_connectivity_rec_2d(l_els, j, &v_index, &e_index, 0, i);

#if ALBERTA_DEBUG == 1
  /* Do a quick and cheap check. For more checks please call check_mesh().  */

  for(i = 0; i < index; i++) {
    for(j = 0; j < N_VERTICES_2D; j++)
      DEBUG_TEST_EXIT(l_els[i].vertex[j] > -1,
	    "Error while checking element %d, vertex %d==-1!\n", i, j);
    for(j = 0; j < N_EDGES_2D; j++)
      DEBUG_TEST_EXIT(l_els[i].edge[j] > -1,
	    "Error while checking element %d, edge %d==-1!\n", i, j);
  }
#endif

  *n_elements   = index;
  *n_vertices   = v_index;
  *n_edges      = e_index;

  return l_els;
}

/* Here we assign each orbit of the action of the group of
 * wall-transformations on the set of vertices a unique number and
 * compute the mapping vertex -> orbit. This enables us to properly
 * identify DOFs across periodic walls. Only vertices already present
 * in the macro triangulation can have orbits with more than 2
 * elements, but it makes the code in adjust_dofs_and_dof_ptrs_2d()
 * more readable to compute the orbits for all vertices her.
 */
static void compute_periodic_orbits_2d(MESH *mesh,
				       LOGICAL_EL_2D *l_els,
				       int n_hier_elements,
				       int n_vertices,
				       int n_macro_vertices,
				       int *wt_orbit_map,
				       int *n_wt_orbits)
{
  FUNCNAME("compute_periodic_orbits_2d");
  int (*wall_trafos)[N_VERTICES(DIM_MAX-1)][2];
  int nwt, n_v_orbs, i, el;
  int per_n_vertices;

  nwt = _AI_compute_macro_wall_trafos(mesh, &wall_trafos);

  for (i = 0; i < n_vertices; i++) {
    wt_orbit_map[i] = -1;
  }
  per_n_vertices = n_macro_vertices;
  n_v_orbs = _AI_wall_trafo_vertex_orbits(mesh->dim, wall_trafos, nwt,
					  wt_orbit_map, &per_n_vertices);
  MEM_FREE(wall_trafos, nwt*sizeof(*wall_trafos), char);

  for (el = 0; el < n_hier_elements; el++) {
    int neigh, vertex, neigh_vertex, edge, neigh_edge;
      
    if (l_els[el].child[0] <= 0) {
      continue;
    }

    if ((neigh = l_els[el].neigh[2]) < 0) {
      continue;
    }

    DEBUG_TEST_EXIT(ABS(l_els[el].oppv[2])-1 == 2, "Non-conforming mesh???\n");
 
    edge = l_els[el].edge[2];
    neigh_edge = l_els[neigh].edge[2];
    if (neigh_edge == edge) {
      continue;
    }

    /* We have a wall transformation; the new vertex and the new
     * vertex in the midpoint of the neighbour edge build a 2 point
     * orbit
     */
    vertex = l_els[l_els[el].child[0]].vertex[2];
    if (wt_orbit_map[vertex] >= 0) {
      continue; /* already assigned */
    }
    neigh_vertex = l_els[l_els[neigh].child[0]].vertex[2];

    DEBUG_TEST_EXIT(vertex != neigh_vertex,
		    "Wall transformation leaves mid-point of wall fixed???\n");
    
    wt_orbit_map[vertex] =
      wt_orbit_map[neigh_vertex] = n_v_orbs++;
    
  }

  *n_wt_orbits = n_v_orbs;
}

/****************************************************************************/
/* adjust_dofs_and_dof_ptrs_2d(mesh, new_admin, old_n_node_el,              */
/*                             old_n_dof, old_node):                        */
/* 1) If el->dof pointers have changed, adjust these. Keep old information! */
/* 2) Determine what types of new DOF pointers must be allocated.           */
/* 3) Build an index based mesh representation using fill_logical_els()     */
/* 4) Change all DOFs on the leaf level. (non-coarse DOFs!)                 */
/* 5) Change all coarse DOFs on non-leaf elements.                          */
/****************************************************************************/

static void adjust_dofs_and_dof_ptrs_2d(MESH *mesh, DOF_ADMIN *new_admin,
					int old_n_node_el,
					int *old_n_dof, int *old_node)
{
  FUNCNAME("adjust_dofs_and_dof_ptrs_2d");
  DOF            **old_dof_ptr;
  int            i, n, n_elements = 0, n_hier_elements;
  int            n_vertices, n_edges = 0, n_hier_edges;
  int            node, change_dofs[N_NODE_TYPES] = {0};
  EL             *el;
  TRAVERSE_STACK *stack = get_traverse_stack();
  const EL_INFO  *el_info = NULL;
  LOGICAL_EL_2D  *l_els = NULL;
  DOF            **new_vertex_dofs = NULL, **new_edge_dofs = NULL;
  int            is_periodic;
  int            n_macro_vertices;
  /* For periodic meshes we need the orbits of the vertices of the
   * macro-triangulation
   */
  int            *wt_orbit_map = NULL;
  int            n_wt_orbits = 0;
  DOF            **vertex_twin_dofs = NULL;
  /* count the number sub-simplexes on the peridic mesh */
  int           per_n_vertices = 0;
  int           per_n_edges    = 0;

  is_periodic = mesh->is_periodic && (new_admin->flags & ADM_PERIODIC);

  /* Adjust the length of el->dof pointer fields. Allocate new ones if      */
  /* necessary and transfer old information.                                */

  if(mesh->n_node_el > old_n_node_el) {
    el_info = traverse_first(stack, mesh, -1, CALL_EVERY_EL_PREORDER);
    while(el_info) {
      el = el_info->el;

      old_dof_ptr = el->dof;
      el->dof = get_dof_ptrs(mesh);

      if(old_n_dof[VERTEX])
	for(i = 0; i < N_VERTICES_2D; i++)
	  el->dof[mesh->node[VERTEX] + i] = old_dof_ptr[old_node[VERTEX] + i];
      if(old_n_dof[EDGE])
	for(i = 0; i < N_EDGES_2D; i++)
	  el->dof[mesh->node[EDGE] + i] = old_dof_ptr[old_node[EDGE] + i];
      if(old_n_dof[CENTER])
	el->dof[mesh->node[CENTER]] = old_dof_ptr[old_node[CENTER]];

      el_info = traverse_next(stack, el_info);
    }
  }

  /* Determine which DOF types have changed on the mesh with the new admin. */

  if(mesh->n_dof[VERTEX] > old_n_dof[VERTEX])
    change_dofs[VERTEX] = 1;
  if(mesh->n_dof[EDGE] > old_n_dof[EDGE])
    change_dofs[EDGE]   = 1;
  if(mesh->n_dof[CENTER] > old_n_dof[CENTER])
    change_dofs[CENTER] = 1;

  /* Build an array containing an index based mesh representation.          */

  l_els = fill_logical_els_2d(mesh, &n_hier_elements, &n_vertices,
			      &n_hier_edges, &n_macro_vertices);

  /* Allocate an array containing new DOF pointers. We make use of the      */
  /* index based mesh representation to ensure the correct setting.         */

  if (change_dofs[VERTEX])
    new_vertex_dofs = MEM_CALLOC(n_vertices, DOF *);
  if (change_dofs[EDGE])
    new_edge_dofs = MEM_CALLOC(n_hier_edges, DOF *);

  /* For the proper handling of vertex DOFs on periodic meshes we need
   * to compute the orbits of the vertices under the action of the
   * group of wall transformations. This cannot be helped. Group
   * theory strikes back :)
   *
   * Luckily this is only needed on the macro element level; higher
   * level vertices are just mapped to a single vertex of a neighbour,
   * but the orbit of macro element vertices can be larger.
   */
  if (is_periodic && change_dofs[VERTEX]) {

    wt_orbit_map = MEM_ALLOC(n_vertices, int);

    compute_periodic_orbits_2d(mesh, l_els, n_hier_elements, n_vertices,
			       n_macro_vertices,
			       wt_orbit_map,
			       &n_wt_orbits);

    vertex_twin_dofs = MEM_CALLOC(n_wt_orbits, DOF *);
  }

  /* First pass: Change all DOFs on the leaf level. EDGE DOFs on some       */
  /* parents of the leaf elements will still be in use and must thus be     */
  /* marked correctly in this pass!                                         */

  for (n = 0; n < n_hier_elements; n++) {
    el = l_els[n].el;

    if (!el->child[0]) {
      n_elements++;

      if (change_dofs[VERTEX]) {
	node = mesh->node[VERTEX];

	for (i = 0; i < N_VERTICES_2D; i++) {
	  int vertex;

	  vertex = l_els[n].vertex[i];

	  /* If we have a periodic mesh then we have to make sure that
	   * periodic admins use the same dof-indices on periodic
	   * walls. This is slightly complicated because we allow
	   * non-periodic DOF_ADMINs on periodic meshes.
	   */
	  if (!new_vertex_dofs[vertex]) {
	    DOF *twin_dof = NULL;
	    int orb_num = -1;

	    if (is_periodic) {
	      if ((orb_num = wt_orbit_map[vertex]) >= 0) {
		twin_dof = vertex_twin_dofs[orb_num];
		if (twin_dof) {
		  --per_n_vertices;
		}
	      }
	    }

	    new_vertex_dofs[vertex] =
	      transfer_dofs(mesh, new_admin, el->dof[node + i], VERTEX,
			    false, twin_dof);

	    if (orb_num >= 0 && !twin_dof) {
	      vertex_twin_dofs[orb_num] = new_vertex_dofs[vertex];
	    }
	  }

	  el->dof[node + i] = new_vertex_dofs[vertex];
	}
      }

      if(change_dofs[CENTER]) {
	node = mesh->node[CENTER];

	el->dof[node] =
	  transfer_dofs(mesh, new_admin, el->dof[node], CENTER, false, NULL);
      }

      if(change_dofs[EDGE]) {
	node = mesh->node[EDGE];

	for(i = 0; i < N_EDGES_2D; i++) {
	  int edge = l_els[n].edge[i];
	  
	  /* If we have a periodic mesh then we have to make sure that
	   * periodic admins use the same dof-indices on periodic
	   * walls.
	   */
	  if(!new_edge_dofs[edge]) {
	    DOF *twin_dof = NULL;

	    if (is_periodic) {
	      int neigh;

	      neigh = l_els[n].neigh[i];
	      if (neigh >= 0) {
		int oppv = ABS(l_els[n].oppv[i])-1;
		int neigh_edge = l_els[neigh].edge[oppv];

		if (neigh_edge != edge) {
		  twin_dof = new_edge_dofs[neigh_edge];
		  if (twin_dof) {
		    per_n_edges--;
		  }
		}
	      }
	    }

	    new_edge_dofs[edge] =
	      transfer_dofs(mesh, new_admin, el->dof[node + i], EDGE,
			    false, twin_dof);
	    n_edges++;
	  }

	  el->dof[node + i] = new_edge_dofs[edge];
	}
      }
    }
  }

  /* Second pass: Change all DOFs which were not yet set. These will be     */
  /* coarse DOFs in the case of EDGE/CENTER DOFs, therefore call            */
  /* transfer_dofs() with "is_coarse_dof=true"!                             */

  for(n = 0; n < n_hier_elements; n++) {
    el = l_els[n].el;

    if(el->child[0]) {
      if(change_dofs[VERTEX]) {
	node = mesh->node[VERTEX];

	for(i = 0; i < N_VERTICES_2D; i++)
	  el->dof[node + i] = new_vertex_dofs[l_els[n].vertex[i]];
      }

      if(change_dofs[CENTER]) {
	node = mesh->node[CENTER];

	el->dof[node] =
	  transfer_dofs(mesh, new_admin, el->dof[node], CENTER, true, NULL);
      }

      if(change_dofs[EDGE]) {
	node = mesh->node[EDGE];

	for(i = 0; i < N_EDGES_2D; i++) {
	  int edge = l_els[n].edge[i];

	  /* If we have a periodic mesh then we have to make sure that
	   * periodic admins use the same dof-indices on periodic
	   * walls.
	   */
	  if(!new_edge_dofs[edge]) {
	    DOF *twin_dof = NULL;

	    if (is_periodic) {
	      int neigh;

	      neigh = l_els[n].neigh[i];
	      if (neigh >= 0) {
		int oppv = ABS(l_els[n].oppv[i])-1;
		int neigh_edge = l_els[neigh].edge[oppv];

		if (neigh_edge != edge) {
		  twin_dof = new_edge_dofs[neigh_edge];
		}
	      }
	    }
	    
	    new_edge_dofs[edge] =
	      transfer_dofs(mesh, new_admin, el->dof[node + i], EDGE,
			    true, twin_dof);
	  }

	  el->dof[node + i] = new_edge_dofs[edge];
	}
      }
    }
  }

  /* A few checks and settings. */

  TEST_EXIT(n_elements == mesh->n_elements,
	    "Did not count correct number of leaf elements in mesh (%d/%d)!\n",
	    n_elements, mesh->n_elements);

  if(mesh->n_vertices > -1) {
    TEST_EXIT(n_vertices == mesh->n_vertices,
	      "Did not count correct number of vertices in mesh (%d/%d)!\n",
	      n_vertices, mesh->n_vertices);
  } else {
    mesh->n_vertices = n_vertices;
  }

  if(change_dofs[EDGE]) {
    if(mesh->n_edges > -1) {
      TEST_EXIT(n_edges == mesh->n_edges,
		"Did not count correct number "
		"of leaf edges in mesh (%d/%d)!\n",
		n_edges, mesh->n_edges);

    } else {
      mesh->n_edges = n_edges;
    }
  }

  if (is_periodic) {
    per_n_edges += n_edges;
    per_n_vertices += n_vertices;

    if (change_dofs[VERTEX]) {
      if(mesh->per_n_vertices > -1) {
	TEST_EXIT(per_n_vertices == mesh->per_n_vertices,
		  "Did not count correct number of vertices "
		  "in periodic mesh (%d/%d)!\n",
		  per_n_vertices, mesh->per_n_vertices);
      } else {
	mesh->per_n_vertices = per_n_vertices;
      }
    }

    if(change_dofs[EDGE]) {
      if(mesh->per_n_edges > -1) {
	TEST_EXIT(per_n_edges == mesh->per_n_edges,
		  "Did not count correct number "
		  "of leaf edges in periodic mesh (%d/%d)!\n",
		  per_n_edges, mesh->per_n_edges);
      } else {
	mesh->per_n_edges = per_n_edges;
      }
    }
  }

  /*  Clean up operations.                                                  */

  if(new_vertex_dofs)
    MEM_FREE(new_vertex_dofs, n_vertices, DOF *);
  if(new_edge_dofs)
    MEM_FREE(new_edge_dofs, n_hier_edges, DOF *);
  if(l_els)
    MEM_FREE(l_els, n_hier_elements, LOGICAL_EL_2D);
  if (is_periodic) {
    if (wt_orbit_map) {
      MEM_FREE(wt_orbit_map, n_vertices, int);
    }
    if (vertex_twin_dofs) {
      MEM_FREE(vertex_twin_dofs, n_wt_orbits, DOF *);
    }
  }

  free_traverse_stack(stack);

  return;
}

/****************************************************************************/
/* fill_missing_dofs_2d(mesh): See master routine in memory.c.              */
/****************************************************************************/

static void fill_missing_dofs_2d(MESH *mesh)
{
  FUNCNAME("fill_missing_dofs_2d");
  int              i, n, n_hier_elements, n_vertices, n_edges, node;
  EL              *el;
  LOGICAL_EL_2D   *l_els = NULL;
  DOF            **new_edge_dofs = NULL;

  if(!mesh->n_dof[CENTER] && !mesh->n_dof[EDGE]) return;
  l_els = fill_logical_els_2d(mesh,
			      &n_hier_elements, &n_vertices, &n_edges, NULL);

  if(mesh->n_dof[EDGE])
    new_edge_dofs = MEM_CALLOC(n_edges, DOF *);

  /* All new DOFs are set to -1 (unused) by transfer_dofs().                */

  for(n = 0; n < n_hier_elements; n++) {
    el = l_els[n].el;

    if(mesh->n_dof[CENTER]) {
      node = mesh->node[CENTER];
      if(!el->dof[node])
	el->dof[node] =
	  transfer_dofs(mesh, NULL, el->dof[node], CENTER, false, NULL);
    }

    if(mesh->n_dof[EDGE]) {
      node = mesh->node[EDGE];
      for(i = 0; i < N_EDGES_2D; i++)
	if(!el->dof[node + i]) {
	  if(!new_edge_dofs[l_els[n].edge[i]])
	    new_edge_dofs[l_els[n].edge[i]] =
	      transfer_dofs(mesh, NULL, el->dof[node + i], EDGE, false, NULL);

	  el->dof[node + i] = new_edge_dofs[l_els[n].edge[i]];
	}
    }
  }

  /*  Clean up operations.                                                  */

  if(new_edge_dofs)
    MEM_FREE(new_edge_dofs, n_edges, DOF *);
  MEM_FREE(l_els, n_hier_elements, LOGICAL_EL_2D);

  return;
}
